mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-15 13:48:09 +00:00
Replace hardcoded icon-X constants with IconSlot layout manager
The parametric edit toolbar row used to assign each feature icon its own ICON_<NAME>_X constant, with a separate FEATURE_ICON_MAX_X override each subclass had to bump whenever a new icon was added. Forgetting the bump silently collided icons — wall's rotate icon and the array button both landed at X=1.24 in edit mode. The new IconSlot dataclass + feature_slots tuple replace the constants-and-override pattern with order-driven positioning: the layout manager assigns each slot an X from its tuple index plus a uniform ICON_ARRAY_GAP. Adding an icon is now a one-line append; the "forget to bump" failure mode is structurally impossible. Slot capabilities cover every existing icon-row shape: * Single icon (wall rotate, array delete). * N-variant slots — N gizmos at the same X with one visible per frame via a subclass picker (stair tread-lock open/closed, wall baseline exterior/center/interior). Pair becomes the N=2 case; triplet the N=3 case. Variant idnames can be authored either as a tuple of explicit names or as a string prefix that auto-suffixes _<variant>. * Visibility prefs gate slot rendering without reflowing the row — hidden slots still consume their X position. * Extra per-slot gap before for visual separation (array's delete trails the routine controls by an extra 0.2 m). * Operator props forwarded to target_set_operator so adjusters (+/-, increment) and generic toggles (property_name=...) work. When the cycle slot is unused, feature slots collapse into the cycle position so the row stays tight — that's how wall's baseline triplet sits at X=0.87 without a gap before it. Three subclasses migrate to the new system: * wall.py — rotate icon + baseline triplet variants. Drops ICON_ROTATE_X, _BASELINE_GIZMO_ATTRS, the manual triplet creation loop, and the matching positioning block in _update_icon_row_extras (it now just picks variant visibility). * stair.py — tread_lock pair (open/closed) + plus + minus. _update_editing_icon_positions reads slot X via _slot_x_positions instead of three hardcoded constants. Also fixes the standalone total_length_lock gizmo, which was broken since PR4 split VIEW3D_GT_lock into open/closed pair (caller wasn't updated). * array.py — count_minus + count_plus + method + delete (with extra_gap_before=0.20 to separate the destructive action). Drops the manual edit-row positioning loop entirely; the base loop handles it. GizmoArrayChild now inherits BillboardingGizmoGroupMixin and uses the shared setup_icon_gizmo helper, dropping its duplicated _make_icon wrapper. Two helpers added on BillboardingGizmoGroupMixin to fold the duplicated prefs/color preamble that appeared at the top of six wall gizmo setups plus the array-child setup: * get_decoration_colors() — (decorations_colour, decorator_color_selected), the active-state pair. * get_unselected_decoration_colors() — (decorator_color_unselected, decorator_color_selected) for gizmos surfaced on already-selected geometry that should not pull focus. Verified: headless smoke green at 1267 BIM_OT_ classes, test_parametric_registry.py 8/8 pass, wall lane 29/29 pass, model lane unchanged at 135 pass + 7 pre-existing v0.8.0 failures (no regressions). ruff + black clean. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -4924,12 +4924,116 @@ class BillboardingGizmoGroupMixin:
|
|||||||
"""Convenience wrapper over `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 get_decoration_colors(self) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
|
||||||
|
"""Standard (default, highlight) color pair for active-state gizmos.
|
||||||
|
Pulls from the addon preferences — same source consumed by every
|
||||||
|
Bonsai decorator. Hover-class gizmos that should not pull focus
|
||||||
|
should use ``get_unselected_decoration_colors`` instead."""
|
||||||
|
prefs = tool.Blender.get_addon_preferences()
|
||||||
|
return prefs.decorations_colour[:3], prefs.decorator_color_selected[:3]
|
||||||
|
|
||||||
|
def get_unselected_decoration_colors(self) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
|
||||||
|
"""Lower-priority (unselected default, highlight) pair for gizmos
|
||||||
|
that surface on already-selected geometry and shouldn't compete
|
||||||
|
visually with the selection outline (e.g. array-child navigation)."""
|
||||||
|
prefs = tool.Blender.get_addon_preferences()
|
||||||
|
return prefs.decorator_color_unselected[:3], prefs.decorator_color_selected[:3]
|
||||||
|
|
||||||
def position_gizmos(self, context: bpy.types.Context) -> None:
|
def position_gizmos(self, context: bpy.types.Context) -> None:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f"{type(self).__name__} must implement position_gizmos(context) when using BillboardingGizmoGroupMixin."
|
f"{type(self).__name__} must implement position_gizmos(context) when using BillboardingGizmoGroupMixin."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class IconSlot:
|
||||||
|
"""One slot in a parametric edit gizmo's icon toolbar row.
|
||||||
|
|
||||||
|
The slot's X coordinate is COMPUTED from its index in ``feature_slots`` —
|
||||||
|
never set explicitly. Adding an icon is a one-line append; the layout
|
||||||
|
manager resolves the X. Hidden slots STILL CONSUME their X position so
|
||||||
|
toggling a visibility preference doesn't shift the row.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- ``gizmo_idname`` — for single-icon slots, the full Blender gizmo
|
||||||
|
idname. For multi-variant slots, EITHER a string PREFIX that auto-
|
||||||
|
suffixes ``_<variant>`` per member (the common case — e.g.
|
||||||
|
``"VIEW3D_GT_lock"`` + variants ``("open", "closed")`` becomes
|
||||||
|
``VIEW3D_GT_lock_open`` / ``VIEW3D_GT_lock_closed``) OR a tuple of
|
||||||
|
explicit idnames matching the variant count when the variants
|
||||||
|
don't share a prefix. Attributes created on the gizmo group are
|
||||||
|
``self.<name>_gizmo`` for single slots, ``self.<name>_<variant>_gizmo``
|
||||||
|
for each variant in multi-variant slots.
|
||||||
|
- ``variants`` — variant suffixes, e.g. ``("open", "closed")`` for a
|
||||||
|
lock pair, ``("exterior", "center", "interior")`` for a baseline
|
||||||
|
cycle. Empty tuple = single icon.
|
||||||
|
- ``color`` — RGB tuple. ``None`` falls back to the gizmo group's default
|
||||||
|
decoration color. Use the group's ``COLOR_RED`` / ``COLOR_GREEN`` /
|
||||||
|
``COLOR_BLUE`` literals for state-coded icons.
|
||||||
|
- ``visibility_pref`` — attribute name read off ``get_gizmo_prefs()``.
|
||||||
|
Slot is hidden when that pref is falsy. ``None`` = always visible
|
||||||
|
during edit.
|
||||||
|
- ``extra_gap_before`` — extra spacing past the default uniform gap, in
|
||||||
|
meters. Use sparingly — e.g. to visually separate a destructive
|
||||||
|
action (trash) from the routine edit controls.
|
||||||
|
- ``operator_props`` — tuple of (key, value) pairs forwarded to
|
||||||
|
``target_set_operator``'s return value (e.g. ``increment=1`` for a
|
||||||
|
+/- adjuster, ``property_name="..."`` for a generic toggle)."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
gizmo_idname: str | tuple[str, ...]
|
||||||
|
operator: str
|
||||||
|
# Matches DEFAULT_BILLBOARD_SCALE — the scale validate/cancel render at,
|
||||||
|
# so slots that don't override land at the same visual size by default.
|
||||||
|
# Helper icons (+/- count adjusters, lock pairs, delete) override with
|
||||||
|
# smaller values (0.20 - 0.35) to signal secondary affordance.
|
||||||
|
scale: float = DEFAULT_BILLBOARD_SCALE
|
||||||
|
color: tuple[float, float, float] | None = None
|
||||||
|
variants: tuple[str, ...] = ()
|
||||||
|
visibility_pref: str | None = None
|
||||||
|
extra_gap_before: float = 0.0
|
||||||
|
operator_props: tuple[tuple[str, Any], ...] = ()
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
# Validate shape at class-definition time so a typo doesn't surface
|
||||||
|
# as a runtime error in the gizmo group's setup() three layers deep.
|
||||||
|
if self.variants:
|
||||||
|
if isinstance(self.gizmo_idname, str):
|
||||||
|
pass # prefix form — idname auto-suffixed per variant
|
||||||
|
elif isinstance(self.gizmo_idname, tuple) and len(self.gizmo_idname) == len(self.variants):
|
||||||
|
pass # explicit-tuple form
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
f"IconSlot({self.name!r}): variants={self.variants} requires gizmo_idname "
|
||||||
|
f"to be either a string prefix (auto-suffixed as <prefix>_<variant>) or a "
|
||||||
|
f"tuple of {len(self.variants)} explicit idnames, got {self.gizmo_idname!r}"
|
||||||
|
)
|
||||||
|
elif not isinstance(self.gizmo_idname, str):
|
||||||
|
raise TypeError(
|
||||||
|
f"IconSlot({self.name!r}): single-icon slot requires gizmo_idname str, "
|
||||||
|
f"got {self.gizmo_idname!r} (set variants=(...) if you want a multi-variant slot)"
|
||||||
|
)
|
||||||
|
|
||||||
|
def variant_idnames(self) -> tuple[str, ...]:
|
||||||
|
"""Resolve per-variant gizmo idnames. For prefix form, suffix each
|
||||||
|
variant onto the prefix; for tuple form, return as is. Single-icon
|
||||||
|
slots return a one-element tuple containing the idname."""
|
||||||
|
if not self.variants:
|
||||||
|
assert isinstance(self.gizmo_idname, str)
|
||||||
|
return (self.gizmo_idname,)
|
||||||
|
if isinstance(self.gizmo_idname, str):
|
||||||
|
return tuple(f"{self.gizmo_idname}_{variant}" for variant in self.variants)
|
||||||
|
return self.gizmo_idname
|
||||||
|
|
||||||
|
def gizmo_attrs(self) -> tuple[str, ...]:
|
||||||
|
"""Names of every ``self.*`` attribute this slot writes during setup.
|
||||||
|
Returns one for a single slot, N for an N-variant slot."""
|
||||||
|
if self.variants:
|
||||||
|
return tuple(f"{self.name}_{variant}_gizmo" for variant in self.variants)
|
||||||
|
return (f"{self.name}_gizmo",)
|
||||||
|
|
||||||
|
|
||||||
class BaseParametricGizmoGroup:
|
class BaseParametricGizmoGroup:
|
||||||
"""Base mixin for parametric element gizmo groups (doors, windows, stairs, etc.).
|
"""Base mixin for parametric element gizmo groups (doors, windows, stairs, etc.).
|
||||||
|
|
||||||
@@ -5028,17 +5132,14 @@ class BaseParametricGizmoGroup:
|
|||||||
ICON_VALIDATE_X = 0.0 # X position of validate (checkmark) icon
|
ICON_VALIDATE_X = 0.0 # X position of validate (checkmark) icon
|
||||||
ICON_CANCEL_X = 0.5 # X offset from validate for cancel (X) icon
|
ICON_CANCEL_X = 0.5 # X offset from validate for cancel (X) icon
|
||||||
ICON_CYCLE_X = 0.87 # X offset from validate for cycle (arrow) icon
|
ICON_CYCLE_X = 0.87 # X offset from validate for cycle (arrow) icon
|
||||||
# Rightmost local-X used by feature-specific icons (across both idle and
|
# Subclasses append to declare feature icons in the edit-mode toolbar row.
|
||||||
# edit states). Subclasses override when they add icons past the cycle
|
# The layout manager assigns each slot an X position from its tuple
|
||||||
# slot at 0.87 — currently wall (rotate at 1.24) and stair (minus at
|
# index — adding a new icon is a one-line append, no hardcoded X
|
||||||
# 1.98). Drives both the ARRAY button position (this class) AND the
|
# constant, no "remember to bump the right edge" rule. The trailing
|
||||||
# array-layer-icons start position (``GizmoArrayEdition`` runtime lookup),
|
# ARRAY button is positioned past the last slot automatically.
|
||||||
# so non-colliding features get a tight layout while wall / stair shift
|
feature_slots: ClassVar[tuple[IconSlot, ...]] = ()
|
||||||
# the array-related slots outward to avoid stomping on the rotate /
|
# Gap between adjacent slots past the leading validate/cancel/cycle
|
||||||
# tread-lock / +/- icons.
|
# triplet, AND between the last slot and the ARRAY button.
|
||||||
FEATURE_ICON_MAX_X: float = 0.87
|
|
||||||
# Gap between the last feature icon and the ARRAY button (or the first
|
|
||||||
# array layer icon in idle state).
|
|
||||||
ICON_ARRAY_GAP: float = 0.37
|
ICON_ARRAY_GAP: float = 0.37
|
||||||
ICON_Z_OFFSET = 0.5 # Height above element for icons
|
ICON_Z_OFFSET = 0.5 # Height above element for icons
|
||||||
ICON_Y_OFFSET = GIZMO_OFFSET * 2 # Y offset to keep icons clear of geometry
|
ICON_Y_OFFSET = GIZMO_OFFSET * 2 # Y offset to keep icons clear of geometry
|
||||||
@@ -5060,6 +5161,37 @@ class BaseParametricGizmoGroup:
|
|||||||
super().__init_subclass__(**kwargs)
|
super().__init_subclass__(**kwargs)
|
||||||
BaseParametricGizmoGroup.REGISTRY.append(cls)
|
BaseParametricGizmoGroup.REGISTRY.append(cls)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _slot_x_positions(cls) -> dict[str, float]:
|
||||||
|
"""Map each ``feature_slot`` name to its X coordinate in the row.
|
||||||
|
|
||||||
|
Slots are laid out from the cycle position onward at uniform
|
||||||
|
``ICON_ARRAY_GAP`` spacing, plus any per-slot ``extra_gap_before``.
|
||||||
|
When the cycle slot is unused (no ``cycle_type_operator`` /
|
||||||
|
``pick_type_operator``), the first feature slot collapses into the
|
||||||
|
cycle position so the row stays tight — that's how wall's baseline
|
||||||
|
triplet ends up at X=0.87 without a gap before it. Tuple order is
|
||||||
|
the only thing that controls X; rearranging the tuple rearranges
|
||||||
|
the row."""
|
||||||
|
positions: dict[str, float] = {}
|
||||||
|
has_cycle = bool(cls.cycle_type_operator) or bool(cls.pick_type_operator)
|
||||||
|
next_x = (cls.ICON_CYCLE_X + cls.ICON_ARRAY_GAP) if has_cycle else cls.ICON_CYCLE_X
|
||||||
|
for slot in cls.feature_slots:
|
||||||
|
next_x += slot.extra_gap_before
|
||||||
|
positions[slot.name] = next_x
|
||||||
|
next_x += cls.ICON_ARRAY_GAP
|
||||||
|
return positions
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _feature_row_right_edge(cls) -> float:
|
||||||
|
"""Right edge of the feature icon row, fed to the trailing ARRAY
|
||||||
|
button's X. Computed strictly from slot order + gaps; empty
|
||||||
|
``feature_slots`` collapses to the cycle position."""
|
||||||
|
positions = cls._slot_x_positions()
|
||||||
|
if not positions:
|
||||||
|
return cls.ICON_CYCLE_X
|
||||||
|
return max(positions.values())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pick_visible_anchor(cls, context: bpy.types.Context, world_base: Vector, world_top: Vector) -> Vector:
|
def pick_visible_anchor(cls, context: bpy.types.Context, world_base: Vector, world_top: Vector) -> Vector:
|
||||||
"""Choose between two anchor candidates so vertical separation stays
|
"""Choose between two anchor candidates so vertical separation stays
|
||||||
@@ -5762,6 +5894,17 @@ class BaseParametricGizmoGroup:
|
|||||||
"VIEW3D_GT_menu", default_color, self.pick_type_operator, highlight_color
|
"VIEW3D_GT_menu", default_color, self.pick_type_operator, highlight_color
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Feature-specific edit-row icons. Subclasses declare them via
|
||||||
|
# ``feature_slots``; multi-variant slots create one gizmo per
|
||||||
|
# variant at the same X (e.g. a lock pair, a baseline triplet) and
|
||||||
|
# the subclass picks which is visible per frame.
|
||||||
|
for slot in self.feature_slots:
|
||||||
|
slot_color = slot.color if slot.color is not None else default_color
|
||||||
|
kwargs = dict(slot.operator_props)
|
||||||
|
for attr, idname in zip(slot.gizmo_attrs(), slot.variant_idnames()):
|
||||||
|
gz = self.create_icon_gizmo(idname, slot_color, slot.operator, **kwargs)
|
||||||
|
setattr(self, attr, gz)
|
||||||
|
|
||||||
# ARRAY button — visible during the feature edit lifecycle only (positioned by
|
# ARRAY button — visible during the feature edit lifecycle only (positioned by
|
||||||
# ``update_editing_gizmos``). Click commits the current edit and adds a
|
# ``update_editing_gizmos``). Click commits the current edit and adds a
|
||||||
# Blender-vanilla-defaulted array (count=2, X-offset = bbox extent). The
|
# Blender-vanilla-defaulted array (count=2, X-offset = bbox extent). The
|
||||||
@@ -6024,10 +6167,51 @@ class BaseParametricGizmoGroup:
|
|||||||
billboard_rot=billboard_rot,
|
billboard_rot=billboard_rot,
|
||||||
scale=0.30,
|
scale=0.30,
|
||||||
)
|
)
|
||||||
# ARRAY button sits past the last feature-specific icon. Each
|
# Feature slots: per-class IconSlot tuples driven by tuple order.
|
||||||
# gizmo group declares its own ``FEATURE_ICON_MAX_X`` (default
|
# Hidden slots STILL CONSUME their X position — toggling a pref
|
||||||
# 0.87 past the cycle slot; wall / stair override it) so the
|
# mustn't reflow the row (otherwise the array button drifts left
|
||||||
# ARRAY button never lands on top of a rotate / tread-lock icon.
|
# whenever a user disables the rotate icon).
|
||||||
|
slot_positions = self._slot_x_positions()
|
||||||
|
# Only fetch prefs if some slot has a visibility_pref — groups
|
||||||
|
# without a prefs entry (e.g. array) would raise on the lookup.
|
||||||
|
needs_prefs = any(slot.visibility_pref for slot in self.feature_slots)
|
||||||
|
gizmo_prefs = self.get_gizmo_prefs() if needs_prefs else None
|
||||||
|
for slot in self.feature_slots:
|
||||||
|
slot_x = self.ICON_VALIDATE_X + slot_positions[slot.name]
|
||||||
|
attrs = slot.gizmo_attrs()
|
||||||
|
if slot.visibility_pref and not getattr(gizmo_prefs, slot.visibility_pref, True):
|
||||||
|
for attr in attrs:
|
||||||
|
gz = getattr(self, attr, None)
|
||||||
|
if gz is not None:
|
||||||
|
gz.hide = True
|
||||||
|
continue
|
||||||
|
if slot.variants:
|
||||||
|
# Multi-variant slot: write matrix on every variant member
|
||||||
|
# at the same anchor so a state flip never reveals a stale
|
||||||
|
# pose. The subclass's per-frame hook picks which member
|
||||||
|
# is visible — this loop doesn't toggle hide flags.
|
||||||
|
world_pos = mw @ Vector((slot_x, icon_y, icon_z))
|
||||||
|
matrix = billboarded_at(world_pos, billboard_rot, scale=slot.scale)
|
||||||
|
for attr in attrs:
|
||||||
|
gz = getattr(self, attr, None)
|
||||||
|
if gz is not None:
|
||||||
|
gz.matrix_basis = matrix
|
||||||
|
continue
|
||||||
|
gz = getattr(self, attrs[0], None)
|
||||||
|
if gz is None:
|
||||||
|
continue
|
||||||
|
gz.hide = self.is_gizmo_hidden_by_modal(gz)
|
||||||
|
self.set_icon_gizmo_position(
|
||||||
|
attrs[0],
|
||||||
|
mw=mw,
|
||||||
|
x=slot_x,
|
||||||
|
y=icon_y,
|
||||||
|
z=icon_z,
|
||||||
|
billboard_rot=billboard_rot,
|
||||||
|
scale=slot.scale,
|
||||||
|
)
|
||||||
|
# ARRAY button sits past the last feature-specific icon. Slot-based
|
||||||
|
# subclasses derive the right edge from the slot count.
|
||||||
if hasattr(self, "array_gizmo"):
|
if hasattr(self, "array_gizmo"):
|
||||||
self.array_gizmo.hide = self.is_gizmo_hidden_by_modal(self.array_gizmo)
|
self.array_gizmo.hide = self.is_gizmo_hidden_by_modal(self.array_gizmo)
|
||||||
# 30% smaller than the editing-icon-row default (0.50 → 0.35):
|
# 30% smaller than the editing-icon-row default (0.50 → 0.35):
|
||||||
@@ -6037,7 +6221,7 @@ class BaseParametricGizmoGroup:
|
|||||||
self.set_icon_gizmo_position(
|
self.set_icon_gizmo_position(
|
||||||
"array_gizmo",
|
"array_gizmo",
|
||||||
mw=mw,
|
mw=mw,
|
||||||
x=self.ICON_VALIDATE_X + self.FEATURE_ICON_MAX_X + self.ICON_ARRAY_GAP,
|
x=self.ICON_VALIDATE_X + self._feature_row_right_edge() + self.ICON_ARRAY_GAP,
|
||||||
y=icon_y,
|
y=icon_y,
|
||||||
z=icon_z,
|
z=icon_z,
|
||||||
billboard_rot=billboard_rot,
|
billboard_rot=billboard_rot,
|
||||||
@@ -6061,6 +6245,11 @@ class BaseParametricGizmoGroup:
|
|||||||
self.cancel_gizmo.hide = True
|
self.cancel_gizmo.hide = True
|
||||||
if self.cycle_type_operator or self.pick_type_operator:
|
if self.cycle_type_operator or self.pick_type_operator:
|
||||||
self.cycle_gizmo.hide = True
|
self.cycle_gizmo.hide = True
|
||||||
|
for slot in self.feature_slots:
|
||||||
|
for attr in slot.gizmo_attrs():
|
||||||
|
gz = getattr(self, attr, None)
|
||||||
|
if gz is not None:
|
||||||
|
gz.hide = True
|
||||||
if hasattr(self, "array_gizmo"):
|
if hasattr(self, "array_gizmo"):
|
||||||
self.array_gizmo.hide = True
|
self.array_gizmo.hide = True
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from mathutils import Matrix, Vector
|
|||||||
|
|
||||||
import bonsai.bim.module.drawing.gizmos as gizmo
|
import bonsai.bim.module.drawing.gizmos as gizmo
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.module.drawing.gizmos import DimensionGizmoConfig
|
from bonsai.bim.module.drawing.gizmos import DimensionGizmoConfig, IconSlot
|
||||||
from bonsai.bim.parametric_lifecycle import ParametricEditMixinBase
|
from bonsai.bim.parametric_lifecycle import ParametricEditMixinBase
|
||||||
|
|
||||||
|
|
||||||
@@ -1132,22 +1132,53 @@ class GizmoArrayEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
# collapses to a single per-feature pen.
|
# collapses to a single per-feature pen.
|
||||||
hide_pen_button = True
|
hide_pen_button = True
|
||||||
|
|
||||||
# Local-X positions of the editing-row extras (count label + adjuster icons).
|
# Editing row layout: validate | cancel | xN | - | + | method | trash.
|
||||||
# Layout left-to-right at the same Y/Z as validate (ICON_VALIDATE_X = 0.0) and
|
# The count-label (xN) gizmo sits at the cycle slot (X = 0.87), positioned
|
||||||
# cancel (ICON_VALIDATE_X + ICON_CANCEL_X = 0.5):
|
# manually in ``_refresh_element_specific`` because it's a label that
|
||||||
# validate | cancel | xN | - | + | method-toggle | trash.
|
# replaces the cycle icon rather than a row slot. The +/-/method/trash
|
||||||
# Spacing mirrors stair's editing-row constants so the icons match in visual rhythm.
|
# icons live in ``feature_slots`` below — the base class assigns X
|
||||||
# Trash sits past the method toggle with a slightly wider gap so the destructive
|
# positions from tuple order; the trash carries ``extra_gap_before`` to
|
||||||
# action stays visually separated from the routine edit controls.
|
# visually separate the destructive action from the routine controls.
|
||||||
ICON_NUMBER_X = 0.87
|
ICON_NUMBER_X = 0.87
|
||||||
ICON_MINUS_X = 1.24
|
|
||||||
ICON_PLUS_X = 1.61
|
|
||||||
ICON_METHOD_X = 1.98
|
|
||||||
ICON_DELETE_X = 2.55
|
|
||||||
# Render scale for the +/- and method-toggle icons — ~70% of the standard
|
# Render scale for the +/- and method-toggle icons — ~70% of the standard
|
||||||
# 0.5 used for validate/cancel. Makes the helpers look secondary.
|
# 0.5 used for validate/cancel. Makes the helpers look secondary.
|
||||||
ICON_HELPER_SCALE = 0.35
|
ICON_HELPER_SCALE = 0.35
|
||||||
|
|
||||||
|
feature_slots: ClassVar[tuple[IconSlot, ...]] = (
|
||||||
|
IconSlot(
|
||||||
|
name="count_minus",
|
||||||
|
gizmo_idname="VIEW3D_GT_minus",
|
||||||
|
operator="bim.adjust_array_count",
|
||||||
|
scale=ICON_HELPER_SCALE,
|
||||||
|
color=(1.0, 0.2, 0.2),
|
||||||
|
operator_props=(("increment", -1),),
|
||||||
|
),
|
||||||
|
IconSlot(
|
||||||
|
name="count_plus",
|
||||||
|
gizmo_idname="VIEW3D_GT_plus",
|
||||||
|
operator="bim.adjust_array_count",
|
||||||
|
scale=ICON_HELPER_SCALE,
|
||||||
|
color=(0.1, 0.8, 0.1),
|
||||||
|
operator_props=(("increment", 1),),
|
||||||
|
),
|
||||||
|
IconSlot(
|
||||||
|
name="method",
|
||||||
|
gizmo_idname="VIEW3D_GT_cycle",
|
||||||
|
operator="bim.toggle_array_method",
|
||||||
|
scale=ICON_HELPER_SCALE,
|
||||||
|
),
|
||||||
|
IconSlot(
|
||||||
|
name="delete",
|
||||||
|
gizmo_idname="VIEW3D_GT_trash",
|
||||||
|
operator="bim.remove_array_layer_from_edit",
|
||||||
|
scale=ICON_HELPER_SCALE,
|
||||||
|
color=(1.0, 0.2, 0.2),
|
||||||
|
# Extra gap so the destructive action stays visually separated
|
||||||
|
# from the routine edit controls — matches the prior 0.57 gap.
|
||||||
|
extra_gap_before=0.20,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Per-layer ARRAY icons — one shown in idle state per existing array
|
# Per-layer ARRAY icons — one shown in idle state per existing array
|
||||||
# layer, surfaced to the right of the pen. Pre-allocated at setup time
|
# layer, surfaced to the right of the pen. Pre-allocated at setup time
|
||||||
# (Blender's gizmo API doesn't support creating gizmos on demand at draw
|
# (Blender's gizmo API doesn't support creating gizmos on demand at draw
|
||||||
@@ -1249,36 +1280,23 @@ class GizmoArrayEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
return tool.Parametric.is_array(element)
|
return tool.Parametric.is_array(element)
|
||||||
|
|
||||||
def setup_element_specific_gizmos(self, context: bpy.types.Context) -> None:
|
def setup_element_specific_gizmos(self, context: bpy.types.Context) -> None:
|
||||||
"""Create the +/- count adjusters, the method toggle, and the
|
"""Create the world-space count label and per-layer ARRAY entry icons.
|
||||||
per-layer ARRAY entry icons (one per existing array layer)."""
|
The +/- count adjusters, method toggle, and delete button live in
|
||||||
self.count_plus_gizmo = self.create_icon_gizmo(
|
``feature_slots`` and are auto-created by the base class."""
|
||||||
"VIEW3D_GT_plus", self.COLOR_GREEN, "bim.adjust_array_count", increment=1
|
|
||||||
)
|
|
||||||
self.count_minus_gizmo = self.create_icon_gizmo(
|
|
||||||
"VIEW3D_GT_minus", self.COLOR_RED, "bim.adjust_array_count", increment=-1
|
|
||||||
)
|
|
||||||
# Method toggle uses the cycle icon (circular arrow) — same affordance
|
|
||||||
# the stair / roof type-cycle gizmos use, signalling "click to swap".
|
|
||||||
default_color, highlight_color = self.get_decoration_colors()
|
default_color, highlight_color = self.get_decoration_colors()
|
||||||
self.method_gizmo = self.create_icon_gizmo("VIEW3D_GT_cycle", default_color, "bim.toggle_array_method")
|
|
||||||
# World-space count display for the edit row. Click opens a numeric
|
# World-space count display for the edit row. Click opens a numeric
|
||||||
# input dialog (``bim.input_array_count``) so the user can type a
|
# input dialog (``bim.input_array_count``) so the user can type a
|
||||||
# value directly instead of clicking +/- repeatedly. Renders the same
|
# value directly instead of clicking +/- repeatedly. Renders the same
|
||||||
# ``xN`` glyph as the idle-state per-layer icons for visual consistency.
|
# ``xN`` glyph as the idle-state per-layer icons for visual consistency.
|
||||||
|
# Sits at the cycle slot — it's a label that replaces the cycle icon,
|
||||||
|
# not a row slot, so it's positioned manually below rather than via
|
||||||
|
# ``feature_slots``.
|
||||||
self.count_label_gizmo = self.gizmos.new("BIM_GT_array_layer_indicator")
|
self.count_label_gizmo = self.gizmos.new("BIM_GT_array_layer_indicator")
|
||||||
self.count_label_gizmo.use_draw_scale = False
|
self.count_label_gizmo.use_draw_scale = False
|
||||||
self.count_label_gizmo.color = default_color
|
self.count_label_gizmo.color = default_color
|
||||||
self.count_label_gizmo.color_highlight = highlight_color
|
self.count_label_gizmo.color_highlight = highlight_color
|
||||||
self.count_label_gizmo.alpha = 0.8
|
self.count_label_gizmo.alpha = 0.8
|
||||||
self.count_label_gizmo.target_set_operator("bim.input_array_count")
|
self.count_label_gizmo.target_set_operator("bim.input_array_count")
|
||||||
# Destructive delete button at the far right of the edit row — red
|
|
||||||
# like the minus gizmo so the user reads "destructive" before the
|
|
||||||
# tooltip even appears. The dispatcher cancels the in-progress edit
|
|
||||||
# first (clearing edit state + unhiding children) then removes the
|
|
||||||
# layer in one click.
|
|
||||||
self.delete_gizmo = self.create_icon_gizmo(
|
|
||||||
"VIEW3D_GT_trash", self.COLOR_RED, "bim.remove_array_layer_from_edit"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Per-layer ARRAY icons — pre-allocated up to ``MAX_LAYER_GIZMOS`` and
|
# Per-layer ARRAY icons — pre-allocated up to ``MAX_LAYER_GIZMOS`` and
|
||||||
# shown/hidden in ``_refresh_element_specific`` based on the actual
|
# shown/hidden in ``_refresh_element_specific`` based on the actual
|
||||||
@@ -1354,25 +1372,21 @@ class GizmoArrayEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
return match is not None and match.name != "array"
|
return match is not None and match.name != "array"
|
||||||
|
|
||||||
def _refresh_element_specific(self, context: bpy.types.Context, mw: Matrix, props) -> None:
|
def _refresh_element_specific(self, context: bpy.types.Context, mw: Matrix, props) -> None:
|
||||||
"""Position the editing-row extras and the per-layer ARRAY icons.
|
"""Position the count label and per-layer ARRAY icons.
|
||||||
|
|
||||||
Idle (``not props.is_editing``): show one ARRAY icon per existing
|
Idle (``not props.is_editing``): show one ARRAY icon per existing
|
||||||
array layer to the right of the pen. The +/-, method, and editing-row
|
array layer to the right of the pen. The +/-, method, and delete
|
||||||
icons stay hidden.
|
slot icons stay hidden (handled by the base).
|
||||||
|
|
||||||
Active edit: show the editing row (validate, cancel, − / +, method);
|
Active edit: show the count label; hide the per-layer icons. The
|
||||||
hide the per-layer icons so they don't clutter the edit UX."""
|
+/-, method, and delete icons are positioned by the base class
|
||||||
|
via the slot layout — no manual positioning needed here."""
|
||||||
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
|
||||||
|
|
||||||
if not props.is_editing:
|
if not props.is_editing:
|
||||||
# Idle: show one ARRAY icon per existing layer. Per-edit helpers off.
|
|
||||||
self.count_plus_gizmo.hide = True
|
|
||||||
self.count_minus_gizmo.hide = True
|
|
||||||
self.method_gizmo.hide = True
|
|
||||||
self.count_label_gizmo.hide = True
|
self.count_label_gizmo.hide = True
|
||||||
self.delete_gizmo.hide = True
|
|
||||||
layers = self._read_array_layers(context)
|
layers = self._read_array_layers(context)
|
||||||
layer_count = min(len(layers), self.MAX_LAYER_GIZMOS)
|
layer_count = min(len(layers), self.MAX_LAYER_GIZMOS)
|
||||||
# Feature-aware start X — pushes the layer icons past any
|
# Feature-aware start X — pushes the layer icons past any
|
||||||
@@ -1399,34 +1413,10 @@ class GizmoArrayEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
gz.matrix_basis = gizmo.billboarded_at(world_pos, billboard_rot, scale=0.5)
|
gz.matrix_basis = gizmo.billboarded_at(world_pos, billboard_rot, scale=0.5)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Active edit: hide the layer icons, show the editing row. The
|
# Active edit: hide the layer icons, show the count label. The
|
||||||
# layer-hover bbox lives inside each layer gizmo's draw method, so
|
# +/-, method, and delete slot icons are positioned by the base.
|
||||||
# hiding the gizmos is enough to stop the hover highlight too.
|
|
||||||
for gz in self.layer_gizmos:
|
for gz in self.layer_gizmos:
|
||||||
gz.hide = True
|
gz.hide = True
|
||||||
# Same Y/Z as the validate/cancel icons set by the base
|
|
||||||
# ``update_editing_gizmos`` — they form one horizontal row. Helper
|
|
||||||
# icons (+/-, method) use ``ICON_HELPER_SCALE`` so they look secondary.
|
|
||||||
for gizmo_name, local_x in (
|
|
||||||
("count_minus_gizmo", self.ICON_VALIDATE_X + self.ICON_MINUS_X),
|
|
||||||
("count_plus_gizmo", self.ICON_VALIDATE_X + self.ICON_PLUS_X),
|
|
||||||
("method_gizmo", self.ICON_VALIDATE_X + self.ICON_METHOD_X),
|
|
||||||
("delete_gizmo", self.ICON_VALIDATE_X + self.ICON_DELETE_X),
|
|
||||||
):
|
|
||||||
gizmo_obj = getattr(self, gizmo_name)
|
|
||||||
if self.is_gizmo_hidden_by_modal(gizmo_obj):
|
|
||||||
gizmo_obj.hide = True
|
|
||||||
continue
|
|
||||||
gizmo_obj.hide = False
|
|
||||||
self.set_icon_gizmo_position(
|
|
||||||
gizmo_name,
|
|
||||||
mw=mw,
|
|
||||||
x=local_x,
|
|
||||||
y=icon_y,
|
|
||||||
z=icon_z,
|
|
||||||
billboard_rot=billboard_rot,
|
|
||||||
scale=self.ICON_HELPER_SCALE,
|
|
||||||
)
|
|
||||||
# Clickable world-space ``xN`` between cancel and minus. Mirrors the
|
# Clickable world-space ``xN`` between cancel and minus. Mirrors the
|
||||||
# draft ``props.count`` so the displayed value tracks +/- drags live.
|
# draft ``props.count`` so the displayed value tracks +/- drags live.
|
||||||
if self.is_gizmo_hidden_by_modal(self.count_label_gizmo):
|
if self.is_gizmo_hidden_by_modal(self.count_label_gizmo):
|
||||||
@@ -1492,26 +1482,19 @@ class GizmoArrayEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
return cls._FEATURE_IDLE_MAX_X.get(match.name, 0.0)
|
return cls._FEATURE_IDLE_MAX_X.get(match.name, 0.0)
|
||||||
|
|
||||||
|
|
||||||
class GizmoArrayChild(bpy.types.GizmoGroup):
|
class GizmoArrayChild(bpy.types.GizmoGroup, gizmo.BillboardingGizmoGroupMixin):
|
||||||
"""Three helper icons surfaced on each array child, mirroring the panel
|
"""Two navigation icons on each array child:
|
||||||
actions for that array — Regenerate, Select Parent, Select All Array Objects.
|
|
||||||
|
|
||||||
Standalone gizmo group (not a ``BaseParametricGizmoGroup`` subclass) because
|
|
||||||
the base's ``poll`` early-returns on array children (the mutual-exclusion
|
|
||||||
safeguard for the per-feature gizmo groups) and none of the editing-lifecycle
|
|
||||||
scaffolding applies — there's nothing to edit on a managed replica.
|
|
||||||
|
|
||||||
Two navigation icons:
|
|
||||||
- ``VIEW3D_GT_array_parent`` (hierarchy tree) → modifier-aware select via
|
- ``VIEW3D_GT_array_parent`` (hierarchy tree) → modifier-aware select via
|
||||||
``bim.array_parent_gizmo_click``: click selects the parent, Shift+click
|
``bim.array_parent_gizmo_click``: click selects the parent, Shift+click
|
||||||
selects the whole family, Ctrl+click selects only the children.
|
selects the whole family, Ctrl+click selects only the children.
|
||||||
- ``VIEW3D_GT_array_all`` (2×2 grid) → jump to the parent and enter
|
- ``VIEW3D_GT_array_all`` (2×2 grid) → jump to the parent and enter
|
||||||
array edit (mirrors the per-layer ARRAY icon on the parent, so the
|
array edit.
|
||||||
child has a one-click path into the same edit flow).
|
|
||||||
|
|
||||||
Regenerate isn't surfaced here — it's a maintenance action the panel
|
Standalone gizmo group (not a ``BaseParametricGizmoGroup`` subclass)
|
||||||
still exposes, and adding it as a child gizmo just clutters the viewport
|
because the base's ``poll`` early-returns on array children — there's
|
||||||
without giving anything the panel doesn't."""
|
nothing to edit on a managed replica. Regenerate isn't surfaced as a
|
||||||
|
child gizmo; the panel still exposes it."""
|
||||||
|
|
||||||
bl_idname = "OBJECT_GGT_bim_array_child"
|
bl_idname = "OBJECT_GGT_bim_array_child"
|
||||||
bl_label = "Array Child Helpers"
|
bl_label = "Array Child Helpers"
|
||||||
@@ -1525,7 +1508,6 @@ class GizmoArrayChild(bpy.types.GizmoGroup):
|
|||||||
ICON_ALL_X = 0.5
|
ICON_ALL_X = 0.5
|
||||||
ICON_Z_OFFSET = 0.5
|
ICON_Z_OFFSET = 0.5
|
||||||
ICON_SCALE = 0.5
|
ICON_SCALE = 0.5
|
||||||
ICON_ALPHA = 0.8
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@@ -1542,32 +1524,15 @@ class GizmoArrayChild(bpy.types.GizmoGroup):
|
|||||||
return tool.Blender.Modifier.is_array_child(element)
|
return tool.Blender.Modifier.is_array_child(element)
|
||||||
|
|
||||||
def setup(self, context: bpy.types.Context) -> None:
|
def setup(self, context: bpy.types.Context) -> None:
|
||||||
prefs = tool.Blender.get_addon_preferences()
|
default_color, highlight_color = self.get_unselected_decoration_colors()
|
||||||
default_color = prefs.decorator_color_unselected[:3]
|
self.parent_gizmo = self.setup_icon_gizmo(
|
||||||
highlight_color = prefs.decorator_color_selected[:3]
|
|
||||||
self.parent_gizmo = self._make_icon(
|
|
||||||
"VIEW3D_GT_array_parent", default_color, highlight_color, "bim.array_parent_gizmo_click"
|
"VIEW3D_GT_array_parent", default_color, highlight_color, "bim.array_parent_gizmo_click"
|
||||||
)
|
)
|
||||||
self.all_gizmo = self._make_icon(
|
self.all_gizmo = self.setup_icon_gizmo(
|
||||||
"VIEW3D_GT_array_all", default_color, highlight_color, "bim.edit_array_from_child"
|
"VIEW3D_GT_array_all", default_color, highlight_color, "bim.edit_array_from_child"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _make_icon(
|
def position_gizmos(self, context: bpy.types.Context) -> None:
|
||||||
self,
|
|
||||||
gizmo_type: str,
|
|
||||||
color: tuple[float, float, float],
|
|
||||||
highlight_color: tuple[float, float, float],
|
|
||||||
operator: str,
|
|
||||||
) -> bpy.types.Gizmo:
|
|
||||||
gz = self.gizmos.new(gizmo_type)
|
|
||||||
gz.use_draw_scale = False
|
|
||||||
gz.color = color
|
|
||||||
gz.color_highlight = highlight_color
|
|
||||||
gz.alpha = self.ICON_ALPHA
|
|
||||||
gz.target_set_operator(operator)
|
|
||||||
return gz
|
|
||||||
|
|
||||||
def draw_prepare(self, context: bpy.types.Context) -> None:
|
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
if obj is None or not obj.bound_box:
|
if obj is None or not obj.bound_box:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from mathutils import Matrix, Vector
|
|||||||
import bonsai.core.root
|
import bonsai.core.root
|
||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.module.drawing import gizmos as gizmo
|
from bonsai.bim.module.drawing import gizmos as gizmo
|
||||||
from bonsai.bim.module.drawing.gizmos import DimensionGizmoConfig
|
from bonsai.bim.module.drawing.gizmos import DimensionGizmoConfig, IconSlot
|
||||||
from bonsai.tool.numeric_input import (
|
from bonsai.tool.numeric_input import (
|
||||||
IntegerInputState,
|
IntegerInputState,
|
||||||
run_integer_input_modal,
|
run_integer_input_modal,
|
||||||
@@ -39,7 +39,7 @@ from bonsai.tool.numeric_input import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
V_ = tool.Blender.V_
|
V_ = tool.Blender.V_
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, ClassVar
|
||||||
|
|
||||||
from bmesh.types import BMVert
|
from bmesh.types import BMVert
|
||||||
from bpy.props import IntProperty
|
from bpy.props import IntProperty
|
||||||
@@ -462,16 +462,41 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
bl_region_type = "WINDOW"
|
bl_region_type = "WINDOW"
|
||||||
bl_options = {"3D", "PERSISTENT"}
|
bl_options = {"3D", "PERSISTENT"}
|
||||||
|
|
||||||
# === Stair-Specific Icon Layout (meters) ===
|
# === Stair-Specific Icon Layout ===
|
||||||
# Additional icons for stair editing, positioned after standard icons:
|
# Row order: [Validate] [Cancel] [Cycle] [TreadLock] [Plus] [Minus]
|
||||||
# [Validate] [Cancel] [Cycle] [TreadLock] [Plus] [Minus]
|
# The base class assigns X positions from ``feature_slots`` tuple order —
|
||||||
ICON_TREAD_LOCK_X = 1.24 # X position for tread lock toggle icon
|
# adding an icon is a one-line append, no hardcoded X constant.
|
||||||
ICON_PLUS_X = 1.61 # X position for add tread (+) icon
|
|
||||||
ICON_MINUS_X = 1.98 # X position for remove tread (-) icon
|
|
||||||
ICON_PLUS_MINUS_SCALE = 0.24 # Scale for plus/minus icons (slightly larger)
|
ICON_PLUS_MINUS_SCALE = 0.24 # Scale for plus/minus icons (slightly larger)
|
||||||
ICON_CYCLE_SCALE = 0.3 # Scale for cycle type icon
|
ICON_CYCLE_SCALE = 0.3 # Scale for cycle type icon
|
||||||
ICON_Z_OFFSET = 0.5 # Z offset above geometry for editing icons
|
ICON_Z_OFFSET = 0.5 # Z offset above geometry for editing icons
|
||||||
|
|
||||||
|
feature_slots: ClassVar[tuple[IconSlot, ...]] = (
|
||||||
|
IconSlot(
|
||||||
|
name="tread_lock",
|
||||||
|
gizmo_idname="VIEW3D_GT_lock",
|
||||||
|
variants=("open", "closed"),
|
||||||
|
operator="bim.toggle_stair_property",
|
||||||
|
color=(1.0, 1.0, 1.0),
|
||||||
|
operator_props=(("property_name", "custom_tread_lock"),),
|
||||||
|
),
|
||||||
|
IconSlot(
|
||||||
|
name="plus",
|
||||||
|
gizmo_idname="VIEW3D_GT_plus",
|
||||||
|
operator="bim.adjust_stair_treads",
|
||||||
|
scale=ICON_PLUS_MINUS_SCALE,
|
||||||
|
color=(0.1, 0.8, 0.1),
|
||||||
|
operator_props=(("increment", 1),),
|
||||||
|
),
|
||||||
|
IconSlot(
|
||||||
|
name="minus",
|
||||||
|
gizmo_idname="VIEW3D_GT_minus",
|
||||||
|
operator="bim.adjust_stair_treads",
|
||||||
|
scale=ICON_PLUS_MINUS_SCALE,
|
||||||
|
color=(1.0, 0.2, 0.2),
|
||||||
|
operator_props=(("increment", -1),),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
enable_editing_operator = "bim.enable_editing_stair"
|
enable_editing_operator = "bim.enable_editing_stair"
|
||||||
finish_editing_operator = "bim.finish_editing_stair"
|
finish_editing_operator = "bim.finish_editing_stair"
|
||||||
cancel_editing_operator = "bim.cancel_editing_stair"
|
cancel_editing_operator = "bim.cancel_editing_stair"
|
||||||
@@ -588,25 +613,16 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
return tool.Blender.Modifier.is_stair(element)
|
return tool.Blender.Modifier.is_stair(element)
|
||||||
|
|
||||||
def setup_element_specific_gizmos(self, context: bpy.types.Context) -> None:
|
def setup_element_specific_gizmos(self, context: bpy.types.Context) -> None:
|
||||||
"""Create stair-specific icon gizmos (lock, plus, minus)."""
|
"""Create the total-length lock as an open/closed pair. Click toggles
|
||||||
self.lock_gizmo = self.create_icon_gizmo(
|
``props.total_length_lock``; the per-frame update hook picks which
|
||||||
"VIEW3D_GT_lock",
|
member is visible. Anchored to the stair's far X end (not the edit
|
||||||
|
row) so it's positioned by ``_update_lock_gizmo_position`` rather
|
||||||
|
than the toolbar slot system."""
|
||||||
|
self.total_length_lock_open_gizmo, self.total_length_lock_closed_gizmo = self.create_icon_gizmo_lock_pair(
|
||||||
|
"bim.toggle_stair_property",
|
||||||
self.COLOR_BLUE,
|
self.COLOR_BLUE,
|
||||||
"bim.toggle_stair_property",
|
|
||||||
property_name="total_length_lock",
|
property_name="total_length_lock",
|
||||||
)
|
)
|
||||||
self.tread_lock_gizmo = self.create_icon_gizmo(
|
|
||||||
"VIEW3D_GT_lock",
|
|
||||||
(1.0, 1.0, 1.0),
|
|
||||||
"bim.toggle_stair_property",
|
|
||||||
property_name="custom_tread_lock",
|
|
||||||
)
|
|
||||||
self.plus_gizmo = self.create_icon_gizmo(
|
|
||||||
"VIEW3D_GT_plus", self.COLOR_GREEN, "bim.adjust_stair_treads", increment=1
|
|
||||||
)
|
|
||||||
self.minus_gizmo = self.create_icon_gizmo(
|
|
||||||
"VIEW3D_GT_minus", self.COLOR_RED, "bim.adjust_stair_treads", increment=-1
|
|
||||||
)
|
|
||||||
|
|
||||||
def _refresh_element_specific(
|
def _refresh_element_specific(
|
||||||
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties" # noqa: ARG002
|
self, context: bpy.types.Context, mw: Matrix, props: "BIMStairProperties" # noqa: ARG002
|
||||||
@@ -618,19 +634,38 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
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
|
"""Show the open/closed total-length lock variant matching
|
||||||
per-frame by the dimension-positioning hook."""
|
``props.total_length_lock``. Positioning is handled per-frame by
|
||||||
gizmo_prefs = self.get_gizmo_prefs()
|
the dimension-positioning hook."""
|
||||||
if not self.update_gizmo_visibility(self.lock_gizmo, props.is_editing, gizmo_prefs.lock):
|
if not hasattr(self, "total_length_lock_open_gizmo"):
|
||||||
return # Hidden, skip color update
|
|
||||||
self.lock_gizmo.color = self.COLOR_RED if props.total_length_lock else self.COLOR_GREEN
|
|
||||||
|
|
||||||
def update_tread_lock_gizmo(self, props: "BIMStairProperties") -> None:
|
|
||||||
"""Update visibility of tread lock gizmo. Positioning is handled in _update_editing_icon_positions."""
|
|
||||||
if not hasattr(self, "tread_lock_gizmo"):
|
|
||||||
return
|
return
|
||||||
gizmo_prefs = self.get_gizmo_prefs()
|
gizmo_prefs = self.get_gizmo_prefs()
|
||||||
self.update_gizmo_visibility(self.tread_lock_gizmo, props.is_editing, gizmo_prefs.lock)
|
visible = props.is_editing and gizmo_prefs.lock
|
||||||
|
if not visible:
|
||||||
|
self.total_length_lock_open_gizmo.hide = True
|
||||||
|
self.total_length_lock_closed_gizmo.hide = True
|
||||||
|
return
|
||||||
|
self.total_length_lock_open_gizmo.hide = props.total_length_lock
|
||||||
|
self.total_length_lock_closed_gizmo.hide = not props.total_length_lock
|
||||||
|
|
||||||
|
def update_tread_lock_gizmo(self, props: "BIMStairProperties") -> None:
|
||||||
|
"""Show the open/closed lock variant matching ``props.custom_tread_lock``.
|
||||||
|
|
||||||
|
Both pair members share an X position (set by the base's slot
|
||||||
|
positioning); this picks which one is visible per frame so a state
|
||||||
|
flip can't reveal both at once."""
|
||||||
|
if not hasattr(self, "tread_lock_open_gizmo"):
|
||||||
|
return
|
||||||
|
gizmo_prefs = self.get_gizmo_prefs()
|
||||||
|
visible = props.is_editing and gizmo_prefs.lock
|
||||||
|
# When the pref or edit state hides the slot, hide both members; the
|
||||||
|
# base loop already wrote their matrices so re-showing later is safe.
|
||||||
|
if not visible:
|
||||||
|
self.tread_lock_open_gizmo.hide = True
|
||||||
|
self.tread_lock_closed_gizmo.hide = True
|
||||||
|
return
|
||||||
|
self.tread_lock_open_gizmo.hide = props.custom_tread_lock
|
||||||
|
self.tread_lock_closed_gizmo.hide = not props.custom_tread_lock
|
||||||
|
|
||||||
def update_tread_count_gizmos(self, props: "BIMStairProperties") -> None:
|
def update_tread_count_gizmos(self, props: "BIMStairProperties") -> None:
|
||||||
"""Update visibility of +/- tread count gizmos. Positioning is handled in _update_editing_icon_positions."""
|
"""Update visibility of +/- tread count gizmos. Positioning is handled in _update_editing_icon_positions."""
|
||||||
@@ -719,10 +754,12 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
billboard_rot: Matrix,
|
billboard_rot: Matrix,
|
||||||
total_run: float,
|
total_run: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update lock gizmo position based on Y view direction."""
|
"""Update lock gizmo pair position based on Y view direction. Writes
|
||||||
|
the matrix on both members so a state flip can't reveal a stale pose."""
|
||||||
y_pos = self.get_y_position_for_view(props, viewing_from_negative_y, use_offset=True)
|
y_pos = self.get_y_position_for_view(props, viewing_from_negative_y, use_offset=True)
|
||||||
self.set_icon_gizmo_position(
|
self.set_icon_gizmo_pair_position(
|
||||||
"lock_gizmo",
|
"total_length_lock_open_gizmo",
|
||||||
|
"total_length_lock_closed_gizmo",
|
||||||
mw,
|
mw,
|
||||||
total_run + self.ICON_Z_OFFSET,
|
total_run + self.ICON_Z_OFFSET,
|
||||||
y_pos,
|
y_pos,
|
||||||
@@ -734,30 +771,36 @@ class GizmoStairEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
def _update_editing_icon_positions(
|
def _update_editing_icon_positions(
|
||||||
self, mw: Matrix, props: "BIMStairProperties", viewing_from_negative_y: bool, billboard_rot: Matrix
|
self, mw: Matrix, props: "BIMStairProperties", viewing_from_negative_y: bool, billboard_rot: Matrix
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update editing icon positions, flipping Y based on viewing angle."""
|
"""Reposition the editing icons at stair's view-dependent Y. The base
|
||||||
|
class's update_editing_gizmos already placed them at the default
|
||||||
|
``get_icon_y_offset`` Y — this overrides with the stair-specific
|
||||||
|
``get_icon_y_for_view`` flip so the icons land on the side the
|
||||||
|
camera is looking from."""
|
||||||
if not props.is_editing:
|
if not props.is_editing:
|
||||||
return
|
return
|
||||||
|
|
||||||
icon_z = props.height + self.ICON_Z_OFFSET
|
icon_z = props.height + self.ICON_Z_OFFSET
|
||||||
y_pos = self.get_icon_y_for_view(props, viewing_from_negative_y)
|
y_pos = self.get_icon_y_for_view(props, viewing_from_negative_y)
|
||||||
|
slot_x = self._slot_x_positions()
|
||||||
|
|
||||||
self.set_icon_gizmo_position("validate_gizmo", mw, 0, y_pos, icon_z, billboard_rot)
|
self.set_icon_gizmo_position("validate_gizmo", mw, 0, y_pos, icon_z, billboard_rot)
|
||||||
self.set_icon_gizmo_position("cancel_gizmo", mw, self.ICON_CANCEL_X, y_pos, icon_z, billboard_rot)
|
self.set_icon_gizmo_position("cancel_gizmo", mw, self.ICON_CANCEL_X, y_pos, icon_z, billboard_rot)
|
||||||
self.set_icon_gizmo_position(
|
self.set_icon_gizmo_position(
|
||||||
"cycle_gizmo", mw, self.ICON_CYCLE_X, y_pos, icon_z, billboard_rot, scale=self.ICON_CYCLE_SCALE
|
"cycle_gizmo", mw, self.ICON_CYCLE_X, y_pos, icon_z, billboard_rot, scale=self.ICON_CYCLE_SCALE
|
||||||
)
|
)
|
||||||
self.set_icon_gizmo_position(
|
self.set_icon_gizmo_pair_position(
|
||||||
"tread_lock_gizmo",
|
"tread_lock_open_gizmo",
|
||||||
|
"tread_lock_closed_gizmo",
|
||||||
mw,
|
mw,
|
||||||
self.ICON_TREAD_LOCK_X,
|
slot_x["tread_lock"],
|
||||||
y_pos,
|
y_pos,
|
||||||
icon_z - self.EDITING_ICON_SCALE / 2,
|
icon_z - self.EDITING_ICON_SCALE / 2,
|
||||||
billboard_rot,
|
billboard_rot,
|
||||||
scale=self.EDITING_ICON_SCALE,
|
scale=self.EDITING_ICON_SCALE,
|
||||||
)
|
)
|
||||||
self.set_icon_gizmo_position(
|
self.set_icon_gizmo_position(
|
||||||
"plus_gizmo", mw, self.ICON_PLUS_X, y_pos, icon_z, billboard_rot, scale=self.ICON_PLUS_MINUS_SCALE
|
"plus_gizmo", mw, slot_x["plus"], y_pos, icon_z, billboard_rot, scale=self.ICON_PLUS_MINUS_SCALE
|
||||||
)
|
)
|
||||||
self.set_icon_gizmo_position(
|
self.set_icon_gizmo_position(
|
||||||
"minus_gizmo", mw, self.ICON_MINUS_X, y_pos, icon_z, billboard_rot, scale=self.ICON_PLUS_MINUS_SCALE
|
"minus_gizmo", mw, slot_x["minus"], y_pos, icon_z, billboard_rot, scale=self.ICON_PLUS_MINUS_SCALE
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ import bonsai.core.root
|
|||||||
import bonsai.tool as tool
|
import bonsai.tool as tool
|
||||||
from bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
from bonsai.bim.module.drawing import gizmos as gizmo
|
from bonsai.bim.module.drawing import gizmos as gizmo
|
||||||
from bonsai.bim.module.drawing.gizmos import DimensionGizmoConfig
|
from bonsai.bim.module.drawing.gizmos import DimensionGizmoConfig, IconSlot
|
||||||
from bonsai.bim.module.model import preview_base
|
from bonsai.bim.module.model import preview_base
|
||||||
from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator
|
from bonsai.bim.module.model.decorator import PolylineDecorator, ProductDecorator
|
||||||
from bonsai.bim.module.model.polyline import PolylineOperator
|
from bonsai.bim.module.model.polyline import PolylineOperator
|
||||||
@@ -1988,19 +1988,27 @@ class GizmoWallEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
(0, 0, 1),
|
(0, 0, 1),
|
||||||
)
|
)
|
||||||
|
|
||||||
# X offsets in the editing icon row, additive from ICON_VALIDATE_X (0.0).
|
# Row layout: validate / cancel / baseline-triplet / rotate / array.
|
||||||
# Matches the cadence used by the base class (0.0 / 0.5 / 0.87 = step ≈ 0.37).
|
# Wall has no ``cycle_type_operator``, so the cycle slot collapses and
|
||||||
# The baseline icons (EXT / CEN / INT) all share ICON_CYCLE_X — only one is
|
# the baseline triplet takes the cycle X position (0.87). Rotate
|
||||||
# ever visible at a time so they don't overlap.
|
# follows at 1.24. Both slots are declared here — the layout manager
|
||||||
ICON_ROTATE_X = 1.24
|
# assigns the X positions from tuple order.
|
||||||
|
feature_slots: ClassVar[tuple[IconSlot, ...]] = (
|
||||||
# Mapping from BIMWallProperties.desired_offset_baseline value to the
|
IconSlot(
|
||||||
# attribute on `self` that holds the corresponding state icon.
|
name="baseline",
|
||||||
_BASELINE_GIZMO_ATTRS: ClassVar[dict[str, str]] = {
|
gizmo_idname="VIEW3D_GT_offset",
|
||||||
"EXTERIOR": "offset_exterior_gizmo",
|
variants=("exterior", "center", "interior"),
|
||||||
"CENTER": "offset_center_gizmo",
|
operator="bim.cycle_wall_offset",
|
||||||
"INTERIOR": "offset_interior_gizmo",
|
visibility_pref="cycle",
|
||||||
}
|
),
|
||||||
|
IconSlot(
|
||||||
|
name="rotate",
|
||||||
|
gizmo_idname="VIEW3D_GT_cycle",
|
||||||
|
operator="bim.rotate_wall_90",
|
||||||
|
scale=0.30,
|
||||||
|
visibility_pref="rotate",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def setup_element_specific_gizmos(self, context: bpy.types.Context) -> None:
|
def setup_element_specific_gizmos(self, context: bpy.types.Context) -> None:
|
||||||
"""Wall-specific gizmos.
|
"""Wall-specific gizmos.
|
||||||
@@ -2015,16 +2023,15 @@ class GizmoWallEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
wall top (Z=height in wall-local). Clicking extends the wall's height to
|
wall top (Z=height in wall-local). Clicking extends the wall's height to
|
||||||
the cursor's Z.
|
the cursor's Z.
|
||||||
|
|
||||||
Icon-row (always visible during edit mode, fixed position):
|
Idle-row icon outside the toolbar slot system:
|
||||||
|
|
||||||
- ``offset_{exterior,center,interior}_gizmo`` — three state-specific icons,
|
- ``toggle_openings_gizmo`` — toggles opening fill visibility (Alt+O),
|
||||||
only one visible at a time. Reflects ``props.desired_offset_baseline``.
|
surfaced in idle state next to the pen.
|
||||||
Clicking any of them cycles the baseline (the operator is the same).
|
|
||||||
- ``rotate_gizmo`` — rotates the wall 90° around Z (Shift+R). Uses the
|
The baseline-state triplet (exterior/center/interior) and the rotate-90
|
||||||
revolving-arrows icon now that the cycle slot is occupied by the
|
icon live in ``feature_slots`` — the base class handles creation and
|
||||||
stateful baseline icons.
|
edit-row positioning; this group only picks variant visibility per
|
||||||
- ``toggle_openings_gizmo`` — toggles opening fill visibility (Alt+O).
|
frame in ``_update_icon_row_extras``."""
|
||||||
"""
|
|
||||||
default_color, highlight_color = self.get_decoration_colors()
|
default_color, highlight_color = self.get_decoration_colors()
|
||||||
self.split_gizmo = self._setup_icon_gizmo(
|
self.split_gizmo = self._setup_icon_gizmo(
|
||||||
"VIEW3D_GT_split",
|
"VIEW3D_GT_split",
|
||||||
@@ -2044,26 +2051,6 @@ class GizmoWallEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
"bim.extend_wall_height_to_cursor",
|
"bim.extend_wall_height_to_cursor",
|
||||||
highlight_color,
|
highlight_color,
|
||||||
)
|
)
|
||||||
# Three baseline-state icons — only one is visible at a time, picked by
|
|
||||||
# the current props.desired_offset_baseline. All point to the same cycle
|
|
||||||
# operator so clicking any of them advances the cycle.
|
|
||||||
for baseline, attr_name in self._BASELINE_GIZMO_ATTRS.items():
|
|
||||||
setattr(
|
|
||||||
self,
|
|
||||||
attr_name,
|
|
||||||
self._setup_icon_gizmo(
|
|
||||||
f"VIEW3D_GT_offset_{baseline.lower()}",
|
|
||||||
default_color,
|
|
||||||
"bim.cycle_wall_offset",
|
|
||||||
highlight_color,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.rotate_gizmo = self._setup_icon_gizmo(
|
|
||||||
"VIEW3D_GT_cycle",
|
|
||||||
default_color,
|
|
||||||
"bim.rotate_wall_90",
|
|
||||||
highlight_color,
|
|
||||||
)
|
|
||||||
self.toggle_openings_gizmo = self._setup_icon_gizmo(
|
self.toggle_openings_gizmo = self._setup_icon_gizmo(
|
||||||
"VIEW3D_GT_add_opening",
|
"VIEW3D_GT_add_opening",
|
||||||
default_color,
|
default_color,
|
||||||
@@ -2132,57 +2119,48 @@ class GizmoWallEdition(bpy.types.GizmoGroup, gizmo.BaseParametricGizmoGroup):
|
|||||||
gz.matrix_basis = gizmo.billboarded_at(world_pos, billboard_rot)
|
gz.matrix_basis = gizmo.billboarded_at(world_pos, billboard_rot)
|
||||||
_apply_wall_extend_flips(gz, self, world_pos, mw, cursor_local, props, billboard_rot)
|
_apply_wall_extend_flips(gz, self, world_pos, mw, cursor_local, props, billboard_rot)
|
||||||
|
|
||||||
|
# Map ``props.desired_offset_baseline`` (storage form) to the slot variant
|
||||||
|
# name. Centralised here so the variant strings stay aligned with the slot
|
||||||
|
# declaration in feature_slots.
|
||||||
|
_BASELINE_TO_VARIANT: ClassVar[dict[str, str]] = {
|
||||||
|
"EXTERIOR": "exterior",
|
||||||
|
"CENTER": "center",
|
||||||
|
"INTERIOR": "interior",
|
||||||
|
}
|
||||||
|
|
||||||
def _update_icon_row_extras(self, context: bpy.types.Context, mw: Matrix, props: "BIMWallProperties") -> None:
|
def _update_icon_row_extras(self, context: bpy.types.Context, mw: Matrix, props: "BIMWallProperties") -> None:
|
||||||
"""Position the wall-specific icons in the icon row.
|
"""Pick which baseline variant is visible during edit, and position
|
||||||
|
the idle-row toggle-openings icon.
|
||||||
|
|
||||||
Edit-mode icons (visible only when ``props.is_editing``):
|
Baseline triplet: the base class's slot loop already wrote a billboard
|
||||||
|
matrix on each variant member at the same X (the cycle slot, since
|
||||||
|
wall has no ``cycle_type_operator``). This hook only flips ``hide``
|
||||||
|
on each member based on ``props.desired_offset_baseline`` so exactly
|
||||||
|
one variant shows. The rotate-90 icon is a single-icon feature slot
|
||||||
|
and is fully handled by the base.
|
||||||
|
|
||||||
- Three baseline icons (Exterior / Centreline / Interior) share the cycle
|
Toggle-openings is NOT in the slot system — it surfaces in IDLE
|
||||||
slot — only the one matching ``props.desired_offset_baseline`` shows.
|
state (alongside the pen, not in the edit row), so it's positioned
|
||||||
- Rotate-90 icon at ``ICON_ROTATE_X``.
|
manually here."""
|
||||||
|
if not hasattr(self, "toggle_openings_gizmo"):
|
||||||
Non-edit-mode icons (visible alongside the pen icon, hidden during edit):
|
|
||||||
|
|
||||||
- Toggle-openings icon next to the pen. Lives outside edit mode because
|
|
||||||
opening visibility is a viewport-display concern, not a wall-edit action.
|
|
||||||
|
|
||||||
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
|
return
|
||||||
gizmo_prefs = self.get_gizmo_prefs()
|
gizmo_prefs = self.get_gizmo_prefs()
|
||||||
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
|
||||||
|
|
||||||
# --- Edit-mode icons (baseline indicator + rotate-90) ---
|
# --- Baseline variant visibility ---
|
||||||
if props.is_editing:
|
active_variant = self._BASELINE_TO_VARIANT.get(props.desired_offset_baseline)
|
||||||
# Stateful baseline indicator at the cycle slot. Show exactly one of the
|
for variant in ("exterior", "center", "interior"):
|
||||||
# three icons (the one matching the current baseline), hide the others.
|
gz = getattr(self, f"baseline_{variant}_gizmo", None)
|
||||||
for baseline, attr in self._BASELINE_GIZMO_ATTRS.items():
|
if gz is None:
|
||||||
gz = getattr(self, attr)
|
continue
|
||||||
if gizmo_prefs.cycle and baseline == props.desired_offset_baseline:
|
if props.is_editing and gizmo_prefs.cycle and variant == active_variant:
|
||||||
gz.hide = self.is_gizmo_hidden_by_modal(gz)
|
gz.hide = self.is_gizmo_hidden_by_modal(gz)
|
||||||
world_pos = mw @ Vector((self.ICON_VALIDATE_X + self.ICON_CYCLE_X, icon_y, icon_z))
|
|
||||||
gz.matrix_basis = gizmo.billboarded_at(world_pos, billboard_rot)
|
|
||||||
else:
|
|
||||||
gz.hide = True
|
|
||||||
if gizmo_prefs.rotate:
|
|
||||||
self.rotate_gizmo.hide = self.is_gizmo_hidden_by_modal(self.rotate_gizmo)
|
|
||||||
world_pos = mw @ Vector((self.ICON_VALIDATE_X + self.ICON_ROTATE_X, icon_y, icon_z))
|
|
||||||
# VIEW3D_GT_cycle is authored for the base class's 0.30 scale; at 0.5
|
|
||||||
# it looks roughly 2x too big next to the validate / cancel icons.
|
|
||||||
self.rotate_gizmo.matrix_basis = gizmo.billboarded_at(world_pos, billboard_rot, scale=0.30)
|
|
||||||
else:
|
else:
|
||||||
self.rotate_gizmo.hide = True
|
gz.hide = True
|
||||||
else:
|
|
||||||
for attr in self._BASELINE_GIZMO_ATTRS.values():
|
|
||||||
getattr(self, attr).hide = True
|
|
||||||
self.rotate_gizmo.hide = True
|
|
||||||
|
|
||||||
# --- Non-edit-mode icons (toggle openings) ---
|
# --- Idle-row toggle-openings (outside the slot system) ---
|
||||||
# Sits at the slot the cancel icon occupies during editing — that way the
|
# Sits at the slot the cancel icon occupies during editing — that way the
|
||||||
# pen + openings pair is compact and visually grouped.
|
# pen + openings pair is compact and visually grouped.
|
||||||
if not props.is_editing and gizmo_prefs.toggle_openings:
|
if not props.is_editing and gizmo_prefs.toggle_openings:
|
||||||
@@ -3309,9 +3287,7 @@ class GizmoWallAddOpening(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMixin
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def setup(self, context: bpy.types.Context) -> None:
|
def setup(self, context: bpy.types.Context) -> None:
|
||||||
prefs = tool.Blender.get_addon_preferences()
|
default_color, highlight_color = self.get_decoration_colors()
|
||||||
default_color = prefs.decorations_colour[:3]
|
|
||||||
highlight_color = prefs.decorator_color_selected[:3]
|
|
||||||
self.add_opening_icon = self.setup_icon_gizmo(
|
self.add_opening_icon = self.setup_icon_gizmo(
|
||||||
"VIEW3D_GT_add_opening", default_color, highlight_color, "bim.add_opening"
|
"VIEW3D_GT_add_opening", default_color, highlight_color, "bim.add_opening"
|
||||||
)
|
)
|
||||||
@@ -3376,9 +3352,7 @@ class GizmoWallExtendVertically(bpy.types.GizmoGroup, _WallGeomCachedBillboardin
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def setup(self, context: bpy.types.Context) -> None:
|
def setup(self, context: bpy.types.Context) -> None:
|
||||||
prefs = tool.Blender.get_addon_preferences()
|
default_color, highlight_color = self.get_decoration_colors()
|
||||||
default_color = prefs.decorations_colour[:3]
|
|
||||||
highlight_color = prefs.decorator_color_selected[:3]
|
|
||||||
self.extend_vertical_icon = self.setup_icon_gizmo(
|
self.extend_vertical_icon = self.setup_icon_gizmo(
|
||||||
"VIEW3D_GT_extend_vertical",
|
"VIEW3D_GT_extend_vertical",
|
||||||
default_color,
|
default_color,
|
||||||
@@ -3456,9 +3430,7 @@ class GizmoWallJoinIntersection(bpy.types.GizmoGroup, _WallGeomCachedBillboardin
|
|||||||
ICON_STACK_OFFSET_Y: ClassVar[float] = 0.4
|
ICON_STACK_OFFSET_Y: ClassVar[float] = 0.4
|
||||||
|
|
||||||
def setup(self, context: bpy.types.Context) -> None:
|
def setup(self, context: bpy.types.Context) -> None:
|
||||||
prefs = tool.Blender.get_addon_preferences()
|
default_color, highlight_color = self.get_decoration_colors()
|
||||||
default_color = prefs.decorations_colour[:3]
|
|
||||||
highlight_color = prefs.decorator_color_selected[:3]
|
|
||||||
self.unjoin_icon = self.setup_icon_gizmo("VIEW3D_GT_split", default_color, highlight_color, "bim.unjoin_walls")
|
self.unjoin_icon = self.setup_icon_gizmo("VIEW3D_GT_split", default_color, highlight_color, "bim.unjoin_walls")
|
||||||
self.merge_icon = self.setup_icon_gizmo("VIEW3D_GT_merge", default_color, highlight_color, "bim.merge_wall")
|
self.merge_icon = self.setup_icon_gizmo("VIEW3D_GT_merge", default_color, highlight_color, "bim.merge_wall")
|
||||||
self.join_icon = self.setup_icon_gizmo(
|
self.join_icon = self.setup_icon_gizmo(
|
||||||
@@ -3618,9 +3590,7 @@ class GizmoWallUnjoinSingle(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMix
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def setup(self, context: bpy.types.Context) -> None:
|
def setup(self, context: bpy.types.Context) -> None:
|
||||||
prefs = tool.Blender.get_addon_preferences()
|
default_color, highlight_color = self.get_decoration_colors()
|
||||||
default_color = prefs.decorations_colour[:3]
|
|
||||||
highlight_color = prefs.decorator_color_selected[:3]
|
|
||||||
# Bind the operator on each pool icon ONCE at setup time and keep the returned
|
# Bind the operator on each pool icon ONCE at setup time and keep the returned
|
||||||
# OperatorProperties handles. target_set_operator allocates a fresh handle on
|
# OperatorProperties handles. target_set_operator allocates a fresh handle on
|
||||||
# every call, so calling it from position_gizmos (which fires every redraw
|
# every call, so calling it from position_gizmos (which fires every redraw
|
||||||
@@ -3720,9 +3690,7 @@ class GizmoWallFilletPreview(bpy.types.GizmoGroup):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def setup(self, context: bpy.types.Context) -> None:
|
def setup(self, context: bpy.types.Context) -> None:
|
||||||
prefs = tool.Blender.get_addon_preferences()
|
default_color, highlight_color = self.get_decoration_colors()
|
||||||
default_color = tuple(prefs.decorations_colour[:3])
|
|
||||||
highlight_color = tuple(prefs.decorator_color_selected[:3])
|
|
||||||
|
|
||||||
# Lazy-fetched closures re-resolve the Scene per call so the freed-RNA
|
# Lazy-fetched closures re-resolve the Scene per call so the freed-RNA
|
||||||
# crash on file open / undo doesn't hit the gizmo callbacks.
|
# crash on file open / undo doesn't hit the gizmo callbacks.
|
||||||
@@ -3991,9 +3959,7 @@ class GizmoWallFilletReedit(bpy.types.GizmoGroup, _WallGeomCachedBillboardingMix
|
|||||||
return tool.Parametric.is_fillet_corner_wall(element)
|
return tool.Parametric.is_fillet_corner_wall(element)
|
||||||
|
|
||||||
def setup(self, context: bpy.types.Context) -> None:
|
def setup(self, context: bpy.types.Context) -> None:
|
||||||
prefs = tool.Blender.get_addon_preferences()
|
default_color, highlight_color = self.get_decoration_colors()
|
||||||
default_color = prefs.decorations_colour[:3]
|
|
||||||
highlight_color = prefs.decorator_color_selected[:3]
|
|
||||||
self.edit_icon = self.setup_icon_gizmo(
|
self.edit_icon = self.setup_icon_gizmo(
|
||||||
"VIEW3D_GT_pen",
|
"VIEW3D_GT_pen",
|
||||||
default_color,
|
default_color,
|
||||||
|
|||||||
Reference in New Issue
Block a user