Animation & Rendering¶
Animations add motion to entities. Renderers convert scenes (with or without animations) into output formats.
See also
For a tutorial introduction, see Animation.
Entity Animation Methods¶
All entities inherit these methods from the base Entity class. Each returns self for chaining.
Universal Methods¶
animate_move ¶
animate_move(to: RelCoordLike | None = None, *, by: RelCoordLike | None = None, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'ease-in-out', hold: bool = True, repeat: RepeatLike = False, bounce: bool = False) -> Entity
Animate position using relative coordinates.
Two modes:
- Absolute:
entity.animate_move(to=(0.8, 0.5)) - Relative:
entity.animate_move(by=(0.1, 0))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to
|
RelCoordLike | None
|
Target position as RelCoord or (rx, ry) tuple. Mutually exclusive with by. |
None
|
by
|
RelCoordLike | None
|
Movement offset as RelCoord or (drx, dry) tuple. Mutually exclusive with to. |
None
|
duration
|
float
|
Duration in seconds. |
1.0
|
delay
|
float
|
Seconds before animation starts. |
0.0
|
easing
|
EasingLike
|
Speed curve (default "ease-in-out" for natural motion). |
'ease-in-out'
|
hold
|
bool
|
Hold final value after completion. |
True
|
repeat
|
RepeatLike
|
|
False
|
bounce
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Entity
|
Self, for method chaining. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If both to and by are given, or neither. |
animate_spin ¶
animate_spin(angle: float = 360, *, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'linear', hold: bool = True, repeat: RepeatLike = False, bounce: bool = False, pivot: RelCoordLike | None = None) -> Entity
Animate rotation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
angle
|
float
|
Total rotation in degrees (default 360 = full turn). |
360
|
duration
|
float
|
Duration in seconds. |
1.0
|
delay
|
float
|
Seconds before animation starts. |
0.0
|
easing
|
EasingLike
|
Speed curve (default "linear" for constant rotation). |
'linear'
|
hold
|
bool
|
Hold final value after completion. |
True
|
repeat
|
RepeatLike
|
|
False
|
bounce
|
bool
|
If |
False
|
pivot
|
RelCoordLike | None
|
Custom rotation center as |
None
|
Returns:
| Type | Description |
|---|---|
Entity
|
Self, for method chaining. |
animate_scale ¶
animate_scale(to: float, *, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'ease-in-out', hold: bool = True, repeat: RepeatLike = False, bounce: bool = False, pivot: RelCoordLike | None = None) -> Entity
Animate scale factor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to
|
float
|
Target scale factor (1.0 = original, 2.0 = double, 0.5 = half). |
required |
duration
|
float
|
Duration in seconds. |
1.0
|
delay
|
float
|
Seconds before animation starts. |
0.0
|
easing
|
EasingLike
|
Speed curve (default "ease-in-out"). |
'ease-in-out'
|
hold
|
bool
|
Hold final value after completion. |
True
|
repeat
|
RepeatLike
|
|
False
|
bounce
|
bool
|
If |
False
|
pivot
|
RelCoordLike | None
|
Custom scale origin as |
None
|
Returns:
| Type | Description |
|---|---|
Entity
|
Self, for method chaining. |
animate_follow ¶
animate_follow(path: FullPathable, *, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'linear', hold: bool = True, rotate: bool | float = False, repeat: RepeatLike = False, bounce: bool = False) -> Entity
Animate along a path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
FullPathable
|
A Pathable object (Wave, Spiral, etc.) to follow. |
required |
duration
|
float
|
Duration in seconds. |
1.0
|
delay
|
float
|
Seconds before animation starts. |
0.0
|
easing
|
EasingLike
|
Speed curve. |
'linear'
|
hold
|
bool
|
Hold final value after completion. |
True
|
rotate
|
bool | float
|
True for auto-rotation along tangent, float for fixed angle. |
False
|
repeat
|
RepeatLike
|
|
False
|
bounce
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Entity
|
Self, for method chaining. |
animate ¶
animate(prop: str, *, to: Any | None = None, keyframes: dict[float, Any] | list[Any] | None = None, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'linear', hold: bool = True, repeat: RepeatLike = False, bounce: bool = False) -> Entity
Animate any property.
Two modes:
- Simple:
entity.animate("opacity", to=0.0, duration=2.0) - Keyframes (dict):
entity.animate("opacity", keyframes={0: 1, 1: 0.3, 2: 1}) - Keyframes (list):
entity.animate("fill", keyframes=["red", "blue", "red"], duration=2.0)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prop
|
str
|
Property name (e.g., "opacity", "r", "fill", "width"). |
required |
to
|
Any | None
|
Target value (simple mode). |
None
|
keyframes
|
dict[float, Any] | list[Any] | None
|
Dict of {time_seconds: value}, or list of values (evenly spaced over duration). |
None
|
duration
|
float
|
Duration for simple mode, or total duration when keyframes is a list. |
1.0
|
delay
|
float
|
Seconds before animation starts. |
0.0
|
easing
|
EasingLike
|
Speed curve. |
'linear'
|
hold
|
bool
|
Hold final value after completion. |
True
|
repeat
|
RepeatLike
|
|
False
|
bounce
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Entity
|
Self, for method chaining. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither |
then ¶
then(gap: float = 0) -> Entity
Chain: start the next animation after all current ones finish.
Computes when all current animations end, and offsets the next animation call by that amount (plus an optional gap).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gap
|
float
|
Optional pause in seconds between the end of current animations and the start of the next one. |
0
|
Returns:
| Type | Description |
|---|---|
Entity
|
Self, for method chaining. |
Example::
dot.animate_fade(to=0.0, duration=1.0).then().animate_spin(360, duration=1.0)
dot.animate_fade(to=0.0, duration=1.0).then(0.5).animate_spin(360, duration=1.0)
# Bounce the whole chain with .loop():
dot.animate_fade(to=0.3, duration=1.5).then().animate_spin(360, duration=2.0)
dot.loop(bounce=True)
loop ¶
Loop all animations on this entity.
Terminal method — returns None. Call this as the last step
after building your animation chain, not in the middle.
Stamps bounce and repeat onto every animation currently
on the entity. Its primary use case is finishing a .then() chain
so the whole sequence repeats as one unit::
# Natural fit — the chain [move → fade] loops together:
dot.animate_move(...).then().animate_fade(...).loop(bounce=True)
For a single animation, prefer passing repeat= and bounce=
directly to the animate_* call — it is self-contained and
impossible to accidentally apply to the wrong animation::
# Preferred:
dot.animate_fade(to=0.0, duration=1.0, repeat=True, bounce=True)
# Works but unnecessary indirection:
dot.animate_fade(to=0.0, duration=1.0).loop(bounce=True)
.. warning::
If an entity has multiple animations with different desired
settings, use per-animation repeat=/bounce= rather
than .loop(). Because .loop() stamps every animation
on the entity, it can silently apply bounce to an animation
you didn't intend — for example, reversing an animate_follow
while only meaning to bounce an animate_radius.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bounce
|
bool
|
If |
False
|
times
|
RepeatLike
|
|
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If times is |
ValueError
|
If no animations have been added yet. |
Note
For bounced finite loops (bounce=True, times=N), the
value held after completion depends on parity: odd N freezes
at the end value, even N freezes at the start value
(the last bounce cycle reverses back). Only relevant when
hold=True (the animate_* default).
Typed Property Methods¶
Each entity subclass exposes only the animate_* methods that match its constructor parameters. These are factory-generated with typed signatures — IDE autocomplete shows exactly what's available.
Color properties — to accepts ColorLike (str | tuple[int, int, int]), keyframes accepts dict[float, ColorLike] or list[ColorLike]:
| Method | Dot | Rect | Polygon | Ellipse | Line/Curve | Path | Text |
|---|---|---|---|---|---|---|---|
animate_color |
Yes | — | — | — | Yes | Yes | Yes |
animate_fill |
— | Yes | Yes | Yes | — | Yes | — |
animate_stroke |
— | Yes | Yes | Yes | — | — | — |
Numeric properties — to accepts float, keyframes accepts dict[float, float] or list[float]:
| Method | Dot | Rect | Polygon | Ellipse | Line/Curve | Path | Text |
|---|---|---|---|---|---|---|---|
animate_radius |
Yes | — | — | — | — | — | — |
animate_width |
— | Yes | — | — | Yes | Yes | — |
animate_height |
— | Yes | — | — | — | — | — |
animate_stroke_width |
— | Yes | Yes | Yes | — | — | — |
animate_rx |
— | — | — | Yes | — | — | — |
animate_ry |
— | — | — | Yes | — | — | — |
animate_font_size |
— | — | — | — | — | — | Yes |
animate_fill_opacity |
— | Yes | Yes | Yes | — | — | — |
animate_stroke_opacity |
— | Yes | Yes | Yes | — | — | — |
Stroke Draw¶
Lines, curves, and paths have a stroke-reveal animation method:
animate_draw ¶
animate_draw(*, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'ease-in-out', hold: bool = True, reverse: bool = False, repeat: RepeatLike = False, bounce: bool = False) -> Line
Animate the line drawing itself from start to end.
The stroke progressively reveals as if being drawn by a pen.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
duration
|
float
|
Animation duration in seconds. |
1.0
|
delay
|
float
|
Seconds to wait before starting. |
0.0
|
easing
|
EasingLike
|
Easing function name or (x1,y1,x2,y2) tuple. |
'ease-in-out'
|
hold
|
bool
|
Hold final value after animation ends. |
True
|
reverse
|
bool
|
Draw from end to start instead. |
False
|
repeat
|
RepeatLike
|
|
False
|
bounce
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Line
|
self, for method chaining. |
animate_draw ¶
animate_draw(*, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'ease-in-out', hold: bool = True, reverse: bool = False, repeat: RepeatLike = False, bounce: bool = False) -> Curve
Animate the curve drawing itself from start to end.
The stroke progressively reveals as if being drawn by a pen.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
duration
|
float
|
Animation duration in seconds. |
1.0
|
delay
|
float
|
Seconds to wait before starting. |
0.0
|
easing
|
EasingLike
|
Easing function name or (x1,y1,x2,y2) tuple. |
'ease-in-out'
|
hold
|
bool
|
Hold final value after animation ends. |
True
|
reverse
|
bool
|
Draw from end to start instead. |
False
|
repeat
|
RepeatLike
|
|
False
|
bounce
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Curve
|
self, for method chaining. |
animate_draw ¶
animate_draw(*, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'ease-in-out', hold: bool = True, reverse: bool = False, repeat: RepeatLike = False, bounce: bool = False) -> Path
Animate the path drawing itself from start to end.
The stroke progressively reveals as if being drawn by a pen.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
duration
|
float
|
Animation duration in seconds. |
1.0
|
delay
|
float
|
Seconds to wait before starting. |
0.0
|
easing
|
EasingLike
|
Easing function name or (x1,y1,x2,y2) tuple. |
'ease-in-out'
|
hold
|
bool
|
Hold final value after animation ends. |
True
|
reverse
|
bool
|
Draw from end to start instead. |
False
|
repeat
|
RepeatLike
|
|
False
|
bounce
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Path
|
self, for method chaining. |
Connection Animation Methods¶
Connections support a subset of animation methods:
animate_draw ¶
animate_draw(*, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'ease-in-out', hold: bool = True, reverse: bool = False, repeat: RepeatLike = False, bounce: bool = False) -> Connection
Animate the connection drawing itself.
The stroke progressively reveals as if being drawn by a pen.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
duration
|
float
|
Duration in seconds. |
1.0
|
delay
|
float
|
Seconds before animation starts. |
0.0
|
easing
|
EasingLike
|
Speed curve (default "ease-in-out"). |
'ease-in-out'
|
hold
|
bool
|
Hold final value after completion. |
True
|
reverse
|
bool
|
If |
False
|
repeat
|
RepeatLike
|
|
False
|
bounce
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Connection
|
Self, for method chaining. |
animate ¶
animate(prop: str, *, to: Any | None = None, keyframes: dict[float, Any] | list[Any] | None = None, duration: float = 1.0, delay: float = 0.0, easing: EasingLike = 'linear', hold: bool = True, repeat: RepeatLike = False, bounce: bool = False) -> Connection
Animate any property.
then ¶
then(gap: float = 0) -> Connection
Chain: start the next animation after all current ones finish.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gap
|
float
|
Optional pause in seconds after current animations end. |
0
|
Returns:
| Type | Description |
|---|---|
Connection
|
Self, for method chaining. |
loop ¶
Loop all animations on this connection.
Terminal method — returns None. Call this as the last step
after building your animation chain, not in the middle.
Stamps bounce and repeat onto every animation on the
connection. Its primary use case is finishing a .then() chain::
conn.animate_fade(to=0.2).then().animate_draw(duration=1.5).loop()
For a single animation, prefer inline repeat=/bounce=
parameters instead::
# Preferred:
conn.animate_draw(duration=2.0, repeat=True, bounce=True)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bounce
|
bool
|
If |
False
|
times
|
RepeatLike
|
|
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If times is |
ValueError
|
If no animations have been added yet. |
Utility Functions¶
stagger ¶
stagger(*entities: Entity | Connection, offset: float = 0.1, each: Callable[[Entity | Connection], Entity | Connection]) -> list[Entity | Connection]
Apply an animation to entities with staggered timing.
Calls each(entity) for every entity, then adjusts the delay
of newly added animations by i * offset seconds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*entities
|
Entity | Connection
|
Entities (or connections) to animate. |
()
|
offset
|
float
|
Seconds between each entity's animation start. |
0.1
|
each
|
Callable[[Entity | Connection], Entity | Connection]
|
Callable that applies animation(s) to an entity. |
required |
Returns:
| Type | Description |
|---|---|
list[Entity | Connection]
|
List of entities (same objects, with animations applied). |
Example::
stagger(*dots, offset=0.2, each=lambda d: d.animate_fade(to=0.0, duration=1.0))
Animation Data Model¶
These classes live in pyfreeform.animation and represent animation data. You rarely need to create them directly — the entity methods do it for you. They're useful for inspection and custom renderers.
bounce / repeat fields
The bounce and repeat fields on animation model classes are storage — they are stamped by .loop(), not passed directly to animate_* methods.
Easing
dataclass
¶
Cubic-bezier easing function.
Defines the speed curve of an animation using four control points of a cubic Bezier curve (x1, y1, x2, y2). The curve maps input time (0–1) to output progress (0–1).
Predefined easings are available as class attributes::
Easing.LINEAR # constant speed
Easing.EASE_IN # slow start
Easing.EASE_OUT # slow end
Easing.EASE_IN_OUT # slow start and end
Custom easing via constructor::
Easing(0.68, -0.55, 0.27, 1.55) # back-overshoot
evaluate ¶
Map input time (0–1) to eased progress (0–1).
Uses De Casteljau subdivision to solve the cubic-bezier curve.
Keyframe
dataclass
¶
A single value at a point in time.
Attributes:
| Name | Type | Description |
|---|---|---|
time |
float
|
Time in seconds. |
value |
Any
|
The property value at this time. |
PropertyAnimation
dataclass
¶
PropertyAnimation(prop: str, keyframes: list[Keyframe] = list(), easing: Easing = (lambda: LINEAR)(), hold: bool = True, repeat: RepeatLike = False, bounce: bool = False, delay: float = 0.0, pivot: RelCoord | None = None, chain_id: int | None = None, chain_seq: int = 0)
Animation of a single entity property over time.
Stores the property name (pyfreeform name, NOT SVG attribute),
keyframes, and playback parameters. The renderer decides how
to output this — SVG SMIL emits <animate>, a game renderer
calls evaluate(t).
Attributes:
| Name | Type | Description |
|---|---|---|
prop |
str
|
Property name (e.g., "opacity", "r", "fill"). |
keyframes |
list[Keyframe]
|
Ordered list of (time, value) pairs. |
easing |
Easing
|
Speed curve between keyframes. |
hold |
bool
|
If True, hold final value after animation ends. |
repeat |
RepeatLike
|
False=once, True=forever, int=N times. |
bounce |
bool
|
If True, alternate direction each cycle. |
delay |
float
|
Seconds before animation starts. |
pivot |
RelCoord | None
|
Custom transform origin for rotation/scale animations.
Surface-relative |
chain_id |
int | None
|
Shared ID for all animations in one |
chain_seq |
int
|
Position within the chain (0, 1, 2, …). |
keyframes
class-attribute
instance-attribute
¶
keyframes: list[Keyframe] = field(default_factory=list)
evaluate ¶
Compute the interpolated value at time t (seconds).
Handles delay, repeat, bounce, and easing. Works for numeric values (float, int) and color strings (interpolated in RGB).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
float
|
Absolute time in seconds since animation start. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
Interpolated property value at time t. |
MotionAnimation
dataclass
¶
MotionAnimation(path: FullPathable, duration: float, easing: Easing = (lambda: LINEAR)(), hold: bool = True, repeat: RepeatLike = False, bounce: bool = False, delay: float = 0.0, rotate: bool | float = False, reverse: bool = False, chain_id: int | None = None, chain_seq: int = 0)
Animation that moves an entity along a path.
The path is stored as a Pathable object. The renderer extracts
geometry as needed — SVG SMIL uses <animateMotion>, a game
renderer calls path.point_at(t).
Attributes:
| Name | Type | Description |
|---|---|---|
path |
FullPathable
|
The path to follow. |
duration |
float
|
Total duration in seconds. |
easing |
Easing
|
Speed curve along the path. |
hold |
bool
|
If True, hold final position. |
repeat |
RepeatLike
|
False=once, True=forever, int=N times. |
bounce |
bool
|
If True, alternate direction each cycle. |
delay |
float
|
Seconds before animation starts. |
rotate |
bool | float
|
True for auto-rotation along tangent, float for fixed angle. |
reverse |
bool
|
If True, traverse the path backwards (end → start instead of
start → end). Used internally by the chain renderer to produce the
backward pass of a bounced |
chain_id |
int | None
|
Shared ID for all animations in one |
chain_seq |
int
|
Position within the chain (0, 1, 2, …). |
evaluate ¶
Compute the (x, y) position at time t.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
float
|
Absolute time in seconds. |
required |
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
(x, y) position on the path. |
DrawAnimation
dataclass
¶
DrawAnimation(duration: float, easing: Easing = (lambda: EASE_IN_OUT)(), hold: bool = True, repeat: RepeatLike = False, bounce: bool = False, delay: float = 0.0, reverse: bool = False, chain_id: int | None = None, chain_seq: int = 0)
Animation that draws a path/connection progressively.
Uses stroke-dashoffset technique: the path appears to be drawn from start to end (or end to start if reversed).
Attributes:
| Name | Type | Description |
|---|---|---|
duration |
float
|
Total duration in seconds. |
easing |
Easing
|
Speed curve of the drawing. |
hold |
bool
|
If True, hold final state (path fully drawn). |
repeat |
RepeatLike
|
False=once, True=forever, int=N times. |
bounce |
bool
|
If True, alternate draw/erase each cycle. |
delay |
float
|
Seconds before animation starts. |
reverse |
bool
|
If True, draw from end to start. |
chain_id |
int | None
|
Shared ID for all animations in one |
chain_seq |
int
|
Position within the chain (0, 1, 2, …). |
easing
class-attribute
instance-attribute
¶
easing: Easing = field(default_factory=lambda: EASE_IN_OUT)
evaluate ¶
Compute the draw progress (0.0 = hidden, 1.0 = fully drawn).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
t
|
float
|
Absolute time in seconds. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Progress from 0.0 to 1.0. |
Renderers¶
Renderers convert a scene into an output format. The base class defines the interface; concrete renderers implement it.
Renderer ¶
Bases: ABC
Abstract base class for PyFreeform renderers.
A renderer converts a Scene (model) into an output string. Subclasses implement type-specific rendering for each entity kind.
The default render_entity dispatches to render_<typename>
methods (e.g., render_dot, render_rect) based on the
entity's class name.
render_scene
abstractmethod
¶
render_scene(scene: Scene) -> str
Render a complete scene to output string.
render_entity ¶
render_entity(entity: Entity) -> str
Dispatch to type-specific render method.
Looks up render_<classname> (lowercase) on this renderer.
render_connection
abstractmethod
¶
render_connection(conn: Connection) -> str
Render a connection to output string.
SVGRenderer ¶
Bases: Renderer
Renders PyFreeform scenes as static SVG.
This renderer produces output identical to the original inline
to_svg() methods. It ignores any animations on entities —
use :class:SMILRenderer for animated SVG output.
render_connection ¶
render_connection(conn: Connection) -> str
Render Connection as SVG <line> or <path>.
SMILRenderer ¶
Bases: SVGRenderer
SVG renderer with SMIL animation support.
Extends :class:SVGRenderer. For entities with no animations,
produces identical output. For entities with animations, wraps
SVG elements with <animate>, <animateTransform>, and
<animateMotion> children.
render_scene ¶
render_scene(scene: Scene) -> str
Render a complete animated SVG with fill-layer batching.
Extends the parent renderer with a pre-scan pass that detects
entities sharing the same fill animation timing. Overlays from
those entities are grouped into shared <g> elements with a
single <animate>, reducing the total number of SMIL animation
elements the browser must evaluate.
Renderer Selection¶
| Scenario | Renderer | Notes |
|---|---|---|
scene.save("out.svg") |
SMILRenderer (auto) |
Default — includes animations if present |
scene.render() |
SMILRenderer (auto) |
Same as save |
scene.render(SVGRenderer()) |
SVGRenderer |
Forces static SVG, ignores animations |
scene.render(SMILRenderer()) |
SMILRenderer |
Explicit animated SVG |
Non-animated entities produce identical output from both renderers.