Bonsai: re-point an occurrence's material usage when the mapping is skipped

assign_type passes should_map_representations=False for a per-instance profile
occurrence so the new type's shared representation is not mapped over its own
body. That flag gates more than the representation: in api type.assign_type it
also guards map_material_usages, the step that re-points an occurrence's
IfcMaterialProfileSetUsage at the new type's material set.

The occurrence therefore kept a usage whose ForProfileSet still referenced the
OLD type's IfcMaterialProfileSet. get_material(should_skip_usage=True) follows
that pointer, so the material panel showed, and edited, the old type's profile.
Duplicating a profile-based type and then changing the copy's profile silently
rewrote the original's profile instead, taking every other occurrence of the
original with it.

Re-point the usage here when the mapping is suppressed. Reusing
material.assign_material rather than setting ForProfileSet by hand matters: its
update_representation_profile rewrites SweptArea to the new type's profile but
never touches the extrusion depth, so the per-instance length this branch exists
to protect still survives. restore_material_usage_attributes runs afterwards, so
recorded usage attributes land on the new usage.

Traced in Blender against a duplicated IfcFurnitureType: before the fix the
profile edit reported the source type's IfcMaterialProfile, after it the copy's,
while the occurrence's representation entity ids were unchanged across the
retype.

Generated with the assistance of an AI coding tool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Ryan Schultz
2026-07-30 21:14:07 -05:00
parent 44339de804
commit c1515fe2bb
+14
View File
@@ -54,6 +54,20 @@ def assign_type(
relating_type=type,
should_map_representations=should_map,
)
if not should_map:
# should_map_representations=False also suppresses api type.assign_type's
# map_material_usages, which is what re-points an occurrence's usage at the new type's
# material set. Skipping it leaves the occurrence's IfcMaterialProfileSetUsage pointing
# at the OLD type's set, so the material panel then edits the old type's profile and
# every other occurrence of that type moves with it. Re-point it here instead: this
# rewrites SweptArea to the new type's profile but leaves the extrusion depth alone,
# so the per-instance length this branch exists to protect still survives.
type_material = ifcopenshell.util.element.get_material(type)
if type_material and (material_class := type_material.is_a()) in (
"IfcMaterialLayerSet",
"IfcMaterialProfileSet",
):
ifc.run("material.assign_material", products=[element], type=f"{material_class}Usage")
obj = ifc.get_object(element)
if (usage := model.get_usage_type(type)) and usage_attributes:
type_tool.restore_material_usage_attributes(element, usage_attributes)