mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 23:36:20 +00:00
Sweep docstrings for rot-prone references
Docstrings naming sibling methods, private helpers, test files, or historical symbols silently go wrong on rename. Strip Sphinx :meth: / :class: / :func: / :attr: markup that mostly added noise (no Sphinx in this project), and rewrite five docstrings that cited specific test paths or private hooks to describe the behaviour instead. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -31,18 +31,11 @@ from . import handler, operator, prop, ui
|
|||||||
|
|
||||||
|
|
||||||
def _parametric_gizmo_preference_classes() -> list[type]:
|
def _parametric_gizmo_preference_classes() -> list[type]:
|
||||||
"""Lazy resolution. ``bonsai.tool/__init__.py`` transitively loads
|
"""Resolves the registry-driven ``GizmoPreferences<X>`` classes for the
|
||||||
``tool/ifc.py`` (and several siblings) which import ``from bonsai.bim.ifc
|
``classes`` list below. ``import bonsai.tool`` is kept local to surface
|
||||||
import IfcStore`` at module top — that ``tool → bim`` cycle means
|
the load-order constraint: it relies on ``from . import handler, …``
|
||||||
``bonsai.tool`` cannot be imported here before ``from . import handler, …``
|
above having primed the
|
||||||
above has primed the bim partial-import dance through ``handler``'s own
|
``tool/ifc.py → bim/ifc.py → bim/handler.py → bonsai.tool`` cycle."""
|
||||||
``import bonsai.tool``. By the time this function runs (during the
|
|
||||||
classes-tuple build below), ``handler`` has fully loaded and ``bonsai.tool``
|
|
||||||
is safely importable.
|
|
||||||
|
|
||||||
The architectural root cause is ``IfcStore`` living in ``bim/ifc.py``;
|
|
||||||
moving it to ``tool/ifc.py`` would let ``tool/`` stop reaching into ``bim/``
|
|
||||||
and eliminate the need for this indirection. Tracked separately."""
|
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
|
|
||||||
return tool.Parametric.iter_gizmo_preference_classes(ui)
|
return tool.Parametric.iter_gizmo_preference_classes(ui)
|
||||||
|
|||||||
@@ -3168,7 +3168,7 @@ class GizmoMerge(TrisGizmoMixin, bpy.types.Gizmo):
|
|||||||
|
|
||||||
class GizmoSplit(TrisGizmoMixin, bpy.types.Gizmo):
|
class GizmoSplit(TrisGizmoMixin, bpy.types.Gizmo):
|
||||||
"""Two arrows pointing outward away from each other — conveys splitting/cutting
|
"""Two arrows pointing outward away from each other — conveys splitting/cutting
|
||||||
one element into two. Visual inverse of :class:`GizmoMerge`."""
|
one element into two. Visual inverse of `GizmoMerge`."""
|
||||||
|
|
||||||
bl_idname = "VIEW3D_GT_split"
|
bl_idname = "VIEW3D_GT_split"
|
||||||
|
|
||||||
@@ -3216,7 +3216,7 @@ class GizmoExtend(TrisGizmoMixin, bpy.types.Gizmo):
|
|||||||
|
|
||||||
|
|
||||||
class GizmoExtendVertical(TrisGizmoMixin, bpy.types.Gizmo):
|
class GizmoExtendVertical(TrisGizmoMixin, bpy.types.Gizmo):
|
||||||
"""Vertical sibling of :class:`GizmoExtend` — arrow pointing UP into a horizontal
|
"""Vertical sibling of `GizmoExtend` — arrow pointing UP into a horizontal
|
||||||
bar. Conveys extending an element's height to a target Z."""
|
bar. Conveys extending an element's height to a target Z."""
|
||||||
|
|
||||||
bl_idname = "VIEW3D_GT_extend_vertical"
|
bl_idname = "VIEW3D_GT_extend_vertical"
|
||||||
@@ -4211,7 +4211,7 @@ class BillboardingGizmoGroupMixin:
|
|||||||
operator: str,
|
operator: str,
|
||||||
alpha: float = 0.8,
|
alpha: float = 0.8,
|
||||||
) -> bpy.types.Gizmo:
|
) -> bpy.types.Gizmo:
|
||||||
"""Convenience wrapper over :func:`setup_icon_gizmo` for subclasses."""
|
"""Convenience wrapper over `setup_icon_gizmo` for subclasses."""
|
||||||
return setup_icon_gizmo(self, gizmo_type, color, highlight_color, operator, alpha)
|
return setup_icon_gizmo(self, gizmo_type, color, highlight_color, operator, alpha)
|
||||||
|
|
||||||
def position_gizmos(self, context: bpy.types.Context) -> None:
|
def position_gizmos(self, context: bpy.types.Context) -> None:
|
||||||
@@ -4445,7 +4445,7 @@ class BaseParametricGizmoGroup:
|
|||||||
) -> float:
|
) -> float:
|
||||||
"""Y coordinate just outside the camera-facing face of an element.
|
"""Y coordinate just outside the camera-facing face of an element.
|
||||||
|
|
||||||
Generalises :meth:`get_y_position_for_view` for elements whose near face
|
Generalises `get_y_position_for_view` for elements whose near face
|
||||||
isn't at the local origin. ``near_y`` is the local-Y of the -Y face;
|
isn't at the local origin. ``near_y`` is the local-Y of the -Y face;
|
||||||
``far_y`` is the local-Y of the +Y face. Returns the Y just *outside* the
|
``far_y`` is the local-Y of the +Y face. Returns the Y just *outside* the
|
||||||
face the camera is currently looking at, pushed by ``gizmo_offset`` (use
|
face the camera is currently looking at, pushed by ``gizmo_offset`` (use
|
||||||
@@ -4670,11 +4670,10 @@ class BaseParametricGizmoGroup:
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Frame-scoped caches populated by :meth:`_prime_frame_caches` at the top of
|
# Frame-scoped caches primed at the top of ``refresh()`` and ``draw_prepare()``.
|
||||||
# ``refresh()`` and ``draw_prepare()``. Every per-frame helper — preferences
|
# Every per-frame helper — preferences access, view-direction lookup, billboard
|
||||||
# access, view-direction lookup, billboard rotation — reads these instead of
|
# rotation — reads these instead of re-deriving the same values, since each
|
||||||
# re-deriving the same values, since each gizmo group ends up needing them
|
# gizmo group ends up needing them 2–5× per frame across its position helpers.
|
||||||
# 2–5× per frame across its position helpers.
|
|
||||||
_frame_prefs: Any = None
|
_frame_prefs: Any = None
|
||||||
_frame_view_dir: tuple[bool, bool] | None = None
|
_frame_view_dir: tuple[bool, bool] | None = None
|
||||||
_frame_billboard_rot: "Matrix | None" = None
|
_frame_billboard_rot: "Matrix | None" = None
|
||||||
@@ -4939,7 +4938,7 @@ class BaseParametricGizmoGroup:
|
|||||||
) -> bpy.types.Gizmo:
|
) -> bpy.types.Gizmo:
|
||||||
"""Create and configure an icon gizmo with standard settings.
|
"""Create and configure an icon gizmo with standard settings.
|
||||||
|
|
||||||
Thin wrapper over :func:`setup_icon_gizmo` that defaults ``highlight_color``
|
Thin wrapper over `setup_icon_gizmo` that defaults ``highlight_color``
|
||||||
to the addon-prefs selection color via ``get_decoration_colors``.
|
to the addon-prefs selection color via ``get_decoration_colors``.
|
||||||
"""
|
"""
|
||||||
if highlight_color is None:
|
if highlight_color is None:
|
||||||
@@ -5112,33 +5111,39 @@ class BaseParametricGizmoGroup:
|
|||||||
icon_z = self.get_element_height(props) + self.ICON_Z_OFFSET
|
icon_z = self.get_element_height(props) + self.ICON_Z_OFFSET
|
||||||
icon_y = self.get_icon_y_offset(context, mw)
|
icon_y = self.get_icon_y_offset(context, mw)
|
||||||
billboard_rot = self._frame_billboard_rot
|
billboard_rot = self._frame_billboard_rot
|
||||||
|
# set_icon_gizmo_position no-ops on hidden gizmos (via get_gizmo_if_visible),
|
||||||
# This ensures icons face camera regardless of object rotation
|
# so the hide flag must be set first; that gates whether the matrix is written.
|
||||||
local_pos_validate = Vector((self.ICON_VALIDATE_X, icon_y, icon_z))
|
|
||||||
world_pos_validate = mw @ local_pos_validate
|
|
||||||
|
|
||||||
icon_matrix_base = Matrix.Translation(world_pos_validate) @ billboard_rot @ Matrix.Scale(0.5, 4)
|
|
||||||
|
|
||||||
if props.is_editing:
|
if props.is_editing:
|
||||||
self.pen_gizmo.hide = True
|
self.pen_gizmo.hide = True
|
||||||
self.validate_gizmo.hide = self.is_gizmo_hidden_by_modal(self.validate_gizmo)
|
self.validate_gizmo.hide = self.is_gizmo_hidden_by_modal(self.validate_gizmo)
|
||||||
self.validate_gizmo.matrix_basis = icon_matrix_base
|
self.set_icon_gizmo_position(
|
||||||
|
"validate_gizmo", mw=mw, x=self.ICON_VALIDATE_X, y=icon_y, z=icon_z, billboard_rot=billboard_rot
|
||||||
|
)
|
||||||
self.cancel_gizmo.hide = self.is_gizmo_hidden_by_modal(self.cancel_gizmo)
|
self.cancel_gizmo.hide = self.is_gizmo_hidden_by_modal(self.cancel_gizmo)
|
||||||
local_pos_cancel = Vector((self.ICON_VALIDATE_X + self.ICON_CANCEL_X, icon_y, icon_z))
|
self.set_icon_gizmo_position(
|
||||||
world_pos_cancel = mw @ local_pos_cancel
|
"cancel_gizmo",
|
||||||
self.cancel_gizmo.matrix_basis = Matrix.Translation(world_pos_cancel) @ billboard_rot @ Matrix.Scale(0.5, 4)
|
mw=mw,
|
||||||
|
x=self.ICON_VALIDATE_X + self.ICON_CANCEL_X,
|
||||||
|
y=icon_y,
|
||||||
|
z=icon_z,
|
||||||
|
billboard_rot=billboard_rot,
|
||||||
|
)
|
||||||
if self.cycle_type_operator:
|
if self.cycle_type_operator:
|
||||||
self.cycle_gizmo.hide = self.is_gizmo_hidden_by_modal(self.cycle_gizmo)
|
self.cycle_gizmo.hide = self.is_gizmo_hidden_by_modal(self.cycle_gizmo)
|
||||||
local_pos_cycle = Vector((self.ICON_VALIDATE_X + self.ICON_CYCLE_X, icon_y, icon_z))
|
self.set_icon_gizmo_position(
|
||||||
world_pos_cycle = mw @ local_pos_cycle
|
"cycle_gizmo",
|
||||||
self.cycle_gizmo.matrix_basis = (
|
mw=mw,
|
||||||
Matrix.Translation(world_pos_cycle) @ billboard_rot @ Matrix.Scale(0.30, 4)
|
x=self.ICON_VALIDATE_X + self.ICON_CYCLE_X,
|
||||||
|
y=icon_y,
|
||||||
|
z=icon_z,
|
||||||
|
billboard_rot=billboard_rot,
|
||||||
|
scale=0.30,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.pen_gizmo.hide = self.is_gizmo_hidden_by_modal(self.pen_gizmo)
|
self.pen_gizmo.hide = self.is_gizmo_hidden_by_modal(self.pen_gizmo)
|
||||||
self.pen_gizmo.matrix_basis = icon_matrix_base
|
self.set_icon_gizmo_position(
|
||||||
|
"pen_gizmo", mw=mw, x=self.ICON_VALIDATE_X, y=icon_y, z=icon_z, billboard_rot=billboard_rot
|
||||||
|
)
|
||||||
self.validate_gizmo.hide = True
|
self.validate_gizmo.hide = True
|
||||||
self.cancel_gizmo.hide = True
|
self.cancel_gizmo.hide = True
|
||||||
if self.cycle_type_operator:
|
if self.cycle_type_operator:
|
||||||
|
|||||||
@@ -614,15 +614,14 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties" # noqa: ARG002
|
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties" # noqa: ARG002
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update stair-specific lock and tread count gizmos. Lock positioning is
|
"""Update stair-specific lock and tread count gizmos. Lock positioning is
|
||||||
handled per-frame in :py:meth:`_update_lock_gizmo_position`."""
|
handled per-frame in the dimension-positioning hook."""
|
||||||
self.update_lock_gizmo(props)
|
self.update_lock_gizmo(props)
|
||||||
self.update_tread_lock_gizmo(props)
|
self.update_tread_lock_gizmo(props)
|
||||||
self.update_tread_count_gizmos(props)
|
self.update_tread_count_gizmos(props)
|
||||||
|
|
||||||
def update_lock_gizmo(self, props: "BIMStairProperties") -> None:
|
def update_lock_gizmo(self, props: "BIMStairProperties") -> None:
|
||||||
"""Update lock gizmo color and visibility. Positioning is handled in
|
"""Update lock gizmo color and visibility. Positioning is handled
|
||||||
:py:meth:`_update_lock_gizmo_position` (called per frame via
|
per-frame by the dimension-positioning hook."""
|
||||||
:py:meth:`_update_dimension_gizmo_positions`)."""
|
|
||||||
gizmo_prefs = self.get_gizmo_prefs()
|
gizmo_prefs = self.get_gizmo_prefs()
|
||||||
if not self.update_gizmo_visibility(self.lock_gizmo, props.is_editing, gizmo_prefs.lock):
|
if not self.update_gizmo_visibility(self.lock_gizmo, props.is_editing, gizmo_prefs.lock):
|
||||||
return # Hidden, skip color update
|
return # Hidden, skip color update
|
||||||
|
|||||||
@@ -1617,6 +1617,7 @@ class FinishEditingWall(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
height_changed = not tool.Cad.is_x(props.height, props.snap_height, tolerance=1e-5)
|
height_changed = not tool.Cad.is_x(props.height, props.snap_height, tolerance=1e-5)
|
||||||
x_angle_changed = not tool.Cad.is_x(props.x_angle, props.snap_x_angle, tolerance=1e-5)
|
x_angle_changed = not tool.Cad.is_x(props.x_angle, props.snap_x_angle, tolerance=1e-5)
|
||||||
baseline_changed = props.desired_offset_baseline != props.snap_offset_baseline
|
baseline_changed = props.desired_offset_baseline != props.snap_offset_baseline
|
||||||
|
any_change = length_changed or height_changed or x_angle_changed or baseline_changed
|
||||||
|
|
||||||
# Order matters: baseline shifts the layer-set reference line, then length
|
# Order matters: baseline shifts the layer-set reference line, then length
|
||||||
# adjusts endpoints relative to that, then x_angle changes the slope (and
|
# adjusts endpoints relative to that, then x_angle changes the slope (and
|
||||||
@@ -1638,7 +1639,7 @@ class FinishEditingWall(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if height_changed:
|
if height_changed:
|
||||||
bpy.ops.bim.change_extrusion_depth(depth=props.height)
|
bpy.ops.bim.change_extrusion_depth(depth=props.height)
|
||||||
|
|
||||||
if length_changed or height_changed or x_angle_changed or baseline_changed:
|
if any_change:
|
||||||
props.mesh_dirty = False
|
props.mesh_dirty = False
|
||||||
else:
|
else:
|
||||||
_restore_wall_mesh_if_dirty(obj)
|
_restore_wall_mesh_if_dirty(obj)
|
||||||
@@ -1816,7 +1817,6 @@ class GizmoWallEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
# The baseline icons (EXT / CEN / INT) all share ICON_CYCLE_X — only one is
|
# The baseline icons (EXT / CEN / INT) all share ICON_CYCLE_X — only one is
|
||||||
# ever visible at a time so they don't overlap.
|
# ever visible at a time so they don't overlap.
|
||||||
ICON_ROTATE_X = 1.24
|
ICON_ROTATE_X = 1.24
|
||||||
ICON_TOGGLE_OPENINGS_X = 1.61
|
|
||||||
|
|
||||||
# Mapping from BIMWallProperties.desired_offset_baseline value to the
|
# Mapping from BIMWallProperties.desired_offset_baseline value to the
|
||||||
# attribute on `self` that holds the corresponding state icon.
|
# attribute on `self` that holds the corresponding state icon.
|
||||||
@@ -1969,8 +1969,10 @@ class GizmoWallEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
- Toggle-openings icon next to the pen. Lives outside edit mode because
|
- Toggle-openings icon next to the pen. Lives outside edit mode because
|
||||||
opening visibility is a viewport-display concern, not a wall-edit action.
|
opening visibility is a viewport-display concern, not a wall-edit action.
|
||||||
|
|
||||||
Uses ``billboarded_at`` directly for parity with the base class's
|
Calls ``billboarded_at`` directly rather than routing through
|
||||||
``update_editing_gizmos`` validate/cancel/cycle pattern."""
|
``set_icon_gizmo_position`` because the icon row has wall-specific
|
||||||
|
visibility/state branching (baseline-indicator selection, edit-mode
|
||||||
|
toggle for opening-visibility) that the helper does not model."""
|
||||||
if not hasattr(self, "rotate_gizmo"):
|
if not hasattr(self, "rotate_gizmo"):
|
||||||
return
|
return
|
||||||
gizmo_prefs = self.get_gizmo_prefs()
|
gizmo_prefs = self.get_gizmo_prefs()
|
||||||
@@ -2030,7 +2032,7 @@ def _commit_active_wall_edit_if_any(context: bpy.types.Context) -> bpy.types.Obj
|
|||||||
|
|
||||||
|
|
||||||
def _commit_pending_wall_edits_for_selection(context: bpy.types.Context) -> None: # noqa: ARG001
|
def _commit_pending_wall_edits_for_selection(context: bpy.types.Context) -> None: # noqa: ARG001
|
||||||
"""Thin wall-scoped alias for :meth:`tool.Parametric.commit_pending_edits_for_selection`.
|
"""Thin wall-scoped alias for `tool.Parametric.commit_pending_edits_for_selection`.
|
||||||
|
|
||||||
Kept as a named helper because every multi-wall operator (split / join / merge /
|
Kept as a named helper because every multi-wall operator (split / join / merge /
|
||||||
unjoin / extend-to-wall …) calls it at the top of ``_execute``; centralising the
|
unjoin / extend-to-wall …) calls it at the top of ``_execute``; centralising the
|
||||||
@@ -2198,12 +2200,12 @@ def _wall_axis_world_segment_from_geom(obj: bpy.types.Object, geom: dict) -> tup
|
|||||||
|
|
||||||
|
|
||||||
class _WallGeomCachedBillboardingMixin(gizmo.BillboardingGizmoGroupMixin):
|
class _WallGeomCachedBillboardingMixin(gizmo.BillboardingGizmoGroupMixin):
|
||||||
"""Adds IFC-read caching to :class:`BillboardingGizmoGroupMixin` for wall-driven
|
"""Adds IFC-read caching to `BillboardingGizmoGroupMixin` for wall-driven
|
||||||
gizmo groups. ``refresh()`` is Blender's "something state-relevant changed"
|
gizmo groups. ``refresh()`` is Blender's "something state-relevant changed"
|
||||||
signal — that's when we drop the cache. ``draw_prepare()`` (every redraw) reuses
|
signal — that's when we drop the cache. ``draw_prepare()`` (every redraw) reuses
|
||||||
whatever ``_get_wall_geom_cached`` populated, so plain camera orbits don't re-hit
|
whatever ``_get_wall_geom_cached`` populated, so plain camera orbits don't re-hit
|
||||||
IFC. ``_get_wall_geom_cached`` also drops entries on its own when
|
IFC. ``_get_wall_geom_cached`` also drops entries on its own when
|
||||||
:meth:`tool.Parametric.get_geom_generation` advances (any ``tool.Ifc.Operator``
|
`tool.Parametric.get_geom_generation` advances (any ``tool.Ifc.Operator``
|
||||||
commit) so external ``bpy.ops`` mutations on the same selection don't leave
|
commit) so external ``bpy.ops`` mutations on the same selection don't leave
|
||||||
stale geometry behind."""
|
stale geometry behind."""
|
||||||
|
|
||||||
@@ -2274,7 +2276,7 @@ def _are_walls_collinear(
|
|||||||
parallel_threshold: float = 0.9994,
|
parallel_threshold: float = 0.9994,
|
||||||
line_tolerance: float = 0.05,
|
line_tolerance: float = 0.05,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Vector wrapper around :func:`core.are_axes_collinear` — converts Vector
|
"""Vector wrapper around `core.are_axes_collinear` — converts Vector
|
||||||
endpoints to plain tuples at the boundary so the math stays unit-testable in
|
endpoints to plain tuples at the boundary so the math stays unit-testable in
|
||||||
``test/core/`` without a mathutils dependency."""
|
``test/core/`` without a mathutils dependency."""
|
||||||
return core.are_axes_collinear(
|
return core.are_axes_collinear(
|
||||||
@@ -2286,7 +2288,7 @@ def _are_walls_collinear(
|
|||||||
|
|
||||||
|
|
||||||
def _collinear_boundary_world(seg_a: tuple[Vector, Vector], seg_b: tuple[Vector, Vector]) -> Vector:
|
def _collinear_boundary_world(seg_a: tuple[Vector, Vector], seg_b: tuple[Vector, Vector]) -> Vector:
|
||||||
"""Vector wrapper around :func:`core.closest_endpoint_midpoint`."""
|
"""Vector wrapper around `core.closest_endpoint_midpoint`."""
|
||||||
return Vector(
|
return Vector(
|
||||||
core.closest_endpoint_midpoint(
|
core.closest_endpoint_midpoint(
|
||||||
(tuple(seg_a[0]), tuple(seg_a[1])),
|
(tuple(seg_a[0]), tuple(seg_a[1])),
|
||||||
@@ -2302,7 +2304,7 @@ class GizmoWallAddOpening(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMixin
|
|||||||
object's projected origin. Clicking dispatches `bim.add_opening`, which lets the
|
object's projected origin. Clicking dispatches `bim.add_opening`, which lets the
|
||||||
existing FilledOpeningGenerator decide how the opening is applied.
|
existing FilledOpeningGenerator decide how the opening is applied.
|
||||||
|
|
||||||
Per-frame positioning via :class:`BillboardingGizmoGroupMixin` ensures the icon
|
Per-frame positioning via `BillboardingGizmoGroupMixin` ensures the icon
|
||||||
keeps facing the camera as the viewport is orbited."""
|
keeps facing the camera as the viewport is orbited."""
|
||||||
|
|
||||||
bl_idname = "OBJECT_GGT_bim_wall_add_opening"
|
bl_idname = "OBJECT_GGT_bim_wall_add_opening"
|
||||||
@@ -2447,7 +2449,7 @@ class GizmoWallJoinIntersection(bpy.types.GizmoGroup, _WallGeomCachedBillboardin
|
|||||||
"join the corner" vs "extend this wall into the other."
|
"join the corner" vs "extend this wall into the other."
|
||||||
- **None of the above**: all icons hidden.
|
- **None of the above**: all icons hidden.
|
||||||
|
|
||||||
Per-frame positioning via :class:`BillboardingGizmoGroupMixin` ensures the icons
|
Per-frame positioning via `BillboardingGizmoGroupMixin` ensures the icons
|
||||||
keep facing the camera as the viewport is orbited."""
|
keep facing the camera as the viewport is orbited."""
|
||||||
|
|
||||||
bl_idname = "OBJECT_GGT_bim_wall_join_intersection"
|
bl_idname = "OBJECT_GGT_bim_wall_join_intersection"
|
||||||
|
|||||||
@@ -22,12 +22,12 @@
|
|||||||
|
|
||||||
Two mixins fit the parametric-edit triads in ``bim/module/model/``:
|
Two mixins fit the parametric-edit triads in ``bim/module/model/``:
|
||||||
|
|
||||||
:class:`FeatureModifierEditMixin`
|
`FeatureModifierEditMixin`
|
||||||
Door, Window — BBIM_<Type> pset with nested ``lining_properties`` /
|
Door, Window — BBIM_<Type> pset with nested ``lining_properties`` /
|
||||||
``panel_properties``; Finish calls ``update_<type>_modifier_representation``
|
``panel_properties``; Finish calls ``update_<type>_modifier_representation``
|
||||||
via ``ifcopenshell.api.feature``; Cancel restores via ``switch_representation``.
|
via ``ifcopenshell.api.feature``; Cancel restores via ``switch_representation``.
|
||||||
|
|
||||||
:class:`PathPreservingEditMixin`
|
`PathPreservingEditMixin`
|
||||||
Railing, Roof — BBIM_<Type> pset whose ``path_data`` is preserved through
|
Railing, Roof — BBIM_<Type> pset whose ``path_data`` is preserved through
|
||||||
edit (only general kwargs are user-editable); Finish calls
|
edit (only general kwargs are user-editable); Finish calls
|
||||||
``update_<type>_modifier_bmesh`` / ``update_<type>_modifier_ifc_data``;
|
``update_<type>_modifier_bmesh`` / ``update_<type>_modifier_ifc_data``;
|
||||||
@@ -38,7 +38,7 @@ fit either mixin without optional escape hatches (Stair has a unique
|
|||||||
``update_ifc_stair_props`` post-Finish step + a separate ``get_props_kwargs_for_ifc_export``;
|
``update_ifc_stair_props`` post-Finish step + a separate ``get_props_kwargs_for_ifc_export``;
|
||||||
Wall is validation-first, snapshot-driven, no preview regen in operators).
|
Wall is validation-first, snapshot-driven, no preview regen in operators).
|
||||||
|
|
||||||
This module sits separately from :class:`bonsai.tool.Parametric` (the registry +
|
This module sits separately from `bonsai.tool.Parametric` (the registry +
|
||||||
auto-commit) because it imports ``bonsai.tool`` freely, while the registry
|
auto-commit) because it imports ``bonsai.tool`` freely, while the registry
|
||||||
itself must stay light — ``tool/blender.py`` consumes the registry at module load."""
|
itself must stay light — ``tool/blender.py`` consumes the registry at module load."""
|
||||||
|
|
||||||
@@ -201,9 +201,9 @@ class PathPreservingEditMixin(_ParametricEditMixinBase):
|
|||||||
|
|
||||||
Enable:
|
Enable:
|
||||||
Fetch pset data via ``tool.Model.get_modeling_bbim_pset_data`` → set
|
Fetch pset data via ``tool.Model.get_modeling_bbim_pset_data`` → set
|
||||||
draft props → ``is_editing = True``. Subclass override
|
draft props → ``is_editing = True``. The subclass post-load hook
|
||||||
:meth:`_post_load_data` lets railing JSON-serialise ``path_data`` for
|
lets railing JSON-serialise ``path_data`` for the PropertyGroup
|
||||||
the PropertyGroup string field.
|
string field.
|
||||||
|
|
||||||
Finish:
|
Finish:
|
||||||
Read fresh pset → keep ``path_data`` → gather ``general`` kwargs
|
Read fresh pset → keep ``path_data`` → gather ``general`` kwargs
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"""Registry + save-time auto-commit for parametric draft edits.
|
"""Registry + save-time auto-commit for parametric draft edits.
|
||||||
|
|
||||||
Single source of truth: adding a new parametric element type is one entry in
|
Single source of truth: adding a new parametric element type is one entry in
|
||||||
:attr:`Parametric.EDIT_TYPES`. Every consumer — save-time auto-commit, the
|
`Parametric.EDIT_TYPES`. Every consumer — save-time auto-commit, the
|
||||||
finish/cancel chains in ``tool.Blender.Modifier``, the ``PointerProperty``
|
finish/cancel chains in ``tool.Blender.Modifier``, the ``PointerProperty``
|
||||||
attachment in ``bim/module/model/__init__.py``, and the per-type
|
attachment in ``bim/module/model/__init__.py``, and the per-type
|
||||||
``GizmoPreferences<X>`` registration in ``bim/__init__.py`` — derives the
|
``GizmoPreferences<X>`` registration in ``bim/__init__.py`` — derives the
|
||||||
@@ -45,7 +45,7 @@ implementation files it references — if a step's example code stops matching
|
|||||||
the real registration site, the step is out of date.
|
the real registration site, the step is out of date.
|
||||||
|
|
||||||
STEP 1 — Add the registry entry (this file)
|
STEP 1 — Add the registry entry (this file)
|
||||||
Append to :attr:`Parametric.EDIT_TYPES`::
|
Append to `Parametric.EDIT_TYPES`::
|
||||||
|
|
||||||
ParametricObject("slab", has_non_editable_path=False),
|
ParametricObject("slab", has_non_editable_path=False),
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ STEP 1 — Add the registry entry (this file)
|
|||||||
|
|
||||||
STEP 2 — Define the ``PropertyGroup`` (``bim/module/model/prop.py``)
|
STEP 2 — Define the ``PropertyGroup`` (``bim/module/model/prop.py``)
|
||||||
Class name **must** be ``BIM<Name>Properties`` — capitalisation matches
|
Class name **must** be ``BIM<Name>Properties`` — capitalisation matches
|
||||||
:attr:`ParametricObject.props_attr`::
|
`ParametricObject.props_attr`::
|
||||||
|
|
||||||
class BIMSlabProperties(bpy.types.PropertyGroup):
|
class BIMSlabProperties(bpy.types.PropertyGroup):
|
||||||
is_editing: BoolProperty(...)
|
is_editing: BoolProperty(...)
|
||||||
@@ -70,7 +70,7 @@ STEP 3 — Register the PropertyGroup class
|
|||||||
Add it to the ``classes`` tuple in ``bim/module/model/__init__.py`` (near
|
Add it to the ``classes`` tuple in ``bim/module/model/__init__.py`` (near
|
||||||
the existing ``prop.BIM<X>Properties`` entries). The
|
the existing ``prop.BIM<X>Properties`` entries). The
|
||||||
``bpy.types.Object.BIMSlabProperties`` attachment is automatic —
|
``bpy.types.Object.BIMSlabProperties`` attachment is automatic —
|
||||||
:meth:`Parametric.register_object_properties` loops the registry.
|
`Parametric.register_object_properties` loops the registry.
|
||||||
|
|
||||||
STEP 4 — Implement the Enable / Finish / Cancel triad
|
STEP 4 — Implement the Enable / Finish / Cancel triad
|
||||||
In ``bim/module/model/slab.py``, define three ``bpy.types.Operator``
|
In ``bim/module/model/slab.py``, define three ``bpy.types.Operator``
|
||||||
@@ -81,7 +81,7 @@ STEP 4 — Implement the Enable / Finish / Cancel triad
|
|||||||
- ``CancelEditingSlab`` → ``bl_idname = "bim.cancel_editing_slab"``
|
- ``CancelEditingSlab`` → ``bl_idname = "bim.cancel_editing_slab"``
|
||||||
|
|
||||||
**First, check if your new type fits one of the existing lifecycle
|
**First, check if your new type fits one of the existing lifecycle
|
||||||
shapes** in :mod:`bonsai.bim.parametric_lifecycle`. If it does, inherit
|
shapes** in `bonsai.bim.parametric_lifecycle`. If it does, inherit
|
||||||
the matching mixin and the triad collapses to ~25 lines total:
|
the matching mixin and the triad collapses to ~25 lines total:
|
||||||
|
|
||||||
- ``FeatureModifierEditMixin`` — BBIM_<Type> pset with nested
|
- ``FeatureModifierEditMixin`` — BBIM_<Type> pset with nested
|
||||||
@@ -128,7 +128,7 @@ STEP 6 — Add the element-type predicate (``tool/blender.py``)
|
|||||||
return tool.Pset.get_element_pset(element, "BBIM_Slab")
|
return tool.Pset.get_element_pset(element, "BBIM_Slab")
|
||||||
|
|
||||||
The method name **must** be ``is_<name>`` to match
|
The method name **must** be ``is_<name>`` to match
|
||||||
:attr:`ParametricObject.name` — :meth:`Parametric.find_for_element`
|
`ParametricObject.name` — `Parametric.find_for_element`
|
||||||
looks it up by string.
|
looks it up by string.
|
||||||
|
|
||||||
STEP 7 — OPTIONAL: typed property accessor (``tool/model.py``)
|
STEP 7 — OPTIONAL: typed property accessor (``tool/model.py``)
|
||||||
@@ -153,9 +153,9 @@ STEP 8 — OPTIONAL: gizmo visibility preferences (``bim/ui.py``)
|
|||||||
slab: bpy.props.PointerProperty(type=GizmoPreferencesSlab)
|
slab: bpy.props.PointerProperty(type=GizmoPreferencesSlab)
|
||||||
|
|
||||||
Do **not** add ``GizmoPreferencesSlab`` to the ``classes`` list in
|
Do **not** add ``GizmoPreferencesSlab`` to the ``classes`` list in
|
||||||
``bim/__init__.py`` — :meth:`Parametric.iter_gizmo_preference_classes`
|
``bim/__init__.py`` — the registry-driven discovery in this module finds
|
||||||
discovers it from the registry automatically by its name
|
it by name (``GizmoPreferences`` + capitalised registry token) and
|
||||||
(``GizmoPreferences`` + capitalised registry token).
|
registers it automatically.
|
||||||
|
|
||||||
STEP 9 — OPTIONAL: pure geometry helpers (``core/model.py``)
|
STEP 9 — OPTIONAL: pure geometry helpers (``core/model.py``)
|
||||||
Per-type math (collinearity checks, slope/displacement conversions,
|
Per-type math (collinearity checks, slope/displacement conversions,
|
||||||
@@ -172,13 +172,12 @@ STEP 10 — Verify
|
|||||||
pytest test/core/ -x -q
|
pytest test/core/ -x -q
|
||||||
blender -b -P runpytest.py -- test/bim/ -x -q -m model
|
blender -b -P runpytest.py -- test/bim/ -x -q -m model
|
||||||
|
|
||||||
The Blender-backed lane runs the registration smoke test in
|
The Blender-backed lane runs a registry smoke test that iterates the
|
||||||
``test/bim/test_parametric_registry.py`` — it iterates
|
EDIT_TYPES list and asserts each entry's enable/finish/cancel operator
|
||||||
:attr:`Parametric.EDIT_TYPES` and asserts each ``enable_op`` /
|
resolves to a registered ``bpy.ops.bim.*``, that ``bpy.types.Object``
|
||||||
``finish_op`` / ``cancel_op`` resolves to a registered operator, that
|
carries the matching ``BIM<Name>Properties`` attribute, and that the
|
||||||
``bpy.types.Object`` carries the matching ``BIM<Name>Properties``
|
``is_<name>`` predicate exists on ``tool.Blender.Modifier``. Forget any
|
||||||
attribute, and that ``tool.Blender.Modifier.is_<name>`` exists. Forget
|
of the steps above and that test fails with a precise pointer at
|
||||||
any of the steps above and that test fails with a precise pointer at
|
|
||||||
what's missing.
|
what's missing.
|
||||||
|
|
||||||
Then manually in Blender:
|
Then manually in Blender:
|
||||||
@@ -223,14 +222,19 @@ class ParametricObject:
|
|||||||
"wall", …) drives every derived identifier: the ``BIM<Name>Properties``
|
"wall", …) drives every derived identifier: the ``BIM<Name>Properties``
|
||||||
attribute on ``bpy.types.Object`` and the ``bim.enable_editing_<name>`` /
|
attribute on ``bpy.types.Object`` and the ``bim.enable_editing_<name>`` /
|
||||||
``bim.finish_editing_<name>`` / ``bim.cancel_editing_<name>`` operator
|
``bim.finish_editing_<name>`` / ``bim.cancel_editing_<name>`` operator
|
||||||
``bl_idname``s. The ``name`` is validated at construction time —
|
``bl_idname``s. The ``name`` is validated at construction time — a
|
||||||
multi-word IFC types (e.g. ``IfcCurtainWall``) would silently mis-derive
|
multi-word IFC type would silently mis-derive through
|
||||||
through ``str.capitalize()`` and need a different approach than
|
``str.capitalize()`` and breaks the single-token assumption.
|
||||||
appending to :data:`Parametric.EDIT_TYPES` directly.
|
|
||||||
|
|
||||||
``has_non_editable_path`` flags element types whose modifier exposes no
|
``has_non_editable_path`` flags element types whose modifier exposes no
|
||||||
user-editable path (door, window, stair) — historically queried via
|
user-editable path (door, window, stair).
|
||||||
``tool.Blender.Modifier.is_modifier_with_non_editable_path``."""
|
|
||||||
|
The paired runtime predicate ``tool.Blender.Modifier.is_<name>(element)``
|
||||||
|
is part of the registry contract: it MUST be **total** — accept any
|
||||||
|
IFC entity and return a boolean, never raise. The registry iterates
|
||||||
|
every predicate against the active element on save; a raising predicate
|
||||||
|
propagates upward and breaks the save path for *all* parametric types,
|
||||||
|
not just its own."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
has_non_editable_path: bool = False
|
has_non_editable_path: bool = False
|
||||||
@@ -342,12 +346,17 @@ class Parametric(bonsai.core.tool.Parametric):
|
|||||||
def run_bim_op(cls, bl_idname: str) -> None:
|
def run_bim_op(cls, bl_idname: str) -> None:
|
||||||
"""Invoke a ``bim.*`` operator by its ``bl_idname``.
|
"""Invoke a ``bim.*`` operator by its ``bl_idname``.
|
||||||
|
|
||||||
Constraint: only use with operators that are themselves
|
Constraint enforced via ``assert``: the operator MUST be a
|
||||||
``tool.Ifc.Operator`` subclasses — their transaction wrap is what
|
``tool.Ifc.Operator`` subclass — its transaction wrap is what
|
||||||
makes the IFC mutation undo-aware. Direct ``bpy.ops.bim.*`` invocation
|
makes the IFC mutation undo-aware. Direct ``bpy.ops.bim.*`` invocation
|
||||||
of a non-``Ifc.Operator`` would mutate IFC outside Bonsai's
|
of a non-``Ifc.Operator`` would mutate IFC outside Bonsai's
|
||||||
transaction system."""
|
transaction system."""
|
||||||
getattr(bpy.ops.bim, bl_idname.removeprefix("bim."))()
|
verb = bl_idname.removeprefix("bim.")
|
||||||
|
op_cls = getattr(bpy.types, f"BIM_OT_{verb}", None)
|
||||||
|
assert op_cls is not None and issubclass(
|
||||||
|
op_cls, tool.Ifc.Operator
|
||||||
|
), f"{bl_idname!r} must be a registered tool.Ifc.Operator subclass for undo-safe IFC mutation"
|
||||||
|
getattr(bpy.ops.bim, verb)()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def commit_object_draft(cls, obj: bpy.types.Object, finish_op: str) -> bool:
|
def commit_object_draft(cls, obj: bpy.types.Object, finish_op: str) -> bool:
|
||||||
@@ -397,7 +406,7 @@ class Parametric(bonsai.core.tool.Parametric):
|
|||||||
def commit_pending_edits_for_selection(
|
def commit_pending_edits_for_selection(
|
||||||
cls, names: Optional[tuple[str, ...]] = None
|
cls, names: Optional[tuple[str, ...]] = None
|
||||||
) -> tuple[int, list[bpy.types.Object]]:
|
) -> tuple[int, list[bpy.types.Object]]:
|
||||||
"""Selection-scoped variant of :meth:`commit_pending_edits`. ``names``
|
"""Selection-scoped variant of `commit_pending_edits`. ``names``
|
||||||
filters which registry entries to consider — e.g. ``("wall",)`` to commit
|
filters which registry entries to consider — e.g. ``("wall",)`` to commit
|
||||||
only wall drafts among selected objects; ``None`` considers every type.
|
only wall drafts among selected objects; ``None`` considers every type.
|
||||||
|
|
||||||
@@ -439,7 +448,7 @@ class Parametric(bonsai.core.tool.Parametric):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def iter_gizmo_preference_classes(cls, ui_module) -> list[type]:
|
def iter_gizmo_preference_classes(cls, ui_module) -> list[type]:
|
||||||
"""``GizmoPreferences<Name>`` classes that exist on ``ui_module`` for
|
"""``GizmoPreferences<Name>`` classes that exist on ``ui_module`` for
|
||||||
every registry entry. Order matches :attr:`EDIT_TYPES`. Used by
|
every registry entry. Order matches `EDIT_TYPES`. Used by
|
||||||
``bim/__init__.py`` to inject the per-type ``GizmoPreferences<X>``
|
``bim/__init__.py`` to inject the per-type ``GizmoPreferences<X>``
|
||||||
classes at the correct point — before ``ui.GizmoPreferences``, which
|
classes at the correct point — before ``ui.GizmoPreferences``, which
|
||||||
references them via ``PointerProperty``."""
|
references them via ``PointerProperty``."""
|
||||||
|
|||||||
Reference in New Issue
Block a user