simvx.core.skeleton2d

2D skeletal animation: Skeleton2D, Bone2D, and IK modifications.

Spine/DragonBones-style 2D skeletal animation built on the Node2D hierarchy. Bones are Node2D nodes that add rest-pose semantics; a Skeleton2D groups them and provides batch operations (reset, enumerate, IK).

Module Contents

Classes

Bone2D

A bone in a 2D skeleton hierarchy.

Skeleton2D

Container node for a 2D bone hierarchy.

SkeletonModification2D

Abstract base for 2D skeleton modifications (IK, constraints, etc.).

SkeletonModification2DCCDIK

Cyclic Coordinate Descent IK for 2D bone chains.

SkeletonModification2DTwoBoneIK

Analytical two-bone IK solver.

Data

API

simvx.core.skeleton2d.log

‘getLogger(…)’

simvx.core.skeleton2d.__all__

[‘Bone2D’, ‘Skeleton2D’, ‘SkeletonModification2D’, ‘SkeletonModification2DCCDIK’, ‘SkeletonModificat…

class simvx.core.skeleton2d.Bone2D(bone_length: float = 0.0, rest_transform: simvx.core.math.Transform2D | None = None, **kwargs)[source]

Bases: simvx.core.nodes_2d.node2d.Node2D

A bone in a 2D skeleton hierarchy.

Extends Node2D with a rest transform: the bone’s default pose. Skeletal animation works by offsetting the bone from its rest pose.

Attributes: rest_transform: Default pose (Transform2D). bone_length: Visual length of the bone (pixels). bone_angle: Additional angle applied on top of the rest rotation (radians).

Initialization

bone_length

‘Property(…)’

property rest_transform: simvx.core.math.Transform2D[source]

The bone’s rest (bind) pose.

property bone_angle: float[source]

Pose angle offset from rest rotation (radians).

apply_pose(angle: float, length: float | None = None) None[source]

Set the bone to a specific pose.

Args: angle: Rotation offset from rest pose (radians). length: Optional override for bone_length.

reset_to_rest() None[source]

Reset this bone to its rest transform.

set_as_rest() None[source]

Capture current local transform as the new rest pose.

property skeleton: simvx.core.skeleton2d.Skeleton2D | None[source]

Parent Skeleton2D, found by walking up the tree.

add_child(node: simvx.core.node.T) simvx.core.node.T[source]
remove_child(node: simvx.core.node.Node) None[source]
property bone_tip: simvx.core.math.types.Vec2[source]

Global position of the bone tip, bone_length along the bone’s local x axis.

Equivalently: where a child node at (bone_length, 0) sits, so the tip follows a scaled skeleton. world_rotation already includes bone_angle (applied in _recompute_global_transform).

position

‘_SpatialVecProperty(…)’

rotation

‘Property(…)’

scale

‘_SpatialVecProperty(…)’

z_index

‘Property(…)’

z_as_relative

‘Property(…)’

render_layer

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None
is_on_render_layer(index: int) bool
property absolute_z_index: int
property rotation_degrees: float
property world_position: simvx.core.math.types.Vec2
property world_rotation: float
property world_scale: simvx.core.math.types.Vec2
property world_transform: tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2, float]
property forward: simvx.core.math.types.Vec2
property right: simvx.core.math.types.Vec2
translate(offset: tuple[float, float] | numpy.ndarray)
rotate(radians: float)
rotate_deg(degrees: float)
look_at(target: tuple[float, float] | numpy.ndarray)
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)
wrap_screen(margin: float = 20)
hdr

‘Property(…)’

property transform_render_dirty: bool
strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

script_error_raised

‘Signal(…)’

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
reset_error() None
reparent(new_parent: simvx.core.node.Node)
node_at(path, default=_NO_DEFAULT)
find(target, *, direct: bool = False)
find_all(target, *, direct: bool = False)
expect(target, *, direct: bool = False)
ancestor(target)
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]
property path: str
property is_scene_root: bool
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
on_ready() None
on_enter_tree() None
on_exit_tree() None
on_update(dt: float) None
on_fixed_update(dt: float) None
on_draw(renderer) None
on_picked(event: simvx.core.events.InputEvent) None
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
queue_redraw() None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None
property app
property tree: simvx.core.scene_tree.SceneTree
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()
class simvx.core.skeleton2d.Skeleton2D(**kwargs)[source]

Bases: simvx.core.nodes_2d.node2d.Node2D

Container node for a 2D bone hierarchy.

Holds Bone2D children (which may themselves have Bone2D children) and provides batch operations and IK modification support.

Membership rule: a bone belongs to this skeleton only when every node between it and the skeleton is itself a :class:Bone2D. A bone parented under a plain grouping :class:~simvx.core.Node2D is therefore not collected, and reading :attr:bones logs a warning naming it. The rule exists so a bone’s index is a function of the bone hierarchy alone: inserting or removing an unrelated organiser node must not renumber the bones an animation or an IK modification refers to.

Signals: bones_changed: Emitted when a bone is added to or removed from this skeleton. Adding or removing a non-bone node does not emit. Reparenting a bone within one skeleton emits twice, because reparenting is a removal followed by an addition.

Initialization

bones_changed

‘Signal(…)’

property bones: tuple[simvx.core.skeleton2d.Bone2D, ...][source]

All Bone2D nodes in hierarchy order (cached).

Bones excluded by the membership rule are reported through the log the first time the list is rebuilt after the hierarchy changes.

property bone_count: int[source]

Number of Bone2D nodes in the hierarchy.

get_bone(index: int) simvx.core.skeleton2d.Bone2D[source]

Get Bone2D by index in hierarchy order.

Raises: IndexError: If index is out of range.

find_bone(name: str) simvx.core.skeleton2d.Bone2D | None[source]

Find a bone by name. Returns None if not found.

find_bone_index(name: str) int[source]

Find bone index by name. Returns -1 if not found.

set_bone_rest(index: int, transform: simvx.core.math.Transform2D) None[source]

Set the rest pose for the bone at index.

reset_to_rest() None[source]

Reset every bone in the skeleton to its rest pose.

capture_rest() None[source]

Capture the current pose of all bones as the rest pose.

add_modification(mod: simvx.core.skeleton2d.SkeletonModification2D) None[source]

Register a skeleton modification (e.g. IK solver).

remove_modification(mod: simvx.core.skeleton2d.SkeletonModification2D) None[source]

Remove a previously registered modification.

execute_modifications(delta: float) None[source]

Run all registered modifications (call from process or manually).

add_child(node: simvx.core.node.T) simvx.core.node.T[source]
remove_child(node: simvx.core.node.Node) None[source]
on_update(dt: float)[source]

Execute IK modifications each frame.

position

‘_SpatialVecProperty(…)’

rotation

‘Property(…)’

scale

‘_SpatialVecProperty(…)’

z_index

‘Property(…)’

z_as_relative

‘Property(…)’

render_layer

‘Property(…)’

set_render_layer(index: int, enabled: bool = True) None
is_on_render_layer(index: int) bool
property absolute_z_index: int
property rotation_degrees: float
property world_position: simvx.core.math.types.Vec2
property world_rotation: float
property world_scale: simvx.core.math.types.Vec2
property world_transform: tuple[simvx.core.math.types.Vec2, simvx.core.math.types.Vec2, float]
property forward: simvx.core.math.types.Vec2
property right: simvx.core.math.types.Vec2
translate(offset: tuple[float, float] | numpy.ndarray)
rotate(radians: float)
rotate_deg(degrees: float)
look_at(target: tuple[float, float] | numpy.ndarray)
transform_points(points: list[simvx.core.math.types.Vec2]) list[simvx.core.math.types.Vec2]
draw_polygon(renderer, points: list[simvx.core.math.types.Vec2], closed=True, colour=None)
wrap_screen(margin: float = 20)
hdr

‘Property(…)’

property transform_render_dirty: bool
strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

script_error_raised

‘Signal(…)’

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
reset_error() None
reparent(new_parent: simvx.core.node.Node)
node_at(path, default=_NO_DEFAULT)
find(target, *, direct: bool = False)
find_all(target, *, direct: bool = False)
expect(target, *, direct: bool = False)
ancestor(target)
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]
property path: str
property is_scene_root: bool
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
on_ready() None
on_enter_tree() None
on_exit_tree() None
on_fixed_update(dt: float) None
on_draw(renderer) None
on_picked(event: simvx.core.events.InputEvent) None
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
queue_redraw() None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None
property app
property tree: simvx.core.scene_tree.SceneTree
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()
class simvx.core.skeleton2d.SkeletonModification2D[source]

Bases: abc.ABC

Abstract base for 2D skeleton modifications (IK, constraints, etc.).

Subclasses must implement :meth:execute. The base class cannot be instantiated directly: a subclass that forgets execute will fail at instantiation rather than silently no-opping.

Initialization

property skeleton: simvx.core.skeleton2d.Skeleton2D | None[source]
abstractmethod execute(delta: float) None[source]

Apply the modification to the bound skeleton. Implemented by subclasses.

__slots__

()

class simvx.core.skeleton2d.SkeletonModification2DCCDIK(tip_bone_index: int = 0, chain_length: int = 2, max_iterations: int = 10, tolerance: float = 1.0)[source]

Bases: simvx.core.skeleton2d.SkeletonModification2D

Cyclic Coordinate Descent IK for 2D bone chains.

Iteratively rotates each bone in the chain (from tip to root) to point toward the target. Converges quickly for simple chains.

Attributes: target: World-space target position (Vec2). tip_bone_index: Index of the end-effector bone. chain_length: Number of bones in the chain (walking up from tip). max_iterations: CCD iterations per execute call. tolerance: Distance threshold to consider the target reached.

Initialization

execute(delta: float) None[source]

Run CCD IK iterations.

property skeleton: simvx.core.skeleton2d.Skeleton2D | None
__slots__

()

class simvx.core.skeleton2d.SkeletonModification2DTwoBoneIK(upper_bone_index: int = 0, lower_bone_index: int = 1, flip: bool = False)[source]

Bases: simvx.core.skeleton2d.SkeletonModification2D

Analytical two-bone IK solver.

Given a two-bone chain (upper + lower), computes exact joint angles using the law of cosines. Faster and more stable than CCD for exactly two bones.

The lower bone must be a direct child of the upper one, and both segments are measured in world space: the upper segment runs from the upper bone’s origin to the joint (wherever lower.position puts it, which need not be upper.bone_length along the upper bone), and the lower segment runs one bone_length along the lower bone. The chain therefore solves under a scaled skeleton and under a joint offset from the parent’s tip.

Attributes: target: World-space target position (Vec2). upper_bone_index: Index of the upper (root-side) bone. lower_bone_index: Index of the lower (tip-side) bone. flip: Mirror the elbow direction.

Initialization

execute(delta: float) None[source]

Solve two-bone IK analytically.

Raises: ValueError: If the two indices do not name a parent and its direct child, or if either segment measures zero. Both are rigs that cannot be solved, and both used to produce a silent wrong answer.

property skeleton: simvx.core.skeleton2d.Skeleton2D | None
__slots__

()