Source code for simvx.editor.panels.inspector_sections._collision_section

"""CollisionShapeSection -- shape type + parameters.

Registered with the section registry via @register_inspector_section at
import time.
"""

import numpy as np

from simvx.core import (
    CollisionShape2D,
    CollisionShape3D,
    Control,
    DropDown,
    SpinBox,
)
from simvx.core.collision import BoxShape, CapsuleShape, SphereShape

from ._base import (
    InspectorSection,
    _font_size,
    _make_property_row,
    _make_vector_row,
    register_inspector_section,
)


[docs] @register_inspector_section class CollisionShapeSection(InspectorSection): section_title = "Collision Shape" priority = 30
[docs] def can_handle(self, node): return isinstance(node, CollisionShape2D | CollisionShape3D)
[docs] def handled_properties(self, node): return {"radius"}
[docs] def build_rows(self, node, ctx): rows: list[Control] = [] is_3d = isinstance(node, CollisionShape3D) shape = getattr(node, "_collision_shape", None) shape_types = ["Sphere", "Box", "Capsule"] current_idx = 0 if isinstance(shape, BoxShape): current_idx = 1 elif isinstance(shape, CapsuleShape): current_idx = 2 dd = DropDown(items=shape_types, selected=current_idx) dd.font_size = _font_size() dd.item_selected.connect( lambda idx, c=ctx, n=node, st=shape_types: _change_shape_type(n, st[idx], c)) rows.append(_make_property_row("Shape", dd)) ctx.register_widget("shape_type", dd) if isinstance(shape, BoxShape): he = shape.half_extents comps = 3 if is_3d else 2 he_vals = tuple(float(he[i]) for i in range(comps)) he_row = _make_vector_row("", comps, he_vals, step=0.1, min_val=0.01) for i, spin in enumerate(he_row._spinboxes): axis = i spin.value_changed.connect( lambda val, ax=axis, c=ctx, n=node, s=shape: _change_box_extents(n, s, ax, val, c)) rows.append(_make_property_row("Half Ext", he_row)) ctx.register_widget("shape_half_extents", he_row) elif isinstance(shape, CapsuleShape): rad_spin = SpinBox(min_val=0.01, max_val=10000, value=shape.radius, step=0.1) rad_spin.font_size = _font_size() rad_spin.value_changed.connect( lambda val, c=ctx, n=node, s=shape: _change_capsule_param(n, s, "radius", val, c)) rows.append(_make_property_row("Radius", rad_spin)) ctx.register_widget("shape_capsule_radius", rad_spin) height_spin = SpinBox(min_val=0.01, max_val=10000, value=shape.height, step=0.1) height_spin.font_size = _font_size() height_spin.value_changed.connect( lambda val, c=ctx, n=node, s=shape: _change_capsule_param(n, s, "height", val, c)) rows.append(_make_property_row("Height", height_spin)) ctx.register_widget("shape_capsule_height", height_spin) else: # Sphere rad_spin = SpinBox(min_val=0.01, max_val=10000, value=node.radius, step=0.1) rad_spin.font_size = _font_size() rad_spin.value_changed.connect( lambda val, c=ctx, n=node: c.on_property_changed(n, "radius", n.radius, val)) rows.append(_make_property_row("Radius", rad_spin)) ctx.register_widget("shape_sphere_radius", rad_spin) return rows
def _change_shape_type(node, shape_name, ctx): old_shape = getattr(node, "_collision_shape", None) isinstance(node, CollisionShape3D) if shape_name == "Sphere": new_shape = SphereShape(radius=node.radius) elif shape_name == "Box": new_shape = BoxShape(half_extents=(0.5, 0.5, 0.5)) elif shape_name == "Capsule": new_shape = CapsuleShape(radius=0.5, height=2.0) else: return def do_fn(): node._collision_shape = new_shape def undo_fn(): node._collision_shape = old_shape ctx.on_callable_command(do_fn, undo_fn, description=f"Set {node.name} shape to {shape_name}") ctx.property_changed_signal.emit(node, "_collision_shape", old_shape, new_shape) ctx.rebuild() def _change_box_extents(node, shape, axis, value, ctx): if not isinstance(shape, BoxShape): return old_he = np.copy(shape.half_extents) new_he = np.copy(shape.half_extents) new_he[axis] = value def do_fn(): shape.half_extents = np.copy(new_he) def undo_fn(): shape.half_extents = np.copy(old_he) ctx.on_callable_command(do_fn, undo_fn, description=f"Set {node.name} box half_extents") def _change_capsule_param(node, shape, param, value, ctx): if not isinstance(shape, CapsuleShape): return old_val = getattr(shape, param) if old_val == value: return def do_fn(): setattr(shape, param, value) def undo_fn(): setattr(shape, param, old_val) ctx.on_callable_command(do_fn, undo_fn, description=f"Set {node.name} capsule {param}")