Add array parametric edit lifecycle + GizmoArrayEdition / Child

Ports the array parametric-edit lifecycle, gizmo group, child guard,
per-layer ARRAY entry icons, and the array bbox decorators
(preview + selection highlight + layer-children) from gizmos-8088.
Restores the array_gizmo icon's positioning + visibility in the
framework's parametric edit row.

Registry (tool/parametric.py):
* EDIT_TYPES adds ParametricObject("array", supports_build_edit_lifecycle=True).
  _ArrayEditMixin in array.py feeds build_edit_lifecycle which auto-
  generates EnableEditingArray / FinishEditingArray / CancelEditingArray
  with the conventional bl_idnames the gizmo references.

tool/blender.py:
* Adds is_array predicate wrapper around tool.Parametric.is_array.
  The registry contract test test_every_entry_has_modifier_predicate
  enforces every EDIT_TYPES entry has a matching is_<name> wrapper on
  tool.Blender.Modifier.

array.py (+1130 LOC port from gizmos-8088):
* _ArrayEditMixin(ParametricEditMixinBase) drives the auto-generated
  enable / finish / cancel lifecycle.
* GizmoArrayEdition: validate + cancel + count display + +/- adjusters
  + method toggle + delete button + per-layer ARRAY entry icons
  (preallocated pool of MAX_LAYER_GIZMOS=8).
* GizmoArrayChild: child-array gizmo for the array-replica case.
* EditArrayFromChild: resolves the spawning layer via
  tool.Array.get_child_layer_index so clicking a child's array gizmo
  opens the layer that produced that child rather than always layer 0
  (the gizmos-8088 source itself hardcoded item=0; HEAD has the helper
  to do it right).
* New operators: EnableEditingArrayItem, ArrayParentGizmoClick,
  ArrayGizmoClick, ToggleArrayMethod, RemoveArrayLayerFromEdit,
  InputArrayCount, AdjustArrayCount.

prop.py: BIMArrayProperties gets per_child_opening BoolProperty
(when the array parent fills a host, give each child its own
opening + filling pair).

Bug fix: guard update_relating_array_from_object against the
cleanup-time None set. _finish_one writes relating_array_object = None
to clear the source-array reference; that fired the update callback,
which dispatched bpy.ops.bim.enable_editing_array(item=self.is_editing).
With is_editing just flipped to False, the bool coerced to 0 and
re-opened layer-0 edit immediately after every validate. The guard
short-circuits on None; item is also fixed to 0 (the bool-as-layer-
index was always meaningless for the legitimate user-pick path).

decorator.py (+312 LOC, all ports from gizmos-8088):
* bbox_world_edges / draw_polyline_segments / _BBOX_EDGES - shared
  geometry helpers usable across array decorators.
* draw_array_layer_children_bbox - green wireframe bbox per child of
  one array layer, drawn inline from a gizmo's draw() so the highlight
  tracks the hover cursor without POST_VIEW lag.
* ArrayPreviewDecorator - faint cyan ghost bboxes at each future
  array instance during the edit lifecycle (offset math mirrors
  Model.regenerate_array, gated on props.is_editing).
* ArraySelectionHighlightDecorator - bounding-box overlay surfacing
  the array family of the selected object. Child selected -> parent
  in special color + siblings in unselected color; parent selected
  (idle) -> all children in unselected color. TokenCache-backed.

handler.py: imports + uninstall/install the 2 always-on decorators in
_install_viewport_overlays. Both self-poll, so installation has no
cost when no array is selected / in edit mode.

Registration (bim/module/model/__init__.py):
* Adds the 3 lifecycle classes generated by build_edit_lifecycle
  (CancelEditingArray, EnableEditingArray, FinishEditingArray) -
  they exist as module-level names but are only visible to Blender's
  operator registry when included in the classes tuple.
* Adds the 8 new operators + 2 new gizmo groups in alphabetical order.

gizmos.py: restores the array_gizmo icon position + visibility block
in BaseParametricGizmoGroup.update_editing_gizmos. Was force-hidden
in c250b2c1a because no array gizmo existed; the icon's plumbing
comes back online now that GizmoArrayEdition is registered.

Verified by test/bim/test_parametric_registry.py: all 8 tests pass -
enable/finish/cancel ops resolve, PropertyGroup attached, is_array
predicate present, predicate is total on non-matching elements.

Generated with the assistance of an AI coding tool.
This commit is contained in:
Gorgious56
2026-06-01 14:39:58 +02:00
committed by Thomas Krijnen
parent e0ad34ffd4
commit ea487fb17c
8 changed files with 1521 additions and 74 deletions
+11
View File
@@ -43,6 +43,8 @@ from bonsai.bim.module.aggregate.decorator import AggregateDecorator
from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator from bonsai.bim.module.georeference.decorator import GeoreferenceDecorator
from bonsai.bim.module.model.data import AuthoringData from bonsai.bim.module.model.data import AuthoringData
from bonsai.bim.module.model.decorator import ( from bonsai.bim.module.model.decorator import (
ArrayPreviewDecorator,
ArraySelectionHighlightDecorator,
BoundingBoxDecorator, BoundingBoxDecorator,
SlabDirectionDecorator, SlabDirectionDecorator,
WallAxisDecorator, WallAxisDecorator,
@@ -464,6 +466,8 @@ def _install_viewport_overlays() -> None:
WallAxisDecorator.uninstall() WallAxisDecorator.uninstall()
SlabDirectionDecorator.uninstall() SlabDirectionDecorator.uninstall()
WallFilletPreviewDecorator.uninstall() WallFilletPreviewDecorator.uninstall()
ArrayPreviewDecorator.uninstall()
ArraySelectionHighlightDecorator.uninstall()
uninstall_decorator_cache_handlers() uninstall_decorator_cache_handlers()
try: try:
if georeference_props.should_visualise: if georeference_props.should_visualise:
@@ -482,6 +486,13 @@ def _install_viewport_overlays() -> None:
# wall_fillet.is_active, so installation has no cost when no preview # wall_fillet.is_active, so installation has no cost when no preview
# is open. No corresponding addon-preference toggle. # is open. No corresponding addon-preference toggle.
WallFilletPreviewDecorator.install(bpy.context) WallFilletPreviewDecorator.install(bpy.context)
# Always-installed: draw() self-polls on the active object's array
# family membership, so installation has no cost when no array
# element is selected.
ArraySelectionHighlightDecorator.install(bpy.context)
# Always-installed: draw() self-polls on props.is_editing — only
# paints during an active array edit lifecycle.
ArrayPreviewDecorator.install(bpy.context)
finally: finally:
install_decorator_cache_handlers() install_decorator_cache_handlers()
+18 -7
View File
@@ -5916,14 +5916,25 @@ class BaseParametricGizmoGroup:
billboard_rot=billboard_rot, billboard_rot=billboard_rot,
scale=0.30, scale=0.30,
) )
# Array gizmo integration is in-progress: the icon binds to # ARRAY button sits past the last feature-specific icon. Each
# bim.add_array_from_feature_edit but the array-from-parametric-draft # gizmo group declares its own ``FEATURE_ICON_MAX_X`` (default
# operator + per-feature gizmo positioning haven't fully landed. # 0.87 past the cycle slot; wall / stair override it) so the
# Force-hide the icon while parametric-item editing is active to # ARRAY button never lands on top of a rotate / tread-lock icon.
# keep the user from triggering a half-wired add-array flow. Drop
# this gate when array integration completes.
if hasattr(self, "array_gizmo"): if hasattr(self, "array_gizmo"):
self.array_gizmo.hide = True 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):
# the array button is a tertiary affordance compared to the
# primary pen / validate / cancel triad, and the smaller
# footprint keeps the edit-mode row from sprawling.
self.set_icon_gizmo_position(
"array_gizmo",
mw=mw,
x=self.ICON_VALIDATE_X + self.FEATURE_ICON_MAX_X + self.ICON_ARRAY_GAP,
y=icon_y,
z=icon_z,
billboard_rot=billboard_rot,
scale=0.35,
)
else: else:
# ``hide_pen_button = True`` keeps the pen permanently hidden — for # ``hide_pen_button = True`` keeps the pen permanently hidden — for
# groups whose edit-mode entry is already provided by another widget # groups whose edit-mode entry is already provided by another widget
@@ -50,19 +50,31 @@ from . import (
classes = ( classes = (
array.AddArray, array.AddArray,
array.CancelEditingArray,
array.DisableEditingArray, array.DisableEditingArray,
array.EditArray, array.EditArray,
array.EnableEditingArray, array.EnableEditingArray,
array.EnableEditingArrayItem,
array.FinishEditingArray,
array.ApplyArray, array.ApplyArray,
array.RegenerateArray, array.RegenerateArray,
array.RemoveArray, array.RemoveArray,
array.SelectAllArrayObjects, array.SelectAllArrayObjects,
array.SelectArrayParent, array.SelectArrayParent,
array.ArrayParentGizmoClick,
array.EditArrayFromChild,
array.Input3DCursorXArray, array.Input3DCursorXArray,
array.Input3DCursorYArray, array.Input3DCursorYArray,
array.Input3DCursorZArray, array.Input3DCursorZArray,
array.EnableEditingParametric, array.EnableEditingParametric,
array.AddArrayFromFeatureEdit, array.AddArrayFromFeatureEdit,
array.ArrayGizmoClick,
array.ToggleArrayMethod,
array.RemoveArrayLayerFromEdit,
array.InputArrayCount,
array.AdjustArrayCount,
array.GizmoArrayEdition,
array.GizmoArrayChild,
product.AddDefaultType, product.AddDefaultType,
product.AddEmptyType, product.AddEmptyType,
product.AddOccurrence, product.AddOccurrence,
File diff suppressed because it is too large Load Diff
@@ -18,6 +18,7 @@
from __future__ import annotations from __future__ import annotations
import json
import math import math
from math import cos, pi, radians, sin, tan from math import cos, pi, radians, sin, tan
from typing import Any, Literal from typing import Any, Literal
@@ -41,6 +42,7 @@ from mathutils import Matrix, Quaternion, Vector
import bonsai.core.geometry import bonsai.core.geometry
import bonsai.tool as tool import bonsai.tool as tool
from bonsai.bim.decorator_cache import TokenCache
from bonsai.bim.module.drawing.helper import format_distance from bonsai.bim.module.drawing.helper import format_distance
@@ -2177,3 +2179,313 @@ class WallFilletPreviewDecorator(tool.Blender.ViewportDecorator):
d1 = (p1.x - intersection[0]) ** 2 + (p1.y - intersection[1]) ** 2 + (p1.z - intersection[2]) ** 2 d1 = (p1.x - intersection[0]) ** 2 + (p1.y - intersection[1]) ** 2 + (p1.z - intersection[2]) ** 2
d2 = (p2.x - intersection[0]) ** 2 + (p2.y - intersection[1]) ** 2 + (p2.z - intersection[2]) ** 2 d2 = (p2.x - intersection[0]) ** 2 + (p2.y - intersection[1]) ** 2 + (p2.z - intersection[2]) ** 2
return p2 if d2 >= d1 else p1 return p2 if d2 >= d1 else p1
_BBOX_EDGES = (
(0, 1), (1, 2), (2, 3), (3, 0),
(4, 5), (5, 6), (6, 7), (7, 4),
(0, 4), (1, 5), (2, 6), (3, 7),
) # fmt: skip
def bbox_world_edges(
obj: bpy.types.Object,
) -> list[tuple[tuple[float, float, float], tuple[float, float, float]]]:
"""Return world-space (start, end) tuples for the 12 edges of ``obj``'s
bounding box. Empty list if the object has no bound_box (e.g. Empties)."""
if not obj.bound_box:
return []
mw = obj.matrix_world
corners = [mw @ Vector(c) for c in obj.bound_box]
return [(tuple(corners[a]), tuple(corners[b])) for a, b in _BBOX_EDGES]
def draw_polyline_segments(
context: bpy.types.Context,
segments: list[tuple[tuple[float, float, float], tuple[float, float, float]]],
color_rgb: tuple[float, float, float],
alpha: float,
line_width: float,
) -> None:
"""Render ``segments`` as one anti-aliased LINES batch in world space."""
if not segments:
return
verts: list[tuple[float, float, float]] = []
indices: list[tuple[int, int]] = []
for start, end in segments:
base = len(verts)
verts.append(start)
verts.append(end)
indices.append((base, base + 1))
if not tool.Blender.validate_shader_batch_data(verts, indices):
return
region = getattr(context, "region", None)
if region is None:
return
shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR")
shader.bind()
shader.uniform_float("viewportSize", (region.width, region.height))
shader.uniform_float("lineWidth", line_width)
shader.uniform_float("color", (*color_rgb, alpha))
batch = batch_for_shader(shader, "LINES", {"pos": verts}, indices=indices)
gpu.state.blend_set("ALPHA")
batch.draw(shader)
gpu.state.blend_set("NONE")
_ARRAY_LAYER_BBOX_LINE_WIDTH = 1.8
_ARRAY_LAYER_BBOX_LINE_ALPHA = 0.8
_ARRAY_LAYER_BBOX_MAX_CHILDREN = 200
def draw_array_layer_children_bbox(
context: bpy.types.Context,
parent_element: ifcopenshell.entity_instance,
layer_index: int,
max_children: int = _ARRAY_LAYER_BBOX_MAX_CHILDREN,
) -> None:
"""Paint a wireframe bbox around every child of one array layer in the
same 3D pass. Called inline from gizmo ``draw()`` methods so the highlight
tracks the hover cursor one-for-one — no POST_VIEW handler, no timing lag.
Total: silently no-ops on missing pset, unparseable JSON, out-of-range
layer index, unresolvable child GUIDs, or empty child geometry."""
if layer_index < 0:
return
data_text = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array", "Data")
if not data_text:
return
try:
layers = json.loads(data_text)
except (ValueError, TypeError):
return
if layer_index >= len(layers):
return
child_guids = layers[layer_index].get("children", [])
if not child_guids:
return
ifc_file = tool.Ifc.get()
segments: list[tuple[tuple[float, float, float], tuple[float, float, float]]] = []
for guid in child_guids[:max_children]:
try:
child_element = ifc_file.by_guid(guid)
except RuntimeError:
continue
child_obj = tool.Ifc.get_object(child_element)
if child_obj is None:
continue
segments.extend(bbox_world_edges(child_obj))
if not segments:
return
prefs = tool.Blender.get_addon_preferences()
color = prefs.decorator_color_special[:3]
draw_polyline_segments(
context,
segments,
color,
_ARRAY_LAYER_BBOX_LINE_ALPHA,
_ARRAY_LAYER_BBOX_LINE_WIDTH,
)
class ArrayPreviewDecorator(tool.Blender.ViewportDecorator):
"""Faint bbox wireframe at each future array instance during the edit lifecycle.
Pure GPU preview gated on the array's draft props — no IFC mutation."""
LINE_WIDTH = 1.2
LINE_ALPHA = 0.45
MAX_PREVIEW_INSTANCES = 200
def draw(self, context: bpy.types.Context) -> None:
if not tool.Blender.are_viewport_gizmos_enabled():
return
prefs = tool.Blender.get_addon_preferences()
obj = context.active_object
if obj is None or not obj.bound_box:
return
element = tool.Ifc.get_entity(obj)
if not element or not tool.Parametric.is_array(element):
return
props = tool.Model.get_array_props(obj)
if not props.is_editing:
return
count = int(props.count)
if count <= 1 or count > self.MAX_PREVIEW_INSTANCES:
return
segments = self._compute_segments(obj, props, count)
if not segments:
return
color = prefs.decorator_color_selected[:3]
draw_polyline_segments(context, segments, color, self.LINE_ALPHA, self.LINE_WIDTH)
def _compute_segments(
self,
parent_obj: bpy.types.Object,
props,
count: int,
) -> list[tuple[tuple[float, float, float], tuple[float, float, float]]]:
"""World-space (start, end) line segments for the bbox edges of
every future instance (i = 1 … count-1; i = 0 is the parent itself).
props.x/y/z are SI — the edit-lifecycle Enable hydrates them via
si_conversion, so no unit_scale multiplier here."""
offset = Vector((props.x, props.y, props.z))
if props.method == "DISTRIBUTE":
divider = (count - 1) if count > 1 else 1
offset = offset / divider
parent_mw = parent_obj.matrix_world
parent_corners = [Vector(c) for c in parent_obj.bound_box]
segments: list[tuple[tuple[float, float, float], tuple[float, float, float]]] = []
for i in range(1, count):
delta = offset * i
child_mw = parent_mw.copy()
if props.use_local_space:
child_mw.translation = parent_mw @ delta
else:
child_mw.translation = parent_mw.translation + delta
world_corners = [child_mw @ corner for corner in parent_corners]
for a, b in _BBOX_EDGES:
segments.append((tuple(world_corners[a]), tuple(world_corners[b])))
return segments
class ArraySelectionHighlightDecorator(tool.Blender.ViewportDecorator):
"""Bounding-box overlay surfacing the array family of the selected object.
Two activation modes:
- **Child selected** — parent drawn in the addon's *special*
decorator color (bright accent); other siblings in the *unselected*
color at lower alpha so the parent stands out. The selected child
itself keeps Blender's standard selection outline.
- **Parent selected** (idle, not editing) — every existing child drawn
in the *unselected* color at lower alpha. The parent is already
visually flagged by Blender's selection outline. Suppressed during
an active array edit lifecycle so the live preview wireframes don't
double-draw with the existing-children overlay."""
LINE_WIDTH = 1.5
PARENT_ALPHA = 0.7
SIBLING_ALPHA = 0.35
MAX_SIBLINGS = 200
def __init__(self) -> None:
self._family_cache: TokenCache = TokenCache()
def draw(self, context: bpy.types.Context) -> None:
if not tool.Blender.are_viewport_gizmos_enabled():
return
prefs = tool.Blender.get_addon_preferences()
obj = context.active_object
if obj is None:
return
if not obj.select_get():
return
element = tool.Ifc.get_entity(obj)
if not element:
return
if tool.Blender.Modifier.is_array_child(element):
self._draw_for_child(context, prefs, element, obj)
elif tool.Parametric.is_array(element):
props = tool.Model.get_array_props(obj)
if not props.is_editing:
self._draw_for_parent(context, prefs, element, obj)
def _draw_for_child(self, context, prefs, element, obj):
family = self._resolve_family_for_child(obj, element)
if family is None:
return
parent_obj, sibling_objs = family
parent_segments = bbox_world_edges(parent_obj)
if parent_segments:
draw_polyline_segments(
context,
parent_segments,
prefs.decorator_color_special[:3],
self.PARENT_ALPHA,
self.LINE_WIDTH,
)
self._draw_siblings(context, prefs, sibling_objs)
def _draw_for_parent(self, context, prefs, element, obj):
child_objs = self._resolve_children_for_parent(obj, element)
self._draw_siblings(context, prefs, child_objs)
def _resolve_family_for_child(self, obj, element):
return self._family_cache.get_or_compute(
("child", obj.session_uid, element.id()),
lambda: self._collect_family_from_child(element, obj),
)
def _resolve_children_for_parent(self, obj, element):
return (
self._family_cache.get_or_compute(
("parent", obj.session_uid, element.id()),
lambda: self._collect_children(element, exclude=obj),
)
or []
)
def _draw_siblings(self, context, prefs, sibling_objs):
if not sibling_objs:
return
if len(sibling_objs) > self.MAX_SIBLINGS:
sibling_objs = sibling_objs[: self.MAX_SIBLINGS]
segments: list[tuple[tuple[float, float, float], tuple[float, float, float]]] = []
for sib_obj in sibling_objs:
segments.extend(bbox_world_edges(sib_obj))
draw_polyline_segments(
context,
segments,
prefs.decorator_color_unselected[:3],
self.SIBLING_ALPHA,
self.LINE_WIDTH,
)
def _collect_family_from_child(self, element, obj):
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
if not pset:
return None
parent_guid = pset.get("Parent")
if not parent_guid:
return None
try:
parent_element = tool.Ifc.get().by_guid(parent_guid)
except RuntimeError:
return None
parent_obj = tool.Ifc.get_object(parent_element)
if not parent_obj:
return None
siblings = self._collect_children(parent_element, exclude=obj, also_exclude=parent_obj)
return parent_obj, siblings
def _collect_children(self, parent_element, exclude=None, also_exclude=None):
parent_data_text = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array", "Data")
if not parent_data_text:
return []
try:
layers = json.loads(parent_data_text)
except (ValueError, TypeError):
return []
children: list[bpy.types.Object] = []
seen_ids: set[int] = set()
if exclude is not None:
seen_ids.add(id(exclude))
if also_exclude is not None:
seen_ids.add(id(also_exclude))
for layer in layers:
for child_guid in layer.get("children", []):
try:
child_element = tool.Ifc.get().by_guid(child_guid)
except RuntimeError:
continue
child_obj = tool.Ifc.get_object(child_element)
if child_obj is None or id(child_obj) in seen_ids:
continue
seen_ids.add(id(child_obj))
children.append(child_obj)
return children
+25 -5
View File
@@ -103,8 +103,12 @@ def update_type_page(self: "BIMModelProperties", context: bpy.types.Context) ->
def update_relating_array_from_object(self: "BIMArrayProperties", context: bpy.types.Context) -> None: def update_relating_array_from_object(self: "BIMArrayProperties", context: bpy.types.Context) -> None:
bpy.ops.bim.enable_editing_array(item=self.is_editing) # Skip the cleanup-time clear: Finish/Cancel sets relating_array_object back to None,
return # which has no source to hydrate from. Only the user-driven pick (None → some array)
# should auto-enter edit on the picked source's layer 0.
if self.relating_array_object is None:
return
bpy.ops.bim.enable_editing_array(item=0)
def is_object_array_applicable(self: "BIMArrayProperties", obj: bpy.types.Object) -> bool: def is_object_array_applicable(self: "BIMArrayProperties", obj: bpy.types.Object) -> bool:
@@ -397,8 +401,13 @@ class BIMModelProperties(PropertyGroup):
class BIMArrayProperties(PropertyGroup): class BIMArrayProperties(PropertyGroup):
is_editing: bpy.props.IntProperty( is_editing: bpy.props.BoolProperty(
default=-1, description="Currently edited array index. -1 if not in array editing mode." default=False,
description="True while an array layer is in parametric edit mode. The specific layer is in editing_item_index.",
)
editing_item_index: bpy.props.IntProperty(
default=-1,
description="Index of the array layer currently being edited; -1 when not in edit mode.",
) )
count: bpy.props.IntProperty(name="Count", default=0, min=0) count: bpy.props.IntProperty(name="Count", default=0, min=0)
x: bpy.props.FloatProperty(name="X", default=0, subtype="DISTANCE") x: bpy.props.FloatProperty(name="X", default=0, subtype="DISTANCE")
@@ -414,6 +423,15 @@ class BIMArrayProperties(PropertyGroup):
name="Method", name="Method",
default="OFFSET", default="OFFSET",
) )
per_child_opening: bpy.props.BoolProperty(
name="Per-Child Opening",
description=(
"When the array parent fills a wall (or any voidable host), give each array child its own opening + "
"filling pair so the host is cut once per child. Disable to leave the host uncut by the children — "
"only the parent's original opening remains"
),
default=True,
)
relating_array_object: bpy.props.PointerProperty( relating_array_object: bpy.props.PointerProperty(
type=bpy.types.Object, type=bpy.types.Object,
name="Copy Array Properties", name="Copy Array Properties",
@@ -422,13 +440,15 @@ class BIMArrayProperties(PropertyGroup):
) )
if TYPE_CHECKING: if TYPE_CHECKING:
is_editing: int is_editing: bool
editing_item_index: int
count: int count: int
x: float x: float
y: float y: float
z: float z: float
use_local_space: bool use_local_space: bool
method: Literal["OFFSET", "DISTRIBUTE"] method: Literal["OFFSET", "DISTRIBUTE"]
per_child_opening: bool
sync_children: bool sync_children: bool
relating_array_object: Union[bpy.types.Object, None] relating_array_object: Union[bpy.types.Object, None]
+4
View File
@@ -1363,6 +1363,10 @@ class Blender(bonsai.core.tool.Blender):
def is_window(cls, element: entity_instance) -> bool: def is_window(cls, element: entity_instance) -> bool:
return tool.Parametric.is_window(element) return tool.Parametric.is_window(element)
@classmethod
def is_array(cls, element: entity_instance) -> bool:
return tool.Parametric.is_array(element)
class Array: class Array:
@classmethod @classmethod
def bake_children_transform(cls, parent_element: ifcopenshell.entity_instance, item: int) -> None: def bake_children_transform(cls, parent_element: ifcopenshell.entity_instance, item: int) -> None:
+6 -5
View File
@@ -147,17 +147,17 @@ class Parametric(bonsai.core.tool.Parametric):
self._data.clear() self._data.clear()
self._gen = None self._gen = None
# FIXME(PR4): array / pipe_segment / duct_segment land with their # FIXME(PR5): pipe_segment / duct_segment land with their finish/cancel
# finish/cancel operators in PR4. Adding them to EDIT_TYPES without those # operators in the MEP slice of PR5 (PR5d). Until then they stay out of
# operators makes auto-commit-on-save dispatch bim.finish_editing_<name> # EDIT_TYPES so auto-commit-on-save doesn't try to dispatch a
# for objects flagged as in-edit, which then raises because the operator # non-existent operator.
# doesn't exist. PR4 re-adds the three entries together with the operators.
EDIT_TYPES: list[ParametricObject] = [ EDIT_TYPES: list[ParametricObject] = [
ParametricObject("door", has_non_editable_path=True, supports_build_edit_lifecycle=True), 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("window", has_non_editable_path=True, supports_build_edit_lifecycle=True),
ParametricObject("stair", 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("railing", supports_build_edit_lifecycle=True),
ParametricObject("roof", supports_build_edit_lifecycle=True), ParametricObject("roof", supports_build_edit_lifecycle=True),
ParametricObject("array", supports_build_edit_lifecycle=True),
ParametricObject("wall"), ParametricObject("wall"),
] ]
@@ -169,6 +169,7 @@ class Parametric(bonsai.core.tool.Parametric):
STAIR: ClassVar[ParametricObject] STAIR: ClassVar[ParametricObject]
RAILING: ClassVar[ParametricObject] RAILING: ClassVar[ParametricObject]
ROOF: ClassVar[ParametricObject] ROOF: ClassVar[ParametricObject]
ARRAY: ClassVar[ParametricObject]
WALL: ClassVar[ParametricObject] WALL: ClassVar[ParametricObject]
_geom_generation: int = 0 _geom_generation: int = 0