Compile a sequence of panels#
Consecutive panels in a scene are mostly identical — the same cast, the same staging, the same camera — with one thing changed. Restating all of it per panel is tedious, and it is where continuity errors come from: the ones readers notice.
So a panel can state only what differs from another one.
over: — sparse override#
panels:
establishing:
camera: {shot: full_shot}
cast:
alice: {reference: alice, at: left_third}
bob: {reference: bob, at: right_third, facing: left}
staging:
- alice left_of bob
reaction:
over: establishing # same cast, same staging
camera: {shot: close_up} # move in
The keyword is borrowed from OpenUSD’s composition arcs,
where over means exactly this: name a parent, then state the difference.
from scenet import compile_scene
panels = compile_scene("""
panels:
establishing:
camera: {shot: full_shot}
cast:
alice: {reference: alice, at: left_third}
bob: {reference: bob, at: right_third, facing: left}
staging: [alice left_of bob]
reaction:
over: establishing
camera: {shot: close_up}
""")
# The cast came through untouched.
assert len(panels["reaction"].core.actors) == 2
# The camera did not: a close-up draws everybody larger.
establishing = panels["establishing"].core.actor("alice").transform.scale
reaction = panels["reaction"].core.actor("alice").transform.scale
assert reaction > establishing
Merge rules#
Two rules, and they are worth knowing precisely:
Mappings merge recursively. Overriding one actor’s pose leaves the rest of that actor, and every other actor, alone.
Lists replace wholesale. Give a panel its own
staging:and you replace the parent’s entirely — there is no appending. A list has no keys to merge on, and guessing at identity by position would be worse than a clear rule.
from scenet import compile_scene
panels = compile_scene("""
panels:
base:
cast:
alice: {reference: alice, pose: pointing, at: left_third}
bob: {reference: bob, at: right_third}
staging: [alice left_of bob]
changed:
over: base
cast:
alice: {pose: arms_crossed}
""")
alice = panels["changed"].core.actor("alice")
# `pose` was overridden; `reference` and `at` survived the merge.
assert alice.pose == "arms_crossed"
assert alice.reference == "alice"
Chains and cycles#
over: chains as deep as you like. A cycle is a compile error rather than a hang:
from scenet import CompositionError, compile_scene
try:
compile_scene("panels: {a: {over: b}, b: {over: a}}")
except CompositionError as exc:
assert "cyclic" in str(exc)
Panels are compiled independently#
Each panel is compiled on its own, and nothing about its composition depends on what sits beside it. That is deliberate: if a panel laid out differently in a sequence than it did alone, panels would not be reusable and golden-file tests would mean nothing.
Rendering the whole thing#
scenet build sequence.scene.yaml --strip
--strip writes one extra file laying every panel out side by side in reading order, on
top of the individual SVGs. From Python that is
render_strip:
from scenet import compile_scene, render_strip
panels = compile_scene("""
panels:
one: {cast: {a: {reference: alice}}}
two: {over: one, camera: {shot: close_up}}
""")
strip = render_strip([(name, result.core) for name, result in panels.items()])
assert strip.lstrip().startswith("<?xml")
Panel order in the output follows declaration order in the source, which is reading order.
What this is not#
over: is composition, not animation. There is no interpolation between panels and no
notion of time — a sequence is a set of independent panels that happen to share most of
their description. Page composition proper (tiers, panels of varying size, gutters) is
not yet built.