Module 4


If you haven’t had the joy of modelling finishes in Revit, it can be quite a tedious task moving from room to room modelling the same thing, especially on large projects. Module 4 helps to reduce the time needed for this task by automatically applying wall and floor finishes to multiple rooms (by number or name input), using a specified wall and floor family type. The specified finish is applied to all boundary walls and internal columns. Walls are automatically joined to base wall to ensure openings and doors are cut and height of applied finish can be adjusted. This is ideal for any projects with multiple rooms requiring applied and scheduled finishes.

The script, as you can see below, is quite large with a lot of interconnected nodes which has resulted in noodles all over the place. I have numbered the order in which I will go through the script from 1 – 4 below. I encourage you to download the script from the bottom of the page and go through it with me. I have also tested this script across several projects so if you find any bugs, or have any suggestions, please leave a comment or get in touch with me via the About page.


To start, the Room Name/Number node, which is a renamed String node, is the first input which allows us to specify which rooms to apply finishes to. For example, the string “WC” can be input to find all rooms which contain WC in their name. Alternatively, parts of room numbers can be searched for also. This input is fed into a Python Script node along with the Boolean node. An in depth look at what is happening in this Python Script can be found at part 1.1.


As described in part 1.1 below, the output of the Python Script node in part 1 is extracted here as python outputs one list with several sublists. In the order they are shown, List.GetItemAtIndex is used to retrieve the room boundary curves from index 2 of the output which is flattened into a single list using the Flatten node. List.FirstItem retrieves all the Dynamo type curves from the room boundaries and these are used to obtain the start points of each curve using Curve.StartPoint. List.GetItemAtIndex and the index 3 gets all the levels of the rooms which are sorted by elevation using List.Sort. Finally, List.GetItemAtIndex and the index 1 are used to retrieve the internal column curves, if the room contains any. If it does not, List.Clean allows us to remove all list instances that are empty so only lists that contains curves remain.

learn Dynamo module 3

Here we need to select the finish that will line the selected walls using the Wall Types node. The wall type is then fed into a Python Script node which gets the wall type width, the details of this are shown in part 3.1. Once we have the width of the wall type, we need to convert it to the Project units being used, in this case it is millimetres. It is crucial this conversion is correct as this number is then divided in half using the / or Division node which will be used to set out the core centre lines of new finish walls we create later on.


As per previous modules, I am not going to explain the imports into Python or variable/list creation, this is explained in earlier modules. If you have any questions though, please feel free to contact me.

The toggle ( Boolean input ) is used to reset the Python code. If it is set to True, the script starts by creating a FilteredElementCollector which filters the document (doc) of the  built in category OST_Rooms which as you can probably guess, is the category for rooms. This is set to the variable collector.


Once we have all the rooms in the document set to collector, a for loop is created to check each one. For every room, we check if the string variable namnum (string input at part 1) is present in either the built in parameter ROOM_NAME or ROOM_NUMBER. If this is True, we then check if the room location does not (!=) equal to None which is basically checking if the room is placed or not, if not the loop stops. If the room does have a location, the room is appended to the rooms list and the room level is appended to the roomLevels list.



The rooms list now contains every room which contains the string specified in part 1. A loop is formed to get the boundaries of each room. First an empty list is created (arr) and a default SpatialElementBoundaryOptions which can be used to influence the results of the boundary calculations. In this case, the SpatialElementBoundaryLocation option is set to wall finish so we get the actual boundaries of each room. Using these options, GetBoundarySegments is then called and all the room boundaries are appended to the arr list.


The arr list now contains a list of sublists, each sublist containing a set of boundaries of the rooms extent and any obstruction inside the room, such as a column. As these sublists contain sublists of their own, allItems uses a python concept called list comprehension to flatten the sublists so the list only has one tier of sublists… confusing I know. Basically we are filtering out all the sublists of sublists.

Two lists are created for later (wallCurves, wallElements) and then a list (boundy) is created which contains the first item of allItems. This first item is a sublist which contains the first set of boundaries which will always be the extents of the room. This item is the deleted (del) from allItems so it will only contain all other boundaries such as columns or other obstructions within the room.


Since we have the boundary extents of the room stored in the boundy room, a loop can be created to cycle through each boundary line. For each line, we try to append the boundary as the Dynamo curve type Curve.ToProtoType to wallCurves and the element associated to the boundary to wallElements. This means we have the boundary as a curve and the wall associated with that boundary.


As we have all the curves and walls of the room boundary, we need to do the same of columns within the room. First an empty list cols is created and then we loop each item in the allItems list. Another for loop is created for each sublist in allItems which converts every boundary line into Dynamo geometry using Curve.ToProtoType and appends it to a list newLoop which is then appended to cols. We now have each sublist of each column curves contained in the cols list.


Once we have all the curves and boundary lines we need from the rooms, we append these to the lists created at the start of the script; wallLines, colCurves, bounds & roomLevels. For each room, these contain the boundary of each room as curves, the curves around each column, the boundary of each room as boundaries and the level of each room respectively.


Once we have all the curves and boundary lines we need from the rooms, we append these to the lists created at the start of the script; wallLines, colCurves, bounds & roomLevels. For each room, these contain the boundary of each room as curves, the curves around each column, the boundary of each room as boundaries and the level of each room respectively.


This simple script is used to get the width of the wall type input. The element is first unwrapped and set to the variable wall. The Width property of this wall type is then set to the variable width which is the output of the script. I’m sure, if you are getting handy with python, you should be able to reduce this code to one output line.

learn dynamo module 4

Once we have all the curves of the room  boundaries, we use these to create the wall finish. As we retrieved the Curve.StartPoint of all room boundary curves in part 2 , these can now be input to the PolyCurve.ByPoints node which creates a continuous polycurve. The Boolean node is set to True as we want the polycurve to be a complete loop.


The curves of the room boundary need to be offset to create the centre line of which the new wall lining will be based off. Here we use PolyCurve.Offset to do this by the amount obtained in part 3. This is half the wall width of the specified wall type. As we don’t want filleted corners, the extendCircular input is set to False.


As offset curves do not always offset in the direction we intend, this part helps to always ensure the room boundary curves are offset towards the inside of the room. Thanks is due to Dimitar on this thread for this idea. The Curve.Length of the original room polycurve is compared to the newly offset polycurve. These length values are fed into the comparison > node to check if the newly offset curve is larger in length to the original (which it shouldn’t be if it offset inside the original curve). These True/False results are fed into the List.FilterByBoolMask node which filters out the True and False items from the original offset polycurves. The example result is shown below. We now know which curves are True and therefore larger than the original, these need to be offset in the opposite direction.


As the polycurves leaving the in parameter in part 6 have been identified to be offsetting in the wrong direction, the original of these polycurves need to be offset in the opposite direction. To do this, PolyCurve.Offset is used again but this time the input for  distance is the negative of what was used originally. A Code Block node and variable name HalfWidth is used to convert the distance coming from part 3 to a negative.


We now have all the offsets of the room boundaries curves so the output from part 7 and the out from part 6 can be joined using List.Join. As these polycurves are now in a different order, we need to sort them using List.SortByKey. This takes a list and sorts them by the order input for keys. We want to order them by height so the Curve.StartPoint of each curve is retrieved and the Point.Z value for each of these curves is fed into the List.SortByKey. As the Z values will be sorted numerically from zero, the polycurves will be sorted in the same manner.


Wall.ByCurveAndHeight allows us to now create walls based on these offset polycurves. Firstly, they need to be broken down into the Curve type which Geometry.Explode does and this is then used for the curves input. The height input can be set by user and will specify the height of the new walls, make sure this is relative to the project units. The level input is set to the room levels obtained in part 2. As these room levels were sorted in part 2, they will correspond to the sorted polycurves. The wall type input is the wall type specified in part 3. Wall.ByCurveAndHeight node will now create walls that line the room boundary. 


As you can probably notice, this part is doing the same work that parts 4 – 7 achieve; making sure curves are offset in the right direction. However, the curves input here are the column curves obtained in part 2. The Curve.StartPoint is attained from each column curve  and the same procedure is used to offset them away from the columns.


As we did with the room boundary curves, we now have a list of polycurves that have been offset in the correct direction and joined in part 10. We then need to clean them as some rooms won’t have columns or obstructions. List.Clean does this and the result is flattened using Flatten to remove all sublists.


Now that we have all the offset column curves, they need to be associated to the right room levels. They can’t be sorted as easily as we did with the room boundaries as some rooms may not contain columns so they can’t be associated with the level order coming out of part 2. Therefore,python is needed to create this link, first we get the Curve.Startpoint from each curve and this is fed into the Python Script node. The details of this node can be seen below in part 12.1, the result will be a list of levels in the same order of the column curves.


The wall finish to wrap around columns can now be created using the Wall.ByCurveAndHeight node. First the column polycurves are exploded to extract each curve using Geometry.Explode. The height input is the same integer set in part 9. The level input is the list of room levels obtained in part 12 which are ordered according to the polycurves and the wall type is as set in part 3.


An empty list output is declared and the incoming points are unwrapped and stored under pts. These need to be unwrapped so we can use the dynamo geometry with the Revit API. Next a FilteredElementCollector of the document (doc) is created to filter evey element of class Level and store the elements under the variable levels.



Once we have all the levels in the project we need to create a loop to cycle through each point stored in pts. For each point, the point is converted to a revit geometry type using ToXyz(). Then, another loop is created to cycle through each level to check if the level.Elevation is equal to the Z value of the point. Once the loop finds an elevation at the same height as the point, the level is appended to the output list. Therefore, each point is looped through to find what level it is at and so the output of the Python script will be a series of levels at the same heights as the points input.


The new wall finishes created in part 9 need to be joined with the core walls of the room boundary. This is so openings will be cut for doors and windows. The walls output at part 9 are flattened using the Flatten node and these are input to the Python Script along with the room boundaries retrieved in part 2. The details of this python node are outlined in part 14.1. It is IMPORTANT to note also that I discovered a weird bug in Dynamo when joining walls. If the wall finish height is set below the height of the door head, it will join but the opening cut will not appear. To fix this, simply adjust the height of the wall finish, in Dynamo, to be above the door which will then show the openings and the wall can be readjusted to height desired from within Dynamo.


Here the floor finishes are created which is a little easier than creating the wall finishes. The Floor.ByOutlineTypeAndLevel node is used to achieve this. The outline(s) we use for this are the original room boundary polycurves created in part 4, before they are offset. The floorType is input from the floor type selected in the Floor Types node. The room levels retrieved in part 2 are used for the level input as these will correspond with the polycurves used. This should create the floors in the specified rooms with the specified floor finish. We can then extract the Thickness of these floors using Element.GetParameterValueByName which will be used next.


The floor finishes created in part 15 need to be offset so they sit on top of the floor slab. To do this, we need need to use the Element.SetParameterByName node to adjust the parameter of each floor. The String node is used to specify the Height Offset From Level parameter and the value is the Thickness of the floor obtained in part 15. Transaction.End is needed to ensure the floors have been offset before we cut the column holes out in part 18. 


The Thickness value obtained in part 15 is also needed to offset the newly created walls so that they sit on top of the floor and not clashing. To do this, we use the Element.SetParameterByName node again. The element input is all the room and column wall finishes we have created which are joined with the List.Join node and flattened. The parameter name is defined in the String node and is the Base Offset parameter found in the wall element. Finally the value is the Thickness value obtained in part 15, we use the List.FirstItem as the values coming out of part 15 are a list.


The last thing we need to do is cut the column lines out of the new floors. To do this, we are using another Python Script node which is explained in part 18.1 below. The inputs required for this are the new floors (IN[0]), the original curves of the columns (IN[1]) from part 2 and polygons of the room boundary curves (IN[1]), also from part 2. These polygons are created using Polygon.ByPoints and the points of the room curves obtained in part 2. List.Map and is also needed as the column curves are nested in sublists of sublists. This node lets us apply a function, Flatten in this case, to the sublists of a list so we have only one list with sublists entering the Python Script.


In this python script we need to join the finish walls (walls) to the original core walls which will be extracted from the entering Boundary segments (bounds). To start we need to loop through these boundary segments. The intentionally large number 999 is set to dist and a wall is set to fin, the meaning of these will be explained shortly. We then get the Curve geometry of the boundary and set it to cur. We find a point halfway along this curve by using Evaluate and the parameter 0.5.


Another for loop is created to cycle through each wall in the walls list. The Location.Curve geometry is obtained from the wall and we can use this curve to find the Distance from this curve to the point (param) found in 14.1.We check if the distance is smaller than 999, which is most likely True and if so, the new distance will override the dist parameter and the current wall in the loop will be set to fin. This is done so that we can find which wall is closest to the boundary as it will always override dist and fin by the time the loop ends.


Once we have the wall finish closest to the boundary, we can try to get the element associated with the boundary by using the GetElement method and boundary Element.Id as the parameter. This retrieves the core wall so we can use JoinGeometryUtils.JoinGeometry to join the wall finish (fin) to the core wall (ele) in the document (doc). If successful, “Walls Joined.” will be appended to the output list, if not, “Walls No Join.” will be appended.


This last python script will cut holes in the new floors, around the columns if there are any. First the document (doc) is retrieved along with the inputs including the new floor finishes  (floors),column curves  (holes) and room polygons (polys).


Next we start a transaction in Revit using EnsureInTransaction and great a loop of the polys list and floors list. Using the Zip function allows us to loop through each list at the same time and using j and l as the variables that each item of the list will point to. j will be the polys item and l the floors item.

For each of these items, another loop is created to cycle through the holes list of column curves. For each list of curves the first item is set to the Revit curve geometry using ToRevitType and set to the ln variable. With this curve we can then get the first end point using GetEndPoint and convert this to Dynamo geometry with ToPoint.


With the initial polygon in the loop, the function ContainmentTest lets us check if the point (pt) is located in the inside of this polygon, the result is set to test. If the point is contained in the polygon, a new CurveArray is created and a new for loop for the list of column curves. Each curve is appended to the curve array as a Revit type. With this new array, we can perform the function Create.NewOpening which takes the floor element (l), the curve array (crvArr) and True because we want the opening perpendicular to the face. This should cut the new curve array out of the floor and if successful, “Floor Cut.” is appended to the output list (lst) and the transaction is ended.

The final script will cut all the columns out of the new floors and the finishes should all be spot on. If there are not any columns or obstructions at all in any of the rooms, don’t be surprised if the columns section of the script fails. This doesn’t mean the script is broken, it just means there are no columns/obstructions and all the other finishes should be created as required.

An example of how this should work can be seen below. This quick example is adding floor and wall finishes to multiple rooms across two levels. As you can see, “Learndynamo” has been used as the search string but obviously this can be changed to whatever tickles your fancy as long as the room names or numbers contain the value.

If you come across any problems, please let me know but I have done a bit of testing so hopefully this isn’t the case. Also, if there is anything I haven’t fully explained or you do not fully understand please give me a shout via email or in the comments below. I’m sure there’s heaps of ways this script can be adapted to suit all sorts of situations so please let me know if you make a nice update. If you liked the script and want more, please follow me on twitter @LearnDynamo and subscribe to the emailing list below for the next module release and don’t forget to download the script below!

workflow exampl 2e



  1. Amir
    July 20, 2016 @ 8:28 pm


    great workflow. i have a question which is not related to this workflow. i Hope you can help me.

    is that possible to get the area of all opening within a floor with dynamo?

    thank you


    • Jeremy
      July 21, 2016 @ 12:04 am

      Hi Amir!

      Thanks, I’m glad you like.

      Yes I think that’s possible. I would most likely start by referencing the floor and retrieving its geometry. With the floor outline and opening curves, you could calculate the areas, minus the overall floor.

      If you need help doing this, please feel free to contact me directly via the About page.



  2. Shrikant Heerekar
    August 16, 2016 @ 5:54 am

    Hi Jeremy

    I have a really basic question. How do I know which method to use in python? Like how do I know when to use SpatialElementBoundaryOptions or SpatialElementBoundaryLocation or GetBoundarySegments? Is there some place or repository where I can read and try to understand how all these things tie up? Thank you very much in advance. Cheers!! 🙂


    • Jeremy
      August 25, 2016 @ 10:07 am

      Hi Shrikant,

      That’s a really good question. You will need to investigate the revit api. One place to do this is Basically, Each object has a set of methods that it can use, the API will show you what methods can be used from each object… That’s explaining it very simply so please drop me an email if you need more info.


  3. Jasper
    December 6, 2016 @ 1:55 pm

    Dear Jeremy,

    I have download your module 4.
    But the graph don’t work with my dynamo or project. (Revit 2017 & Dynamo 1.2.0

    I filled in al the things (wall type, floor type, etc) but if I want to create the wall finish and floor finish, it won’t work.

    The list that the python scripts must make are empty, so the polycurve nodes won’t work.

    Would you take a look at it.

    Kind Regards


    • Jeremy
      January 10, 2017 @ 10:06 am

      Hey Jasper,

      Sorry to hear this. I’ve been a little snowed under lately but will have a look when I get a chance and post result here.



  4. Alexandra
    June 20, 2017 @ 12:35 pm

    Hi, Jeremy.

    Thank you for a very usefull source of learning!

    I have a favour to ask: I’m new to programming, therefore, never used c#, visual basic or c++ . As there is no Python specifix syntax explained on, maybe you can dedicate one of the modules to explain what is the formalised way to translate c# syntax to python syntax, for instance? 🙂

    This will be of great help!

    Thank you again,

    Best regards,


    • Jeremy
      June 23, 2017 @ 2:32 pm

      Hi Alexandra,

      No problem, i’m glad you’re enjoying it!

      That’s a great idea… never really thought about doing that! I’m actually working on some python specific training modules that might help with the problems you might be having. If you have any specific problems though, please feel free to drop me a message and i will try to help out

      Thanks, Jeremy


      • Loek van Steijn
        July 12, 2017 @ 8:53 am

        Hello Jeremy.
        I am really interested in those Python training modules.
        When you expect to drop some? Which other courses do you recommend in the meantime.?

        Regards, Loek van Steijn


        • Jeremy
          July 26, 2017 @ 8:46 pm

          Hey mate, course is coming, will keep you posted! In the meantime, I would jump into Dynamo as much as possible!


Leave a Reply

Your email address will not be published. Required fields are marked *