Split/ Duplicate&Delete Walls at Chosen Levels

Hi,

I need to split walls that were modelled from GF to Roof into each level. The scripts I found here use all levels on the models, but I want to be able to choose which levels to be considered as I have multiple towers (and their levels) on the same file.

I started from a script that duplicates the walls changing the base/ top constraints and deletes the original. I’m trying to have the walls and levels as INPUT, but currently, the code is simply duplicating the walls with the same original base and top constraints… Any ideas why?

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 its 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]))
levels = tolist(IN[2])
 
# 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"

Thanks,

Please make sure you post code as preformatted text so that it shows correctly.
image

1 Like

Where are you trying to assign the supplied levels? Here you still get the original base and top levels and then search for their indices. You need to ignore the original levels and just used the ones you’ve specified.

I thought that in here I would have a list of all chosen levels from that building to be considered.
i equals to the index number of the base level of that wall on the list of all levels
j equals to the index number of the top level of that wall on the list of all levels
By using the function as CopyWallByLevel(w,levels[i], levels[i+1]) I’m creating walls from the base to the next level, and so forth… no?

No. If you look at the section of code I referenced above, you’ll see i and j come from FindLevelIndex() which is looking for levels matching the original base and top levels. If those levels don’t exist in your list then they don’t get set. You can remove that section of code and go straight to the wall creation using your inputs.

Hi, I think the problem is the levels list is not on the correct format/ order…

If I use this command that selects all levels in the document, it works…

    #Get All Levels in the Document and cast to .net List...
    levels = list([l for l in FilteredElementCollector(doc).OfClass(Level).ToElements()])

but if I delete that portion to use the levels as a INPUT, as shown below, it doesn’t work. What am I missing?

def tolist(obj1):
    if hasattr(obj1,"__iter__"): return obj1
    else: return [obj1]

# IN-Variables...
run = tolist(IN[0])[0]
walls = tolist(UnwrapElement(IN[1]))
levels = tolist(UnwrapElement(IN[2]))
 
# OUT-Variables...
outList = []
 
# Main Script...
# Test if user has selected Run as True...
if run:
    # Sort Levels by Elevation using a lamda expression...
    levels.sort(key=lambda x: x.Elevation, reverse=False)

We’d have to see the rest of the code to know for sure, but I can see now that your levels input is not unwrapped. They have to be unwrapped to use the API.

Sure!

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]))
levels = tolist(UnwrapElement(IN[2]))
 
# OUT-Variables...
outList = []
 
# Main Script...
# Test if user has selected Run as True...
if run:
    # 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"

Regarding your comment about the index of the levels:
The input will contain a list of ALL levels pertaining to that building. So on this portion of the code, if we have a wall that goes from L03-L06, I’m assuming it will make 3 copies of the wall, and change the base and top constraints to:
Interation 0: i = L03 i+1= L04
Interation 1: i = L04 i+1= L05
Interation 2: i = L05 i+1= L06

                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)

Regarding your comment about unwrapping the levels, isn’t this portion sufficient for that?

def tolist(obj1):
    if hasattr(obj1,"__iter__"): return obj1
    else: return [obj1]

# IN-Variables...
levels = tolist(UnwrapElement(IN[2]))

Sorry for repeating my questions, but I really couldn’t find the issues on the code!

Thanks,

Yes. You were just missing the UnwrapElement() part before.

I think I may have misunderstood your initial question, but yes, as long as your levels are supplied in order and you’ve included the base and top levels. Are you still trying to do this only for specific levels or for all levels within the span of the wall?

About the unwrap, that portion of the code seems to be the problem!
If I use python to get all levels on the model, it works. My main problem is I only want to get the levels that pertain to that building (and that’s what I’m getting to use as input).

I’m still trying to break the walls on specific levels (only the ones I’m using getting as input)

Hey, just sharing the code fixed (with ChatGPT help too :laughing:)

The script filters the levels that should be ignored when breaking the walls based on a parameter value.
The walls are still an input.

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 as 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
    collector = FilteredElementCollector(doc)
    levels = collector.OfClass(Level).ToElements()

    # Initialize an empty list to store the filtered levels
    filtered_levels = []

    # Iterate through the levels and filter the ones that do not have the value "NORTH" in the "BUILDING" parameter
    for level in levels:
        # Get the "BUILDING" parameter value
        building_param = level.LookupParameter("BUILDING")
        if building_param is not None:
            building_value = building_param.AsString()
            # Check if the value is not "SOUTH"
            if building_value != "SOUTH":
                filtered_levels.append(level)

    # Sort the filtered levels by elevation
    sorted_levels = sorted(filtered_levels, key=lambda lvl: lvl.Elevation)

    # 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(sorted_levels, base)
	            j = FindLevelIndex(sorted_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, sorted_levels[i], sorted_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 it helps :slight_smile:

can we have a look at the whole Dynamo script and the parameters you set on the levels ? I am trying to do something similar. We have a lot of levels in our model and I am trying to walls only by selected levels.