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. 🙂

Advertisement