How to get Model-in-Place Element Level or Work Plane using dynamo or Python

Hi! i’d like to get the Model-in-Place Element level or workplane. I have the following code so far but it’s not working…This element is the one of the index 184 which level resulted in “null”.

import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)

def GetLevel(item):
	val = None
	if hasattr(item, "LevelId"): 
		val = item.Document.GetElement(item.LevelId)
		if val: return val
	if hasattr(item, "Level"):
		val = item.Level
		if val: return val
	if hasattr(item, "GenLevel"):
		val = item.GenLevel
		if val: return val
	if hasattr(item, "SketchPlane"):
		val = item.SketchPlane
		if val: return val		
	if not val:
		try: return item.Document.GetElement(item.get_Parameter(BuiltInParameter.INSTANCE_REFERENCE_LEVEL_PARAM).AsElementId())
		except:
			try: return item.Document.GetElement(item.get_Parameter(BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM).AsElementId())
			except: return None

items = UnwrapElement(IN[0])

if isinstance(IN[0], list): OUT = [GetLevel(x) for x in items]
else: OUT = GetLevel(items)

After just a quick look it doesn’t appear that in-place families have reference levels or planes. The geometries do, but the top level family itself does not. You would have to query the geometries within the family to get that information. I didn’t see an obvious way to get them, but it can likely be done through the API somehow.

There’s no way of getting this information?

As I said, those parameters are for the individual pieces of geometry (as you’ve shown) within the family, not the family itself. From the little bit of time I spent looking, I did not see an obvious way to get those geometries (and therefore their parameters) directly from the in-place family. That’s not to say it isn’t possible, I just didn’t see anything pointing towards the next step. I’d do some research into the API for in-place families.

3 Likes

If you edit the Model In Place family, and select the Extrusion solid for example using a Select Model Element node and get the parameter value by name for “Work Plane”, the node will return the solid hosting level, but the thing is the In Places families are not accessible via the API, yet, there is a wishlist for adding this functionality to the API… I guess we need to wait for this one :slight_smile:

Hi @tiagofrbr,

You can indeed get the Level from in-place Families…

import clr

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
doc =  DocumentManager.Instance.CurrentDBDocument

clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)

clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *

def to_list(obj1):
	if hasattr(obj1,"__iter__"): return obj1
	else: return [obj1]
	

inPlaceElems = to_list(UnwrapElement(IN[0]))

outlist = []

for e in inPlaceElems:
	outlist.append(doc.GetElement(e.get_Parameter(BuiltInParameter.FAMILY_LEVEL_PARAM).AsElementId()))
	
OUT = outlist

In-Place families are actually Families (or instances of Families). They are somewhat of a special case in Revit.

2 Likes

@Daniel_Woodcock1, it looks like you have the editor open and have manually selected the in-place family geometry. The geometry itself has a level parameter that is accessible, but do you know of a way to get the in-place geometry from the family?

Nope, editor is closed. I just have the in place family selected to show the properties hence the grab arrows

OK, thought so, just confirming.

@Daniel_Woodcock1 the family category specified in the IN Place model (Floors), permits a level outside the family editor, an Element.GetParameterValueByName can get that, but if the family category is a Generic Model for example, that hosting level does not show in the Properties palette, unless the In place family is edited , and the geometry selected

1 Like

Ah, right. I’ll have another look a little later, but the geometry in there is instance based and unlikely that you can get that info.

Alright, I can’t get the Level ID from the in place family, but can get the name and since these are unique then should be sufficient…

import clr

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
doc =  DocumentManager.Instance.CurrentDBDocument

clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)

clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *

def to_list(obj1):
	if hasattr(obj1,"__iter__"): return obj1
	else: return [obj1]
	
def get_sketch_ids(e):
	eIds = []
	t = Transaction(doc, "Temp")
	t.Start()   
	eIds = doc.Delete(e.Id) 
	t.RollBack()  
	return eIds	


inPlaceElems = to_list(UnwrapElement(IN[0]))

outlist = []

for e in inPlaceElems:
    TransactionManager.Instance.ForceCloseTransaction()
	ids = get_sketch_ids(e)
	elems = [doc.GetElement(id) for id in ids]
	ext = [[p.Definition.Name, p.AsValueString(), p.StorageType] for p in next((ex.GetOrderedParameters() for ex in elems if ex.GetType() == Extrusion), None)]
	outlist = ext
	
OUT = outlist

In the code above I am reporting all parameters for an Extrusion, but you could write it more succinctly like so…

ext = next((ex.get_Parameter(BuiltInParameter.SKETCH_PLANE_PARAM).AsString() for ex in elems if ex.GetType() == Extrusion), None)

After this, just parse the Level Name by string.split(" : ")[1] and do a filtered element collector for Levels and match Name.

Does that sort it?

1 Like

Full working code which outputs a Level for completeness…

import clr

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc =  DocumentManager.Instance.CurrentDBDocument

clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)

clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *

def to_list(obj1):
	if hasattr(obj1,"__iter__"): return obj1
	else: return [obj1]
	
def get_sketch_ids(e):
	TransactionManager.Instance.ForceCloseTransaction()

	eIds = []
	t = Transaction(doc, "Temp")
	t.Start()   
	eIds = doc.Delete(e.Id) 
	t.RollBack()  
	return eIds	

def parse_level_name(name, levels):
	n = name.split(":")[1].Trim()
	return next((l for l in levels if l.Name == n), None)
	

inPlaceElems = to_list(UnwrapElement(IN[0]))

levels = FilteredElementCollector(doc).OfClass(Level).WhereElementIsNotElementType().ToElements()

outlist = []

for e in inPlaceElems:
	ids = get_sketch_ids(e)
	elems = [doc.GetElement(id) for id in ids]
	
	lName = next((ex.get_Parameter(BuiltInParameter.SKETCH_PLANE_PARAM).AsString() for ex in elems if ex.GetType() == Extrusion), None)
	
	outlist = parse_level_name(lName, levels)
	
OUT = outlist

And demo…

4 Likes

@Daniel_Woodcock1 nice workaround :grinning:,
here is a variant with the same process for levels and workplanes

import clr
import System
from System.Collections.Generic import List
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc =  DocumentManager.Instance.CurrentDBDocument

clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *

def to_list(obj1):
	if hasattr(obj1,"__iter__"): return obj1
	else: return [obj1]
	
	
def myRollBack(func):
	def wrapper(*args, **kwargs):
		TransactionManager.Instance.ForceCloseTransaction()
		t = Transaction(doc, func.__name__)
		t.Start()
		ret = func(*args, **kwargs)
		t.RollBack()
		return ret
	return wrapper 


def get_WorkPlane(elem):
	# sub function
	@myRollBack
	def getSubIds(elem):
		eIds = doc.Delete(elem.Id) 
		return eIds
	# main function
	lstElem =  [doc.GetElement(id) for id in getSubIds(elem)]
	for e in lstElem:
		if isinstance(e, Extrusion):
			namePlane = e.Sketch.SketchPlane.Name
			planeZ = e.Sketch.SketchPlane.GetPlane().Origin.Z
			# get Levels and Reference Plane
			typed_list = List[BuiltInCategory]([BuiltInCategory.OST_CLines, BuiltInCategory.OST_Levels])
			filtercat = ElementMulticategoryFilter(typed_list)
			fecMultiCat = FilteredElementCollector(doc).WherePasses(filtercat).WhereElementIsNotElementType().ToElements().FindAll(lambda x : x.Name == namePlane)
			if fecMultiCat.Count == 1:
				return fecMultiCat[0]
			else:
				#filter by Z
				for j in fecMultiCat:
					if j.Category.Id == ElementId(BuiltInCategory.OST_CLines) and j.GetPlane().Origin.Z == planeZ:
						return j

inPlaceElems = to_list(UnwrapElement(IN[0]))
outlist = []
for e in inPlaceElems:
	if isinstance(e, FamilyInstance) and e.Symbol.Family.IsInPlace:
		outlist.append(get_WorkPlane(e))

OUT = outlist
4 Likes

Is there a way to merge these scripts into an unique python script in order to get the level from all elements such as Model-in-place (extrusion, sweep, blend, revolve, swept blend) and Revit families?

Hi @tiagofrbr
an example

import clr
import System
from System.Collections.Generic import List
clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc =  DocumentManager.Instance.CurrentDBDocument

clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *

def to_list(obj1):
	if hasattr(obj1,"__iter__"): return obj1
	else: return [obj1]
	
	
def myRollBack(func):
	def wrapper(*args, **kwargs):
		TransactionManager.Instance.ForceCloseTransaction()
		t = Transaction(doc, func.__name__)
		t.Start()
		ret = func(*args, **kwargs)
		t.RollBack()
		return ret
	return wrapper 


def get_LevelInPlace(elem):
	# sub function
	@myRollBack
	def getSubIds(elem):
		eIds = doc.Delete(elem.Id) 
		return eIds
	# main function
	lstElem =  [doc.GetElement(id) for id in getSubIds(elem)]
	for e in lstElem:
		if isinstance(e, Extrusion):
			namePlane = e.Sketch.SketchPlane.Name
			planeZ = e.Sketch.SketchPlane.GetPlane().Origin.Z
			filter = System.Predicate[System.Object](lambda x : x.Name == namePlane)
			lvl = List[Element](FilteredElementCollector(doc).OfCategory( BuiltInCategory.OST_Levels).WhereElementIsNotElementType().ToElements()).Find(filter)
			return lvl


def get_Level(item):
	if hasattr(item, "LevelId"): 
		lvl = doc.GetElement(item.LevelId)		
	elif hasattr(item, "Level"):
		lvl = item.Level
	elif hasattr(item, "GenLevel"):
		lvl = item.GenLevel	
	elif hasattr(item, "ReferenceLevel"): 
		lvl = doc.GetElement(item.ReferenceLevel.Id)	
	else:
		lvl = None
		
	if lvl is not None:
		return lvl
	else:
		for bip in [BuiltInParameter.INSTANCE_REFERENCE_LEVEL_PARAM, BuiltInParameter.INSTANCE_SCHEDULE_ONLY_LEVEL_PARAM, BuiltInParameter.SCHEDULE_LEVEL_PARAM]:
			para = item.get_Parameter(bip)
			if para is not None:
				lvl = doc.GetElement(para.AsElementId())
				if lvl is not None:
					return lvl
				
	return None


inPlaceElems = to_list(UnwrapElement(IN[0]))
outlist = []
for e in inPlaceElems:
	if isinstance(e, FamilyInstance):
		if e.Symbol.Family.IsInPlace:
			outlist.append(get_LevelInPlace(e))
		else:
			outlist.append(get_Level(e))
	else:
		outlist.append(get_Level(e))	
OUT = outlist
4 Likes

Hi! Nice script @c.poupin , however it’s not working for other model-in-place types besides extrusion… Is there a way to add the other types like sweep, blend, revolve, swept blend too?

you can try to build your solution from this node (it is quite complete and under MIT license)

image

1 Like

Hi @c.poupin ! Why isn’t this code working with floors?

EDIT :
I modify the code in the last post