Module 5
[Clean Separation lines by view]
As the last module was rather lengthy, this module is a little simpler yet still solves a common problem in Revit. That problem is room separation lines and the warnings they create. If you have ever worked on a large project with users of varying Revit ability, I’m sure you have witnessed the warnings associated with room separation lines which include lines overlapping and lines overlapping with walls. These warnings can accumulate rather quickly and bog down the project files if they aren’t cleaned up accordingly. Cleaning up other peoples warnings isn’t usually that much fun so this module aims to reduce the time taken to do this by automatically cleaning up these warnings.
How the script works is very simple. If a room separation line intersects a wall, it will move the intersect end to the wall end and if it is completely inside the wall, it will be deleted. If room separation lines are overlapping, it takes the two ends that are overlapping and joins them at one common point or, deletes one of the separation lines if it isn’t needed. As this script is editing multiple elements in Revit, I have only allowed for single view filtering so it will only affect the current visible view although I’m sure you can figure out how to expand this search. It is very important to backup work before running a script such as this as multiple elements are getting deleted or edited.
As usual, would love to hear if you have any feedback or problems, please comment below or email via the About page. The script file is available for download at the bottom of the page.
As you can see from the image above, this is a very simple little script which packs a bit of a punch. To start, a Boolean node is fed into a Python Script node. This is the usual trigger to reset the workflow. I recommend setting this to False and running if you have already run the script and need to run again. This clears out the elements already referenced in the workflow. This first Python Script node clears up any intersections between separation lines and walls, the details can be seen below in part 1.1.
After the wall and separation lines have been cleaned up, we need to clean up all the overlapping separation lines. Before we do this, we need to ensure the first Python Script has finished it’s job. If we were to run both Python Script nodes at the same time, the script won’t work as intended as both scripts will be working on the same elements which will cause issues. Therefore, the output at index [0] is extracted here using the List.FirstItem which is True if the first Python Script is set to True. This is then fed into the Transaction.End node which outputs the same value. So, Transaction.End will not output True or False until the first Python Script is complete.
If the output from part 2 is True, the second Python Script will run. This script cleans up separation lines that are overlapping with one another, the details are shown in part 3.1 below.
I have not included the usual python imports in this module but previous modules explain what needs to be imported. Here we simply set in the variable toggle as the only input and set up several empty lists which will be used later.
If the input is set to True, the script commences by firstly setting up two FilteredElementCollectors. The first collector is set to sepCollector which filters the document (doc) and the current view (doc.ActiveView.Id) of the BuiltInCategory for room separation lines (OST_RoomSeparationLines) and returns them as elements. The second collector, wallCollector, performs the same action but this time filters the BuiltInCategory for walls (OST_Walls). We now have two lists, one which contains the separation lines in the current view and the other that contains all the walls in the current view.
Before we find the intersections between the separation lines and walls, we need to get the solid geometry of the walls. To do this, we set up a for loop which cycles through each wall in the wallCollector list. For each loop, an empty Options is created which will be used to get the geometry of the wall. We use these options to change the user preferences for parsing of geometry. In this case, ComputeReferences option is set to True so we get all references to the wall and DetailLevel is set to Fine. With these options, we can then extract the geometry of the wall using the get_Geometry method with the options used as the input parameter. The result is appended to the geometry list. Once this loop is finished, the list of geometry elements are flattened using python list comprehension which is explained in more detail in Module 4.
Once we have all the wall geometry, we need to loop through all the separation lines so another for loop is created. For each element (j) in the sepCollector list, another for loop is created which will loop through each piece of geometry (g) in the list of geometry. So, for each separation line, we check each piece of geometry and try to check if the two intersect. This is done by first setting up some more options which are specifically for this, these are SolidCurveIntersectionOptions which lets us specify how the intersection check will work. Here we are happy with default options as we just need to find out if they intersect at all. IntersectWithCurve method is used on the piece of geometry (g) with the separation line as a curve (j.GeometryCurve) and the options as the inputs.
If there was any intersection between the wall geometry and separation line, the inter variable from part 1.4 would return curve segments. So, here we check if inter contains more than (>) 0 curves. If it does, another SolidCurveIntersectionOptions is created but this time the ResultType is set to CurveSegmentsOutside which means the next intersection test will return the curve segments that are not intersecting the geometry. Another IntersectWithCurve is called with the new options (opts2) and the results are stored in the inter2 variable.
If there are any segments stored in inter2 then we can assume that this separation line was overlapping the wall but not entirely inside the wall. So, we need to adjust the separation line rather than delete it. To do this, TransactionManager is used to create a new transaction in the Revit document so we can edit the element. SetGeometryCurve is then called on the separation line (j) with the outside curve segment from inter2 used as the new geometry curve for the separation line. We retrieve the segment curve from inter2 using GetCurveSegment and the index 0 which will always be the segment which is not intersecting with the wall geometry. Once this is finished the transaction is ended. The separation line that was intersecting the wall has now been adjusted so the endpoint is outside of the wall.
If the second intersection test (inter2) returned no segments then we can assume that the separation line is entirely within the wall geometry and therefore needs to be removed. To do this, we create another transaction using EnsureInTransaction and Delete is called on the current document (doc) to delete the separation line by its element Id (j.Id). The “Line Deleted.” string is appended to the lst list and the transaction is finished using TransactionTaskDone.
In part 1.4, a try statement is used as separation lines might be deleted as the loop cycles through sepCollector. As they are deleted, a error might be thrown as the line is no longer in the Revit database. So, here, if the try statement fails, the except component picks up and the loop element is passed using pass. Once the loop through sepCollector is finished the Boolean True and the list (lst) of strings is output. The Boolean allows for the next python script to commence in part 3.
If the output of the Python Script from part 1 is True then this script will run. Firstly, a FilteredElementCollector is created to filter the active document for room separation lines and the results are stored in the sepCollector variable. This is identical to the collector in part 1.2. I haven’t included the python imports in this module however, it is important to note that itertools has been imported in addition to the usual imports.
The reason we import itertools is so we can use the combinations function shown here. A for loop is created to cycle through each of the sepCollector elements which are the separation lines in the current view. itertools.combinations lets us loop through each pair of elements in the sepCollector list without using the same two elements twice as specified in the arguments with the numerical 2. So, we can compare each line in the list with every other line. These elements are stored in the i and j variables for each loop. With these elements in hand, we start with a try statement for the same reasons stated in part 1.8. For each loop, the curve geometry is extracted using GeometryCurve as the separation are model lines. We then chech if the two curves intersect using the Intersect methods. If the results is SetComparisonResult.Equal then the code continues. This, I found, is the result which is returned if the two curves overlap.
Now that we have found the two curves intersect, we need to get information from the curves. The end points are extracted using GetEndPoint which is then converted to dynamo geometry using ToPoint. These need to be converted as the function ParametertAtPoint is a Dynamo function, hence the curves being converted to dynamo curves also, using ToProtoType. ParameterAtPoint is used to we can check if the end points of each curves lies on the other curves.
By checking the parameter of the points on the other curves, we can find out if that point actually lies on the curve. So, here we are checking if the parameter of the ic curve end points (iEnd1 & iEnd2) are within the bounds of the ij curve. If the parameter of the ic end points are both below 0 or above 1, it means they can not be on the ij curve which means it must be a longer curve. Therefore, we start a transaction and Delete the j curve as we know it is within the i curve. This is a little confusing I know but please don’t hesitate to contact me if you need further explanation.
If part 3.4 failed then we can check the reverse here to see if the i curve is within the j curve bounds. We are essentially doing the reverse here and check if the endpoints of the i curve are both greater than 0 and less than 1. If they are, it means both end points are on the j curve and therefore it is contained on the curve and needs to be deleted. We do the same as part 3.4 and use Delete to achieve this within a transaction.
If both of the past 2 if statements failed, then we can safely assume that the two lines intersect but neither is completely contained on the other. Therefore, we need to adjust both lines so their ends meet but don’t overlap. To do this, we use a series of if else statements to check each end point and its relation to the other line. If the endpoint as parameter on line (ipap1 for example) is larger than 0 and smaller than 1 then we know it is on the other line and we set that endpoint to the variable iinpt. If it wasn’t on the other line, then we know it is the only point not on the line and so it is set to ioutpt. As there can only be one point on the other curve, we can use this logic to set each point of the two curves to either iinpt (on the other curve) or ioutpt (not on the other curve). When this process is finished, we will have references to the lines overlapping endpoints (iinpt & jinpt). This is quite confusing i’m sure and maybe someone out there has a simpler way of doing this which I would love to hear! Once we have those points, we can create two new curves using CreateBound and the new points as arguments. These arguments are converted to Revit type (ToXyz) first as there were converted to dynamo type in part 3.3 Notice that both new lines use the iinpt point as this is where they will join. We now have two new curves along the same old curves but no longer overlapping.
Once we have these new lines, we can use SetGeometryCurve once again to set the separation lines to the new lines which are not intersecting. This is done in a transaction as before.
With this two relatively simple Python scripts, all the separation lines in the current view should be spot on. Lines completely overlapping others will result in the shorter lines being deleted and lines overlapping will be adjusted to suit. I have tested this across many room/wall/separation line situations but there is always the chance something is missed. Because of this, I encourage you to back up your work and test in small situations first before rolling out on large projects.
An example of the separation lines being cleaned on a small scale is shown below. I know the python logic was a little tricky in this module so if there is anything I haven’t fully explained or you do not fully understand please give me a shout via email or in the comments below. If you liked the script and want more, please follow me on twitter @LearnDynamo and subscribe to the emailing list below for the next module release.
Kevin
November 13, 2018 @ 8:03 am
Hi Jeremy,
Thanks for sharing the valuable work.
I noticed when there are multiple lines overlapping each other, there is still an extra segment left over after running the script. Is that something easy to resolve?
Jeremy
January 30, 2019 @ 9:45 am
Hi Kevin, I don’t think I accounted for multiple lines overlapping unfortunately, you will need to edit the Python script to do so. Give me a shout if you need a hand.
Omar Elwan
December 8, 2021 @ 3:09 am
Hello Jeremy,
Hope you are doing well.
I am happy to say that I have spent about 3 days on this article trying to understand and apply its principles and concepts as I am new to Revit API. I also have finished your course “Jump into Python” on Lynda and it was a perfect start for me.
Now, I am trying my best to write down my own scripts to find out solutions for many tasks. One of the challenging tasks I am trying to automate is to split room separation lines wherever they intersect with each other. This will ease the process of controlling the room lines. I have made great progress with the script but I just need finishing help. So, if you have time for me, I will be happy to share my script with you.
Thank you for everything. 🙂
Jeremy
December 8, 2021 @ 7:50 pm
Hi Omar, thanks for the message and great to hear you are enjoying learning Dynamo. I would be happy to help, shoot me a message on LinkedIn and we can chat there. Thanks
Pablo
January 18, 2022 @ 3:46 pm
Hi, Thank you for sharing this knowledge. Great stuff. I made some improvements. In the first python node, I added a check to remove walls not room bounding and I made another check to delete lines where the doors are. Regarding the second python node, I noticed that it does not remove overlapping lines with ends in the same place. The solution was simple to change “if (ipap1 1) and (ipap2 1)” to “if (ipap1 = 1) and (ipap2 = 1)” and do the same for “j”.
Below is the first node code:#Copyright(c) 2016 http://www.Learndynamo.com
#Please contact at jeremy@learndynamo.com
import clr
clr.AddReference(‘RevitAPI’)
clr.AddReference(“RevitNodes”)
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
import Autodesk
from Autodesk.Revit.DB import *
clr.AddReference(“RevitServices”)
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
toggle = IN[0]
out = []
lst = []
geometry = []
intersections = []
dependentElems = []
sepLinesToDelete = []
currentView = FilteredElementCollector(doc, doc.ActiveView.Id)
# Checks if Line center point is inside a Door BoundingBox
def checkDoorIntersection(sepLine, doorsBoundingBoxList):
if doorsBoundingBoxList:
curve = sepLine.GeometryCurve
point = curve.Evaluate(0.5, True) # Gets curve centre point
for doorBox in doorsBoundingBoxList:
pointMax = doorBox.Max
pointMin = doorBox.Min
if point.X = pointMin.X and point.Y = pointMin.Y and point.Z = pointMin.Z:
sepLinesToDelete.append(sepLine.Id)
lst.append(“Line in Door to be Deleted.”)
if toggle == True:
sepCollector = FilteredElementCollector(doc, doc.ActiveView.Id).OfCategory(BuiltInCategory.OST_RoomSeparationLines).ToElements()
wallCollector = FilteredElementCollector(doc, doc.ActiveView.Id).OfCategory(BuiltInCategory.OST_Walls).ToElements()
#Removes walls not Room Bounding
walls = []
for wall in wallCollector:
if wall.get_Parameter(BuiltInParameter.WALL_ATTR_ROOM_BOUNDING).AsInteger() == 1:
walls.append(wall)
for wall in walls:
opt = Options()
opt.ComputeReferences = True
opt.DetailLevel = ViewDetailLevel.Fine
geo = wall.get_Geometry(opt)
geometry.append(geo)
filter = ElementClassFilter( FamilyInstance )
dep = wall.GetDependentElements( filter )
dlist = []
for d in dep:
d = doc.GetElement(d)
if “Doors” == d.Category.Name:
box = d.get_BoundingBox( doc.ActiveView )
dlist.append(box)
dependentElems.append( dlist )
geometry = [item for sublist in geometry for item in sublist]
for j in sepCollector:
for g in geometry:
try:
opts = SolidCurveIntersectionOptions()
doorsBoundingBoxList = dependentElems[geometry.index(g)]
inter = g.IntersectWithCurve(j.GeometryCurve, opts)
if inter.SegmentCount > 0:
opts2 = SolidCurveIntersectionOptions()
opts2.ResultType = SolidCurveIntersectionMode.CurveSegmentsOutside
exter = g.IntersectWithCurve(j.GeometryCurve, opts2)
if exter.SegmentCount == 1:
intersections.append(exter.GetCurveSegment(0).ToProtoType() )
TransactionManager.Instance.EnsureInTransaction(doc)
j.SetGeometryCurve(exter.GetCurveSegment(0), True)
checkDoorIntersection(j, doorsBoundingBoxList)
lst.append(“Line Reset.”)
TransactionManager.Instance.TransactionTaskDone()
elif exter.SegmentCount > 1:
for i in xrange(exter.SegmentCount):
if i+1 < exter.SegmentCount:
TransactionManager.Instance.EnsureInTransaction(doc)
elems = ElementTransformUtils.CopyElement(doc, j.Id, XYZ(0,0,0))
elem = doc.GetElement(elems[0])
elem.SetGeometryCurve(exter.GetCurveSegment(i), True)
lst.append("Line Duplicated.")
checkDoorIntersection(elem, doorsBoundingBoxList)
TransactionManager.Instance.TransactionTaskDone()
else:
TransactionManager.Instance.EnsureInTransaction(doc)
j.SetGeometryCurve(exter.GetCurveSegment(i), True)
lst.append("Last Line Reset.")
checkDoorIntersection(j, doorsBoundingBoxList)
TransactionManager.Instance.TransactionTaskDone()
else:
TransactionManager.Instance.EnsureInTransaction(doc)
doc.Delete(j.Id)
lst.append("Line Deleted.")
TransactionManager.Instance.TransactionTaskDone()
except:
import traceback
lst.append(traceback.format_exc())
TransactionManager.Instance.EnsureInTransaction(doc)
for lineId in sepLinesToDelete:
try:
doc.Delete(lineId)
lst.append("Line in Door Deleted.")
except:
import traceback
lst.append(traceback.format_exc())
lst.append("Line not Deleted.")
TransactionManager.Instance.TransactionTaskDone()
OUT = True
else:
OUT = "Set toggle to True."