A Door hosted in a Wall hosted in a Curtain Wall - find and copy type parameter into instance parameter

Hi,

I am trying to write a script to find a door hosted by a wall that is hosted in a curtain wall.
In other words, instead of using a curtain wall doors I was instructed to use a loadable door that is hosted by a wall that is a curtain wall panel now.
A CW system panel has been replaced with a Basic Wall to allow to host said door.

As convoluted as it is, I think it may be solvable but not by me just yet.

I’ve used Revit LookUp to find the connection between these three elements with a parameter called StackedWallOwnerId and even though it say Stacked Wall it actually refers to a CW hosting a Basic Wall as a system panel. Which I can’t extract in any way.

Well, anyway, here is how it looks:

and here are two streams I have created, one for the doors, and the other for the walls, to extract relevant info - let’s ignore “Copy Type Parameter and Paste It Into Instance Parameter” part for a moment.

At some point the Element.Id node reads Host ID instead of that unfortunate “basic wall as a curtain panel” Element ID.

So, what do you think, is it possible to find that info and then copy/paste or it is a manual labour?

P.S. I’ve replaced the pic as the previous was the wrong one

There’s’ the basic issue. You were instructed wrong. Now you have much extra work.
The right approach would be to use a kit of un-hosted parts for all your doors that could easily be crafted into standard or curtain wall doors. But that’s a bigger philosophical issue we could discuss at length.

You’ll need to get the curtain wall panels to get the door.
Curtain Wall > Panels > Wall hosted as Panel > Door and work your way back up.

Couldnt agree more - as bad as this intruction is, sadly it was followed by even worse and murky explanation re door schedulling. Which was inherently flawed back then as it is now. I’ve set up external CW schedules where CW have CW doors and the schedules work beautifully as they should.

Anyhoo, one stream does just that: CW->Panels->Panels that are Walls and that’s where your advise doesn’t work I’m afraid.
I’ve tried already the Element.GetHostedElements node to no avail, hence the other stream trying to work it out backwards: Door->Hosting Wall->Wall that is a Curtain Panel-> hosting Curtain Wall - it doesn’t go past the Wall=Curtain Panel.

Well, anyway, if you think something is missing here or there is a node that does what I need please let me know. I suspect that a specific Python script may be required.

Assuming that your graph is filtering the information correctly (which it looks like it is), it looks like your only issue is list levels for the final Wall ElementId comparison. You have a list of ElementIds from the Doors as a single list and a list of ElementIds from the Walls as a list of lists. You need to flatten the list of lists and then use list levels to check each ElementId in the first list against every ElementId in the second list. Right now it’s just comparing the first ElementId against all the other ElementIds.

Thank you Nick,

I think ive done it as you said but TBH it seems that both lists display entirely different IDs.
The 1. from Door PlaceHolder walls hosting doors, whereas 2. is a list of Host IDs rather than Element IDs. For some reason I can’t comprehend the Element ID is not available for extraction.
I suppose that’s why it doesn’t work.

Also, @ 3., weirdly, as it works in other locations, the Element.GetHostedElements doesn’t work with Walls that are Curtain Panels.

Maybe there is a way of exctracting that StackedWallOwnerId values, as shown in the first pic, but until then I remain as clueless as before :wink:

Your LookUp screenshot is entirely too small to read unfortunately, but if it shows the information that you want then it’s possible to get. You would just need to use the API to follow those same steps.

We can’t see the full list of ElementIds to know whether they’re different or not. If they are, then there’s nothing we can do about it. That being said, you’d want just one of the inputs to use @L1 list levels, not both. Setting both to @L1 means that you want both lists to be treated equally and will compare each index within both lists rather than the full list to each index.

It might be worth sharing a small example of this setup with your graph so that we can test for ourselves.

Sample of getting doors in walls in curtain walls. Return is a list [0] as the curtain wall and [1] as the list of doors. Adapt as needed. (Hacked out in Revit Python Shell)

Note - updated: Tinkered with it with it in code editor because I didn’t like the structure. Not sure I left it in a usable state as I wasn’t at my Revit computer. But stick some IN, OUT and some imports aand your should have a node.

selection = uidoc.Selection.GetElementIds()
cwall = next((doc.GetElement(elemid) for elemid in selection), None)

def get_curtainwall_doors(cwall):
    if not cwall.CurtainGrid:
        return []
    
    cwall_doors = []
    doors = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Doors).WhereElementIsNotElementType().ToElements()
    curtain_grid = cwall.CurtainGrid
    panel_ids = curtain_grid.GetPanelIds()

    for panel_id in panel_ids:
        panel = doc.GetElement(panel_id)

        if panel.Name == "Wall":
            wall = doc.GetElement(panel.FindHostPanel())
            panel_doors = [door for door in doors if door.Host and door.Host.Id == wall.Id]
            cwall_doors.extend(panel_doors)

    return cwall_doors

doors = get_curtainwall_doors(cwall)

for door in doors:
    print(door)

You can work efficiently if you use the Curtain Panel category and work from there. Walls as panels in curtain walls can then be filtered by class. Once you have the walls, find the inserts like doors, and if you need the curtain wall work back using the StackedWallOwnerId property
Here’s python - the output is a dictionary where the keys are the curtain wall ElementId as a string

import clr

clr.AddReference("RevitServices")
from RevitServices.Persistence import DocumentManager

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


def get_element(id):
    return doc.GetElement(id)


doc = DocumentManager.Instance.CurrentDBDocument

bic = BuiltInCategory.OST_CurtainWallPanels
fi = [False, False, False, True]

walls = FilteredElementCollector(doc).OfCategory(bic).OfClass(Wall).ToElements()

OUT = {}
for w in walls:
    OUT.setdefault(str(w.StackedWallOwnerId), []).append(
        (w, map(get_element, w.FindInserts(*fi)))
    )

And with nodes


There isn’t a readily available node to get a curtain wall from wall panel so requires a little python

import clr

clr.AddReference("RevitServices")
from RevitServices.Persistence import DocumentManager

doc = DocumentManager.Instance.CurrentDBDocument

if isinstance(IN[0], list):
    OUT = [doc.GetElement(panel.StackedWallOwnerId) for panel in UnwrapElement(IN[0])]
else:
    OUT = doc.GetElement(UnwrapElement(IN[0]).StackedWallOwnerId)

Thank you so much guys!
I will try and test these today. In the meantime I’ve attached a basic model and the script.

WIP.DoorHostedByWallHostedInCW.dyn (59.1 KB)

TEST.CW.Wall.Door.01.rvt (5.3 MB)

Guys, thanks again.

I’ve tested Mike’s Get.CurtainWallFromCurtainPanel script and it works like a charm.
I’ve tried to insert IN/OUT to Aaron’s script but my adventure with Python is far from over so I got confused and couldn’t make it work :wink:

I wonder if when using Curtain Panels category we could filter them by the Curtain Wall type they sit within?

Hence Aaron’s script should help me do that once I learn how to fit in Ins and Outs.

Here is a current script (with both streams: Mike’s and Aaron’s) and a short video showing how the Curtain Panel one works:

WIP.CurtainPanel.IsWall.HostedDoor.CopyFromTypeParameter.PasteIntoInstanceParameter.dyn (64.4 KB)

If you guys could help me push it past the post that would be absolutely marvellous!

Many thanks again!

More complete for a dyamo node.:
No need for an IN as the script will get all curtainwalls

wall.CurtainGridls is a more direct way of determining if a wall is a curtain wall or not without going through a stacked wall property. Then you can get the panels directly from there.

What this doesn’t do is get doors in a wall in a curtainwall in a curtainwall in a curtainwall in a curtainwall (you get the idea). It only gets the parent curtainwall of the door in a wall in a curtain wall. So if you are nesting curtainwalls, you’ll need additional logic.

Output is a list of lists grouped by curtainwall:

[
[curtainwall, [door, fdoor, door, etc...]],
[curtainwall, [door, fdoor, door, etc...]],
etc...
]

Should work.

import clr

clr.AddReference("RevitServices")
from RevitServices.Persistence import DocumentManager

clr.AddReference("RevitAPI")
clr.AddReference("RevitAPIUI")

import Autodesk 
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *

doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication 
app = uiapp.Application 
uidoc = uiapp.ActiveUIDocument

walls = FilteredElementCollector(doc).OfClass(Wall).WhereElementIsNotElementType().ToElements()
cwalls = [wall for wall in walls if wall.CurtainGrid]

def get_curtainwall_doors(cwall):
    if not cwall.CurtainGrid:
        return []
    
    cwall_doors = []
    doors = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Doors).WhereElementIsNotElementType().ToElements()
    curtain_grid = cwall.CurtainGrid
    panel_ids = curtain_grid.GetPanelIds()

    for panel_id in panel_ids:
        panel = doc.GetElement(panel_id)

        if panel.Name == "Wall":
            wall = doc.GetElement(panel.FindHostPanel())
            panel_doors = [door for door in doors if door.Host and door.Host.Id == wall.Id]
            cwall_doors.extend(panel_doors)
    return cwall_doors

# result = []
# for cwall in cwalls:
#     cwall_doors = get_curtainwall_doors(cwall)
#     result.append([cwall, cwall_doors])
result = [[cwall, get_curtainwall_doors(cwall)] for cwall in cwalls]

for i in result:
    print(i)

OUT = result

Thank you so much Aaron,

Sadly, Im gettng a warning - I’ve tried to run it without the IN and also plug AllElementsOfCategory into the script - the warning comes either way.

My limited knowledge and skills leave me befuddled on how to fix it.

From what you said I understand the scripts collect all CWs in the model and extracts Curtain Panels that are Walls that host Doors and then spits it out as a list of elements.
As confused as I am, im trying to select specific CW types (in this case Internal Glazed Partition) and then extract their Type Mark and copy/paste them into Doors’ instance parameter.
If I get the doors how would I know what IGPs they come from?

I appreciate your patience and do not mean to sound ungrateful or picky, it is just my brain that works in mysterious ways…

Apologies. The last line should have been:

OUT = result

Forgot to update that when I pushed it from RPS to Dynamo. I updated my previous post. Only tested it in RPS. But good for you to see how that works.

return in Python is much like a node OUT. It takes a function with input ( def mycommend( IN): ) and then return it from where it was called. get_curtainwall_doors(cwall) is the function. And it is inside a list comprehension. That loops on the items in cwalls, sending the info back to result (I called that doors before, but it really isn’t just doors as it is curtainwalls and doors.)

result = [[cwall, get_curtainwall_doors(cwall)] for cwall in cwalls]

That can also be written as:

result = []
for cwall in cwalls:
    cwall_doors = get_curtainwall_doors(cwall)
    result.append([cwall, cwall_doors])

That’s a little easier to follow if you are new at python. But list comprehension (single line) is faster and not so hard when you get the hanng of it.

That’s the great thing about python. It is great at structuring data in any format you want.
As I mentioned before, the list is being created as [wall, [door, door, door]]
Output from three curtainwalls, each with two doors loioks like:

[
[<Autodesk.Revit.DB.Wall object at 0x0000000000000061 [Autodesk.Revit.DB.Wall]>, [<Autodesk.Revit.DB.FamilyInstance object at 0x0000000000000062 [Autodesk.Revit.DB.FamilyInstance]>, <Autodesk.Revit.DB.FamilyInstance object at 0x0000000000000063 [Autodesk.Revit.DB.FamilyInstance]>]],
[<Autodesk.Revit.DB.Wall object at 0x0000000000000064 [Autodesk.Revit.DB.Wall]>, [<Autodesk.Revit.DB.FamilyInstance object at 0x0000000000000065 [Autodesk.Revit.DB.FamilyInstance]>, <Autodesk.Revit.DB.FamilyInstance object at 0x0000000000000066 [Autodesk.Revit.DB.FamilyInstance]>]],
[<Autodesk.Revit.DB.Wall object at 0x0000000000000067 [Autodesk.Revit.DB.Wall]>, [<Autodesk.Revit.DB.FamilyInstance object at 0x0000000000000068 [Autodesk.Revit.DB.FamilyInstance]>, <Autodesk.Revit.DB.FamilyInstance object at 0x0000000000000069 [Autodesk.Revit.DB.FamilyInstance]>]]
]

A list of lists. You get each item from the list - which is also a list.
Then that list has as item [0] - the curtainwall.
Item[1] is then another list of doors associated with that particular wall at item[0]
It is a tree structure you would see in the dynamo output.

From there just cycle thought that list of doors.

So the code does all the searching and grouping for you. Short fast and concise. You can see the benefit of adding in some basic python to your dynamo scripts. More control of getting the exact data you want from Revit in the format you want - fast.

Mike, Aaron,

Many thanks guys, both scripts work like a charm!
Absolutely fantastic. So grateful.
Still cant get my head around how they work - seems like magic to me :grinning_face:

Anyhoo, if anyone wants the scripts here we are:

WIP.GetDoors.HostedByWall.HostedByCW.CopyFromTypeParameter.PasteIntoInstanceParameter.dyn (64.3 KB)
WIP.CurtainPanel.IsWall.HostedDoor.CopyFromTypeParameter.PasteIntoInstanceParameter.dyn (42.3 KB)