How to get door opening boundary on wall in Dynamo or Revit API?


solid

Hi everyone
I want to create opening like above picture to solid in Dynamo but I don’t kwon how to created.

thank a lot.

hi @peerawit.ru
You can try this node:
image

1 Like

Revit Bounding Boxes are aligned with global axes (AABB), so the bounding box solution will only work with doors in walls that are aligned with global axes and not walls that are diagonal in plan.

A better approach would be to get the position of the door, it’s orientation and dimensions and recreate the box. You can find code for this below with annotations explaining what is happening…

Door.AsDSBox (Py)

import clr

clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript import Geometry as geom

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 *

"""
Ensures list of objects.
"""
def tolist(obj1):
	if hasattr(obj1,"__iter__"): return obj1
	else: return [obj1]


"""
Converts doors to dynamo boxes with correct orientation and size...
"""
def DoorToBox(door):
	
	# Get door location Revit API Object...
	loc = door.Location
	
	# If there is no Location for this element, then return null...
	if loc == None:
		return None
	
	# Get the Position of the door and cast to Dynamo Type...
	pos = door.Location.Point.ToPoint()
	
	# Get the Orientation of the door and cast to Dynamo Type...
	orient = d.FacingOrientation.ToVector()
	
	# Here we flip the orientation vector if dot product to Y-Axis is less than 0. This ensures that the angle calculation is correct...
	if geom.Vector.Dot(geom.Vector.YAxis(), orient) < 0:
		orient = orient.Reverse()
	
	# Get door Type. We need this to get Type Width/Height values as these are Type Params...
	dType = doc.GetElement(door.GetTypeId())
	
	# Get door dimensions...	
	thk = door.Host.Width * ft2mm
	w = dType.get_Parameter(BuiltInParameter.DOOR_WIDTH).AsDouble() * ft2mm
	h = dType.get_Parameter(BuiltInParameter.DOOR_HEIGHT).AsDouble() * ft2mm
	
	# Height Adjust as box is placed centre height...
	hAdj = geom.Vector.ByCoordinates(0,0,h/2)
	# Create a coordinate system that matches the door...
	cs = geom.CoordinateSystem.ByOriginVectors(pos + hAdj, orient, geom.Vector.Cross(geom.Vector.ZAxis(), orient))
	
	# Create a box at the origin of the Coordinate System and with the dimensions we got earlier...
	box = geom.Cuboid.ByLengths(cs, thk, w, h)
	
	return box

# Unit conversion helper value...
ft2mm = 304.8

# Doors that are passed via node input...
doors = tolist(UnwrapElement(IN[0]))

# If no doors, then get ALL doors in the project...
if IN[0] == None:
	doors = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Doors).WhereElementIsNotElementType().ToElements()

# Apply DoorToBox method to all doors and return boxes...
OUT = [DoorToBox(d) for d in doors]

You could also do this purely with nodes if you preferred. Just follow the code in the DoorToBox definition.

5 Likes

Hello,
here an alternative with obtaining the solid from the opening
getSolidOpening

import clr
import sys
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS

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

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

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

toList = lambda x : x if hasattr(x, '__iter__') else [x]

lstelems = toList(UnwrapElement(IN[0]))

opt = Options()
opt.IncludeNonVisibleObjects = True

TransactionManager.Instance.EnsureInTransaction(doc)

out = []
for door in lstelems:
	temp = []
	geoDs = None
	pos = door.Location.Point
	dType = doc.GetElement(door.GetTypeId())
	#
	host = door.Host
	curvWall = host.Location.Curve
	#
	w = dType.get_Parameter(BuiltInParameter.DOOR_WIDTH).AsDouble() 
	h = dType.get_Parameter(BuiltInParameter.DOOR_HEIGHT).AsDouble() 
	# get data to create opening
	interResult = curvWall.Project(pos)
	paraOnWallCurve = interResult.Parameter 
	ptA = curvWall.Evaluate(paraOnWallCurve - w * 0.5, False)
	ptB = curvWall.Evaluate(paraOnWallCurve + w * 0.5, False)
	ptB = ptB + XYZ(0,0,h)
	# create opening
	openning = doc.Create.NewOpening(host, ptA, ptB)
	# get geometry openning
	doc.Regenerate()
	geoSet = openning.get_Geometry(opt)
	for g in geoSet:
		if isinstance(g, Solid):
			geoDs = g.ToProtoType()
			break
	out.append([door, openning, geoDs])

TransactionManager.Instance.TransactionTaskDone()

OUT = lstelems, out
4 Likes

Thank for your guidance.
This node is not work for me in this case

Thank for your guidance.
When i used family from many supplier i’m not sure all family use same parameter for control opening may be there have shared parameter from each company.

Thank for your guidance.
I’m not sure all family use same parameter for control opening may be there have shared parameter from each company. :disappointed_relieved:

Could you share here one sample family? It will help others who’s trying to help you for a cleaner solution.

1 Like

OK. Most doors use the Built-In Parameters for width/height. However, I see that this could be an issue if the Family Creator deviates from this with their own custom parameters (which I guess is not unusual - but annoying for us programmers! Ha! :stuck_out_tongue: ).

An alternative would be to use a bounding box method, unlike the suggestion earlier which gets the instance bounding box, this instead gets the Element Type (or FamilySymbols) bounding box. Then we apply the transformations.

Object Aligned Bounding Box Method

import clr

clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript import Geometry as geom

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 tolist(obj1):
	if hasattr(obj1,"__iter__"): return obj1
	else: return [obj1]


"""
Converts doors to dynamo boxes with correct orientation and size...
"""
def DoorToBox(door):
	# Get Wall Thickness, we need this for setting box thickness...
	thk = door.Host.Width * ft2mm
	
	# Get the Door FamilySymbol (aka Type)...
	dType = doc.GetElement(door.GetTypeId())
	
	# Get BoundingBox of Type...
	# NOTE: Revit works by creating instances of FamilySymbols (FamilyInstances) and then applying tranformations to show FamilyInstance in the correct location, so here we take advantage of the FamilySymbol's BoundingBox, which is a valid BoundingBox prior to applied transformations...
	bbox = dType.get_BoundingBox(None)
	
	# Here we adjust the bounds od the bbox to be same width/height as bbox, but we manipulate the y values to be half +/-Wall Thickness...
	min = geom.Point.ByCoordinates(bbox.Min.X * ft2mm, -thk/2, 0)
	max = geom.Point.ByCoordinates(bbox.Max.X * ft2mm, thk/2, bbox.Max.Z * ft2mm)
	
	# Create the DS Box with these new BoudningBox Min/Max points...
	box = geom.Cuboid.ByCorners(min, max)
	
	# Get the doors Transform (this is the Transform applied to the FamilySymbol that defines positioning of FamilyInstance)...
	t = door.GetTransform()
	# Convert Transform to DS Coordinae System...
	cs = geom.CoordinateSystem.ByOriginVectors(t.Origin.ToPoint(), t.BasisX.ToVector(), t.BasisY.ToVector())
	
	# Apply transformation and return transformed box...
	return geom.Geometry.Transform(box, cs)
	

# Unit conversion helper value...
ft2mm = 304.8

# Doors that are passed via node input...
doors = tolist(UnwrapElement(IN[0]))

# If no doors, then get ALL doors in the project...
if IN[0] == None:
	doors = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Doors).WhereElementIsNotElementType().ToElements()

# Apply DoorToBox method to all doors and return boxes...
OUT = [DoorToBox(d) for d in doors]

This is not perfect, there is one issue which is that the bounding box circumscribes ALL the geometry in the Family (inc lines etc), so bounding box could be an erroneous representation of the actual geometry if other non solid geometries are outside the bounds of the opening.

Another method, similar to the one above, but instead using the Geometry of the FamilyType prior to transformations is as follows…

Obejct Aligned Bounding Box Method 2

import clr

clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript import Geometry as geom

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 tolist(obj1):
	if hasattr(obj1,"__iter__"): return obj1
	else: return [obj1]


"""
Converts doors to dynamo boxes with correct orientation and size...
"""
def DoorToBox(door):
	# Get Wall Thickness, we need this for setting box thickness...
	thk = door.Host.Width * ft2mm
	
	# Get the Door FamilySymbol (aka Type)...
	dType = doc.GetElement(door.GetTypeId())
	
	# Get Solid from Door Type...
	# NOTE: Revit works by creating instances of FamilySymbols (FamilyInstances) and then applying tranformations to show FamilyInstance in the correct location, so here we take advantage of the FamilySymbol's Geometry, which is a valid Geometry prior to applied transformations...
	sld = GetSolidFromType(dType)
	
	# Get BoundingBox of Geometry...
	bbox = sld.GetBoundingBox()
	
	# Here we adjust the bounds od the bbox to be same width/height as bbox, but we manipulate the y values to be half +/-Wall Thickness...
	min = geom.Point.ByCoordinates(bbox.Min.X * ft2mm, -thk/2, 0)
	max = geom.Point.ByCoordinates(bbox.Max.X * ft2mm, thk/2, (bbox.Max.Z - bbox.Min.Z) * ft2mm)
	
	# Create the DS Box with these new BoudningBox Min/Max points...
	box = geom.Cuboid.ByCorners(min, max)
	
	# Get the doors Transform (this is the Transform applied to the FamilySymbol that defines positioning of FamilyInstance)...
	t = door.GetTransform()
	# Convert Transform to DS Coordinae System...
	cs = geom.CoordinateSystem.ByOriginVectors(t.Origin.ToPoint(), t.BasisX.ToVector(), t.BasisY.ToVector())
	
	# Apply transformation and return transformed box...
	return geom.Geometry.Transform(box, cs)

def GetSolidFromType(doorType):

	sld = None
	gElem = doorType.get_Geometry(Options())
	
	for gObj in gElem:
		if gObj.GetType() == Solid:
			if sld == None:
				sld = gObj
				continue
			sld = BooleanOperationsUtils.ExecuteBooleanOperation(sld, gObj, BooleanOperationsType.Union)
	return sld

# Unit conversion helper value...
ft2mm = 304.8

# Doors that are passed via node input...
doors = tolist(UnwrapElement(IN[0]))

# If no doors, then get ALL doors in the project...
if IN[0] == None:
	doors = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Doors).WhereElementIsNotElementType().ToElements()

# Apply DoorToBox method to all doors and return boxes...
OUT = [DoorToBox(d) for d in doors]

This one I find less stable than bounding box method, especially when door is on curtain walls etc. These could probably be made better and optimised some more.

Hope these help.

2 Likes