From 7fcdec0a4c16c5943c0478725b9349558fb330c7 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Sat, 27 Dec 2025 09:23:33 -0600 Subject: [PATCH] extending a2a5780d59df97bfe31fdd41947b66253b65dcc3: Reuse opening if mapped. --- src/bonsai/bonsai/bim/module/model/opening.py | 100 ++++++++++++++---- 1 file changed, 78 insertions(+), 22 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/opening.py b/src/bonsai/bonsai/bim/module/model/opening.py index 962cec8bc2..b1f91a6398 100644 --- a/src/bonsai/bonsai/bim/module/model/opening.py +++ b/src/bonsai/bonsai/bim/module/model/opening.py @@ -133,6 +133,7 @@ class FilledOpeningGenerator: existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling) + # CREATE THE OPENING FIRST opening = ifcopenshell.api.root.create_entity( tool.Ifc.get(), ifc_class="IfcOpeningElement", @@ -146,25 +147,31 @@ class FilledOpeningGenerator: is_si=True, ) + # NOW HANDLE REPRESENTATION + # Variables to track if we should reuse a mapped representation + reuse_mapped_representation = False + existing_mapping_source = None + representation = None + if existing_opening_occurrence: representation = ifcopenshell.util.representation.get_representation( existing_opening_occurrence, "Model", "Body", "MODEL_VIEW" ) assert representation - # Check if mapped representation - preserve it + # Check if mapped representation - PRESERVE the mapping structure if ( representation.RepresentationType == "MappedRepresentation" and len(representation.Items) == 1 and representation.Items[0].is_a("IfcMappedItem") ): - source_rep = representation.Items[0].MappingSource.MappedRepresentation - representation = ifcopenshell.util.element.copy_deep( - tool.Ifc.get(), source_rep, exclude=["IfcGeometricRepresentationContext"] - ) + # Store the existing RepresentationMap to reuse it + existing_mapping_source = representation.Items[0].MappingSource + reuse_mapped_representation = True else: representation = ifcopenshell.util.representation.resolve_representation(representation) - else: + + if not reuse_mapped_representation: # Check for library template before generating from filling template_rep = self.get_opening_template_from_type(filling) @@ -175,9 +182,34 @@ class FilledOpeningGenerator: filling, filling_obj, opening_thickness_si=opening_thickness_si ) - mapped_representation = ifcopenshell.api.geometry.map_representation( - tool.Ifc.get(), representation=representation - ) + # Create mapped representation + if reuse_mapped_representation: + # Reuse the existing RepresentationMap - don't create a new one! + context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") + new_mapped_item = tool.Ifc.get().create_entity( + "IfcMappedItem", + MappingSource=existing_mapping_source, + MappingTarget=tool.Ifc.get().create_entity( + "IfcCartesianTransformationOperator3D", + Axis1=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(1., 0., 0.)), + Axis2=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(0., 1., 0.)), + LocalOrigin=tool.Ifc.get().create_entity("IfcCartesianPoint", Coordinates=(0., 0., 0.)), + Scale=1., + Axis3=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(0., 0., 1.)) + ) + ) + mapped_representation = tool.Ifc.get().create_entity( + "IfcShapeRepresentation", + ContextOfItems=context, + RepresentationIdentifier="Body", + RepresentationType="MappedRepresentation", + Items=[new_mapped_item] + ) + else: + mapped_representation = ifcopenshell.api.geometry.map_representation( + tool.Ifc.get(), representation=representation + ) + ifcopenshell.api.geometry.assign_representation( tool.Ifc.get(), product=opening, representation=mapped_representation ) @@ -249,12 +281,14 @@ class FilledOpeningGenerator: existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling) # Priority order for choosing representation: - # 1. Existing occurrence with Tessellation (best quality) + # 1. Existing occurrence with MappedRepresentation (preserve mapping!) # 2. Library template with Tessellation # 3. Preserved representation from old opening (maintain user's work) # 4. Generate from filling (last resort) representation_to_use = None + reuse_mapped_representation = False + existing_mapping_source = None if existing_opening_occurrence: representation = ifcopenshell.util.representation.get_representation( @@ -267,24 +301,21 @@ class FilledOpeningGenerator: and len(representation.Items) == 1 and representation.Items[0].is_a("IfcMappedItem") ): - source_rep = representation.Items[0].MappingSource.MappedRepresentation - # Prefer Tessellation from existing occurrence over preserved representation - if source_rep.RepresentationType == "Tessellation": - representation_to_use = ifcopenshell.util.element.copy_deep( - tool.Ifc.get(), source_rep, exclude=["IfcGeometricRepresentationContext"] - ) + # PRESERVE the mapped structure - reuse the same RepresentationMap + existing_mapping_source = representation.Items[0].MappingSource + reuse_mapped_representation = True else: representation_to_use = ifcopenshell.util.representation.resolve_representation(representation) - if not representation_to_use: + if not representation_to_use and not reuse_mapped_representation: template_rep = self.get_opening_template_from_type(filling) if template_rep and template_rep.RepresentationType == "Tessellation": representation_to_use = template_rep - if not representation_to_use and preserved_representation: + if not representation_to_use and not reuse_mapped_representation and preserved_representation: representation_to_use = preserved_representation - if not representation_to_use: + if not representation_to_use and not reuse_mapped_representation: opening_obj = tool.Ifc.get_object(opening) if opening_obj: tool.Ifc.unlink(element=opening) @@ -293,9 +324,34 @@ class FilledOpeningGenerator: filling_obj = tool.Ifc.get_object(filling) representation_to_use = self.generate_opening_from_filling(filling, filling_obj) - mapped_representation = ifcopenshell.api.geometry.map_representation( - tool.Ifc.get(), representation=representation_to_use - ) + # Create the mapped representation + if reuse_mapped_representation: + # Reuse existing RepresentationMap - don't create a new one! + context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") + new_mapped_item = tool.Ifc.get().create_entity( + "IfcMappedItem", + MappingSource=existing_mapping_source, + MappingTarget=tool.Ifc.get().create_entity( + "IfcCartesianTransformationOperator3D", + Axis1=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(1., 0., 0.)), + Axis2=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(0., 1., 0.)), + LocalOrigin=tool.Ifc.get().create_entity("IfcCartesianPoint", Coordinates=(0., 0., 0.)), + Scale=1., + Axis3=tool.Ifc.get().create_entity("IfcDirection", DirectionRatios=(0., 0., 1.)) + ) + ) + mapped_representation = tool.Ifc.get().create_entity( + "IfcShapeRepresentation", + ContextOfItems=context, + RepresentationIdentifier="Body", + RepresentationType="MappedRepresentation", + Items=[new_mapped_item] + ) + else: + mapped_representation = ifcopenshell.api.geometry.map_representation( + tool.Ifc.get(), representation=representation_to_use + ) + ifcopenshell.api.geometry.assign_representation( tool.Ifc.get(), product=opening, representation=mapped_representation )