cutan.impacts.objects

The things that strike: a stick and a ball, as ordinary an props.

An ImpactObject is data: the prop to draw, where it stands, the ONE property the stroke animates and the affine map from stroke height h to that property’s value, and the named keypoints a tracker would report. Being affine is the whole contract — it is what lets cutan.impacts.stroke reason in h while the renderer tweens the property, with no approximation between them.

The objects are real props (an#108), stored in a real props store and drawn by the real cutout rig builder, so the harness exercises the same path an animation does. Their art is generated here as plain SVG, sized so the rig’s view-box factor is exactly 1 (view_box height = the compiler’s SCENE_PX_PER_VIEW_BOX): one SVG pixel is one scene pixel, and a keypoint’s local coordinates are read straight off the drawing.

Coordinates are scene pixels relative to the canvas centre, y down — the space StagePlacement.at and the camera use. Rotation is in radians, clockwise-positive on screen, as PixiJS applies it.

>>> s = stick()
>>> s.pose(0.0) == {("stick", "rotation"): 0.35}
True
>>> (ch,) = s.channels
>>> round(ch.value(1.0) - ch.value(0.0), 6) == -ch.stroke_extent
True
>>> list(ball().pose(1.0))
[('ball', 'y')]

Module Attributes

IMPACT_OBJECTS

Name -> factory.

Functions

ball(*[, radius, x, floor_y, drop, ...])

A ball moving onto a floor whose top is at floor_y.

impact_object(name, **kwargs)

Build a registered object by name.

stick(*[, length, thickness, pivot, ...])

A drumstick rotating about pivot (its butt — the hand).

Classes

ImpactObject(name, art, at, channels, ...[, ...])

One striking object, its surface, and how the stroke moves it.

PropArt(ref, descriptor, parts)

A prop's descriptor and its SVG parts — what goes into a props store.

StrokeChannel(target, property, ...)

One animated property, AFFINE in stroke height h.

cutan.impacts.objects.IMPACT_OBJECTS: dict[str, Callable[..., ImpactObject]] = {'ball': <function ball>, 'stick': <function stick>}

Name -> factory. The registry the clip spec and the CLI resolve names through.

class cutan.impacts.objects.ImpactObject(name, art, at, channels, keypoints, impact_keypoint, keypoint_nodes=dict(), surface_art=None, surface_at=None, params=dict())[source]

Bases: object

One striking object, its surface, and how the stroke moves it.

channels are the properties the stroke drives — one for a stick or a ball, two for a forearm-plus-stick limb (each affine in the SAME h, so the motion stays exact). keypoints are local points by name; each lives on the node keypoint_nodes[name] names, the entity itself by default.

impact_keypoint: str

The keypoint that does the striking (a stick’s tip, a ball’s bottom).

keypoints: Mapping[str, tuple[float, float]]

Local points by name. What a tracker would report.

pose(h)[source]

{(node path, property): value} at stroke height h.

Return type:

dict[tuple[str, str], float]

surface_at: tuple[float, float] | None

Where the surface’s top edge is centred, in scene coordinates.

class cutan.impacts.objects.PropArt(ref, descriptor, parts)[source]

Bases: object

A prop’s descriptor and its SVG parts — what goes into a props store.

class cutan.impacts.objects.StrokeChannel(target, property, contact_value, stroke_extent)[source]

Bases: object

One animated property, AFFINE in stroke height h.

value(h) = contact_value - stroke_extent * h: h = 0 is contact and stroke_extent is how far a full stroke moves the property away from it (radians, or pixels). Affine is the whole contract — it is what makes an eased tween of the property exactly the same easing of h.

cutan.impacts.objects.ball(*, radius=18.0, x=0.0, floor_y=90.0, drop=170.0, arc_radius=None, color='#111827', surface_color='#9ca3af', surface_size=(160.0, 24.0))[source]

A ball moving onto a floor whose top is at floor_y.

A full stroke lifts it drop pixels. Keypoints: center and bottom (its contact point).

Straight by default. With arc_radius (cutan#27, a wide stick arc: a hard hit) it hangs on a circle of that radius about a pivot straight above its contact point, and the stroke ROTATES the pivot: the ball swings up to one side and falls back along the arc, its contact the arc’s lowest point. The angle that lifts it drop pixels is acos(1 - drop / arc_radius); rotation is the one channel, affine in h, so the curve stays exact.

>>> b = ball(arc_radius=300.0)
>>> round(b.params["arc_sweep"], 4), b.at == (0.0, b.params["pivot_y"])
(1.1226, True)
Return type:

ImpactObject

cutan.impacts.objects.impact_object(name, **kwargs)[source]

Build a registered object by name.

>>> impact_object("ball").name
'ball'
>>> impact_object("hammer")
Traceback (most recent call last):
  ...
KeyError: "no impact object 'hammer'; known: ['ball', 'stick']"
Return type:

ImpactObject

cutan.impacts.objects.stick(*, length=200.0, thickness=12.0, pivot=(-120.0, -30.0), contact_angle=0.35, swing=0.95, color='#111827', surface_color='#9ca3af', surface_size=(140.0, 24.0))[source]

A drumstick rotating about pivot (its butt — the hand).

At contact it points contact_angle radians below horizontal; a full stroke raises it by swing radians. Keypoints: pivot and tip (the end of its axis). The surface’s top meets the lowest point of its rounded end.

Return type:

ImpactObject