Move dimension anchor/regen buttons to annotation tool; ForcePerpendicularToFace toggle updates selection

workspace.py:
- Move "Set Dimension Anchor" and "Regenerate" buttons from the properties
  panel (ui.py) into draw_edit_object_interface in the annotation tool,
  visible whenever a selected object is a dimension-type IfcAnnotation
- Change force_perpendicular_to_face from a push-button (toggle=True) to
  a standard checkbox for clearer on/off state

ui.py:
- Remove the "Parametric Dimension" section (now lives in the tool header)

prop.py:
- Add _update_force_perpendicular update callback: when the checkbox is
  toggled, iterates all selected dimension annotations, writes the new
  ForcePerpendicularToFace value to each BBIM_Dimension pset, and calls
  regenerate_dimension so the constraint is applied immediately

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ryan Schultz
2026-05-16 15:24:07 -05:00
parent 9b39dd629b
commit c35023de88
3 changed files with 72 additions and 14 deletions
+58 -1
View File
@@ -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",
@@ -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):
@@ -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)