Delete Nested Families From Families in Project and Reload

rhythm
danedu
beaker

#1

Here we go…

I have a user with a rogue library of detail items that are filled with unused nested families, that are themselves nested in some cases. I am putting together a script that will compare used families and types versus those that are not. This piece works great to delete elements of a specific category. But now I would like to run it on a model against the 300+ detail item families to “purge” the nested stuff out. Oh, and this is the same case for several current models so really doing this manually is just out.

Here is the current graph:
Elements - Purge Detail Items (beta).dyn (21.7 KB)

Before:

After:

Where I am having trouble is doing this work on a family while in the project environment. I have got the nodes getting the families and getting all of the elements of the category from the family, but can’t get the elements to delete from the Family and then load back in.

Here is code from “Delete Elements in Family”

#Based on DanEDU
    import clr
    clr.AddReference('RevitAPI')
    clr.AddReference('RevitServices')

# Revit and Dynamo modules
from Autodesk.Revit.DB import FamilyManager, BuiltInParameterGroup, ParameterType
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

docs = IN[0]
elems = IN[1]

if docs == 'Current.Document':
docs = DocumentManager.Instance.CurrentDBDocument

# make a list
if not isinstance(docs, list):
docs = [docs]
if not isinstance(elems, list):
elems = [elems]

log = []

#Do the work
if docs > 0:
for doc in docs:
TransactionManager.Instance.EnsureInTransaction(doc)
for elem in elems:
try:
log.append('ok')
doc.FamilyManager.Delete(elem.Id)
except:
log.append('No')
TransactionManager.Instance.ForceCloseTransaction()

OUT = docs, elems, log

Here is the code for “All Elements of Category in Doc”

#based on node by Julien Benoit @jbenoit44
import System
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference('DSCoreNodes')
import DSCore
from DSCore import *
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument

cats = IN[1]
if not isinstance(cats, list): cats = [cats]
docs = IN[0]
if not isinstance(docs, list): docs = [docs]

elemlist = []
for doc in docs:
try:
for item in cats:
collector = FilteredElementCollector(doc)
collector.OfClass(FamilySymbol)
bic = System.Enum.ToObject(BuiltInCategory, item.Id)
elemlist.append(collector.OfCategory(bic).ToElements())
except:
log = 'Error'
#TransactionManager.Instance.ForceCloseTransaction()

OUT = elemlist

This node also usually make the graph “run” indefinitely so I have to restart.

I get the Elements from the Families, and the Family Documents just fine, but need to access them to delete and then load.

Any thoughts would be greatly appreciated!


#2

Instead of working in the project environment, maybe you should consider working on the families themselves. Try saving out to a library and then batch process the documents using Orchid or other process. This will also allow you to Archive the contents as they were should you decide you want to ‘go back’ to the old version, and will provide the benefit of reducing the frequency of the indefinitely running graph as you can process things in more manageable groups.


#3

@JacobSmall, I have thought about that and don’t have much opposition to actually, however I think my graph remains mostly unchanged. I still need to get the Family document, collect elements, delete, then load back in. I have all of that except the deleting part working I believe. I am assuming it’s my syntax for deleting elements in the “doc”, but I don’t think that wouldn’t be any different if the families were background loaded. Or am I missing something completely? Thanks for your thoughts!


#4

It changes a bit actually. Once you are in the family document you can use something like the tool.eraser node in the steam nodes package to delete the offending elements. The hard part is knowing if they want to stay or not and you seem to have a handle on this. Remember you only have to process the active family not nested ones here. Once done, you don’t los it into the project, just save them.

Then once all families are saved without the nested horrors, load them into your project as a separate graph. It’s actually quite easy to accomplish that feat unless you have a corrupted family, but that issue would present itself when you save them out.


#5

Maybe my initial post was a bit too much. Here is a simplified version of my question and the issue I am currently having.

Current image of graph after run:

The error says that I have an open transaction, but I have it closed in teach node I think.

Here is the code for the three python nodes:

  1. All Elements of Category in Doc

based on node by Julien Benoit @jbenoit44

import System
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference('DSCoreNodes')
import DSCore
from DSCore import *
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument

cats = IN[1]
if not isinstance(cats, list): cats = [cats]
docs = IN[0]
if not isinstance(docs, list): docs = [docs]

elemlist = []
for doc in docs:
TransactionManager.Instance.EnsureInTransaction(doc)
try:
for item in cats:
collector = FilteredElementCollector(doc)
collector.OfClass(FamilySymbol)
bic = System.Enum.ToObject(BuiltInCategory, item.Id)
elemlist.append(collector.OfCategory(bic).ToElements())
except:
log = 'Error'
TransactionManager.Instance.ForceCloseTransaction()

OUT = elemlist
  1. Delete Elements in Family

Based on DanEDU

 import clr
    clr.AddReference('RevitAPI')
    clr.AddReference('RevitServices')

# Revit and Dynamo modules
from Autodesk.Revit.DB import FamilyManager
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

docs = IN[0]
elems = UnwrapElement(IN[1])

if docs == 'Current.Document':
docs = DocumentManager.Instance.CurrentDBDocument

# make a list
if not isinstance(docs, list):
docs = [docs]
#if not isinstance(elems, list):
# elems = [elems]

logs = []
#Do the work
for i in range(0,len(docs)):
log = []
TransactionManager.Instance.EnsureInTransaction(docs[i])
for j in range(len(elems[i])):
try:
log.append(elems[i][j])
#docs[i].Delete(elems[i][j].Id)
except:
log.append('No')
pass
TransactionManager.Instance.ForceCloseTransaction()
logs.append(log)
OUT = docs, logs
  1. Load Family

Based on DanEDU

import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')

# Revit and Dynamo modules
from Autodesk.Revit.DB import Document, FamilySource, IFamilyLoadOptions
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application

# input assigned the IN variable
fam_docs = IN[0] # document list

# wrap input inside a list (if not a list)
if not isinstance(fam_docs, list):
fam_docs = [fam_docs]

# ensure loaded families can overwrite existing families.
class FamilyOption(IFamilyLoadOptions):
def OnFamilyFound(self, familyInUse, overwriteParameterValues):
overwriteParameterValues = True
return True

def OnSharedFamilyFound(
self, sharedFamily, familyInUse, source, overwriteParameterValues):
source = FamilySource.Family
overwriteParameterValues = True
return True

# core data processing
for fam_doc in fam_docs:
fam_doc.LoadFamily(doc, FamilyOption())

# output assigned the OUT variable
OUT = fam_docs

#6

Much of the code in the DanEDU nodes is deprecated and will not be supported from my side. Please change to Orchid nodes and use my github for issues concerning that.
see my profile for further informations.


#7

I wanted to circle back around and post the solution I came up with. Taking what @JacobSmall suggested (Thanks), I split the process up into two graphs. Getting the families out has been covered many times, so I won’t go into that, but I did end up putting most of the cleaning, savings, and loading into a python script.

import System
import clr
clr.AddReference("RevitNodes")
import Revit
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
clr.AddReference("RevitAPI")
clr.AddReference('DSCoreNodes')
from DSCore import *
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import sys
pyt_path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
sys.path.append(pyt_path)

currDoc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application

paths = IN[0]
if not isinstance(paths, list): paths = [paths]
cats = List.Flatten([IN[1]],1)

save = IN[2]
load = IN[3]

class FamilyOption(IFamilyLoadOptions):
    def OnFamilyFound(self, familyInUse, overwriteParameterValues):
        overwriteParameterValues = True
        return True

    def OnSharedFamilyFound(
            self, sharedFamily, familyInUse, source, overwriteParameterValues):
        source = FamilySource.Family
        overwriteParameterValues = True
        return True

delIds = []
errorReport = None
try:
	for path in paths:
		doc = app.OpenDocumentFile(path)
		for cat in cats:
			trans = Transaction(doc,'del')
			trans.Start()

			allElements = (FilteredElementCollector(doc).OfClass(FamilySymbol).OfCategory(System.Enum.ToObject(BuiltInCategory, cat.Id)).ToElements())
			allFamilyIds = [x.Family.Id for x in allElements]
			allTypeIds = [x.Id for x in allElements]
			
			placedElements = (FilteredElementCollector(doc).OfClass(FamilyInstance).OfCategory(System.Enum.ToObject(BuiltInCategory, cat.Id)).WhereElementIsNotElementType().ToElements())
			placedElementTypeIds = set([x.get_Parameter(BuiltInParameter.ELEM_TYPE_PARAM).AsElementId() for x in placedElements])
			placedElementTypes = [doc.GetElement(x) for x in placedElementTypeIds]
			placedFamilyIds = [x.Family.Id for x in placedElementTypes]
			
			diffTypeIds = list(set(allTypeIds).difference(placedElementTypeIds))
			diffFamilyIds = list(set(allFamilyIds).difference(placedFamilyIds))

			dels = List.Join(diffTypeIds,diffFamilyIds)
			delIds.append(dels)

			for id in dels:
				doc.Delete(id)
			
			trans.Commit()
			trans.Dispose()
			
		if load:
			doc.LoadFamily(currDoc,FamilyOption())
		
		if save:
			opt = SaveAsOptions()
			opt.OverwriteExistingFile = True
			opt.Compact = True
			opt.MaximumBackups = 1
			doc.SaveAs(path,opt)	
			doc.Close(False)
		else:
			doc.Close(False)
			
except:
	import traceback
	errorReport = traceback.format_exc()

if errorReport is None:
    OUT = delIds
else:
    OUT = errorReport

Thanks to @erfajo again for some of the language to load families.