From c1515fe2bb965fdf75640961546de1d130115dd0 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Thu, 30 Jul 2026 21:14:07 -0500 Subject: [PATCH] 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 --- src/bonsai/bonsai/core/type.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/bonsai/bonsai/core/type.py b/src/bonsai/bonsai/core/type.py index 70f4dc9c48..9fcc9c5624 100644 --- a/src/bonsai/bonsai/core/type.py +++ b/src/bonsai/bonsai/core/type.py @@ -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)