# Load the Python Standard and DesignScript Libraries
import sys
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
# The inputs to this node will be stored as a list in the IN variables.
materials = IN[0]
qnt = int(0)
description = str()
# Read list, apply description to variable and concat with period.
for i in materials:
if description == "":
description = i
qnt += 1
else:
description = description + ". " + i
qnt += 1
description += "."
# Assign your output to the OUT variable.
OUT = qnt, description
Separating by topics:
My ultimate goal is to read the room’s finish materials, get some parameters (for now, so I can exercise, I’m using Description), and apply them to some room parameter (let’s say, Wall Finish). This is the first step aiming to be able to speed up what we call “Descriptive Memorial” in Brazil (free translation).
My first and most problematic goal is to get rid of the UniqueItems and Flatten nodes. Obviously the script as it is only works for 1 room, but I couldn’t wrap my head around how to read the items inside a list on the IN[0] and maintain the structure so I can relay them to the corresponding rooms down the line.
The second would be to get rid of the GetParameterValue altogether, otherwise I would need several inputs for several parameters, and I’m sure there’s a better method that can do this easily in the python node. I’m guessing the API docs is the way, but it’s just too confusing for a noob like me. Let’s say I want to retrieve Mark and Manufacturer. Could someone direct me on how to do that?
Lastly, let’s say I have 4 finishes in a room, each one with its own Mark value. In terms of best practices, which way should I go to keep the values separated, so they can be used to relate to a standard finish DB (somehting like Uniformat, but internal)? On the Description above, what I need is exactly what I got, a concatenated set of descriptions to be used as text, but in this case (which could be an alternative) would be to retrieve only the Mark and relate it to that standard, getting all those values from outside the model (which could also help applying those values to multiple projects at once). I can dwell further if necessary.
I am a little confused as to what you’re trying to do… But some general points:
Why are you importing numpy?
You don’t need to declare it’s a string or integer, you can just write:
qnt = 0
description = ""
I can’t quite work out what your code is trying to do… You’re saying if the description is blank then make the description the name of the material. What is the point of this?
I guess you actually want the material description, not just description - " " which it does in your example?
To do this, you’d make it more like this:
materials = IN[0] ##Your list of materials
description = IN[1] ##Your list of the material description (same length as your materials list)
updatedDescription = [ ] ##List for storing your new desciptions
for ind, i in enumerate(materials):
if description[ind] == " ":
updatedDescription.append(i)
else:
updatedDescription.append(description[ind])
OUT = updatedDescription
Here’s an example (not done in Dynamo but you get the gist)
To get your parameter values, you can use the single node but feed in a list …
so in a code block write [“Mark” , “Manufacturer”, “OtherParameter”]
Then feed that into the Python node.
Say that is fed into IN[2] in your Python node.
To get each list you’d write
Mark = IN[2][0]
Manufacturer = IN[2][1]
OtherParameter = IN[2][2]
I was reading some stuff online while learning the “for” syntax and at some point tried a solution that used numpy. I forgot to take it off when I learned that just declaring an “i” solved the problem. I’ll edit the code in the original post.
About the description, my goal is to:
Get the description of the materials;
Concatenate the descriptions to form a long string with all unique descriptions;
Down the line, apply such string to the room’s “Wall Finish” parameter, or some other custom for exporting to a standard construction document required in Brazil.
I made the IF statement, because if the string is empty (first element), it would only concatenate the Description without the ". ". In this specific case, my intention was to create a single parameter from multiple values, not a list.
About the second suggestion, I get the logic behind it. What I don’t understand:
I get a list of rooms, in each room I have more than one item. If I use the IN[0][1], i will get a list that looks like this:
This is a great opportunity to learn python. Dealing with lists is very important. If you have multiple sublists (like you do) then that probably means you need subslist in your output as well. For a single list, you can count all the values and join the materials in a concatenation. For multiple lists you have to do that multiple times. This means you need output lists that you can append each sublist’s output(s) to.
That being said, I think you could accomplish this pretty quickly with a simple IF statement and concatenation using only a few nodes. If you don’t know what your list structure will look like, nodes are much more flexible since they will (typically) use whatever structure you provide them.
I was able to understand a little how to work with list of lists, and the code is as follows:
import sys
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
materiais = IN[0]
#list of concatenated values
ambientes = []
#check if the input is a list (1 room or multiple rooms)
if isinstance(materiais, list):
#define string for each room
for i in materiais:
material = ""
#check if the room ras multiple finishes
if isinstance(i, list):
#concatenate all values with ". " between them and append to a list (the space at the finish won't matter for now)
for x in i:
if not material:
material = x + ". "
else:
material += x + ". "
ambientes.append(material)
else:
material = x + "."
else:
material = ""
for i in materiais:
if not material:
material = i + ". "
else:
material += i + ". "
ambientes.append(material)
OUT = ambientes
Is there anything I could do to make this better, besides defining functions? (I still don’t know how to do it, but I’ll learn soon enough)
Have you tried using this with different list structures? I think you’re going to run into many issues here.
You’re checking to see if materiais is a list. If it’s not, you use a for loop. A for loop won’t work if you don’t have a list to loop through. The top level of your input should be a list - either a single list of materials or a list of multiple rooms.
You define material as an empty string outside its main loop. This means the if not material loop will never trigger since it already has a value and that value will always be blank.
I don’t think it’s even necessary to have separate conditions for the blank material and the concatenated string. If a blank is in the middle of the list it will reset the concatenated string and you’re already treating both conditions the same.
Also, it’s significantly better to define an expected list structure than to try to build logic around an n-dimensional list. You can always check for a single list and then nest it to match a nested list to that you can use the same logic regardless of how many rooms you have.
EDIT: I’m still not sure I understand exactly how you want your output formatted but I think you can simplify to something like this:
As I said, you’re still treating empty strings like values which leads to some floating "."s. If you just want to ignore the empty strings you can use a dynamic separation string like this:
for material in room:
separator = "" if material == "" else ". "
material_list += material + separator
It’s really complex when you’re not used to the thing For now I’m testing the workflow and learning as I go. (Trying to solve real problems feels wayy more efficient than random exercises)
For now, my output is just an excel file with two columns, one with the room name, and other with the concatenated value. The final objective is getting a set of comprehensive spreadsheets to use it paired with a custom GPT or something like that to generate automatic written documentation based on templates.
I tried it only with a sample project. The full dyn is as follows. So far, the excel file is OK, even when there’s only one item in material:
PS: It is also reading floor and ceiling finish, but I’ll filter that later.
I thought the for would run only once, but I get your point. In this specific case, the input will have a consistent structure (it is silly to imagine a real project with only one room where I work).
That is absolutely correct. Not testing for lists returned the same value, so it makes absolute sense to structure the input in a given way and simplify the code (a lot). Still, it was a great exercise.
Great suggestion. In the specific case, empty values are essentialy project errors, so this won’t be necessary, but I get the gist of how it could come in handy in similar situations. Maybe testing for empty values before the code and stopping the operation, directing the user to correct it.
It is also pretty cool to read a bit of code and understand the logic behind it, makes me feel smarter than I am
Correct. Your structure is still a list when it only has one material - that’s the point I was trying to make. The room materials will always be a list and the rooms themselves should always be a list, so there’s no need to check for them.
You’re right in that an empty string will be falsy - I had my conditions backwards. That condition will always trigger. You’re not doing a comparison there, you’re just setting a variable to false and then using a conditional which is then redundant. You already set material equal to a value, so you don’t have to do it again. The starting value is an empty string so you can continue adding to it like normal.
I think you’ve basically got it now. You can continue cleaning or simplifying as much as you want, but if it works for you that’s all that matters.
Two handy techniques that might be handy in addition to those suggested:
A function to ensure an input is a list, and if it’s just one object, put it into a list of one object - this means you can always iterate over it safely:
# Define list/unwrap list functions
def tolist(input):
result = input if isinstance(input, list) else [input]
return result
def uwlist(input):
result = input if isinstance(input, list) else [input]
return UnwrapElement(result)
# Preparing input from dynamo to revit
element = IN[0]
elements = tolist(IN[0])
uw_list = uwlist(IN[0])
Zip iteration, which allows you to loop across parallel lists in tandem in one loop. Alternatively, you could look into dictionaries which are generally more efficient, but take more time to learn and also to construct well:
Strongly echo Nick’s suggestion that Python fundamentals are the place to begin for sure. Forget what you know about Dynamo and flattening/working across levels when working in Python initially and it will be easier to approach/understand. The goal should always be to get a predictable list structure into Python and a predictable structure out of the node as well.