diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index aac604fb48..c27d4d25c5 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -1973,6 +1973,13 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator): ProfileDecorator.install(context) if not bpy.app.background: tool.Blender.set_viewport_tool("bim.cad_tool") + elif item.is_a("IfcAnnotationFillArea"): + tool.Model.import_annotation_fill_area(item, obj=obj) + obj.data.BIMMeshProperties.ifc_definition_id = item.id() + self.enable_edit_mode(context) + ProfileDecorator.install(context) + if not bpy.app.background: + tool.Blender.set_viewport_tool("bim.cad_tool") elif tool.Geometry.is_curvelike_item(item): tool.Model.import_curve(item, obj=obj) obj.data.BIMMeshProperties.ifc_definition_id = item.id() @@ -2126,10 +2133,7 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator): tool.Geometry.import_item(obj) elif item.is_a("IfcSweptAreaSolid"): ProfileDecorator.uninstall() - - profile = tool.Model.export_profile(obj) - - if not profile: + if not (profile := tool.Model.export_profile(obj)): def msg(self, context): self.layout.label(text="INVALID PROFILE") @@ -2186,6 +2190,26 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator): product=element, representation=new_footprint, ) + elif item.is_a("IfcAnnotationFillArea"): + ProfileDecorator.uninstall() + if not (profile := tool.Model.export_annotation_fill_area(obj)): + + def msg(self, context): + self.layout.label(text="INVALID PROFILE") + + bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") + ProfileDecorator.install(bpy.context) + self.enable_edit_mode(bpy.context) + return + + for inverse in tool.Ifc.get().get_inverse(item): + ifcopenshell.util.element.replace_attribute(inverse, item, profile) + ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item) + obj.data.BIMMeshProperties.ifc_definition_id = profile.id() + + tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) + tool.Geometry.import_item(obj) + tool.Geometry.import_item_attributes(obj) elif tool.Geometry.is_curvelike_item(item): ProfileDecorator.uninstall() new = tool.Model.export_curves(obj) diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 2eb5be5aba..709f2403e9 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -131,6 +131,12 @@ class Model(bonsai.core.tool.Model): points.append(cls.convert_si_to_unit(list(local_point))) return tool.Ifc.get().createIfcCartesianPointList2D(points) + @classmethod + def export_annotation_fill_area(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance | None: + result = cls.auto_detect_annotation_fill_area(obj, obj.data) + if isinstance(result, dict) and result["annotation_fill_area"]: + return tool.Ifc.get().add(result["annotation_fill_area"]) + @classmethod def export_profile( cls, obj: bpy.types.Object, position: Optional[Matrix] = None @@ -268,6 +274,12 @@ class Model(bonsai.core.tool.Model): return obj + @classmethod + def import_annotation_fill_area( + cls, annotation_fill_area: ifcopenshell.entity_instance, obj: Optional[bpy.types.Object] = None + ) -> bpy.types.Object: + return cls.import_profile(annotation_fill_area, obj) + @classmethod def import_profile( cls, @@ -299,6 +311,10 @@ class Model(bonsai.core.tool.Model): cls.convert_curve_to_mesh(obj, position, inner_curve) elif profile.is_a() == "IfcRectangleProfileDef": cls.import_rectangle(obj, position, profile) + elif profile.is_a() == "IfcAnnotationFillArea": + cls.convert_curve_to_mesh(obj, position, profile.OuterBoundary) + for inner_boundary in profile.InnerBoundaries or []: + cls.convert_curve_to_mesh(obj, position, inner_boundary) mesh = bpy.data.meshes.new("Profile") mesh.from_pydata(cls.vertices, cls.edges, []) @@ -1575,6 +1591,20 @@ class Model(bonsai.core.tool.Model): ) tool.Model.replace_object_ifc_representation(body, obj, representation) + @classmethod + def auto_detect_annotation_fill_area(cls, obj: bpy.types.Object, mesh: bpy.types.Mesh) -> dict | None: + result = cls.auto_detect_profiles(obj, mesh) + fill_area = None + if isinstance(result, dict) and (profile_def := result["profile_def"]): + if profile_def.is_a("IfcArbitraryClosedProfileDef"): + fill_area = result["ifc_file"].createIfcAnnotationFillArea(profile_def.OuterCurve) + elif profile_def.is_a("IfcArbitraryProfileDefWithVoids"): + fill_area = result["ifc_file"].createIfcAnnotationFillArea( + profile_def.OuterCurve, profile_def.InnerCurves + ) + if fill_area: + return {"ifc_file": result["ifc_file"], "annotation_fill_area": fill_area} + @classmethod def auto_detect_profiles( cls, obj: bpy.types.Object, mesh: bpy.types.Mesh, position: Matrix | None = None