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:
Gorgious56
2026-05-21 11:09:29 +02:00
parent 1e36cc318e
commit 6caf94f1d3
6 changed files with 96 additions and 88 deletions
+5 -12
View File
@@ -31,18 +31,11 @@ from . import handler, operator, prop, ui
def _parametric_gizmo_preference_classes() -> list[type]:
"""Lazy resolution. ``bonsai.tool/__init__.py`` transitively loads
``tool/ifc.py`` (and several siblings) which import ``from bonsai.bim.ifc
import IfcStore`` at module top — that ``tool → bim`` cycle means
``bonsai.tool`` cannot be imported here before ``from . import handler, …``
above has primed the bim partial-import dance through ``handler``'s own
``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."""
"""Resolves the registry-driven ``GizmoPreferences<X>`` classes for the
``classes`` list below. ``import bonsai.tool`` is kept local to surface
the load-order constraint: it relies on ``from . import handler, …``
above having primed the
``tool/ifc.py → bim/ifc.py → bim/handler.py → bonsai.tool`` cycle."""
import bonsai.tool as tool
return tool.Parametric.iter_gizmo_preference_classes(ui)
+33 -28
View File
@@ -3168,7 +3168,7 @@ class GizmoMerge(TrisGizmoMixin, bpy.types.Gizmo):
class GizmoSplit(TrisGizmoMixin, bpy.types.Gizmo):
"""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"
@@ -3216,7 +3216,7 @@ class GizmoExtend(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."""
bl_idname = "VIEW3D_GT_extend_vertical"
@@ -4211,7 +4211,7 @@ class BillboardingGizmoGroupMixin:
operator: str,
alpha: float = 0.8,
) -> 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)
def position_gizmos(self, context: bpy.types.Context) -> None:
@@ -4445,7 +4445,7 @@ class BaseParametricGizmoGroup:
) -> float:
"""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;
``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
@@ -4670,11 +4670,10 @@ class BaseParametricGizmoGroup:
"""
pass
# Frame-scoped caches populated by :meth:`_prime_frame_caches` at the top of
# ``refresh()`` and ``draw_prepare()``. Every per-frame helper — preferences
# access, view-direction lookup, billboard rotation — reads these instead of
# re-deriving the same values, since each gizmo group ends up needing them
# 25× per frame across its position helpers.
# Frame-scoped caches primed at the top of ``refresh()`` and ``draw_prepare()``.
# Every per-frame helper — preferences access, view-direction lookup, billboard
# rotation — reads these instead of re-deriving the same values, since each
# gizmo group ends up needing them 25× per frame across its position helpers.
_frame_prefs: Any = None
_frame_view_dir: tuple[bool, bool] | None = None
_frame_billboard_rot: "Matrix | None" = None
@@ -4939,7 +4938,7 @@ class BaseParametricGizmoGroup:
) -> bpy.types.Gizmo:
"""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``.
"""
if highlight_color is None:
@@ -5112,33 +5111,39 @@ class BaseParametricGizmoGroup:
icon_z = self.get_element_height(props) + self.ICON_Z_OFFSET
icon_y = self.get_icon_y_offset(context, mw)
billboard_rot = self._frame_billboard_rot
# This ensures icons face camera regardless of object rotation
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)
# set_icon_gizmo_position no-ops on hidden gizmos (via get_gizmo_if_visible),
# so the hide flag must be set first; that gates whether the matrix is written.
if props.is_editing:
self.pen_gizmo.hide = True
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)
local_pos_cancel = Vector((self.ICON_VALIDATE_X + self.ICON_CANCEL_X, icon_y, icon_z))
world_pos_cancel = mw @ local_pos_cancel
self.cancel_gizmo.matrix_basis = Matrix.Translation(world_pos_cancel) @ billboard_rot @ Matrix.Scale(0.5, 4)
self.set_icon_gizmo_position(
"cancel_gizmo",
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:
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))
world_pos_cycle = mw @ local_pos_cycle
self.cycle_gizmo.matrix_basis = (
Matrix.Translation(world_pos_cycle) @ billboard_rot @ Matrix.Scale(0.30, 4)
self.set_icon_gizmo_position(
"cycle_gizmo",
mw=mw,
x=self.ICON_VALIDATE_X + self.ICON_CYCLE_X,
y=icon_y,
z=icon_z,
billboard_rot=billboard_rot,
scale=0.30,
)
else:
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.cancel_gizmo.hide = True
if self.cycle_type_operator:
+3 -4
View File
@@ -614,15 +614,14 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties" # noqa: ARG002
) -> None:
"""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_tread_lock_gizmo(props)
self.update_tread_count_gizmos(props)
def update_lock_gizmo(self, props: "BIMStairProperties") -> None:
"""Update lock gizmo color and visibility. Positioning is handled in
:py:meth:`_update_lock_gizmo_position` (called per frame via
:py:meth:`_update_dimension_gizmo_positions`)."""
"""Update lock gizmo color and visibility. Positioning is handled
per-frame by the dimension-positioning hook."""
gizmo_prefs = self.get_gizmo_prefs()
if not self.update_gizmo_visibility(self.lock_gizmo, props.is_editing, gizmo_prefs.lock):
return # Hidden, skip color update
+13 -11
View File
@@ -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)
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
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
# 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:
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
else:
_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
# ever visible at a time so they don't overlap.
ICON_ROTATE_X = 1.24
ICON_TOGGLE_OPENINGS_X = 1.61
# Mapping from BIMWallProperties.desired_offset_baseline value to the
# 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
opening visibility is a viewport-display concern, not a wall-edit action.
Uses ``billboarded_at`` directly for parity with the base class's
``update_editing_gizmos`` validate/cancel/cycle pattern."""
Calls ``billboarded_at`` directly rather than routing through
``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"):
return
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
"""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 /
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):
"""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"
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
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
stale geometry behind."""
@@ -2274,7 +2276,7 @@ def _are_walls_collinear(
parallel_threshold: float = 0.9994,
line_tolerance: float = 0.05,
) -> 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
``test/core/`` without a mathutils dependency."""
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:
"""Vector wrapper around :func:`core.closest_endpoint_midpoint`."""
"""Vector wrapper around `core.closest_endpoint_midpoint`."""
return Vector(
core.closest_endpoint_midpoint(
(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
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."""
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."
- **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."""
bl_idname = "OBJECT_GGT_bim_wall_join_intersection"
@@ -22,12 +22,12 @@
Two mixins fit the parametric-edit triads in ``bim/module/model/``:
:class:`FeatureModifierEditMixin`
`FeatureModifierEditMixin`
Door, Window BBIM_<Type> pset with nested ``lining_properties`` /
``panel_properties``; Finish calls ``update_<type>_modifier_representation``
via ``ifcopenshell.api.feature``; Cancel restores via ``switch_representation``.
:class:`PathPreservingEditMixin`
`PathPreservingEditMixin`
Railing, Roof BBIM_<Type> pset whose ``path_data`` is preserved through
edit (only general kwargs are user-editable); Finish calls
``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``;
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
itself must stay light ``tool/blender.py`` consumes the registry at module load."""
@@ -201,9 +201,9 @@ class PathPreservingEditMixin(_ParametricEditMixinBase):
Enable:
Fetch pset data via ``tool.Model.get_modeling_bbim_pset_data`` set
draft props ``is_editing = True``. Subclass override
:meth:`_post_load_data` lets railing JSON-serialise ``path_data`` for
the PropertyGroup string field.
draft props ``is_editing = True``. The subclass post-load hook
lets railing JSON-serialise ``path_data`` for the PropertyGroup
string field.
Finish:
Read fresh pset keep ``path_data`` gather ``general`` kwargs
+36 -27
View File
@@ -21,7 +21,7 @@
"""Registry + save-time auto-commit for parametric draft edits.
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``
attachment in ``bim/module/model/__init__.py``, and the per-type
``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.
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),
@@ -57,7 +57,7 @@ STEP 1 — Add the registry entry (this file)
STEP 2 Define the ``PropertyGroup`` (``bim/module/model/prop.py``)
Class name **must** be ``BIM<Name>Properties`` capitalisation matches
:attr:`ParametricObject.props_attr`::
`ParametricObject.props_attr`::
class BIMSlabProperties(bpy.types.PropertyGroup):
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
the existing ``prop.BIM<X>Properties`` entries). The
``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
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"``
**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:
- ``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")
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.
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)
Do **not** add ``GizmoPreferencesSlab`` to the ``classes`` list in
``bim/__init__.py`` :meth:`Parametric.iter_gizmo_preference_classes`
discovers it from the registry automatically by its name
(``GizmoPreferences`` + capitalised registry token).
``bim/__init__.py`` the registry-driven discovery in this module finds
it by name (``GizmoPreferences`` + capitalised registry token) and
registers it automatically.
STEP 9 OPTIONAL: pure geometry helpers (``core/model.py``)
Per-type math (collinearity checks, slope/displacement conversions,
@@ -172,13 +172,12 @@ STEP 10 — Verify
pytest test/core/ -x -q
blender -b -P runpytest.py -- test/bim/ -x -q -m model
The Blender-backed lane runs the registration smoke test in
``test/bim/test_parametric_registry.py`` it iterates
:attr:`Parametric.EDIT_TYPES` and asserts each ``enable_op`` /
``finish_op`` / ``cancel_op`` resolves to a registered operator, that
``bpy.types.Object`` carries the matching ``BIM<Name>Properties``
attribute, and that ``tool.Blender.Modifier.is_<name>`` exists. Forget
any of the steps above and that test fails with a precise pointer at
The Blender-backed lane runs a registry smoke test that iterates the
EDIT_TYPES list and asserts each entry's enable/finish/cancel operator
resolves to a registered ``bpy.ops.bim.*``, that ``bpy.types.Object``
carries the matching ``BIM<Name>Properties`` attribute, and that the
``is_<name>`` predicate exists on ``tool.Blender.Modifier``. Forget any
of the steps above and that test fails with a precise pointer at
what's missing.
Then manually in Blender:
@@ -223,14 +222,19 @@ class ParametricObject:
"wall", ) drives every derived identifier: the ``BIM<Name>Properties``
attribute on ``bpy.types.Object`` and the ``bim.enable_editing_<name>`` /
``bim.finish_editing_<name>`` / ``bim.cancel_editing_<name>`` operator
``bl_idname``s. The ``name`` is validated at construction time
multi-word IFC types (e.g. ``IfcCurtainWall``) would silently mis-derive
through ``str.capitalize()`` and need a different approach than
appending to :data:`Parametric.EDIT_TYPES` directly.
``bl_idname``s. The ``name`` is validated at construction time a
multi-word IFC type would silently mis-derive through
``str.capitalize()`` and breaks the single-token assumption.
``has_non_editable_path`` flags element types whose modifier exposes no
user-editable path (door, window, stair) historically queried via
``tool.Blender.Modifier.is_modifier_with_non_editable_path``."""
user-editable path (door, window, stair).
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
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:
"""Invoke a ``bim.*`` operator by its ``bl_idname``.
Constraint: only use with operators that are themselves
``tool.Ifc.Operator`` subclasses their transaction wrap is what
Constraint enforced via ``assert``: the operator MUST be a
``tool.Ifc.Operator`` subclass its transaction wrap is what
makes the IFC mutation undo-aware. Direct ``bpy.ops.bim.*`` invocation
of a non-``Ifc.Operator`` would mutate IFC outside Bonsai's
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
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(
cls, names: Optional[tuple[str, ...]] = None
) -> 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
only wall drafts among selected objects; ``None`` considers every type.
@@ -439,7 +448,7 @@ class Parametric(bonsai.core.tool.Parametric):
@classmethod
def iter_gizmo_preference_classes(cls, ui_module) -> list[type]:
"""``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>``
classes at the correct point before ``ui.GizmoPreferences``, which
references them via ``PointerProperty``."""