I got a weird error that I’m hoping to get some insights on. I’m trying to work with filled regions and fill patterns. Basically trying to rename my filled regions to equal the fill pattern names. Getting the names and Id’s of fill patterns work fine, but the filled regions are giving me some grief.
When I run the code below in the python shell, it works fine. Gives me all the Filled Region type names just like I want them. When I run this in my dynamo python node, I get nothing. Just an error that says “Failed to retrieve elements: property cannot be read”. I thought this might be something wrong with using FilledRegionTypes vs FillRegion, but it wasn’t. I went around it by doing a LookupParameter(“Type Name”), but I’m still curious as to why that wouldn’t work in dynamo but worked in the python shell within Revit.
Any thoughts?
filled_region_types = FilteredElementCollector(doc).OfClass(FilledRegionType).ToElements()
print(f"Found {len(filled_region_types)} filled region types")
for frt in filled_region_types:
print(f"Filled Region Name: {frt.Name}")
Can you share the full code you’re using in Dynamo? Keep in mind, you need to define OUT
if you want an output from the Python node.
@Nick_Boyts Yea, here’s the full code - albeit not fully working right now. I haven’t assigned anything to the OUT label yet since I’m just using this python node to perform a transaction. I don’t actually need the Python node to have anything in the OUT since it’s just performing a transaction for me.
Right now I’m struggling on getting the names to map properly. I need the Filled Region Type names to = the Fill Pattern Foreground name. It’s kind of a mess right now after I went down a rabbit hole last night
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
# Add the necessary Revit API references
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
import Autodesk
from Autodesk.Revit.DB import *
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
# Get the current document
doc = DocumentManager.Instance.CurrentDBDocument
def get_filled_region_name(filled_region_type):
# Try multiple approaches to get the name of the filled region type
param_names = ["Name", "Type Name", "ALL_MODEL_TYPE_NAME"]
for param_name in param_names:
try:
param = filled_region_type.LookupParameter(param_name)
if param:
return param.AsString()
except:
continue
try:
param = filled_region_type.get_Parameter(BuiltInParameter.SYMBOL_NAME_PARAM)
if param:
return param.AsString()
except:
pass
return None
def rename_filled_regions_to_match_patterns():
errors = [] # List to store error messages
# Start transaction
TransactionManager.Instance.EnsureInTransaction(doc)
try:
# Get all fill patterns
fill_pattern_types = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements()
print(f"Found {len(fill_pattern_types)} fill patterns")
# Debugging: Print type of elements in fill_patterns
for fp in fill_pattern_types:
print(f"FILL PATTERNS: Id={fp.Id}, Name={fp.Name}")
# Get all filled region types
filled_region_types = FilteredElementCollector(doc).OfClass(FilledRegionType).ToElements()
print(f"Found {len(filled_region_types)} filled region types")
# Debugging: Use parameter lookup for Name
for frt in filled_region_types:
try:
name_param = frt.LookupParameter("Type Name")
if name_param:
frt_name = name_param.AsString()
print(f"FILLED REGIONS: Id={frt.Id}, Name={frt_name}")
else:
errors.append(f"Name parameter not found for Filled Region Type Id {frt.Id.IntegerValue}")
except Exception as e:
errors.append(f"Failed to access Name of Filled Region Type Id {frt.Id.IntegerValue}: {str(e)}")
# Map fill pattern id to its name
fill_pattern_map = {fp.Id.IntegerValue: fp.Name for fp in fill_pattern_types}
# Loop through each filled region type and rename it to match its fill pattern name
for fr_type in filled_region_types:
try:
fr_foregrndpattern_id = fr_type.ForegroundPatternId.IntegerValue # match the fill pattern id with the filled region type ForegroundPatternId
fill_pattern_name = fill_pattern_map.get(fr_foregrndpattern_id)
print(f"Processing Filled Region Type Id={fr_type.Id}, ForegroundPatternId={fr_foregrndpattern_id}, FillPatternName={fill_pattern_name}")
if fill_pattern_name:
# Access the name parameter
fr_name_param = fr_type.LookupParameter("Name")
if fr_name_param:
old_name = fr_name_param.AsString()
print(f"Old Name: {old_name}, New Name: {fill_pattern_name}")
if old_name == fill_pattern_name:
print(f"Filled Region Type '{old_name}' already has the correct name")
else:
fr_name_param.Set(fill_pattern_name)
print(f"Renamed Filled Region Type from '{old_name}' to '{fill_pattern_name}'")
else:
errors.append(f"Name parameter not found for Filled Region Type Id {fr_type.Id.IntegerValue}")
else:
errors.append(f"No matching fill pattern found for Filled Region Type Id {fr_foregrndpattern_id}")
except Exception as e:
errors.append(f"Failed to rename Filled Region Type Id {fr_type.Id.IntegerValue}: {str(e)}")
except Exception as e:
errors.append(f"Failed to retrieve elements: {str(e)}")
TransactionManager.Instance.TransactionTaskDone()
# Print all collected errors at the end
if errors:
print("\nErrors:")
for error in errors:
print(error)
# Call the function
rename_filled_regions_to_match_patterns()
It runs fine for me and I can see where it errors when a Name parameter is not found. What’s the exact issue that you’re having? Does the error specify the line it comes from? Have you identified where the code is failing?
Yea, it works because I did the work-around by searching for the Type Name parameter. I was wondering why I had to look for the Type Name instead of the .Name property. That FilledRegionType.Name function works in the python shell, but not the dynamo script.
Ah, I misunderstood your question.
Unfortunately, I don’t remember the reasoning or context, but it’s a difference between obj.Name
and obj.get_Name()
. They’re effectively the same property but for different use cases. This is a case where you need to use get_Name()
.
I think @jacob.small can explain the differences.
1 Like
oof that’s it?! I’m guessing because FilledRegionType is a Type Class and FillPatternElements is an Element Class? Is that why they’re handled differently?
Is this how I would also change the name of the FilledRegionType? I’m trying to rename the FilledRegionTypes to their ForegroundPattern Name. I cleaned and updated my script, and while it’s not giving any python errors, it’s not performing the functions.
Here’s my updated script:
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
# Add the necessary Revit API references
clr.AddReference('RevitAPI')
clr.AddReference('RevitServices')
import Autodesk
from Autodesk.Revit.DB import *
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
# Get the current document
doc = DocumentManager.Instance.CurrentDBDocument
def collect_fill_pattern_elements(doc):
# Collect all fill pattern elements in the document
fill_pattern_elements = FilteredElementCollector(doc).OfClass(FillPatternElement).ToElements()
print(f"Found {len(fill_pattern_elements)} Fill Pattern Elements")
return fill_pattern_elements
def build_fill_pattern_dict(fill_pattern_elements):
# Build a dictionary of fill pattern elements mapping ID to name.
fill_pattern_dict = {fp.Id.IntegerValue: fp.Name for fp in fill_pattern_elements}
print("Fill Pattern Dictionary:", fill_pattern_dict)
return fill_pattern_dict
def collect_filled_region_types(doc):
#C ollect all filled region types in the document.
filled_region_types = FilteredElementCollector(doc).OfClass(FilledRegionType).ToElements()
print(f"Found {len(filled_region_types)} Filled Region Types")
return filled_region_types
def build_filled_region_dict(doc, filled_region_types, fill_pattern_dict):
# Build a dictionary of filled region types with relevant information.
filled_region_dict = {}
for frt in filled_region_types:
fg_fill_pattern_id = frt.ForegroundPatternId.IntegerValue
fg_fill_pattern_name = fill_pattern_dict.get(fg_fill_pattern_id, None)
if fg_fill_pattern_name is None:
print(f"ForegroundPatternID '{fg_fill_pattern_id}' - '{fg_fill_pattern_name}' not found in Fill Pattern Dictionary")
continue
type_name_param = frt.LookupParameter("Type Name")
if type_name_param is not None:
type_name = type_name_param.AsString()
else:
type_name = frt.Name
filled_region_dict[frt.Id] = {
"name": type_name,
"id": frt.Id,
"foreground_fill_pattern_name": fg_fill_pattern_name,
"foreground_fill_pattern_id": fg_fill_pattern_id
}
print("Filled Region Dictionary:", filled_region_dict)
return filled_region_dict
def rename_filled_region_types(doc, filled_region_dict):
# Rename filled region types based on their foreground fill pattern names.
name_count = {}
names_changed = 0
TransactionManager.Instance.EnsureInTransaction(doc)
try:
# This line starts a loop over each item in the filled_region_dict dictionary.
# frt_id is the key, representing the ID of a filled region type.
# frt_info is the value, a dictionary containing information about the filled region type.
for frt_id, frt_info in filled_region_dict.items():
# This line retrieves the name of the foreground fill pattern associated with the current filled region type
# from the frt_info dictionary and stores it in the variable fg_name.
fg_name = frt_info["foreground_fill_pattern_name"]
# This line checks if fg_name (the foreground fill pattern name) is not already in the name_count dictionary.
# name_count is used to keep track of how many times each foreground fill pattern name has been used.
# I'm doing this step so I can create duplicated types without having the same name because this would throw an error.
if fg_name not in name_count:
name_count[fg_name] = 1
new_name = fg_name
else:
name_count[fg_name] += 1
new_name = f"{fg_name}_dup{str(name_count[fg_name]).zfill(2)}"
# Rename the filled region type and count how many times this is done so we can report it to the terminal.
filled_region_type = doc.GetElement(frt_id)
if filled_region_type.Name != new_name:
filled_region_type.Name = new_name
names_changed += 1
TransactionManager.Instance.TransactionTaskDone()
except Exception as e:
print(f"Error during renaming: {e}")
TransactionManager.Instance.ForceCloseTransaction()
print(f"Renamed {names_changed} filled region types.")
def duplicate_and_assign_fill_region_types(doc, fill_pattern_elements, filled_region_dict, fill_pattern_dict):
# Duplicate and assign new fill region types for fill patterns without corresponding filled region types.
new_filled_region_count = 0
TransactionManager.Instance.EnsureInTransaction(doc)
try:
existing_fg_pattern_ids = {info['foreground_fill_pattern_id'] for info in filled_region_dict.values()}
for fill_pattern in fill_pattern_elements:
if fill_pattern.Id not in existing_fg_pattern_ids:
# Duplicate an existing filled region type and assign properties
sample_frt_id = next(iter(filled_region_dict.keys()))
sample_frt = doc.GetElement(sample_frt_id)
new_frt_id = sample_frt.Duplicate(sample_frt.Id)
new_frt = doc.GetElement(new_frt_id)
new_frt.ForegroundPatternId = fill_pattern.Id
new_frt.Name = fill_pattern.Name
filled_region_dict[new_frt.Id] = {
"name": new_frt.Name,
"id": new_frt.Id,
"foreground_fill_pattern_name": fill_pattern.Name,
"foreground_fill_pattern_id": fill_pattern.Id
}
new_filled_region_count += 1
TransactionManager.Instance.TransactionTaskDone()
except Exception as e:
print(f"Error during duplication: {e}")
TransactionManager.Instance.ForceCloseTransaction()
print(f"Created {new_filled_region_count} new filled region types.")
# Main function to execute the workflow
def main(doc):
fill_pattern_elements = collect_fill_pattern_elements(doc)
fill_pattern_dict = build_fill_pattern_dict(fill_pattern_elements)
filled_region_types = collect_filled_region_types(doc)
filled_region_dict = build_filled_region_dict(doc, filled_region_types, fill_pattern_dict)
rename_filled_region_types(doc, filled_region_dict)
duplicate_and_assign_fill_region_types(doc, fill_pattern_elements, filled_region_dict, fill_pattern_dict)
main(doc)
**
and my current log when i run it:
**
...lots of dictionary printing...
Python Script: Error during renaming: property cannot be read
Python Script: Renamed 0 filled region types.
Python Script: Error during duplication: No method matches given arguments for Duplicate: (<class 'Autodesk.Revit.DB.ElementId'>)
Python Script: Created 0 new filled region types.
Name is a property which requires that the object have such a property and that the property has been correctly mapped for the Pythonnengine. Common object classes have this set up, but not every one.
get_Name() is a method, meaning it is calling the C# class directly. This will attempt that call even if the property hasn’t been set up correctly.
2 Likes
I believe that’s the when but still don’t remember the why.
Yes, set_Name()
should work.
If you’re duplicating a type then the argument needs to be a string for the new type name.
1 Like
Thanks, Jacob. I knew it was something to do with the native language but couldn’t remember the specifics.
So is this essentially a gap in CLR? Does it just mean that without a mapping, Python is attempting to use the Python property on the object instead of the mapped Revit property?
In simplest terms of functional knowledge, yes.
In technical terms used only by people building virtual machines for Python execution… I am in over my head and my usual go to for ‘hey can you explain something to me like I am a five year old?’ is on vacation. Ask again in 3 weeks.
1 Like
@jacob.small and tagging onto this, could I just use get_Name() all the time, just so I don’t have to remember which one is mapped and which one isn’t? Is the only benefit the simplicity of naming?
This still doesn’t make a ton of sense why this method worked in the python shell, but not in the python node. It was making troubleshooting a real PITA lol
Correct. Except for when you are working with other classes which might not have the method. Revit and most .NET tools should work fine with that, but Python native and custom stuff may not.