RemoveAnnotationFromDrawing read context.active_object, so taking several
annotations off a drawing meant selecting and clicking them one at a time,
even though AddAnnotationToDrawing already works across the whole selection.
It now mirrors the add operator: every selected IfcAnnotation that is actually
on this drawing is removed, drawing annotations are skipped, and the outcome is
reported as removed and skipped counts. The skipped count covers annotations
that must stay on at least one drawing, which the old code could only report
for the active object.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A manual IfcAnnotation can now be shared across several drawings as one
element: editing it once updates it everywhere, and it renders into each
drawing's SVG. Assignment is native IFC - the annotation becomes a member
of multiple drawing IfcGroups - so SVG generation picks it up per-drawing
with no writer changes.
Auto-generated annotations (grid/section/elevation/level tags, via
is_auto_annotation) are excluded since they are regenerated per drawing;
manual annotations, dimensions included, are shareable.
- core.add_annotation_to_drawing / remove_annotation_from_drawing
- tool.Drawing.get_annotation_drawings (plural)
- collector links a shared annotation into every assigned drawing collection
- import_annotations_in_group re-collects already-imported shared annotations
- bim.add_annotation_to_drawing / bim.remove_annotation_from_drawing operators
- BIM_PT_annotation_drawings panel (add/remove drawings on an annotation)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move is_manual_reference UI block to before _DIMENSION_TYPES to match the
position established by PR #7965's conflict resolution already present in
the build. The prior merge fix (66fcad84e3) placed it after _DIMENSION_TYPES,
causing a 3-way merge conflict when the build's #7965 has it before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both PR #7798 (ManualDrawingReference) and PR #8083 (parametric_dimensions)
independently added properties after entry #33 in Psets_BBIM_Annotation.ifc
and new BoolProperty declarations after `type_name` in prop.py and workspace.py
UI rows at the same locations.
Resolution: keep #8083's IDs (#34-#40) unchanged, renumber #7798's
IsManualDrawingReference and IsDocumentReference entries to #41 and #42,
update EPset_Annotation reference list accordingly, keep both UI rows,
and combine hotkey_S_A to use #8083's parametric-dimension routing
with #7798's "INVOKE_DEFAULT" argument for add_annotation.
Vertical faces perpendicular to the camera cannot be hit by raycast, so
the dimension snap tool missed them entirely. Fix by checking nearby
objects whose 3D bbox contains the floor hit point and running
mode-appropriate candidate lookup on each:
- FACE mode: _snap_on_coplanar_faces finds vertical mesh faces with a
bottom edge at the hovered Z, projects the cursor onto the face plane,
and returns a FACE candidate (blue outline + face snap point).
- LAYER mode: get_layer_snap_candidates now runs on nearby bbox objects
the same way VERTEX/EDGE mode already did, using the shared
_snap_cand_multi_cache (cleared on TAB mode switch).
- Remove clear_snap_objs() from PolylineOperator.invoke — BVH cache now
persists across invocations; per-entry staleness is checked in
create_snap_obj via matrix_world equality + vertex count, eliminating
the ~11 s full rebuild on every Shift+A press.
- Add _init_snapping_points() hook to PolylineOperator; DrawParametricDimension
overrides it with a cheap plane-intersection placeholder, deferring full
BVH detection to the first MOUSEMOVE.
- Cache matrix_world in SnapObj and replace O(N_vertices) validation loop
with O(1) matrix equality + single sample vertex check, cutting per-call
create_snap_obj cost from 22-600 ms to <0.2 ms on cache hits.
- Use scene-level BVH pierce-through in SetDimensionAnchor._compute_candidates
instead of per-object ray_cast loop (O(log N) vs O(N_objects)).
- Guard PolylineDecorator snap_mouse_point access against empty collection
to prevent IndexError before first MOUSEMOVE populates the property.
- Wrap closest_point_on_mesh in try/except RuntimeError in
_update_snap_draw_data for annotation objects with no internal mesh data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DrawParametricDimension: TAB cycles snap mode FACE→LAYER→EDGE→VERTEX during
placement; consumes both PRESS and RELEASE when not in input mode to avoid
conflict with polyline Cycle Input
- DrawParametricDimension: IFC-native snap candidate overrides polyline cursor
position in LAYER/EDGE/VERTEX modes; FACE mode shows polygon outline; reuses
_snap_draw_data / _draw_snap_indicator_global infrastructure from SetDimensionAnchor
- DrawParametricDimension: LAYER_BOUNDARY support in _update_perp_constraint,
deriving normal from LayerSetDirection (AXIS1/2/3)
- ClickNearestDimensionAnchor: scan all visible annotations instead of only
selected ones — view3d.select deselects the dimension before this operator
runs, so pre-selection check caused dots to never activate
- AnnotationTool keymap: bim.click_nearest_dimension_anchor placed before
view3d.select so it fires first when the annotation tool is active
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ClickNearestDimensionAnchor: scan all selected objects instead of active
object so clicking a green dot doesn't lose to the underlying IFC geometry
- GizmoAnchorHandle: remove draw_select entirely (any entry in the select
buffer causes Blender's gizmo system to consume clicks); keep purely visual
- Scale anchor dots to scale_basis = 0.2
- Fix ReferenceError in decoration.py draw loop after undo by catching
ReferenceError and resetting DecoratorData.is_loaded
- SetDimensionAnchor: inherit tool.Ifc.Operator so IFC pset writes are
tracked for undo; finish the modal after each face write so each anchor
gets its own undo step
- Fix ReferenceError in _modal after undo when annotation RNA is freed
- handler.py: add regenerate_dims_for_layer; call it from
EditMaterialSetItem._execute so dimensions update when layer thickness changes
- regenerate_dimension.py: fix ForcePerpendicularToFace for LAYER_BOUNDARY
anchors by deriving the thickness-axis normal from LayerSetDirection
(AXIS2→Y, AXIS1→X, AXIS3→Z) instead of requiring a stored normal_local
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GizmoAnchorHandle + DimensionAnchorWidget: colored dot gizmos at each
dimension curve vertex (green=anchored, orange=free); color changes to
blue while SetDimensionAnchor is in PICK_FACE mode for that vertex
- ClickNearestDimensionAnchor (LMB keymap): Python proximity operator that
fires SetDimensionAnchor pre-targeted at the nearest anchor dot within
120px, returning PASS_THROUGH for misses so normal viewport clicks are
unaffected
- SetDimensionAnchor: added anchor_index prop to enter PICK_FACE directly;
set_active_anchor called at all phase transitions (invoke, vertex-pick,
face-pick, alt-click free, ESC/RMB) so gizmo color tracks state correctly
- handler._sync_dimension_anchors_to_curve: proximity-based anchor sync
when curve vertex count changes in Edit Mode (subdivide / delete)
- depsgraph_update_post_handler: regenerates dimensions when referenced
elements move; also handles annotation curve edits directly
- Remove standalone Set Anchor button from annotation tool UI (replaced by
clicking a gizmo dot)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BBIM_Dimension.LinePosition (IfcLengthMeasure): holds the dimension line at
a fixed global coordinate along cross(world_Z, dim_direction), independent of
geometry movement
- regenerate_dimension applies LinePosition only when ForcePerpendicularToFace is
also set (the two are semantically coupled); anchor["pt"] always stores the true
surface hit so the measured length is unaffected
- BIMAnnotationProperties.line_position uses get/set callbacks instead of an update
callback to avoid the 'Writing to ID classes in this context is not allowed' error
that fires when Blender draws the tool header
- DimensionLinePositionWidget (BIM_GGT_dimension_line_position): gizmo group with
two opposing GizmoCone handles at the curve midpoint; poll requires
ForcePerpendicularToFace so the handles only appear when the feature is active
- UI: line_position field and gizmo are hidden when ForcePerpendicularToFace is off
- Psets_BBIM_Annotation.ifc: #40 LinePosition template added to BBIM_Dimension
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
SetDimensionAnchor — hover-select-then-confirm:
- Cursor highlights candidate IFC elements (orange Blender selection outline)
before committing; Tab cycles through overlapping/coplanar candidates
- _compute_candidates: ray-cast all IFC mesh objects; falls back to 2D
bounding-box proximity (5 cm tolerance) for plan-view picks where the
ray misses the mesh by sub-mm amounts
- _write_anchor: after anchoring a face, immediately calls
regenerate_dimension with placement_override (Blender matrix_world)
and _update_blender_curve so the curve vertex moves to the resolved point
DrawParametricDimension — ForcePerpendicularToFace live snap constraint:
- Reads force_perpendicular_to_face toggle from annotation props on invoke
- After anchor[0] is placed on a FACE, _update_perp_constraint extracts
the face normal and stores it as the constraint axis
- _apply_perp_constraint runs every modal tick after handle_snap_selection,
projecting the current snap point onto pt[0] + t*normal
- On finalize, _create_dimension_from_polyline writes ForcePerpendicularToFace
to the BBIM_Dimension pset and calls regenerate_dimension to snap the
stored curve to the constraint before the operator exits
regenerate_dimension.py:
- ForcePerpendicularToFace block: after resolving all anchors, projects
vertices 1…n onto the line through pt[0] along anchor[0]'s face normal
- _get_anchor_face_normal_world: reads normal_local from anchor fingerprint,
calls _rotate_local_to_world with placement_override; falls back to stored
world-space normal
resolve_anchor.py:
- _rotate_local_to_world: transforms an element-local direction vector to
world space using the element's placement or placement_override matrix
pset/operator.py:
- EditPset._execute: after editing a BBIM_Dimension pset on an IfcAnnotation,
auto-calls regenerate_dimension + _update_blender_curve so changes to
anchors/ForcePerpendicularToFace are reflected immediately in the viewport
prop.py / workspace.py:
- Added force_perpendicular_to_face BoolProperty to BIMAnnotationProperties
- UI toggle shown in annotation tool header for DIMENSION/RADIUS/DIAMETER/
ANGLE/PLAN_LEVEL/SECTION_LEVEL types
Psets_BBIM_Annotation.ifc:
- Added ForcePerpendicularToFace property template (#39) to BBIM_Dimension
- Extended BBIM_Dimension applicability to ANGLE, PLAN_LEVEL, SECTION_LEVEL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends parametric dimension support with a modal polyline operator that uses
Bonsai's existing snap infrastructure (same as walls/slabs) for placing anchor
points. Shift+A in the Annotation tool now routes dimension types through this
operator instead of the generic add_annotation path.
- DrawParametricDimension: PolylineOperator subclass; each confirmed snap point
is converted to a BBIM_DimensionTarget anchor via _snap_to_anchor, which reads
face_index from the snap dict for face hits and falls back to closest_point_on_mesh
for vertex/edge hits
- handle_inserting_polyline override tracks anchor list in sync with polyline
points (insert on count increase, pop on BACKSPACE)
- hotkey_S_A dispatches to bim.draw_parametric_dimension for DIMENSION/RADIUS/
DIAMETER/ANGLE/PLAN_LEVEL/SECTION_LEVEL types; all other types keep existing path
- depsgraph_update_post_handler extended to also watch is_updated_geometry so
dimensions auto-regenerate when a referenced mesh is edited in Edit Mode; the
affected element's tessellation is evicted from _dim_shape_cache so resolve_anchor
re-tessellates from the updated IFC representation on the next pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New modal operator (bim.set_dimension_anchor) anchors dimension vertices
to IFC element faces. Anchors are stored as JSON in a BBIM_DimensionTarget
pset on the IfcAnnotation and resolved via tessellation at regeneration time.
- resolve_anchor.py / regenerate_dimension.py: new ifcopenshell API modules
- bim.set_dimension_anchor: 2-phase Object Mode modal (pick vertex → pick face)
- bim.regenerate_dimensions: recomputes all parametric dimensions
- Auto-regeneration via depsgraph_update_post when referenced elements move
- placement_override reads Blender matrix_world for G-moved elements
- Plan-view annotations flattened to annotation plane (Z=0 in local space)
- IfcIndexedPolyCurve.Segments rebuilt to handle n-point chains correctly
The x-angle transformation for LAYER3 slabs assumed SweptArea
is always IfcArbitraryClosedProfileDef (which has OuterCurve),
but composite profiles use IfcCompositeProfileDef instead.
Apply the coord scaling to each sub-profile individually.
Generated with the assistance of an AI coding tool.
generate_section_reference_points had no handler for
MODEL_VIEW target view, causing it to silently return
None. Add MODEL_VIEW branch that clips the section line
to XY camera bounds while preserving the Z coordinate
for correct 3D placement.
Generated with the assistance of an AI coding tool.
Guard the int() cast on CardinalPoint in
BIM_OT_edit_assigned_material so a None value (no cardinal
point set) no longer raises a TypeError.
Generated with the assistance of an AI coding tool.
Extracted from the font file like so:
python3 -c "
from fontTools.ttLib import TTFont
tt = TTFont('src/bonsai/bonsai/bim/data/fonts/OpenGost Type B TT.ttf')
for record in tt['name'].names:
if record.nameID == 13:
print(record.toUnicode())
"