simvx.core.physics.nodes2d

Body + shape-carrier nodes for 2D physics.

The 2D sibling of :mod:~simvx.core.physics.nodes: the user-facing 2D body/shape/area/joint node taxonomy backed by

class:

~simvx.core.physics.world2d.Physics2DWorld. Every node extends

class:

~simvx.core.nodes_2d.node2d.Node2D (scalar world_rotation in radians), resolves its 2D world via :func:resolve_world_2d, and rides the same SceneTree fixed-step plumbing as the 3D nodes.

These nodes reuse the dimension-agnostic pieces by import (:class:BodyMode,

class:

PhysicsMaterial, :class:Bitmask, :class:Property, :class:Signal) and carry 2D-only state (one_way / one_way_normal, scalar spin). All of them are exported from simvx.core.

Module Contents

Classes

Contact2D

Node-level 2D collision-event payload for collided / separated.

CollisionShape2D

A Node2D that carries a :class:Shape2D collision-geometry resource.

PhysicsObject2D

Abstract base of every 2D node that owns a body in the physics world.

PhysicsBody2D

A 2D physics body whose motion mode is a Property.

CharacterBody2D

A KINEMATIC 2D physics body with a swept movement helper.

Area2D

A 2D pure sensor zone (trigger): broadphase-driven overlap detection.

GravityArea2D

A 2D force-field zone: an ADDITIVE gravity effector over the bodies it overlaps.

Joint2D

Base class for the 2D constraint nodes (node-agnostic carriers).

FixedJoint2D

Weld two 2D bodies: lock their full relative transform (position + rotation).

PinJoint2D

Pin two 2D bodies at a single world-space point, rotation free.

HingeJoint2D

Hinge two 2D bodies at :attr:anchor (no axis: 2D rotation is 1-DOF).

SpringJoint2D

Soft distance-spring between the two 2D body centres (compliant, not rigid).

GrooveJoint2D

Slide body_b’s anchor along a groove (line segment) fixed on body_a.

Data

API

simvx.core.physics.nodes2d.log

‘getLogger(…)’

simvx.core.physics.nodes2d.__all__

[‘BodyMode’, ‘Contact2D’, ‘CollisionShape2D’, ‘PhysicsObject2D’, ‘PhysicsBody2D’, ‘CharacterBody2D’,…

class simvx.core.physics.nodes2d.Contact2D[source]

Node-level 2D collision-event payload for collided / separated.

The 2D sibling of :class:~simvx.core.physics.nodes.Contact. Node-typed and built by the tree’s dispatch from a node-agnostic ContactEvent2D. The tree fills other with the peer node and reorients normal / velocity so they always point toward the RECEIVING body.

Distinct from the world-level :class:~simvx.core.physics.world2d.SweepHit2D (a query/sweep result keyed by handle): this carries the resolved peer node.

Attributes: other: The OTHER physics object involved in the collision. point: World-space contact point (Vec2). normal: Unit normal oriented TOWARD the receiving body (the separating direction). Degenerate (Vec2(0)) on separated. impulse: Normal impulse magnitude the solver applied this step. 0.0 on separated and on a collided the solver did not push apart. None iff the resolved backend cannot measure an applied impulse (it does not advertise :attr:~simvx.core.physics.capability.Capability.CONTACT_IMPULSE). impulse_estimate: The portable stand-in for impulse, one formula on every backend: the impulse it would take to arrest the approach, from the pair’s masses and the difference of the two bodies’ LINEAR velocities. Always a number, and 0.0 on separated and for a pair that is not approaching. It is deliberately not computed from velocity below, whose at-point meaning would put each backend’s choice of manifold point into the number. See :attr:~simvx.core.physics.world.ContactEvent.impulse_estimate. velocity: Relative velocity of other w.r.t. the receiver AT THE CONTACT POINT, pre-solve (Vec2). Spin counts: a wheel skidding on the ground reports the speed of its tread, which its centre is not moving at. Degenerate (Vec2(0)) on separated.

other: PhysicsObject2D

None

point: simvx.core.math.Vec2

None

normal: simvx.core.math.Vec2

None

impulse: float | None

None

impulse_estimate: float

None

velocity: simvx.core.math.Vec2

None

class simvx.core.physics.nodes2d.CollisionShape2D(**kwargs: object)[source]

Bases: simvx.core.nodes_2d.node2d.Node2D

A Node2D that carries a :class:Shape2D collision-geometry resource.

Geometry lives in a single :class:Shape2D resource (CircleShape2D / RectangleShape2D / …), so the node stays open for new shape kinds. A node that only carries data: collision detection itself belongs to the physics world. It exists so a body can discover its geometry as a child node.

Initialization

shape: simvx.core.physics.shapes2d.Shape2D

‘Property(…)’

build_shape(world: simvx.core.physics.world2d.Physics2DWorld) simvx.core.physics.world2d.ShapeHandle[source]

Build this node’s shape into an opaque backend shape handle.

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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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.physics.nodes2d.PhysicsObject2D(**kwargs: object)[source]

Bases: simvx.core.physics._pose_reconcile._PhysicsPoseReconcile, simvx.core.nodes_2d.node2d.Node2D

Abstract base of every 2D node that owns a body in the physics world.

The 2D sibling of :class:~simvx.core.physics.nodes.PhysicsObject3D; see it for the rationale. It owns the backend handle and its world, the

Attr:

collision_layer / :attr:collision_mask pair, the :attr:shape convenience collider and its resolution, the initial-pose build, the node-pose-to-body teleport, the :attr:collided / :attr:separated Signals and the exit-tree teardown. Subclasses own body CREATION only.

Initialization

collision_layer: int

‘Bitmask(…)’

collision_mask: int

‘Bitmask(…)’

shape: simvx.core.physics.shapes2d.Shape2D | None

‘Property(…)’

collided

‘Signal(…)’

separated

‘Signal(…)’

property handle: simvx.core.physics.world2d.BodyHandle | None[source]

This object’s opaque backend body handle, or None if none was created.

property world: simvx.core.physics.world2d.Physics2DWorld | None[source]

The :class:Physics2DWorld this object’s body was created in, or None.

on_exit_tree() None[source]

Retire from the tree’s handle->node map and destroy the backend body.

The 2D twin of

Meth:

~simvx.core.physics.nodes.PhysicsObject3D.on_exit_tree: retired rather than dropped, so a peer’s separated / body_exited can still name this body on the step after it goes.

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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.PhysicsBody2D(**kwargs: object)[source]

Bases: simvx.core.physics.nodes2d.PhysicsObject2D

A 2D physics body whose motion mode is a Property.

The 2D sibling of :class:~simvx.core.physics.nodes.PhysicsBody3D: one concrete body node with a runtime-mutable :class:BodyMode mode knob (STATIC | KINEMATIC | DYNAMIC). :class:PhysicsObject2D owns the body lifecycle (resolve 2D world, build shape, unregister + destroy on exit); this class adds the mode-typed create, the mass/material knobs and the 2D-only one_way / one_way_normal Properties. velocity is a Vec2 accessor; spin is a scalar (2D angular is 1-DOF).

Parenting, and which mode follows a moving ancestor. A STATIC body follows: move any ancestor and the collider goes with it, because nothing simulates a STATIC body and its pose is authored data. That is the mode to parent under a node whose motion is authoring rather than gameplay – an editor drag, a group positioned once at setup, a level chunk assembled under a common origin.

A DYNAMIC or KINEMATIC body does NOT follow: the simulation owns its pose, and an ancestor’s move would be erased by the next write-back anyway. So for a platform that must carry or push bodies at RUNTIME, do not parent them to it. Make the platform a KINEMATIC body and move that body itself, because a STATIC pose write carries no derived velocity and would slide riders off rather than transport them. This is a deliberate difference from engines that push the composed transform into every body type regardless of mode.

Initialization

mode: simvx.core.physics.world2d.BodyMode

‘Property(…)’

mass: float

‘Property(…)’

material: simvx.core.physics.material.PhysicsMaterial

‘Property(…)’

linear_damping: float

‘Property(…)’

angular_damping: float

‘Property(…)’

gravity_scale: float

‘Property(…)’

continuous: bool

‘Property(…)’

can_sleep: bool

‘Property(…)’

one_way: bool

‘Property(…)’

one_way_normal: simvx.core.math.Vec2

‘Property(…)’

property is_sleeping: bool[source]

True if the simulated body is asleep. False when inert.

wake() None[source]

Wake this body now, whatever its sleep timer had reached.

Rarely needed, because every write that changes what the solver reads already wakes the body. A no-op when the body is awake, when it is STATIC or KINEMATIC (never asleep), and when inert (not in tree / no body).

sleep() None[source]

Put this body to sleep now, without waiting for it to settle.

Freezes it where it is, still a full collider, until something wakes it. A no-op when the body is STATIC or KINEMATIC, when :attr:can_sleep is False, when inert, and on a backend that does not advertise

Attr:

~simvx.core.physics.capability.Capability.SLEEP.

property velocity: simvx.core.math.Vec2[source]

Live linear velocity (Vec2), read/written straight to the physics world.

Runtime sim state, NOT a serialized :class:Property. Returns Vec2() when inert (not in tree / no body). The setter preserves the current angular velocity (:attr:spin).

property spin: float[source]

Live scalar angular velocity (radians/s, CCW positive), written to the physics world.

Companion to :attr:velocity; same live-state, never-serialized rules. Returns 0.0 when inert. The setter preserves linear velocity.

push(impulse: simvx.core.math.Vec2 | collections.abc.Sequence[float], *, at: simvx.core.math.Vec2 | collections.abc.Sequence[float] | None = None) None[source]

Apply an instantaneous linear impulse NOW (DYNAMIC-only, inert otherwise).

at is a world-space point; its offset r = at - centre adds a scalar angular impulse via the 2D cross product.

spin_up(angular_impulse: float) None[source]

Apply an instantaneous SCALAR angular impulse (DYNAMIC-only, inert otherwise).

Applied via the body’s real inverse moment of inertia (the 2D backend has a per-shape scalar moment, unlike the 3D inverse_mass stand-in).

add_force(force: simvx.core.math.Vec2 | collections.abc.Sequence[float], *, at: simvx.core.math.Vec2 | collections.abc.Sequence[float] | None = None) None[source]

Accumulate a continuous force, applied during the NEXT fixed step.

Auto-cleared each step (re-call per on_fixed_update to sustain). DYNAMIC-only, inert otherwise. at adds a scalar torque cross(r, force).

add_torque(torque: float) None[source]

Accumulate a continuous SCALAR torque for the NEXT fixed step.

Auto-cleared each step like :meth:add_force. DYNAMIC-only, inert otherwise. Applied via the real inverse moment of inertia.

on_enter_tree() None[source]
move_and_collide(velocity: simvx.core.math.Vec2 | collections.abc.Sequence[float], dt: float = 1.0) simvx.core.physics.world2d.SweepHit2D | None[source]

Move by velocity * dt, stop at the first contact, sync, return it.

Meaningful for mode == KINEMATIC. Sweeps the body’s shape and reports the first blocker as a

Class:

~simvx.core.physics.world2d.SweepHit2D. After the sweep the node’s transform is synced SYNCHRONOUSLY from the simulated body. Returns None on no contact or when inert.

A sweep is a geometric query, not a solver pass, so the result carries the blocker’s handle, contact point, separating normal and guaranteed-clear distance, and no impulse: there is no solver lambda to report.

on_exit_tree() None[source]
collision_layer: int

‘Bitmask(…)’

collision_mask: int

‘Bitmask(…)’

shape: simvx.core.physics.shapes2d.Shape2D | None

‘Property(…)’

collided

‘Signal(…)’

separated

‘Signal(…)’

property handle: simvx.core.physics.world2d.BodyHandle | None
property world: simvx.core.physics.world2d.Physics2DWorld | None
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.CharacterBody2D(**kwargs: object)[source]

Bases: simvx.core.physics.nodes2d.PhysicsObject2D

A KINEMATIC 2D physics body with a swept movement helper.

The 2D sibling of :class:~simvx.core.physics.nodes.CharacterBody3D: an ordinary kinematic body in the same body table, visible to raycasts, shape queries, areas and the contact stream, collided with and rested on by dynamic bodies, and blocking another character when their layers and masks mutually opt in. :meth:move_and_slide is the only thing that distinguishes it. It never carries a body resting on it, but it does shove one: at the default

Attr:

push_factor of 1.0 a blocking dynamic body takes the arrest impulse of the pair, so it leaves at no more than the speed the character was walking, scaled by the mass ratio. Set the factor to 0.0 and a dynamic body stops the sweep exactly as a static one does. The contacts it resolved are handed back in

Attr:

collisions either way, so game code can apply its own impulses.

It is deliberately NOT registered for the dynamic auto bulk-sync;

Meth:

move_and_slide syncs the node transform synchronously.

Attr:

velocity is a plain instance attribute, deliberately NOT the world-backed :attr:PhysicsBody2D.velocity property: the simulated body’s velocity must stay zero, or the world’s integrator would re-apply the motion

Meth:

move_and_slide has already performed.

Initialization

slope_limit: float

‘Property(…)’

step_height: float

‘Property(…)’

max_slides: int

‘Property(…)’

skin_width: float

‘Property(…)’

mass: float

‘Property(…)’

push_factor: float

‘Property(…)’

is_on_floor() bool[source]

True if the last :meth:move_and_slide ended on a walkable floor.

is_on_wall() bool[source]

True if the last :meth:move_and_slide hit a wall.

is_on_ceiling() bool[source]

True if the last :meth:move_and_slide hit a ceiling.

move_and_slide(dt: float) None[source]

Collide-and-slide by self.velocity * dt, then sync the transform.

Runs the shared policy in :mod:simvx.core.physics.slide against the simulated body. Writes the deflected post-slide velocity back to :attr:velocity, caches floor/wall/ceiling state + :attr:floor_normal +

Attr:

collisions, shoves each non-floor blocking contact if

Attr:

push_factor is non-zero, and syncs the node’s transform SYNCHRONOUSLY. No-op if inert (no collider).

on_enter_tree() None[source]
collision_layer: int

‘Bitmask(…)’

collision_mask: int

‘Bitmask(…)’

shape: simvx.core.physics.shapes2d.Shape2D | None

‘Property(…)’

collided

‘Signal(…)’

separated

‘Signal(…)’

property handle: simvx.core.physics.world2d.BodyHandle | None
property world: simvx.core.physics.world2d.Physics2DWorld | None
on_exit_tree() None
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.Area2D(**kwargs: object)[source]

Bases: simvx.core.physics.nodes2d.PhysicsObject2D

A 2D pure sensor zone (trigger): broadphase-driven overlap detection.

The 2D sibling of :class:~simvx.core.physics.nodes.Area3D. Owns a SENSOR body (a flag, not a separate class): it participates in the broadphase but is excluded from collision resolution. Detection is ONE-DIRECTIONAL (the area sees another body iff area.collision_mask & other.collision_layer). Overlap edges arrive as a buffered, deferred event stream drained by the tree; the live overlap sets are maintained from those edges (never a per-frame tree scan).

Geometry follows the standard order (shape Property if set, else first CollisionShape2D child, else inert). The sensor body is STATIC.

As in 3D, a STATIC body is reported only where

Attr:

~simvx.core.physics.capability.Capability.SENSOR_DETECTS_STATIC is advertised, which is both 2D backends: pymunk forms no static-vs-static pair, so the adapter holds every sensor KINEMATIC instead, which also lets an Area2D there report another Area2D.

Initialization

monitoring: bool

‘Property(…)’

shape: simvx.core.physics.shapes2d.Shape2D | None

‘Property(…)’

body_entered

‘Signal(…)’

body_exited

‘Signal(…)’

area_entered

‘Signal(…)’

area_exited

‘Signal(…)’

get_overlapping_bodies(*, group=None, type=None)[source]

Bodies currently overlapping this area (live, as of the last step).

A peer destroyed mid-overlap is dropped from the maintained set by the overlap EXIT the seam reports for it on the step after it goes. Detached peers (handle is None) are also filtered here, covering the one step before that event lands.

The optional filters narrow the result without a tree scan: they test the already-maintained overlap set. Both are ANDed when given:

Args: group: When set, keep only bodies that belong to this SceneTree group (body.is_in_group(group)). The canonical “what of kind X am I touching?” query (e.g. area.get_overlapping_bodies(group="mobs")). type: When set, keep only bodies that are instances of this :class:PhysicsObject2D subclass (isinstance(body, type)), and the result is typed as a list of THAT class. A character is an ordinary kinematic body, so it appears here like any other; pass type=PhysicsBody2D when the caller needs the force/mass API a character does not have.

Returns: The overlapping bodies (live, detached peers filtered) matching every supplied filter, in arbitrary order.

get_overlapping_areas() list[simvx.core.physics.nodes2d.Area2D][source]

Other areas currently overlapping this area (live, as of the last step).

on_enter_tree() None[source]
on_exit_tree() None[source]
collision_layer: int

‘Bitmask(…)’

collision_mask: int

‘Bitmask(…)’

collided

‘Signal(…)’

separated

‘Signal(…)’

property handle: simvx.core.physics.world2d.BodyHandle | None
property world: simvx.core.physics.world2d.Physics2DWorld | None
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.GravityArea2D(**kwargs: object)[source]

Bases: simvx.core.physics.nodes2d.Area2D

A 2D force-field zone: an ADDITIVE gravity effector over the bodies it overlaps.

The 2D sibling of :class:~simvx.core.physics.nodes.GravityArea3D. Inherits the entire sensor mechanism unchanged and adds an :meth:on_fixed_update handler that, each fixed step, applies a field (additive on top of world gravity) to every DYNAMIC overlapping body. Two independent SUMMING components:

  • Directional (:attr:gravity): a uniform acceleration (Vec2) applied regardless of body mass, exactly like world gravity.

  • Point (:attr:point_gravity / :attr:point_strength): a CONSTANT acceleration of magnitude :attr:point_strength toward the area centre (:attr:world_position).

The acceleration becomes a force via add_force(mass * accel) (the integrator divides by mass, so the net effect is mass-INDEPENDENT, like gravity). add_force is auto-cleared each step, so the field is freshly re-applied every fixed step and consumed once by the following world.step.

Initialization

gravity: simvx.core.math.Vec2

‘Property(…)’

point_gravity: bool

‘Property(…)’

point_strength: float

‘Property(…)’

on_fixed_update(dt: float) None[source]

Apply the additive gravity field to every DYNAMIC overlapping body.

Runs BEFORE world.step (the tree drives node on_fixed_update first, then steps the worlds), accumulating a fresh force consumed by that step. dt is accepted to match the hook signature but not used to scale (add_force is a continuous force, not an impulse).

A :class:CharacterBody2D overlapping the area IS in the overlap set (it is an ordinary kinematic body) but is never force-driven: it is not a

Class:

PhysicsBody2D and so has no :meth:~PhysicsBody2D.add_force, and the :attr:~CharacterBody2D.mass it does carry is read only by its own push arithmetic, never by an integrator. It is position-driven by

Meth:

CharacterBody2D.move_and_slide instead. The type= filter is what excludes it.

monitoring: bool

‘Property(…)’

shape: simvx.core.physics.shapes2d.Shape2D | None

‘Property(…)’

body_entered

‘Signal(…)’

body_exited

‘Signal(…)’

area_entered

‘Signal(…)’

area_exited

‘Signal(…)’

get_overlapping_bodies(*, group=None, type=None)
get_overlapping_areas() list[simvx.core.physics.nodes2d.Area2D]
on_enter_tree() None
on_exit_tree() None
collision_layer: int

‘Bitmask(…)’

collision_mask: int

‘Bitmask(…)’

collided

‘Signal(…)’

separated

‘Signal(…)’

property handle: simvx.core.physics.world2d.BodyHandle | None
property world: simvx.core.physics.world2d.Physics2DWorld | None
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.Joint2D(*, body_a: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, body_b: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, **kwargs: object)[source]

Bases: simvx.core.nodes_2d.node2d.Node2D

Base class for the 2D constraint nodes (node-agnostic carriers).

The 2D sibling of :class:~simvx.core.physics.nodes.Joint3D. Constrains two

Class:

PhysicsBody2D instances in the 2D world; a thin carrier that owns the constraint lifecycle (create on enter-tree, remove on exit-tree). Body references (:attr:body_a / :attr:body_b) are PLAIN instance attributes, set programmatically or via the constructor.

Same resolution rules as the 3D base: a missing body is a soft-inert no-op; a cross-world (or missing-world) pair after handles resolve is a ValueError. ALWAYS add a joint AFTER both of its bodies (or re-enter it).

Initialization

property joint: simvx.core.physics.world2d.JointHandle | None[source]

This joint’s opaque backend handle, or None if inert / not created.

property world: simvx.core.physics.world2d.Physics2DWorld | None[source]

The :class:Physics2DWorld this joint was created in, or None.

on_enter_tree() None[source]
on_exit_tree() None[source]
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.FixedJoint2D(*, body_a: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, body_b: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, **kwargs: object)[source]

Bases: simvx.core.physics.nodes2d.Joint2D

Weld two 2D bodies: lock their full relative transform (position + rotation).

Captures the current relative pose at enter-tree, in body_a’s frame, and holds it there: the two bodies move as one rigid assembly, and the assembly swings round when body_a turns. No anchor Properties.

Initialization

property joint: simvx.core.physics.world2d.JointHandle | None
property world: simvx.core.physics.world2d.Physics2DWorld | None
on_enter_tree() None
on_exit_tree() None
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.PinJoint2D(*, body_a: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, body_b: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, **kwargs: object)[source]

Bases: simvx.core.physics.nodes2d.Joint2D

Pin two 2D bodies at a single world-space point, rotation free.

The two bodies cannot separate at :attr:anchor but rotate freely about it, so a chain of pins is a rope and a pin to a STATIC body is a pendulum. The anchor is captured into each body’s own frame at enter-tree, so a pin on a spinning body orbits with it.

Built-in backend caveat: convergence is a few sequential-impulse iterations, so a loaded chain sags and a hard yank stretches a link before the solver pulls it straight; the pymunk backend hangs the same chain on its rest length. See docs/core/physics_backends.md for the measured sag.

Initialization

anchor: simvx.core.math.Vec2

‘Property(…)’

property joint: simvx.core.physics.world2d.JointHandle | None
property world: simvx.core.physics.world2d.Physics2DWorld | None
on_enter_tree() None
on_exit_tree() None
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.HingeJoint2D(*, body_a: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, body_b: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, **kwargs: object)[source]

Bases: simvx.core.physics.nodes2d.Joint2D

Hinge two 2D bodies at :attr:anchor (no axis: 2D rotation is 1-DOF).

2D rotation is 1-DOF, so a 2D hinge has no axis argument: this tier it is a pin at anchor (motors / angular limits are a follow-on).

Initialization

anchor: simvx.core.math.Vec2

‘Property(…)’

property joint: simvx.core.physics.world2d.JointHandle | None
property world: simvx.core.physics.world2d.Physics2DWorld | None
on_enter_tree() None
on_exit_tree() None
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.SpringJoint2D(*, body_a: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, body_b: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, **kwargs: object)[source]

Bases: simvx.core.physics.nodes2d.Joint2D

Soft distance-spring between the two 2D body centres (compliant, not rigid).

Pulls the two body centres toward :attr:rest_length apart with

Attr:

stiffness (N/m) and :attr:damping (N*s/m). Intentionally compliant.

Initialization

rest_length: float

‘Property(…)’

stiffness: float

‘Property(…)’

damping: float

‘Property(…)’

property joint: simvx.core.physics.world2d.JointHandle | None
property world: simvx.core.physics.world2d.Physics2DWorld | None
on_enter_tree() None
on_exit_tree() None
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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.physics.nodes2d.GrooveJoint2D(*, body_a: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, body_b: simvx.core.physics.nodes2d.PhysicsBody2D | None = None, **kwargs: object)[source]

Bases: simvx.core.physics.nodes2d.Joint2D

Slide body_b’s anchor along a groove (line segment) fixed on body_a.

The 2D-only pymunk-native slider-on-a-line constraint (no 3D equivalent).

Attr:

groove_a / :attr:groove_b are body-local points in body_a’s frame defining the groove segment; :attr:anchor_b is a body-local point in body_b’s frame. body_b’s anchor is held on that segment: it slides freely ALONG the groove and is pinned PERPENDICULAR to it, clamped between the endpoints.

The groove turns with body_a and the anchor with body_b: both are held in the frames they are declared in.

There is no slide motor on any backend: drive the slider with apply_force on body_b. The endpoint stop differs in firmness: pymunk keeps the anchor on the groove under loads that push the built-in solver’s slider past the end and leave it there, the built-in stop being the same soft positional push its other joints use. docs/core/physics_backends.md has the measurement.

Initialization

groove_a: simvx.core.math.Vec2

‘Property(…)’

groove_b: simvx.core.math.Vec2

‘Property(…)’

anchor_b: simvx.core.math.Vec2

‘Property(…)’

property joint: simvx.core.physics.world2d.JointHandle | None
property world: simvx.core.physics.world2d.Physics2DWorld | None
on_enter_tree() None
on_exit_tree() None
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
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) 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_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__()