It’s been a very, very long time since I posted anything. I’ve been spending most my time focusing on Unity, Dynamo View Extensions and XR (and of course my studies and work) which I hope to start posting more about. However, I thought I would revisit the topic of splitting Elements.

As it happens, this is a hot topic as it something we all hate doing and takes ages and for the life of me, I don’t understand why this is not a Built-In function in Revit. But, this is exactly the reason I learned the Revit API in the first place.  So, without further ado, here is some code to split columns at Levels.

This code was taken from my original post Split Walls and Columns By Level… and re-inspired by that posts comments (Thanks Brandon) and by a variation by Matteo Cominetti (teocomi) who does some really great stuff which you should check out.

This variation is much more efficient than my previous post – which had a tendency to crash with large amounts of columns. Also, this takes the inputs of Structural Columns and Levels so you can do your own logic for selecting which Columns/Levels you wish to split by (similar to teocomi’s variation). Also also, this allows you to specify an offset for how much above or below the Level you want to split – this is more ideal for steel columns as these tend to be spliced about 1m above Level.

StructuralColumns.SplitAtLevel (Py)

import clr

from Autodesk.DesignScript import Geometry as geom

import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc =  DocumentManager.Instance.CurrentDBDocument
import Revit
from Autodesk.Revit.DB import *

# Convert to list if not list...
def tolist(obj1):
    if hasattr(obj1,"__iter__"): return obj1
    else: return [obj1]

# Gets the centreline of the column...
def GetColumnCentreline(e):    
    crv = None    
    fs = e.Symbol
    fm = fs.Family    
    if not fm.GetType() == DirectShape:
        if not fm.IsInPlace:
            if e.IsSlantedColumn:
                    crv = e.Location.Curve.ToProtoType()
                loc = e.Location.Point.ToPoint()
                bLev = (e.Document.GetElement(e.get_Parameter(BuiltInParameter.FAMILY_BASE_LEVEL_PARAM).AsElementId()).Elevation + e.get_Parameter(BuiltInParameter.FAMILY_BASE_LEVEL_OFFSET_PARAM).AsDouble()) * ft2mm
                tLev = (e.Document.GetElement(e.get_Parameter(BuiltInParameter.FAMILY_TOP_LEVEL_PARAM).AsElementId()).Elevation + e.get_Parameter(BuiltInParameter.FAMILY_TOP_LEVEL_OFFSET_PARAM).AsDouble()) * ft2mm
                crv = geom.Line.ByStartPointEndPoint(geom.Point.ByCoordinates(loc.X,loc.Y,bLev), geom.Point.ByCoordinates(loc.X,loc.Y,tLev))
    return crv

# Convert Level to DS Plane...
def LevelToPlane(l):
    pt = geom.Point.ByCoordinates(0,0,round(l.Elevation * ft2mm,0)+offset)
    n = geom.Vector.ZAxis()
    return geom.Plane.ByOriginNormal(pt,n)

# Get the nearest Level above in list of Levels to gien Elevation...
def NearestLevelAbove(elev,lvls):
    lvlAbv = None
    for l in lvls:
        if round(l.Elevation*ft2mm,0) > elev:
            lvlAbv = l
    return lvlAbv

# Get the nearest Level in list of Levels to gien Elevation...
def NearestLevel(elev, lvls):
    return min(lvls, key=lambda x:abs(round(x.Elevation*ft2mm,0)-elev))

# Calculates the location to split column with parameterised column length (between 0 & 1)...
def CalculateSplitParameter(col,lvls):
    # Switch out for mathematical method to speed up computation...

    # abs(NewColBaseElevation-LevelAboveElevation)/NewCol length should give split parameter...
    if col:
        crv = GetColumnCentreline(col)
        elev = round(crv.StartPoint.Z,0)
        l = NearestLevelAbove(elev,lvls)        
        x = geom.Geometry.Intersect(crv,LevelToPlane(l))
        if x:
            x = x[0]
            return geom.Curve.ParameterAtPoint(crv,x)
    return None

# IN Variables...
run = tolist(IN[0])[0]
columns = tolist(UnwrapElement(IN[1]))
lvls = tolist(UnwrapElement(IN[2]))
offset = 0
if IN[3]:
    offset = tolist(IN[3])[0]

# OUT Variables...
outList = []

# Main Body...
if run:
    ft2mm = 304.8
    lvls = sorted(lvls, key=lambda x: x.Elevation)
    # Start Transaction to allow for Document modification...
    for c in columns:
        # Ensure only Structural Columns are used (modify if Arch Columns category is required)
        if c.Category.Name == Category.GetCategory(doc,BuiltInCategory.OST_StructuralColumns).Name:
            arr = []
            newCol = None
            crv = GetColumnCentreline(c)
            bLvlIndex = lvls.index(NearestLevel(crv.StartPoint.Z,lvls))
            tLvlIndex = lvls.index(NearestLevel(crv.EndPoint.Z,lvls))
            i = bLvlIndex
            while i <= tLvlIndex:
                    if not newCol:
                        p = CalculateSplitParameter(c,lvls)
                        newCol = doc.GetElement(c.Split(p))
                        p = CalculateSplitParameter(newCol,lvls)
                        newCol = doc.GetElement(newCol.Split(p))
                except Exception, ex:
                i = i+1        
            outList.append("Element is not of Category Structural Column")            
    OUT = outList
    OUT = "Please set Run to True"

Due to the pre-formatting on wordpress, sometimes copying and pasting code doesn’t work as you lose the indentation e.t.c. I have added a link to the working graph which you can download  here.

I have tested this a fair amount and only took about 5 minutes (Note: I do have a super computer) to split 400 columns by 10 Levels.

I hope this helps you in your time of need…and you can now just go grab a coffee, put your feet up and let the code do it’s thing. 🙂