Bonsai: derive prefs PropertyGroups from EDIT_TYPES

Collapse two parallel hand-maintained lists in the addon-preferences
PropertyGroups into derivations from `tool.Parametric.EDIT_TYPES`:

- GizmoPreferences: the 10 `<name>: BoolProperty` annotations now
  generated from the full EDIT_TYPES list.

- DefaultParameters: add `has_default_parameters` flag to
  ParametricObject (set True on door/window/stair/railing/roof);
  derive the 5 `<name>: PointerProperty(type=BIM<X>Properties)`
  annotations and collapse the 5 hand-written `draw_expandable_panel`
  blocks in `draw_default_parameters` into loops driven by the flag.

Existing `test_gizmo_preferences_field_per_registry_entry` pinned the
GizmoPreferences contract; new
`test_default_parameters_field_per_registry_entry_with_defaults`
pins the DefaultParameters contract (one-directional: flag=True
implies field present, flag=False allows absence).

Generated with the assistance of an AI coding tool.
This commit is contained in:
Gorgious56
2026-06-29 16:14:26 +02:00
parent 8bd22178f7
commit a757641b84
3 changed files with 88 additions and 74 deletions
+43 -68
View File
@@ -41,20 +41,8 @@ import bonsai.bim.helper
import bonsai.tool as tool
from bonsai.bim.ifc import is_cache_locked_by_other_process
from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDProperty
from bonsai.bim.module.model.prop import (
BIMDoorProperties,
BIMRailingProperties,
BIMRoofProperties,
BIMStairProperties,
BIMWindowProperties,
)
from bonsai.bim.module.model.ui import (
draw_door_properties,
draw_railing_properties,
draw_roof_properties,
draw_stair_properties,
draw_window_properties,
)
from bonsai.bim.module.model import prop as _model_prop
from bonsai.bim.module.model import ui as _model_ui
from bonsai.bim.module.pset.prop import IfcProperty
from bonsai.bim.prop import Attribute
@@ -279,34 +267,29 @@ class BIM_UL_panel_visibilities(bpy.types.UIList):
class GizmoPreferences(bpy.types.PropertyGroup):
"""Aggregator for parametric gizmo visibility settings. One flat bool per
parametric feature; controls whether that feature's gizmo group polls
visible in the viewport."""
visible in the viewport.
The per-feature ``<name>: BoolProperty`` fields are derived from
``tool.Parametric.EDIT_TYPES`` at module load — adding a new parametric
type to the registry automatically surfaces its toggle here, with no
parallel hand-maintained list to keep in sync."""
draw_gizmos_in_3d_viewport: BoolProperty(
name="Draw Gizmos In 3D Viewport",
default=True,
description="Show interactive gizmos in the 3D viewport for parametric elements",
)
door: BoolProperty(name="Door", default=True)
window: BoolProperty(name="Window", default=True)
stair: BoolProperty(name="Stair", default=True)
railing: BoolProperty(name="Railing", default=True)
roof: BoolProperty(name="Roof", default=True)
array: BoolProperty(name="Array", default=True)
pipe_segment: BoolProperty(name="Pipe Segment", default=True)
duct_segment: BoolProperty(name="Duct Segment", default=True)
wall: BoolProperty(name="Wall", default=True)
if TYPE_CHECKING:
draw_gizmos_in_3d_viewport: bool
door: bool
window: bool
stair: bool
railing: bool
roof: bool
array: bool
pipe_segment: bool
duct_segment: bool
wall: bool
for _gizmo_pref_entry in tool.Parametric.EDIT_TYPES:
GizmoPreferences.__annotations__[_gizmo_pref_entry.name] = BoolProperty(
name=_gizmo_pref_entry.name.replace("_", " ").title(),
default=True,
)
del _gizmo_pref_entry
class DocPreferences(bpy.types.PropertyGroup):
@@ -402,11 +385,22 @@ class DocPreferences(bpy.types.PropertyGroup):
class DefaultParameters(bpy.types.PropertyGroup):
door: bpy.props.PointerProperty(type=BIMDoorProperties)
window: bpy.props.PointerProperty(type=BIMWindowProperties)
railing: bpy.props.PointerProperty(type=BIMRailingProperties)
roof: bpy.props.PointerProperty(type=BIMRoofProperties)
stair: bpy.props.PointerProperty(type=BIMStairProperties)
"""Per-type preset values used to seed new parametric instances.
The ``<name>: PointerProperty`` fields are derived from the subset of
``tool.Parametric.EDIT_TYPES`` flagged ``has_default_parameters=True``,
each pointing at the matching ``BIM<Name>Properties`` class. Adding a
new entry with that flag automatically surfaces a preferences section
and gives the create operator a preset to copy from."""
for _default_params_entry in tool.Parametric.EDIT_TYPES:
if not _default_params_entry.has_default_parameters:
continue
DefaultParameters.__annotations__[_default_params_entry.name] = bpy.props.PointerProperty(
type=getattr(_model_prop, _default_params_entry.props_attr),
)
del _default_params_entry
class BIM_ADDON_preferences(bpy.types.AddonPreferences):
@@ -828,36 +822,17 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
def draw_default_parameters(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
box = layout.box()
bonsai.bim.helper.draw_expandable_panel(
box,
context,
"Door",
lambda _layout, _context: draw_door_properties(_layout, self.default_parameters.door),
)
bonsai.bim.helper.draw_expandable_panel(
box,
context,
"Window",
lambda _layout, _context: draw_window_properties(_layout, self.default_parameters.window),
)
bonsai.bim.helper.draw_expandable_panel(
box,
context,
"Railing",
lambda _layout, _context: draw_railing_properties(_layout, self.default_parameters.railing),
)
bonsai.bim.helper.draw_expandable_panel(
box,
context,
"Roof",
lambda _layout, _context: draw_roof_properties(_layout, self.default_parameters.roof),
)
bonsai.bim.helper.draw_expandable_panel(
box,
context,
"Stair",
lambda _layout, _context: draw_stair_properties(_layout, self.default_parameters.stair),
)
for entry in tool.Parametric.EDIT_TYPES:
if not entry.has_default_parameters:
continue
props = getattr(self.default_parameters, entry.name)
draw_props = getattr(_model_ui, f"draw_{entry.name}_properties")
bonsai.bim.helper.draw_expandable_panel(
box,
context,
entry.name.replace("_", " ").title(),
lambda _layout, _context, _draw=draw_props, _props=props: _draw(_layout, _props),
)
def draw_other_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(self, "opening_focus_opacity")
+20 -6
View File
@@ -81,11 +81,19 @@ class ParametricObject:
``_cancel_targets``) and that therefore wire their operators through
``build_edit_lifecycle``. Entries with bespoke edit lifecycles (per-attribute
diff dispatch, layer-stack editing, mid-spline gizmo drag) leave this
False and declare their operator classes directly."""
False and declare their operator classes directly.
``has_default_parameters`` marks entries whose ``BIM<Name>Properties``
class exposes ``get_general_kwargs`` / ``copy_to`` and a matching
``draw_<name>_properties`` UI helper, so the addon-preferences panel can
surface a per-type defaults section and the create operator can seed new
instances from the preset. Entries without that machinery leave this False
and don't appear in the preferences ``Default Parameters`` panel."""
name: str
has_non_editable_path: bool = False
supports_build_edit_lifecycle: bool = False
has_default_parameters: bool = False
def __post_init__(self) -> None:
if not _VALID_NAME_RE.match(self.name):
@@ -148,11 +156,17 @@ class Parametric(bonsai.core.tool.Parametric):
self._gen = None
EDIT_TYPES: list[ParametricObject] = [
ParametricObject("door", has_non_editable_path=True, supports_build_edit_lifecycle=True),
ParametricObject("window", has_non_editable_path=True, supports_build_edit_lifecycle=True),
ParametricObject("stair", has_non_editable_path=True, supports_build_edit_lifecycle=True),
ParametricObject("railing", supports_build_edit_lifecycle=True),
ParametricObject("roof", supports_build_edit_lifecycle=True),
ParametricObject(
"door", has_non_editable_path=True, supports_build_edit_lifecycle=True, has_default_parameters=True
),
ParametricObject(
"window", has_non_editable_path=True, supports_build_edit_lifecycle=True, has_default_parameters=True
),
ParametricObject(
"stair", has_non_editable_path=True, supports_build_edit_lifecycle=True, has_default_parameters=True
),
ParametricObject("railing", supports_build_edit_lifecycle=True, has_default_parameters=True),
ParametricObject("roof", supports_build_edit_lifecycle=True, has_default_parameters=True),
ParametricObject("array", supports_build_edit_lifecycle=True),
ParametricObject("pipe_segment", supports_build_edit_lifecycle=True),
ParametricObject("duct_segment", supports_build_edit_lifecycle=True),
@@ -123,6 +123,31 @@ def test_every_predicate_does_not_raise_on_non_matching_element(registry):
)
def test_default_parameters_field_per_registry_entry_with_defaults(registry):
"""Every entry flagged ``has_default_parameters=True`` must have a matching
``<name>: PointerProperty`` field on ``ui.DefaultParameters`` pointing at
its ``BIM<Name>Properties`` class.
The addon-preferences ``Default Parameters`` panel iterates flagged entries
to render per-type defaults sections, and the matching create operator
(``bim.add_door``, ``bim.add_window``, …) reads the field to seed new
instances from the user's preset values. A missing field means the preset
silently never reaches the operator.
Entries WITHOUT the flag are not required to appear — the contract is
one-directional: ``has_default_parameters=True`` implies a field, but
``False`` allows absence."""
from bonsai.bim import ui
annotations = getattr(ui.DefaultParameters, "__annotations__", {})
missing = [e.name for e in registry if e.has_default_parameters and e.name not in annotations]
assert not missing, (
f"ui.DefaultParameters missing PointerProperty field(s) for: {missing}"
f"each EDIT_TYPES entry with has_default_parameters=True must have a matching "
f"<name>: PointerProperty(type=BIM<Name>Properties) field"
)
def test_gizmo_preferences_field_per_registry_entry(registry):
"""Every registry entry must have a matching ``<name>: BoolProperty`` field
on ``ui.GizmoPreferences`` so the addon-preferences UI auto-renders a