Dividing parts with dynamo

Hi ,
Can any one give me some advice on using dynamo to divide parts.
So the scenario is that I need to divide a cross laminated timber wall into modular construction, ie parts that are delivered to site and assembled on site.
The modular units need to be a given size say 2.7m in height and 2m in width but when it come to windows and doors i need a smaller cross laminated section, so i am optimizing on the waste material.(as seen in the image below)

Any advice on how to do this in dynamo is much appreciated

1 Like

Hi Jason,
There is a node custom node from “Steam nodes” package called “Element.makeparts”. Try using that.

Thanks Kulkul, much appreciated

But what im trying to do is to divide parts up into say 2m x 3m units

@Jason_Murray If you create a dynamo graph with division lines, I can help you to set up a python node with the revit API and PartUtils.DivideParts Method.

Here is an example on how create division lines along the wall in the XY-Plane:

For the API metod see http://www.revitapidocs.com/2017/45950f87-1cd6-fdfa-5167-1f42fb7b2c6b.htm:

We need the following:

  • Document (documentmanager)

  • ElementsToDivide (We can get the parts with PartUtils.GetAssociatedParts)

  • Either IntersectionRefrenceIds or a curveArray. We choose the curveArray and make the lines in Dynamo

  • SketchPlane. We can create one in dynamo as shown in the above screenshot.
    *

    public static PartMaker DivideParts(
    Document document,
    ICollection elementIdsToDivide,
    ICollection intersectingReferenceIds,
    IList curveArray,
    ElementId sketchPlaneId
    )

Ultimately that example above would result in something similar to this in Revit:

9 Likes

Now for the python script:
##CLR
Dynamo uses a version of python called IronPython which is capable of dealing with .NET libraries.

To interact with the Revit API we have to load an .NET assembly, a dll file named RevitAPI.dll, and for this we need the clr module from Iron Python. (http://ironpython.net/documentation/dotnet/)

Thus the first lines of the code looks like this:

import clr

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

##Generic collections
If we look at the arguments of DivideParts, three of them are expected to be generic collection types:

ICollection<ElementId> elementIdsToDivide,
ICollection<ElementId> intersectingReferenceIds,
IList<Curve>

To access them we need to reference .NET System.Collections.Generic. The reference and the definition of the collections can be coded like this:

clr.AddReference('System')
from System.Collections.Generic import List

#Create a list for the wall and the divisionLines
wallList = List[ElementId]()
wallList.Add(wall.Id)
intersectionElementsIds = List[ElementId]()
curveArray = List[Curve](divisionLines)

##Document and transaction
The rest of the references are well explained in dynamos wiki page on github: https://github.com/DynamoDS/Dynamo/wiki/Python-0.6.3-to-0.7.x-Migration#revitapi

The first argument in DivideParts() is the document. Dynamo provides us with a documentmanager. In our script we are making changes to the Revit database, and then a Transaction is required. Here is how the document- and transaction manager are imported:

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

Here is how to get the current document:

doc = DocumentManager.Instance.CurrentDBDocument

The DivideParts method must be placed inside a Transaction like this:

TransactionManager.Instance.EnsureInTransaction(doc)

partDivide = PartUtils.DivideParts(doc, parts, intersectionElementsIds, curveArray, sketchPlane.Id)

TransactionManager.Instance.TransactionTaskDone()

##Geometry conversion and wrapping/unwrapping of elements
This is explained in the github wiki.
All Revit elements that lives in the dynamo world are wrapped, and needs to be unwrapped before the revit api can understand them. In our case the wall and the SketchPlane are Revit elements (you can tell because of the green labeled id number. Also the lines in dynamo needs to be converted to Revit-lines, including unit conversion since the Revit api works in decimal feet.

Here are the necessary references:

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)

The elements are unwrapped with UnwrapElement() and the lines are converted with .ToRevitType() like this:

wall = UnwrapElement(IN[0])
divisionLines = [l.ToRevitType(True) for l in IN[1]]
sketchPlane = UnwrapElement(IN[2])

##The part splitting
From the Revit API documentation we find the PartUtils Class that provides us with the methods we need.

We have a wall and it need to be associated with a part that can ble divided. The if block checks if a part can be created from the wall, and if so, create it. (This takes care of walls that already are associated with a part). The next step is to get all the parts from the wall with PartUtils.GetAssociatedParts, finally the divisioning can be done with PartUtils.DivideParts

if PartUtils.AreElementsValidForCreateParts(doc, wallList):
	createParts = PartUtils.CreateParts(doc, wallList)
	doc.Regenerate()

parts = PartUtils.GetAssociatedParts(doc, wall.Id, 0, 0)

partDivide = PartUtils.DivideParts(doc, parts, intersectionElementsIds, curveArray, sketchPlane.Id)

##The complete python code

import clr

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference('System')
from System.Collections.Generic import List

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument

#Preparing input from dynamo to revit
wall = UnwrapElement(IN[0])
divisionLines = [l.ToRevitType(True) for l in IN[1]]
sketchPlane = UnwrapElement(IN[2])

#Create a list for the wall and the divisionLines
wallList = List[ElementId]()
wallList.Add(wall.Id)
intersectionElementsIds = List[ElementId]()
curveArray = List[Curve](divisionLines)

#All actions that makes changes to the Revit database needs to be inside a Transaction
TransactionManager.Instance.EnsureInTransaction(doc)

if PartUtils.AreElementsValidForCreateParts(doc, wallList):
	createParts = PartUtils.CreateParts(doc, wallList)
	doc.Regenerate()

parts = PartUtils.GetAssociatedParts(doc, wall.Id, 0, 0)
partDivide = PartUtils.DivideParts(doc, parts, intersectionElementsIds, curveArray, sketchPlane.Id)

TransactionManager.Instance.TransactionTaskDone()

OUT = partDivide

##The dynamo file
partDivisionLines.dyn (21.7 KB)

##Screenshot

42 Likes

Got nothing to say here, but goddamnit that may be the best answer on this forum ever, @Einar_Raknes! Keep up the good work!

13 Likes

@jostein_olsen Thank you, I do not have a nice blog like your self, and I thought I might share a little something while trying to learn for my self.

##How to make this work for multiple walls?
A limitation with the above python script is that it will only work for a single wall element, and that is not very efficient. We have two options to make it work for a list of walls:

  1. Wrap the python script in a custom node, be sure to define the input correctly as explained by @Dimitar_Venkov in this post. Dimitar have also improved the python re-execution performance in Dynamo with this PR. (Thank you very much for that!)

  2. Handle the list in the python node. This time I want to try the @Konrad_K_Sobon - way, and I’m using this post as a template.

##Handling list in python:
One of the first lines in Konrads code after the references and imports is this:

def ProcessList(_func, _list):
    return map( lambda x: ProcessList(_func, x) if type(x)==list else _func(x), _list )

The above function uses a python Built-in function called map() which does pretty much the same as the List.Map node in Dynamo. It takes a function and a list and calls the function for each element in the list.
The clever thing here is that the function calls it self if a sublist is a list, and so on so this will handle nested lists as well. The lambda is just a function that does not have a name and can be defined in one line. A more “basic” way to do the same as above would be something like this:

def ProcessList(_func, _list)
    ret=[]
    for x in _list:
        if type(x) == list:
            ret.append(ProcessList(_func, x))
        else:
            ret.append(_func(x))
    return ret

The next function in Konrads code is this:

def ProcessParallelLists(_func, *lists):
	return map( lambda *xs: ProcessParallelLists(_func, *xs) if all(type(x) is list for x in xs) else _func(*xs), *lists )

It does pretty much the same as ProcessList(), but the asterix (*) allows an arbitrary amount of lists as argument, if you know you have three lists, you can also make the function like this ProcessThreeLists(func_, list1, list2, list3). This works similar to the List.Combine node in Dynamo, it takes the function with a subelement in each list as an argument, and moves to the next subelements. See docs.python.org for more.

The next step is to convert the data from the input ports as before, but with the new functions:

#Preparing input from dynamo to revit
def Unwrap(item):
	return UnwrapElement(item)
	
def ToRevit(item):
	return item.ToRevitType(True)

if isinstance(IN[0], list):
	wall = ProcessList(Unwrap, IN[0])
else:
	wall = list(Unwrap(IN[0]))
	
if isinstance(IN[1], list):
	divisionLines = ProcessList(ToRevit, IN[1])
else:
	divisionLines = list(ToRevit(IN[1]))

if isinstance(IN[2], list):
	sketchPlane = ProcessList(Unwrap, IN[2])
else:
	sketchPlane = list(Unwrap(IN[2]))

Then to use ProcessParallelLists we have to create a function for our API-calls, just add a def and a colon, indent the code and add a return, and the job is done:

#Define function for API method:

def dividePart(wall,divisionLines,sketchPlane):
	wallList = List[ElementId]()
	wallList.Add(wall.Id)
	intersectionElementsIds = List[ElementId]()
	curveArray = List[Curve](divisionLines)
	
	if PartUtils.AreElementsValidForCreateParts(doc, wallList):
		createParts = PartUtils.CreateParts(doc, wallList)
		doc.Regenerate()
		
	parts = PartUtils.GetAssociatedParts(doc, wall.Id, 0, 0)
	
	return PartUtils.DivideParts(doc, parts, intersectionElementsIds, curveArray, sketchPlane.Id)

The last part is the transaction and the call to the ProcessParallelList with the api-function and the argument lists:

TransactionManager.Instance.EnsureInTransaction(doc)

partDivide = ProcessParallelLists(dividePart, wall, divisionLines, sketchPlane)

TransactionManager.Instance.TransactionTaskDone()

OUT = partDivide

##Complete code:

import clr

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference('System')
from System.Collections.Generic import List

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument

#Functions for list handling
def ProcessList(_func, _list):
    return map( lambda x: ProcessList(_func, x) if type(x)==list else _func(x), _list )

def ProcessParallelLists(_func, *lists):
	return map( lambda *xs: ProcessParallelLists(_func, *xs) if all(type(x) is list for x in xs) else _func(*xs), *lists )

#Preparing input from dynamo to revit
def Unwrap(item):
	return UnwrapElement(item)
	
def ToRevit(item):
	return item.ToRevitType(True)

if isinstance(IN[0], list):
	wall = ProcessList(Unwrap, IN[0])
else:
	wall = list(Unwrap(IN[0]))
	
if isinstance(IN[1], list):
	divisionLines = ProcessList(ToRevit, IN[1])
else:
	divisionLines = list(ToRevit(IN[1]))

if isinstance(IN[2], list):
	sketchPlane = ProcessList(Unwrap, IN[2])
else:
	sketchPlane = list(Unwrap(IN[2]))

#Define function for API method:

def dividePart(wall,divisionLines,sketchPlane):
	wallList = List[ElementId]()
	wallList.Add(wall.Id)
	intersectionElementsIds = List[ElementId]()
	curveArray = List[Curve](divisionLines)
	
	if PartUtils.AreElementsValidForCreateParts(doc, wallList):
		createParts = PartUtils.CreateParts(doc, wallList)
		doc.Regenerate()
		
	parts = PartUtils.GetAssociatedParts(doc, wall.Id, 0, 0)
	
	return PartUtils.DivideParts(doc, parts, intersectionElementsIds, curveArray, sketchPlane.Id)

TransactionManager.Instance.EnsureInTransaction(doc)

partDivide = ProcessParallelLists(dividePart, wall, divisionLines, sketchPlane)

TransactionManager.Instance.TransactionTaskDone()

OUT = partDivide

##Code for getting location line

import clr

clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)

def ProcessList(_func, _list):
    return map( lambda x: ProcessList(_func, x) if type(x)==list else _func(x), _list )

def UnwrapAndGetLocation(item):
	return UnwrapElement(item).Location.Curve.ToProtoType(True)

OUT = ProcessList(UnwrapAndGetLocation, IN[0])

##Dynamo file:
partDivisionLinesMultiple.dyn (26.5 KB)

##Screenshot:

28 Likes

@Einar_Raknes this would be a nice addition to the wiki or primer :slight_smile: (both open source)

Hi Einar, many thanks for this post!

I am using Dynamo 1.0 and cannot resolve the ‘Curve.PointsAtEqualSegmentLength’ node at Get Points. Where do you get this from?

I do have SteamNodes…

Hello Francisco,

I think it has changed its name:

Curve.PointAtDistance is now Curve.PointAtSegmentLength

From Dynamo Node Changes ¡ DynamoDS/Dynamo Wiki ¡ GitHub

But I recommend you to update to latest stable 1.1.0

Hi Einar,
Excellent work and explanation , thank you very much.
I haven’t had a chance to try it yet, but i will.

Much appreciated

@Einar_Raknes

I’ve been trying to create parts via Dynamo in a different way but parameters were missing, I had joints problems…
So thank you so much for your work, it’s just amazing! I should do more Pyhton coding. Haha

Thanks again!

1 Like

@Einar_Raknes :

How would it look like if we didn’t make the input of “Number of divisions”, but made the input “max length is x meter” and/or max weight (volume x desity factor)?

(curve length / max length) + 1 = number of divisions ?

Hi again

Okay. Thanks!

How would you do it with a ‘max volume’ or ‘max weight’?

Best regards

man. 6. mar. 2017 kl. 22.25 skrev Einar Raknes <dynamobim@discoursemail.com

As long as you know the width and height of the wall (use Element.GetParameterValueByName) it’s actually the same.
max length = max volume / (width * height)

Yeah it seems pretty simple (sorry for the dumb question)… :sweat_smile:

Unfortunately i did not get your posted script to work… it did mark the
place to split the element in Revit, but it did not split the element for
reel… am I missing something?

Best regards and thanks for your time!

man. 6. mar. 2017 kl. 23.01 skrev Einar Raknes <dynamobim@discoursemail.com

Hi @simonrbjerre

Next time ask your questions by creating new topic. You can drop references links if your query is related to the same subject. Thanks :slight_smile:

Hi all,
this is an extremely useful post - thanks to the author and contributors. I will try to tweak this to work for many categories instead of just selected walls. Because we can convert selected elements to parts then just deal with parts regardless of their original category. I am right in thinking that if we just change the selection method and define an input the use of this can be diversified?

Fantastic post! thanks for sharing the knowledge!

1 Like