mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
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:
+43
-68
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user