door/window numbering with dynamo

I was recently working on a tool that will help us automate the process of numbering Windows and Doors in Revit based on Room that they are in. Below is a detailed explanation of how it works so far. Please feel free to comment and help me make it better.

1. First I collected all of the doors/windows in the project. “Get Family Instances by Category” is a custom node that can be downloaded from the Package Manager.

Select doors/windows in the project by category.

2. Once you have all the doors in the project it’s time to extract their room assignment information. Each door in Revit can contain information about what room it swings into. That information is generated by enabling Room Calculation Point in a Door Family Editor. I am using Revit API to extract that information:

doc = __doc__

room_number = []
room_name = []
doors = []
room = []
filter_1 = IN1
fam_inst = IN0

collector = FilteredElementCollector(doc)
phase_collector = collector.OfClass(Phase)

for i in phase_collector:
    if i.Name == “New Construction”:
        phase = i
    else:
        print(“no phase w/ specified name exists”)
for i in fam_inst:
    to_room = i.ToRoom[phase]
    from_room = i.FromRoom[phase]

Once we have the information about what rooms doors swing into and from, it’s time to put in place some filters. My general rule was that if door swings into a Circulation space I would use the room that it swings from instead. Also, if door is exterior and swings into space that contains no room (exterior), the i would also use the room that it swings from. Here’s the filter part of code:

if to_room is None or to_room.get_Parameter(“Name”).AsString() == filter_1:
        if from_room is None:
            room_number.append(“No To or From Room”)
            room_name.append(“No To or From Room”)
            doors.append(“No To or From Room”)
        else:
            room_number.append(from_room.get_Parameter(“Number”).AsString())
            room_name.append(from_room.get_Parameter(“Name”).AsString())
            doors.append(i)
            room.append(from_room)
    else:
        room_number.append(to_room.get_Parameter(“Number”).AsString())
        room_name.append(to_room.get_Parameter(“Name”).AsString())
        doors.append(i)
        room.append(to_room)
        
#Assign your output to the OUT variable
OUT = [[room_number], [room_name], [doors], [room]]

3. Now we have four (4) outputs that we can use to assign Mark values to Doors/Windows (Room Number that we decided to use based on conditions, room name, door family to assign mark value to, and room family that will become useful a little later). Since Python node in Dynamo only allows single output I had to group all this information into lists of lists. Let’s separate them with a little bit of list management:

02
4. Next step is to build a numbering sequence for our doors. I decided that I wanted them numbered in a clockwise fashion with each consecutive door/window getting a roomnumber + letter suffix. Ex. 100A, 100B, 100C and so on. First let’s knock out the clockwise order for multiple doors/windows per room.

 03

 In order to do that I measured an angle between the door location and room location(room placement point). I used a custom node called “LunchBox XYZ Angle” for that. However, since Revit like most applications by default returns the inside angle (the smaller angle), I had to figure out a way to figure out which angles are actually larger than 180 in order to get the full 360 range. For that I grabbed the location points (both door and room) and decomposed them to extract the X value only. Now, with a little bit of Python I was able to check which door location X value is smaller than the X value of room location point. If it was smaller it meant that door/window is located on the left side of the room and angles measured are actually larger than 180. For all angles larger than 180 i used a simple math function to get the correct value: angle = 180 + (180 – angle inputed). Here’s a Python code to accomplish that:

import math
door_x = IN0
room_x = IN1
angle = IN2
result = []

for i, j, k in zip(door_x, room_x, angle):
    if i <= j:
        result.append(math.pi+((math.pi)-k))
    else:
        result.append(k)

#Assign your output to the OUT variable
OUT = result

5. Now that we have the proper angles, doors and room numbers all we need is to sort them before we start assigning parameters. I used a Python node to sort all three of them simultaneously (maintaining synchronized order of all families was crucial). Here’s the code:

 from itertools import groupby
room_number = IN0
angle = IN1
door = IN2

grps = sorted(zip(room_number, angle, door), key=lambda x: (x[0], x[1]))
room_number, angle, door = [], [] ,[]
for i, grp in groupby(grps, lambda x: x[0]):
    sub_rm_number, sub_angle, sub_door = [], [] ,[]
    for j in grp:
        sub_rm_number.append(j[0])
        sub_angle.append(j[1])
        sub_door.append(j[2])
    room_number.append(sub_rm_number)
    angle.append(sub_angle)
    door.append(sub_door)

OUT = [[room_number], [angle], [door]] 

This code takes three input lists: room_number, angle and door and sorts it. First it sorts by room number from smallest to largest, then it sorts the lists by angle from smallest to largest. The outputs are three synchronized and sorted lists:

04

Next I used the sorted room number list and doors to assign proper suffix values to them and finally write them to Door’s Instance Mark Parameter.

 sequence = IN
uniq_seq = []

def increment_item(item = ‘A’):
    next_char = [ord(char) for char in item]
    next_char[-1] += 1
    for index in xrange(len(next_char)-1, -1, -1):
            if next_char[index] > ord(‘Z’):
                    next_char[index] = ord(‘A’)
                    if index > 0:
                            next_char[index-1] += 1
                    else:
                            next_char.append(ord(‘A’))
    return ”.join((chr(char) for char in next_char))

def char_generator(start = ‘A’):
    current = start
    yield start
    while True:
        current = increment_item(current)
        yield current

def build_unique_sequence(sequence):
    key_set = dict([item, char_generator()] for item in set(sequence))
    return map(lambda item:'{}{}’.format(item, key_set[item].next()), sequence)
    
OUT = build_unique_sequence(sequence)

This code generates Mark values that will be assigned using “Set List Instance Parameters” node. Since we are modifying Revit elements while doing that the “Transaction” node has to follow it.

05

The result is doors and windows numbered based on Room that they are in/swing into.

06

*Thanks to David Mans and Stack Overflow for all the help with Python coding.

** I still need to test this on a larger sample size (real project), but feel free to download the example file and play with it.

*** For those brave enough that run this script on a real project: I DO NOT TAKE RESPONSIBILITY FOR POSSIBLE CATASTROPHIC RESULTS. These components make irreversible changes to families (Door/Window Mark parameters) so detach from Central before testing.

Download link:

door_window_numbering

-Konrad

 

Support archi-lab on Patreon!

4 Comments

    • Konrad says:

      I don’t know what’s inside of your Python node. I would have to see all of your code because I am not sure what is throwing that error. Again, this was written for Dynamo 0.6.3 and I didn’t have time to translate. I am sorry, but I have no plans on translating that workflow to 0.7.1 anytime soon. If/when i do i will have it posted. Good luck!

  1. Dmitry Dronov says:

    thx a lot

Leave a Comment