diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index 1bc86df319..209a83b8de 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -1038,6 +1038,62 @@ def update_sheet_data(self, context): SheetsData.is_loaded = False +def _update_force_perpendicular(self, context): + """Apply ForcePerpendicularToFace to all selected dimension annotations and regenerate them.""" + import json + import numpy as np + import ifcopenshell.util.element + import ifcopenshell.api.pset + import ifcopenshell.api.drawing as drawing_api + import bonsai.tool as tool + + file = tool.Ifc.get() + if not file: + return + + new_value = self.force_perpendicular_to_face + _DIM_TYPES = frozenset(("DIMENSION", "RADIUS", "DIAMETER", "ANGLE", "PLAN_LEVEL", "SECTION_LEVEL")) + + targets = [] + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element or not element.is_a("IfcAnnotation"): + continue + if ifcopenshell.util.element.get_predefined_type(element) not in _DIM_TYPES: + continue + pset_data = ifcopenshell.util.element.get_pset(element, "BBIM_Dimension") + if not pset_data: + continue + targets.append((obj, element, pset_data)) + + if not targets: + return + + from bonsai.bim.module.drawing.operator import _update_blender_curve + + for obj, element, pset_data in targets: + pset_entity = file.by_id(pset_data["id"]) + ifcopenshell.api.pset.edit_pset(file, pset=pset_entity, properties={"ForcePerpendicularToFace": new_value}) + + anchors = json.loads(pset_data.get("Anchors") or "[]") + placement_override = {} + for a in anchors: + guid = a.get("guid") + if not guid: + continue + try: + elem = file.by_guid(guid) + elem_obj = tool.Ifc.get_object(elem) + if elem_obj: + placement_override[elem.id()] = np.array(elem_obj.matrix_world) + except Exception: + pass + + resolved_pts = drawing_api.regenerate_dimension(file, element, placement_override=placement_override) + if resolved_pts: + _update_blender_curve(element, resolved_pts) + + class BIMAnnotationProperties(PropertyGroup): object_type: bpy.props.EnumProperty( name="Annotation Object Type", items=annotation_classes, default="TEXT", update=update_annotation_object_type @@ -1053,8 +1109,9 @@ class BIMAnnotationProperties(PropertyGroup): type_name: bpy.props.StringProperty(name="Name", default="TYPEX") force_perpendicular_to_face: bpy.props.BoolProperty( name="Force ⊥ to Face", - description="Constrain subsequent dimension vertices to lie on the line through the first vertex along its face normal", + description="Constrain dimension vertices to the face normal of the first anchor. When dimensions are selected, toggling this updates them all.", default=False, + update=_update_force_perpendicular, ) tag_rotation_mode: bpy.props.EnumProperty( name="Tag Rotation Mode", diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py index 09a62de4b8..3e7f386ea4 100644 --- a/src/bonsai/bonsai/bim/module/drawing/ui.py +++ b/src/bonsai/bonsai/bim/module/drawing/ui.py @@ -571,18 +571,6 @@ class BIM_PT_product_assignments(Panel): col.operator("bim.select_assigned_product", icon="RESTRICT_SELECT_OFF", text="") col.enabled = bool(ProductAssignmentsData.data["relating_product"]) - # Parametric dimension controls - element = tool.Ifc.get_entity(obj) - if element: - import ifcopenshell.util.element - ptype = ifcopenshell.util.element.get_predefined_type(element) - if ptype in ("DIMENSION", "RADIUS", "DIAMETER", "ANGLE", "PLAN_LEVEL", "SECTION_LEVEL"): - self.layout.separator() - self.layout.label(text="Parametric Dimension", icon="CONSTRAINT") - row = self.layout.row(align=True) - row.operator("bim.set_dimension_anchor", icon="PIVOT_CURSOR") - op = row.operator("bim.regenerate_dimensions", icon="FILE_REFRESH", text="Regenerate") - op.active_only = True def get_category_icon(category_name): diff --git a/src/bonsai/bonsai/bim/module/drawing/workspace.py b/src/bonsai/bonsai/bim/module/drawing/workspace.py index ee7161645e..285f9f9a9b 100644 --- a/src/bonsai/bonsai/bim/module/drawing/workspace.py +++ b/src/bonsai/bonsai/bim/module/drawing/workspace.py @@ -221,6 +221,8 @@ class AnnotationToolUI: props = tool.Drawing.get_document_props() row.prop(props, "should_draw_decorations", text="Viewport Annotations") + _DIMENSION_TYPES = frozenset(("DIMENSION", "RADIUS", "DIAMETER", "ANGLE", "PLAN_LEVEL", "SECTION_LEVEL")) + @classmethod def draw_edit_object_interface(cls, context): if DecoratorData.get_text_data(bpy.context.active_object): @@ -229,6 +231,17 @@ class AnnotationToolUI: row = cls.layout.row(align=True) row.operator("bim.copy_annotation_to_drawing", icon="PASTEDOWN", text="Copy To Drawing") + obj = context.active_object + element = tool.Ifc.get_entity(obj) if obj else None + if element and element.is_a("IfcAnnotation"): + ptype = ifcopenshell.util.element.get_predefined_type(element) + if ptype in cls._DIMENSION_TYPES: + cls.layout.separator() + row = cls.layout.row(align=True) + row.operator("bim.set_dimension_anchor", icon="PIVOT_CURSOR") + op = row.operator("bim.regenerate_dimensions", icon="FILE_REFRESH", text="Regenerate") + op.active_only = True + @classmethod def draw_type_selection_interface(cls): # shared by both sidebar and header @@ -254,7 +267,7 @@ class AnnotationToolUI: _DIMENSION_TYPES = {"DIMENSION", "RADIUS", "DIAMETER", "ANGLE", "PLAN_LEVEL", "SECTION_LEVEL"} if object_type in _DIMENSION_TYPES: row = cls.layout.row(align=True) - row.prop(cls.props, "force_perpendicular_to_face", toggle=True) + row.prop(cls.props, "force_perpendicular_to_face") if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP: row = cls.layout.row(align=True)