Intersect Elements' geometry within the API

Hi

I am trying to get elements’ geometry and intersect them with floors’ geometry within the API directly. I am doing this in order to avoid potential issues with geometry scaling. I am successfully obtaining all the solids both for floors and other elements as well and when I append them to an output list, I get Autodesk.Revit.DB.Solid elements:

When I try to intersect them though, using this line:

intersections.append((BooleanOperationsUtils.ExecuteBooleanOperation(solid, floorSolid, BooleanOperationsType.Intersect)))

I get the following message:

image

Which is weird, because I only see DB.Solids in the output.

Here’s the whole code:

import clr
import sys
sys.path.append('C:\Program Files (x86)\IronPython 2.7\Lib')

import System
from System import Array
from System.Collections.Generic import *

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

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

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")

import Autodesk
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *

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

opt = Options()
opt.DetailLevel = ViewDetailLevel.Fine

intersections, floorsolids, elementsolids = [], [], []

floors = UnwrapElement(IN[0])
elements = UnwrapElement(IN[1])

for floor in floors:
	templist = []
	floorSolid = floor.get_Geometry(opt)
	floorsolids.append(floorSolid)
	for element in elements:
		#solid = element.Geometry
		solid = element.get_Geometry(opt)
		elementsolids.append(solid)
		#intersections.append((BooleanOperationsUtils.ExecuteBooleanOperation(solid, floorSolid, BooleanOperationsType.Intersect)))
	
OUT = intersections, floorsolids, elementsolids

@danail.momchilov ,

i am also on that topic ! let me know … :wink:

1 Like

Thank you! It’s not exactly the same, but similar, indeed. I am now checking it out

@danail.momchilov ,

i want to get a union of overlapping RevitGeometry

Hello, it seems that you have 2 empty containers too many, I don’t know if this solves your problem.
intersections, floorsolids, elementsolids = [], [], []**, [], []**
cordially
christian.stan

Hi, thank you, Stan! That’s not the issue unfortunattely, I erased some lines before pasting the code, just forgot to erase those containers too :slight_smile:

1 Like

One day, I would be good :slightly_smiling_face:, it will be necessary to wait a little ((day, month, year…) that I progress)
good resolution ahead
cordially
christian.stan

1 Like

I think I see what is happening here. You appear to have a list of solids for each solid, and so you aren’t testing a solid against a solid, but a collection of the geometry, making up a geometry element. The API acts this way as elements can have multiple geometries (think of a floor sketch with two disconnected curve loops, or a beam with a curve element and a solid).

As a test, try adding this line at the end OUT = floorSolid.__doc__, dir(floorSolid), which will tell you the information about what the floor solid variable is, and what you can do with it.

One thing you can start with is filtering out the geometry which isn’t a solid (you don’t have any yet, but that doesn’t mean you won’t later), and then union the remaining solids into one solid. The resulting list should be [DBSolid, DBSolid, DBSolid] not [[DBSolid],[DBSolid],[DBSolid]].

1 Like

I see, that actually makes perfect sense, now I noticed the outputs are indeed lists with solids.

Here’s what your test showed:

Furthermore, I tried uniting all the solids of the floor, using the ExecuteBooleanOperationModifyingOriginalSolid method. I think the GeometryObject is actually a set of Solids, as I see it’s not subscriptable, but it’s elements could be accessed via for loop, am I correct?

That’s why I tried this:

for floor in floors:
	templist = []
	floorSolid = list(floor.get_Geometry(opt))[0]
	
	for floorsubsolid in floor.get_Geometry(opt):
		BooleanOperationsUtils.ExecuteBooleanOperationModifyingOriginalSolid(floorSolid, floorsubsolid, BooleanOperationsType.Union)

But I get the following error:

I see it concenrs the initial floorSolid I am creating, have you got any idea what’s the issue?
I tried creating it as a ‘dummy’ solid, as it follows:

floorSolid = Autodesk.Revit.DB.Solid, but then I got an error, saying the method expected a Solid, but got type :frowning:

I’ll try and have a look at this after the Dynamo office hour starting in 30 minutes. Got some stair building tools to show off. :wink:

1 Like

Thank yout! Good luck with your Dynamo office hour :slight_smile:

1 Like

So it’s likely that the there is a missing step between getting the element solids doing the intersection, which will need to vary based on your element type.

What type of element are you pulling the solids from? Can you share a sample model?

This might help, but without specific project info I can’t test/confirm.

###################################################################
################## set up the Python environment ##################
###################################################################
import clr #imports the common language runtime (CLR) so we can access .net libraries in the Python environment 
[clr.AddReference(i) for i in ["RevitServices","RevitAPI"]] #adds the RevitServices and Revit API to the CLR 
import Autodesk #imports the Autodesk library from the CLR to the Python environment 
from Autodesk.Revit.DB import BooleanOperationsType, BooleanOperationsUtils, Element, Options, Solid, ViewDetailLevel #adds the relevant sections of the Revit API to the Python environment

###################################################################
######## defintion to get the valid solids from an element ########
###################################################################
def GetElementSolids(element,opt): #names the definition and sets the inputs for an element and a geometry options object
    elemSlds = [] #an empty list to hold the solids
    for geoEle in element.get_Geometry(opt): #for every geometry in the list of objects returned by the geometry 
        if geoEle.__class__ == Autodesk.Revit.DB.GeometryInstance: #test if the class is a geometry instance (derrived from repeated element placement, such as family instances) 
            geoSet = geoEle.GetInstanceGeometry() #get the instance geometry from the element 
        else: geoSet = [geoEle] #otherwise wrap the geometry object in a list 
        for geoObj in geoSet: #for every geometry object in the geoSet
            if geoObj.__class__ == Autodesk.Revit.DB.Solid: #if the class is a solid
                if geoObj.Volume > 0: elemSlds.append(geoObj) #and if the solid has a volume greater than 0, append it to the elemSlds list 
    return elemSlds #return the element solids

###################################################################
################### Global variables and inputs ###################
###################################################################
opt = Options() #create a new geometry options object
opt.DetailLevel = ViewDetailLevel.Fine #set the detail level of the options 
elem1 = UnwrapElement(IN[0]) #the first element from the Dynamo environment as a Revit object
elem2 = UnwrapElement(IN[1]) #the second element from the Dynamo environment as a Revit object

###################################################################
############### Get elem1 and elem2 solids as lists ###############
###################################################################
elem1Slds = GetElementSolids(elem1,opt) #runs the get solids command on the first element with the option settings
elem2Slds = GetElementSolids(elem2,opt) #runs the get solids command on the second element with the option settings

###################################################################
################ Intersects elem1 and elem2 solids ################
###################################################################
intersectionSet = ( [ [ BooleanOperationsUtils.ExecuteBooleanOperation(s1,s2,BooleanOperationsType.Intersect) for s1 in elem1Slds ] for s2 in elem2Slds ] ) #intersects the first set of solids with the second set of solids

###################################################################
####################### Push results Dynamo #######################
###################################################################
OUT = intersectionSet #returns the intersecting solids to the Dynamo environment
2 Likes

Thank you so much, Jacob!

I think I might be able to try it over the weekend or on Monday, as it seems today will be quite a busy day… about the elements, it really might be the issue, as they could be ducts, fittings, pipes or conduits, depending on the case and I am also getting them from linked files. I’m just trying to optimize a workflow, finding intersections between structural floors and building systems. It works great in Dynamo so far, but we had a problem with a model with extremely large floor slabs, where we end up with Geometry scaling issues. Other than that, making it though the API will also be significantly faster :slight_smile:

I can make a simplified model, suitable for testing.

Thx again, Cheers!

1 Like

Links will complicate things further… best to test this out as a first step and identify the ‘breaking condition(s) there so that you’re building the right type of data set.

Good luck!

2 Likes

That was actually of great help! After testing, I pimped the code a bit here and there, but it works:

import clr
import sys
sys.path.append('C:\Program Files (x86)\IronPython 2.7\Lib')

import System
from System import Array

from System.Collections.Generic import *
clr.AddReference('ProtoGeometry')

from Autodesk.DesignScript.Geometry import *
clr.AddReference("RevitNodes")

import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")

import Autodesk
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *


def GetElementSolids(element,opt): #names the definition and sets the inputs for an element and a geometry options object
    elemSlds = [] #an empty list to hold the solids
    for geoEle in element.get_Geometry(opt): #for every geometry in the list of objects returned by the geometry 
        if geoEle.__class__ == Autodesk.Revit.DB.GeometryInstance: #test if the class is a geometry instance (derrived from repeated element placement, such as family instances) 
            geoSet = geoEle.GetInstanceGeometry() #get the instance geometry from the element 
        else:
        	geoSet = [geoEle] #otherwise wrap the geometry object in a list 
        for geoObj in geoSet: #for every geometry object in the geoSet
            if geoObj.__class__ == Autodesk.Revit.DB.Solid: #if the class is a solid
                if geoObj.Volume > 0: elemSlds.append(geoObj) #and if the solid has a volume greater than 0, append it to the elemSlds list 
    return elemSlds #return the element solids

opt = Options() 
opt.DetailLevel = ViewDetailLevel.Fine 
floors = UnwrapElement(IN[0])
elements = UnwrapElement(IN[1])

floorSolids, elemSolids, intersections, intersectionSet, outlist = [], [], [], [], []

for floor in floors:
	floorSolids.append(GetElementSolids(floor, opt)[0])
for elem in elements:
	elemSolids.append(GetElementSolids(elem, opt)[0])

intersectionSet = ( [ [ BooleanOperationsUtils.ExecuteBooleanOperation(s1, s2,BooleanOperationsType.Intersect) for s1 in floorSolids ] for s2 in elemSolids ] ) 

for list in intersectionSet:
	templist = []
	for solid in list:
		if solid.Volume > 0:
			try:
				templist.append(solid.ToProtoType())
			except:
				pass
	outlist.append(templist)

OUT =  outlist

One thing I did was getting the zero index for the list of solids for each element, as intersections didn’t work otherwise:

image

I was wondering if this could end up problematic, but I think I would always get a list with a single solid in it, at least after testing for the categories I am working with.

I also found BooleanOperationsUtils would always return a solid, even if intersections were not found. That’s why I am filtering solids by their volume at the end.

I would now need to transform each element input, placing its geometry on the right position, based on the link transform. I think that’s the final step to make it all work perfectly. Other than that, intersecting around 200 elements with an extremely large floor slab now takes around 8 seconds. Previously, the same slab would take 20 minutes through Dynamo and would not even work, depending on the Geometry scaling :frowning: there is just one issue though, I can’t handle Pipe FIttings:

Previously, I was working around this issue, by getting their bounding boxes in Dynamo, but I’ll try to figure it out :slight_smile: thanks once again!

1 Like

In the end, I took elements’ bounding boxes, already placed on their correct coordinates, took their min and max points to rebuild them as solids as per this topic: Create Solid from boundingbox - Autodesk Community

and then intersected them in the API :slight_smile: Works great! After a ton of testing I’ve found this to be the best approach for my purpose. Here’s the final code (some kind of combo between @jacob.small 's code and the topic in the API forum:

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

clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import Point, Line, XYZ, CurveLoop, GeometryCreationUtilities, Options, BooleanOperationsUtils, BooleanOperationsType

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

def GetElementSolids(element,opt):
    elemSlds = []
    for geoEle in element.get_Geometry(opt):
        if geoEle.__class__ == Autodesk.Revit.DB.GeometryInstance:
            geoSet = geoEle.GetInstanceGeometry()
        else:
        	geoSet = [geoEle]
        for geoObj in geoSet:
            if geoObj.__class__ == Autodesk.Revit.DB.Solid:
                if geoObj.Volume > 0: elemSlds.append(geoObj)
    return elemSlds

outlist, floorSolids, bboxSolids = [], [], []

opt = Options()

minPts = UnwrapElement(IN[0])
maxPts = UnwrapElement(IN[1])
floors = UnwrapElement(IN[2])

for i, minPt in enumerate(minPts):
	maxPt = maxPts[i]
	pt0 = XYZ(minPt.X/30.48, minPt.Y/30.48, minPt.Z/30.48)
	pt1 = XYZ(maxPt.X/30.48, minPt.Y/30.48, minPt.Z/30.48)
	pt2 = XYZ(maxPt.X/30.48, maxPt.Y/30.48, minPt.Z/30.48)
	pt3 = XYZ(minPt.X/30.48, maxPt.Y/30.48, minPt.Z/30.48)
	edge0 = Line.CreateBound(pt0, pt1)
	edge1 = Line.CreateBound(pt1, pt2)
	edge2 = Line.CreateBound(pt2, pt3)
	edge3 = Line.CreateBound(pt3, pt0)
	edges = []
	edges.Add(edge0)
	edges.Add(edge1)
	edges.Add(edge2)
	edges.Add(edge3)
	height = maxPt.Z/30.48 - minPt.Z/30.48
	crvLoop = CurveLoop.Create(edges)
	loopList = []
	loopList.Add(crvLoop)
	bboxSolids.Add(GeometryCreationUtilities.CreateExtrusionGeometry(loopList, XYZ.BasisZ, height))
	
for floor in floors:
	floorSolids.append(GetElementSolids(floor, opt)[0])
	
intersectionSet = ( [ [ BooleanOperationsUtils.ExecuteBooleanOperation(s1, s2, BooleanOperationsType.Intersect) for s1 in floorSolids ] for s2 in bboxSolids ] )

for list in intersectionSet:
	templist = []
	for solid in list:
		if solid.Volume > 0:
			try:
				templist.append(solid.ToProtoType())
			except:
				pass
	outlist.append(templist)

OUT = outlist

1 Like

What you’re doing is really inefficient. To improve it:

  1. Use an element bounding box filter to collect surrounding elements.
  2. Use the element intersects element filter to establish if the element intersects with the surrounding elements.
  3. Extract the geometry of any intersecting elements and perform the Boolean operation.
1 Like

Due to problems with taking some elements’ geometry through the API and also getting them from links, I would prefer to work with their bounding boxes directly. Also, I’ve found extracting geometry is way slower than getting the bounding box. I’m also working with a selection of elements, not all the models and floor slabs simultaneously.

Other than that, it would actually make sense to check if bounding boxes intersect, group them and then perform the Boolean like you said. I will give it a try.

I am quite happy so far, as I’ve already come from something really slow in dynamo (or sometimes impossible) to 8 seconds for a set of extremely large floor slabs, intersected with 300 elements. For sure, there’s room for improvement and I will give it a try

1 Like