Dynamo Revit set formula for a parameter in a family - python

I am trying to add a formula to a parameter within a Revit Family.
Currently I have multiple families in a project. I run Dynamo from within that project then I extract the families that I want to modify using Dynamo standard nodes.
Then I use a python script node that goes through every selected family and find the parameter I am interested in, and assign a formula for it.
That seemed fine until I noticed that it is not assigning the formula, but it is entering it as a string — as in it is in quotes. And sure enough, the code i am using will only work with Text type parameters.
Can someone shed the light on how to assign a formula to a parameter using dynamo?
see line 32 in code below
Thanks
for family in families:
TransactionManager.Instance.ForceCloseTransaction()
famdoc = doc.EditFamily(family)
FamilyMan = famdoc.FamilyManager
found.append(family.Name)
TransactionManager.Instance.EnsureInTransaction(famdoc)
check = 0
# Loop thru the list of parameters to assign formula values to them... these are given as imput
for r in range(len(param_name_lst)):
# Loop thru the list of parameters in the current family per the families outter loop above.
for param in FamilyMan.Parameters:
#for param in FamilyMan.get_Parameter(param_name_lst[r]):
# for each of the parameters get their name and store in paramName.
paramName = param.Definition.Name
# Check if we have a match in parameter name.
if param_name_lst[r] in paramName:
if param.CanAssignFormula:
canassignformula.append(param_name_lst[r])
else:
cannotassignformula.append(param_name_lst[r])
try:
# Make sure that the parameter is not locked.
if FamilyMan.IsParameterLocked(param):
FamilyMan.SetParameterLocked(param,False)
locked.append(paraName)
# Enter formula value to parameter.
FamilyMan.SetFormula(param, param_value_lst[r])
check += 1
except:
failed.append(paramName)
else:
continue

Actually, you can access the family from the main project, and you can assign a formula automatically.... That's what i currently do, i load all the families i want in one project and run the script.
After a lot of work, i was able to figure out what i was doing wrong, and in it is not in my code... my code was fine.
The main problem is that i need to have all of my formula's dependencies lined up.... just like in manual mode.
so if my formula is:
size_lookup(MY_ID_tbl, "MY_VAR", "MY_DefaultValue", ND1,ND2)
then i need to have the following:
MY_ID_tbl should exist and be assigned a valid value, in this case it should have a csv filename. Moreover, that file should be also loaded. This is important for the next steps.
MY_VAR should be defined in that csv file, so Does ND1, ND2
The default value (My_Default_Value) should match what that csv file says about that variable...in this case, it is a text.
Needless to say, i did not have all of the above lined up as it should be, once i fixed that, my setFormula code did its job. And i had to change my process altogether, cause i have to first create the MY_ID_tbl and load the csv file which i also do using dynamo, then i go and enter the formulas using dynamo.

Revit parameters can only be assigned to a formula inside the family editor only, that is the first point, so you should run your dynamo script inside the family editor for each family which will be a waste of time and you just edit the parameter's formula manually inside each family.
and the second point, I don't even think that it is possible to set a certain parameter's formula automatically, it must be done manually ( I haven't seen anything for it in the Revit API docs).

Related

How to eval all conditional formatting instances that apply to a cell ? (without rewriting excel formula parser in python)

After trying to use openpyxl to try to know which styles is applied in order to get the the actual background color of a cell after the conditional formatting has been applied and realized that I would have to write a formula parser (and it makes no sense to re-write excel and I would have to deal with chained formula cell values, etc).
I am now reaching the PyUno interface to get access via a libreoffice instance running headless and reaching the XSheetConditionalEntry object trough the PyOO interface.
Looks that I have reached the exact same place, I have the cell and the formula; but no way of knowing which of the conditional formatting styles applies or not:
def processFile(filename):
soffice = subprocess.Popen(officeCommand, shell=True)
desktop = pyoo.Desktop(pipe='hello')
doc = desktop.open_spreadsheet(filename)
sheet = doc.sheets['STOP FS 2023']
cell = sheet[5,24]
cellUno = cell._get_target()
print(f"{cellUno.getPropertyValue('CellBackColor')=}")
print(f"{cellUno.getPropertyValue('CellStyle')=}")
for currentConditionalFormat in cellUno.getPropertyValue('ConditionalFormat'):
print(f"{currentConditionalFormat.getStyleName()=}")
print(f"{currentConditionalFormat.getOperator()=}")
getting the following results
cellUno.getPropertyValue('CellBackColor')=-1
cellUno.getPropertyValue('CellStyle')='Default'
currentConditionalFormat.getStyleName()='ConditionalStyle_4'
currentConditionalFormat.getOperator()=<Enum instance com.sun.star.sheet.ConditionOperator ('BETWEEN')>
currentConditionalFormat.getStyleName()='ConditionalStyle_3'
currentConditionalFormat.getOperator()=<Enum instance com.sun.star.sheet.ConditionOperator ('NONE')>
currentConditionalFormat.getStyleName()='ConditionalStyle_2'
currentConditionalFormat.getOperator()=<Enum instance com.sun.star.sheet.ConditionOperator ('NONE')>
currentConditionalFormat.getStyleName()='ConditionalStyle_1'
currentConditionalFormat.getOperator()=<Enum instance com.sun.star.sheet.ConditionOperator ('NONE')>
The style that is being applied is the ConditoinalStyle_3
This post has helped a bit but it is intended to work inside of a macro, and looks like heir forum sign up is broken, as I would would have tried to ask the same question over there.

How to check if a key already exists in the database using Firebase and Python?

Essentially, I'll be using a database of this structure:
to keep track of the users' xp. Under the xp_data section, there will be multiple timestamps and xp numbers for each timestamp. A function will run every 24 hours, that will log the users' XP. I want to have some way to check if the player is already in the database (and if so, add to their existing xp count) and if not, create a new node for them. Here is my code for writing to the server:
db_ref = db.reference('/')
for i in range(100):
tom = await mee6API.levels.get_leaderboard_page(i)
if xp_trigger:
break
this_lb_list = {}
for l in tom['players']:
if l['xp'] < 300:
xp_trigger = True
break
this_lb_list.update({l['id']: {'name': l['username'], 'xp_data': {time.strftime(time_format_str, time.gmtime()): l['xp']}}})
details += [{ int(l['id']) : l['xp']}]
print(i)
db_ref.update(this_lb_list)
Basically, this code loops through each page in the leaderboard, obtains the XP for each user, and appends it to a dict, which is then used to update the database. there are two problems with this code, one is that it does not check if the user already exists, meaning that, and this is the second problem, that it overwrites the user's existing data. I've also attempted to write the data for each player individually, but problem 1 was still an issue, and it was painfully slow. What can I do to rectify this?
When you pass a value for a property in update(), that value replaces the entire existing value of the property in the database. So while update() leaves the properties you don't specify in the call unmodified, it does completely replace any property you do specify.
To add a value to an existing property, you'll want to specify the entire path as the key, separating the various child nodes with /.
So something like:
this_lb_list.update({'xp_data/13-Auth-2021': l['xp']})
This will write only the 13-Auth-2021 of xp_data, leaving all other child nodes of xp_data unmodified.
You'll of course want to use a variable for the date/time, but the important thing is that you specify it in the key, and not in the value of the dictionary.

How to call a file name when I want to use a small portion of the name?

I am trying for my code to pull a file when only a portion of the file name changes.
Example: I want to pull the file named JEFF_1234.csv where 1234 is an input from a GUI window.
The reason for the file name to have this structure is I want to have one main database that has multiple files for a specific part number. So if the user inputs a part number of 1234 and that will point to 4 files. JEFF_1234.csv, SAM_1234.csv, FRED_1234.csv and JACK_1234.csv.
What you need is a way to update a template with some dynamic value.
A neat way to do this is to define a template string using curly brackets as place-holders for the content that will be generated at runtime.
jefffile_template = "JEFF_{t}.csv"
Then, once you've assigned a value to the unknown pointer, you can convert your template into an appropriate string:
jeff_filename = jefffile_template.format(t="1234")
Which will store the value of JEFF_1234.csv into the variable jeff_filename for usage later in your program.
There are other similar ways of calling formatting functions, but using this by name style is my preferred method.
...and in case you're wondering, yes, this was still valid in 2.7.

How to handle unit conversions while interacting with FMUs?

I have a python script that filters and lists the parameters, their units and default values from a fmu using the read_model_description function from FMPy library and writes in an excel sheet (related discussion). Then using the simulate_fmu function the script simulates the fmu and writes the results with units back in the excel sheet.
In filtering the parameters and output variable, I use this line to get their units.
unit = variable.declaredType.unit if hasattr(variable.declaredType,'unit') else '-'
While interacting with the fmu, the parameter and variable values are in default SI units. I guess this is according to the FMI standard. However, in the modelDescription.xml under <UnitDefinitions> I see that there is information regarding the default SI unit to displayUnit conversion. For example:
<Unit
name="Pa">
<BaseUnit kg="1"
m="-1"
s="-2"/>
<DisplayUnit
name="bar"
factor="1E-05"/>
<DisplayUnit
name="ftH2O"
factor="0.0003345525633129686"/>
</Unit>
Is there a way to be able to get the parameter values and output variables in displayUnits if the conversion factors are already available in the modelDescription.xml?
Or is there a easier solution using python libraries like pint that can act as a wrapper around fmu to convert the units in desired unit system (i.e. SI to IP) while interacting with it?
In the FMPy source I did not find any place where unit conversion is implemented.
But all the relevant information is read in model_description.py.
The display unit information ends up in modelDescription.unitDefinitions. E.g. to convert a value val = 1.013e5 # Pa to all defined display units, the following might work:
for unit in modelDescription.unitDefinitions:
if unit.name == "Pa":
for display_unit in unit.displayUnits:
print(display_unit.name)
# not sure about the brackets here
print( (val - display_unit.offset)/display_unit.factor )
break
Take a look at the FMI Specification 2.01, chapter 2.2.2 Definition of Units (UnitDefinitions) to get the full picture.

py2neo: depending batch insertion

I use py2neo (v 1.9.2) to write data to a neo4j db.
batch = neo4j.WriteBatch(graph_db)
current_relationship_index = graph_db.get_or_create_index(neo4j.Relationship, "Current_Relationship")
touched_relationship_index = graph_db.get_or_create_index(neo4j.Relationship, "Touched_Relationship")
get_rel = current_relationship_index.get(some_key1, some_value1)
if len(get_rel) == 1:
batch.add_indexed_relationship(touched_relationship_index, some_key2, some_value2, get_rel[0])
elif len(get_rel) == 0:
created_rel = current_relationship_index.create(some_key3, some_value3, (my_start_node, "KNOWS", my_end_node))
batch.add_indexed_relationship(touched_relationship_index, some_key4, "touched", created_rel)
batch.submit()
Is there a way to replace current_relationship_index.get(..) and current_relationship_index.create(...) with a batch command? I know that there is one, but the problem is, that I need to act depending on the return of these commands. And I would like to have all statements in a batch due to performance.
I have read that it is rather uncommon to index relationships but the reason I do it is the following: I need to parse some (text) file everyday and then need to check if any of the relations have changed towards the previous day, i.e. if a relation does not exist in the text file anymore I want to mark it with a "replaced" property in neo4j. Therefore, I add all "touched" relationships to the appropriate index, so I know that these did not change. All relations that are not in the touched_relationship_index obviously do not exist anymore so I can mark them.
I can't think of an easier way to do so, even though I'm sure that py2neo offers one.
EDIT: Considering Nigel's comment I tried this:
my_rel = batch.get_or_create_indexed_relationship(current_relationship_index, some_key, some_value, my_start_node, my_type, my_end_node)
batch.add_indexed_relationship(touched_relationship_index, some_key2, some_value2, my_rel)
batch.submit()
This obviously does not work, because i can't refer to "my_rel" in the batch. How can I solve this? Refer with "0" to the result of the previous batch statement? But consider that the whole thing is supposed to run in a loop, so the numbers are not fixed. Maybe use some variable "batch_counter" which refers to the current batch statement and is always incremented, whenever a statement is added to the batch??
Have a look at WriteBatch.get_or_create_indexed_relationship. That can conditionally create a relationship based on whether or not one currently exists and operates atomically. Documentation link below:
http://book.py2neo.org/en/latest/batches/#py2neo.neo4j.WriteBatch.get_or_create_indexed_relationship
There are a few similar uniqueness management facilities in py2neo that I recently blogged about here that you might want to read about.

Categories