If you are like me and you tend to model your verticals all the way up in the early stages of your project to make coordination quicker, you will have had that dreaded moment when you know you really have to split the columns and walls. It’s super painful and annoying that Autodesk hasn’t added a tool to do just this.
I had been meaning to look into this for a while, but it kept falling down the list until a colleague asked for me to look into it as his project needed all concrete verticals by level but retain parameter data. There are probably a few ways to do this, but I went with the ElementTransformUtils.CopyElement() method and here is an example of it in action…
and an example of how not to do it…
Now, for the code…
This is the logic of the splitting walls node. We’ll start by importing all the necessaries to get this to work (Note: I have commented heavily within the code to explain what each part is doing)…
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 * import System from System.Collections.Generic import *
Next we will write our definitions so we can keep the code a little neater when we write the main script…
############## Definitions Start ############## # Convert to List if singleton... def tolist(obj1): if hasattr(obj1,"__iter__"): return obj1 else: return [obj1] # Returns the index of the found level given a Level and a list of Levels... def FindLevelIndex(levels, lev): ind = None i = 0 for l in levels: if l.Id.ToString() == lev.Id.ToString(): ind = i i = i+1 return ind # Copy the original wall and set it's levels using the ## Built-In Parameters for the Base and Top Constraints... def CopyWallByLevel(wall, b, t): wallOut = None try: # Copy the Original Wall with a transformation vector of 0,0,0... w = ElementTransformUtils.CopyElement(doc,wall.Id,XYZ(0,0,0)) # Since the CopyElements method returns the ElementId of ## the new wall, we need to get this Element from the Document... w = doc.GetElement(w[0]) # Update the Base and Top constraints Parameters using the ##Built-In Parameters. # Note: I have explicitly chosen the Overload as I was getting ##flaky behaviour where the wrong overload was being used... p = w.get_Parameter(BuiltInParameter.WALL_BASE_CONSTRAINT) p.Set.Overloads.Functions[2](b.Id) p = w.get_Parameter(BuiltInParameter.WALL_HEIGHT_TYPE) p.Set.Overloads.Functions[2](t.Id) wallOut = w.ToDSType(True) # Write out any exceptions... except Exception, e: wallOut = e.message # Return new wall.. return wallOut ############## Definitions End ##############
As mentioned in the comments in the code above for the definition CopyWallsByLevel(), I have have specifically chosen the overload I needed as for some reason it would want to default to requiring an int when I gave it an ElementId and when I tried passing an int it would want an ElementId instead. It also allows me to show how to choose which Overload you are after should you hit a snag like this.
Now for the main body of the script…
# IN-Variables... run = tolist(IN[0])[0] walls = tolist(UnwrapElement(IN[1])) # OUT-Variables... outList = [] # Main Script... # Test if user has selected Run as True... if run: # Get All Levels in the Document and cast to .net List... levels = list([l for l in FilteredElementCollector(doc).OfClass(Level).ToElements()]) # Sort Levels by Elevation using a lamda expression... levels.sort(key=lambda x: x.Elevation, reverse=False) # Start a new Transaction ready for modifying the Document... TransactionManager.Instance.EnsureInTransaction(doc) for w in walls: arr = [] # Check if the Element is a Wall... if w.GetType() == Wall: # Get Base and Top Constraints as Levels... p = w.get_Parameter(BuiltInParameter.WALL_BASE_CONSTRAINT) base = doc.GetElement(p.AsElementId()) p = w.get_Parameter(BuiltInParameter.WALL_HEIGHT_TYPE) top = doc.GetElement(p.AsElementId()) # Test whether walls Base and Top levels are NOT the same, ## if they are we will skip this wall, ## if they are not then we will get the Index of the Level ## in the sorted list of Levels we collected earlier for ## both the Base and Top of the wall... if not base.Id.IntegerValue == top.Id.IntegerValue: # Note: we are setting the bounds of the Loop below with ## the Indices of the found Levels so we will only loop ## through the Levels in between the Base and Top Levels... i = FindLevelIndex(levels,base) j = FindLevelIndex(levels,top) # Loop through the Levels between the Base and Top Levels ## copying the original wall for each iteration and ## stepping up one Level... while i < j: wCopy = CopyWallByLevel(w,levels[i], levels[i+1]) arr.append(wCopy) i = i+1 outList.append(arr) # Delete original Wall as this has now been split by Level... doc.Delete(w.Id) # End the Transaction... TransactionManager.Instance.TransactionTaskDone() # Return the new Walls... OUT = outList # Return if user has not set input Run to True... else: OUT = "Please Set Run to True"
I hope that was easy enough to follow. Here is the full code for copying Walls and also Columns…
SplitWallsByLevel…
So, for ease, the full code for SplitWallsByLevel Node…
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 * import System from System.Collections.Generic import * ############## Definitions Start ############## # Convert to List if singleton... def tolist(obj1): if hasattr(obj1,"__iter__"): return obj1 else: return [obj1] # Returns the index of the found level given a Level and a list of Levels... def FindLevelIndex(levels, lev): ind = None i = 0 for l in levels: if l.Id.ToString() == lev.Id.ToString(): ind = i i = i+1 return ind # Copy the original wall and set it's levels using the Built-In Parameters for the Base and Top Constraints... def CopyWallByLevel(wall, b, t): wallOut = None try: # Copy the Original Wall with a transformation vector of 0,0,0... w = ElementTransformUtils.CopyElement(doc,wall.Id,XYZ(0,0,0)) # Since the CopyElements method returns the ElementId of the new wall we need to get this Element from the Document... w = doc.GetElement(w[0]) # Update the Base and Top constraints Parameters using the Built-In Parameters. # Note: I have explicitly chosen the Overload as I was getting flaky behaviour where the wrong overload was being used... p = w.get_Parameter(BuiltInParameter.WALL_BASE_CONSTRAINT) p.Set.Overloads.Functions[2](b.Id) p = w.get_Parameter(BuiltInParameter.WALL_HEIGHT_TYPE) p.Set.Overloads.Functions[2](t.Id) wallOut = w.ToDSType(True) # Write out any exceptions... except Exception, e: wallOut = e.message # Return new wall.. return wallOut ############## Definitions End ############## # IN-Variables... run = tolist(IN[0])[0] walls = tolist(UnwrapElement(IN[1])) # OUT-Variables... outList = [] # Main Script... # Test if user has selected Run as True... if run: # Get All Levels in the Document and cast to .net List... levels = list([l for l in FilteredElementCollector(doc).OfClass(Level).ToElements()]) # Sort Levels by Elevation using a lamda expression... levels.sort(key=lambda x: x.Elevation, reverse=False) # Start a new Transaction ready for modifying the Document... TransactionManager.Instance.EnsureInTransaction(doc) for w in walls: arr = [] # Check if the Element is a Wall... if w.GetType() == Wall: # Get Base and Top Constraints as Levels... p = w.get_Parameter(BuiltInParameter.WALL_BASE_CONSTRAINT) base = doc.GetElement(p.AsElementId()) p = w.get_Parameter(BuiltInParameter.WALL_HEIGHT_TYPE) top = doc.GetElement(p.AsElementId()) # Test whether walls Base and Top levels are NOT the same, if they are we will skip this wall, if they are not then we will get the Index of the Level in the sorted list of Levels we collected earlier for both the Base and Top of the wall... if not base.Id.IntegerValue == top.Id.IntegerValue: # Note: we are setting the bounds of the Loop below with the Indices of the found Levels so we will only loop through the Levels in between the Base and Top Levels... i = FindLevelIndex(levels,base) j = FindLevelIndex(levels,top) # Loop through the Levels between the Base and Top Levels copying the original wall for each iteration and stepping up one Level... while i < j: wCopy = CopyWallByLevel(w,levels[i], levels[i+1]) arr.append(wCopy) i = i+1 outList.append(arr) # Delete original Wall as this has now been split by Level... doc.Delete(w.Id) # End the Transaction... TransactionManager.Instance.TransactionTaskDone() # Return the new Walls... OUT = outList # Return if user has not set input Run to True... else: OUT = "Please Set Run to True"
SplitColumnsByLevel…
I have updated this part of the post by creating a new post which you can find here. Split Columns…
I hope these tools help you out when you need to split those verticals.
Things to watch out for…
A couple of things I noticed while testing is that these are quite cumbersome, so try not do too many walls at once. This is probably because I am using the CopyElement() Method which I am thinking is quite laborious, I will try conjure up some other ways perhaps.
Another thing I noticed is that when you have openings on the walls you are splitting, you get an error message saying that the opening is no longer cutting the wall. This is because we are copying and changing the extents of the original wall (which has a bunch of openings attached still). The openings that were cutting (apart from the opening at that level) are now not cutting since we changed the Base/Top Levels (sounds confusing I know, but you’ll see what I mean). The problem with this is that I think that this can cause the script to crash (which is nasty), I’m looking for a get-around for this but haven’t had time this week. I will update this post or make a new one when I have found a prettier solution. It may be a whole new way to do this…or maybe an additional line of code. 🙂
June 9, 2017 at 1:19 am
Hi I’m the guy who commented on your YouTube video. Thanks for sharing your code with me! You definitely got me pointed in the right direction. I needed to split all of my columns in a 13 story hospital building I’m working on at work , so you definitely saved me (or rather the drafters) hours of work. I did tweak the column code a bit and I thought you might be interested in it. I used the FamilyInstance.Split() method from the Revit API and it works like a charm. This way I didn’t have to worry about copying columns and what not.
The code I changed is as follows:
i = 0
for c in col:
arr = []
if c.Category.Name == “Structural Columns”:
p = c.get_Parameter(BuiltInParameter.FAMILY_BASE_LEVEL_PARAM)
bo = c.get_Parameter(BuiltInParameter.FAMILY_BASE_LEVEL_OFFSET_PARAM)
bot = bo.AsDouble()
base = doc.GetElement(p.AsElementId()).Elevation + bot
p = c.get_Parameter(BuiltInParameter.FAMILY_TOP_LEVEL_PARAM)
to = c.get_Parameter(BuiltInParameter.FAMILY_TOP_LEVEL_OFFSET_PARAM)
too = to.AsDouble()
top = doc.GetElement(p.AsElementId()).Elevation + too
lev = level[0].Elevation
if lev > base and lev < top:
a = lev – base
b = top – base
s = a/b
outlist = s
c.Split(s)
i += 1
i = str(i) + ' column(s) have been split.'
outlist = i
In the Split method you have to pass in a value of where you want to split the column from 0 to 1. This code only does one level at a time and the level has to be selected manually in the Dynamo graph, but I did that intentionally as there are a few levels that aren't related to some columns.
Thanks again for sharing your code and getting me started. I hope this is helpful for you too.
LikeLiked by 1 person
June 9, 2017 at 7:21 am
Ha, nice! I totally missed the FamilyInstance.Split() method as I was so focused on the workaround for walls . I much prefer this way as I was trying to get away from the copy method, I’ll create a new update post and give you a shout out.
Thanks for sharing! 😊
LikeLike
June 28, 2017 at 6:16 pm
Could you Post your .py script here?
LikeLike
January 13, 2020 at 6:59 pm
Hello Brandon,
I have been working on the code original code provided by daminosite and your part using FamiliInstance.Split. I am new with Python and Dynamo so I do not know where exactly your provided code has to be placed (inside the daminosite´s code). Could you please help me with that?
Also, I want to know how to select the level in Dynamo graph.
LikeLike
January 13, 2020 at 11:02 pm
Hi Sinahiluna,
Is it walls or columns you are trying to split? If it’s walls, the code on this page should do the trick. If you are looking to split columns, then try this post…
https://danimosite.wordpress.com/2018/09/08/split-columns/
The reason for 2 posts about splitting is that walls does not allow for FamilyInstance.Split, but columns do.
As for how to select a level in Dynamo. Use the OOTB Levels node under Revit > Selection in the Node Library (or just search Levels).
Cheers,
Dan
LikeLiked by 1 person
June 28, 2017 at 7:01 pm
I cant get the code to work for me. It looks like its copying the columns but not putting them at the right location. Their is just multiple tall columns now.
LikeLike
July 10, 2017 at 9:09 pm
Hi Zachary, do you have an example of your dynamo script so I can have a look?
LikeLike
July 31, 2017 at 3:59 pm
Please have a look at this..it’s another variation, the columns are splitted by slab instead of by level but the code is the same:
https://drive.google.com/open?id=0BxH7XsYIEQEhU3pId25McmZ5ckU
https://drive.google.com/open?id=0BxH7XsYIEQEhM0lkZE1ia3ZzaTA
LikeLiked by 1 person
September 7, 2018 at 8:39 am
Hi! very useful script. But I want to split by select level only. Not all level from project. I’m not familiar with python code. How to modify to feed level list?
LikeLike
September 7, 2018 at 9:41 am
I managed to split by selected level. I modified your script.
run = tolist(IN[0])[0]
cols = tolist(UnwrapElement(IN[1]))
levz = tolist(UnwrapElement(IN[2]))
outList = []
if run:
levels = list([l for l in levz])
levels.sort(key=lambda x: x.Elevation, reverse=False)
Thank you so much.
LikeLike
September 8, 2018 at 12:22 pm
You’re welcome Zaw! Well done on modifying the code to your needs, this is great! I have since updated the code for splitting columnswhich you can find here if you are interested…https://danimosite.wordpress.com/2018/09/08/split-columns/
LikeLike
January 16, 2019 at 11:32 am
Hey Dani.
Love the script. Have been extremely useful on some of the work im doing at the moment.
I wanted to ask if you’d tried to make your code work with anything other than “Elements.of.category” ? I’m specifically trying to split certain walls in my model.
Hope you can help me out 😉
LikeLike
February 9, 2019 at 12:23 pm
Hi Mathias,
Thanks, I’m glad that you find this useful! 🙂
I’m not sure what you mean with Elements.OfCategory? You should be able to plug in whatever walls you want to split? Is this not working?
Cheers,
Dan
LikeLike
June 7, 2019 at 6:13 am
hey danimosite, the script u have there are really useful for my project and ya like what u mention there was totally correct as I also can’t split the wall if I have too many element that I wish to split. so I decided to use split list to lesser the amount of element needed to put through the script. But I was wondering if it’s possible to split the wall by level, is it possible to split the exterior wall base by the interior wall that is touching exterior wall? I wouldn’t say intersecting because whenever we create exterior together with interior wall to make the room boundary, the wall will not intersect because they are only just to cover the gap.
LikeLike
July 19, 2019 at 12:37 pm
Hi Crystal, I’m glad that you are finding the script useful. I’m not sure exactly what you mean, perhaps an image or an example case would be useful so I can better understand?
LikeLike
November 1, 2019 at 1:45 am
hi, danimosite. ur video is very useful. what is a py package?
LikeLike
January 10, 2020 at 9:40 pm
Hi, sorry for the late reply, wordpress doesn’t seem to notify me on comments anymore… found this one by accident.
I couldn’t find a reference to Py Package… where about’s did I write that? There are python modules or Dynamo packages though… Python Modules are libraries you import into your code that add extended functionality or there is Dynamo Packages that are packages of nodes.
Hope that helps! 🙂
LikeLiked by 1 person
January 9, 2020 at 7:58 am
Hello danimosite,
I have found the script very interesting, despite the fact that I am not an expert on Python. I am currently working on a project with only one wall (no columns, no slabs), and I want to split the wall into smaller pieces. I would like to know if there is a solution to split the wall by height instead of by levels. As well as, an explanation of how to do that.
Thanks in advance!
LikeLiked by 1 person
January 9, 2020 at 8:04 am
Hello danimosite,
I have found the script very interesting, despite the fact that I am not an expert on Python. I am currently working on a project with only one wall (no columns, no slabs), and I want to split the wall into smaller pieces. I would like to know if there is a solution to split the wall by height instead of by levels. If possible an explanation of how to do that will be very helpful.
Thanks in advance!
LikeLiked by 1 person