The language#
The intermediate representation: a validated semantic scene graph, and the real definition of the language. Nothing here carries a coordinate.
scenet.ir#
.. py:module:: scenet.ir
The intermediate representation: a validated semantic scene graph.
This is the language’s real definition. The YAML surface syntax is one way to produce it; a comic-script frontend will be another. Nothing here carries a coordinate – computing those is the solver’s job.
Validation is strict on purpose. Panel sources are untrusted input, and a typo in a predicate or an actor id should be a clear error at parse time rather than a silently wrong picture.
.. py:class:: AnchorX
- module:
scenet.ir
Bases: :py:class:
~enum.StrEnumWhere along the panel width an actor would like to stand.
Horizontal only. Actors stand on a ground line, so their vertical position is derived from the camera rather than requested – which is why this has no vertical counterpart and :class:
PlacementZone <scenet.ir.PlacementZone>, used for balloons, does.These are weak preferences. Non-overlap and declared left-to-right ordering are required constraints and will override an anchor without complaint; two actors both asking for
centerwill simply be pushed apart around it... py:attribute:: AnchorX.LEFT_EDGE
- module:
scenet.ir
- value:
‘left_edge’
.. py:attribute:: AnchorX.LEFT_THIRD
- module:
scenet.ir
- value:
‘left_third’
.. py:attribute:: AnchorX.CENTRE
- module:
scenet.ir
- value:
‘center’
.. py:attribute:: AnchorX.RIGHT_THIRD
- module:
scenet.ir
- value:
‘right_third’
.. py:attribute:: AnchorX.RIGHT_EDGE
- module:
scenet.ir
- value:
‘right_edge’
.. py:method:: AnchorX.new(value)
- module:
scenet.ir
.. py:class:: BalloonKind
- module:
scenet.ir
Bases: :py:class:
~enum.StrEnumWhat kind of balloon carries a line, which is how it gets drawn.
The kind changes the outline and the tail, never the placement: a whisper is subject to exactly the same face-avoidance and reading-order rules as a shout.
Kind
Outline
Tail
speechplain ellipse
tapered pointer
thoughtscalloped cloud
trail of bubbles
whisperdashed ellipse
tapered pointer
shoutjagged burst
tapered pointer
.. py:attribute:: BalloonKind.SPEECH
- module:
scenet.ir
- value:
‘speech’
.. py:attribute:: BalloonKind.THOUGHT
- module:
scenet.ir
- value:
‘thought’
.. py:attribute:: BalloonKind.WHISPER
- module:
scenet.ir
- value:
‘whisper’
.. py:attribute:: BalloonKind.SHOUT
- module:
scenet.ir
- value:
‘shout’
.. py:method:: BalloonKind.new(value)
- module:
scenet.ir
.. py:class:: CameraAngle
- module:
scenet.ir
Bases: :py:class:
~enum.StrEnumThe camera’s height relative to the subject.
Affects headroom rather than perspective: this is a flat, orthographic compiler, so a tilted camera does not foreshorten anything. What it changes is how much air sits above the head – which is the compositional cue readers actually take from an angle, and one that survives being drawn flat.
A low camera looks up and the subject looms, so the head rides high in the frame with little space above it. A high camera looks down, so the head sits lower and more space opens up above. See
- func:
headroom_for <scenet.solve.camera.headroom_for>for the exact factors.
.. py:attribute:: CameraAngle.LOW
- module:
scenet.ir
- value:
‘low’
.. py:attribute:: CameraAngle.EYE_LEVEL
- module:
scenet.ir
- value:
‘eye_level’
.. py:attribute:: CameraAngle.HIGH
- module:
scenet.ir
- value:
‘high’
.. py:method:: CameraAngle.new(value)
- module:
scenet.ir
.. py:class:: CameraSpec
- module:
scenet.ir
Bases: :py:class:
~scenet.ir.StrictHow the panel is framed.
.. attribute:: shot
Requested framing; an upper bound on tightness, see
- class:
ShotType <scenet.ir.ShotType>.
.. attribute:: angle
Camera height, see :class:
CameraAngle <scenet.ir.CameraAngle>.There is exactly one camera per panel, and every actor is drawn at the scale it implies. Scaling each actor to its own crop landmark instead would make everybody the same apparent height and erase the body differences a comic uses to tell characters apart.
.. py:attribute:: CameraSpec.shot
- module:
scenet.ir
- type:
~scenet.ir.ShotType
.. py:attribute:: CameraSpec.angle
- module:
scenet.ir
- type:
~scenet.ir.CameraAngle
.. py:class:: CastMember
- module:
scenet.ir
Bases: :py:class:
~scenet.ir.StrictOne character present in the panel.
.. attribute:: reference
Name of a puppet in the library. This is what gets drawn; the key this member is filed under in
castis the actor id used everywhere else... attribute:: pose
Named pose from that puppet’s declared set.
.. attribute:: at
Preferred horizontal anchor.
.. attribute:: facing
Which way the figure is turned.
The split between actor id and
referenceis what lets one puppet appear twice in a panel as two different people:cast: guard_left: {reference: bob, pose: arms_crossed} guard_right: {reference: bob, pose: standing_neutral, facing: left}.. py:attribute:: CastMember.reference
- module:
scenet.ir
- type:
str
.. py:attribute:: CastMember.pose
- module:
scenet.ir
- type:
str
.. py:attribute:: CastMember.at
- module:
scenet.ir
- type:
~scenet.ir.AnchorX
.. py:attribute:: CastMember.facing
- module:
scenet.ir
- type:
~scenet.ir.Facing
.. py:class:: Facing
- module:
scenet.ir
Bases: :py:class:
~enum.StrEnumWhich way an actor is turned.
Mirroring the whole puppet, gaze vector included. Defaults to
right, so a cast written left to right ends up looking into the panel rather than out of it... py:attribute:: Facing.LEFT
- module:
scenet.ir
- value:
‘left’
.. py:attribute:: Facing.RIGHT
- module:
scenet.ir
- value:
‘right’
.. py:method:: Facing.new(value)
- module:
scenet.ir
.. py:class:: PanelIR
- module:
scenet.ir
Bases: :py:class:
~scenet.ir.StrictA complete, validated panel: the language’s real definition.
Every frontend produces one of these and nothing else, which is what lets the YAML syntax and the comic-script syntax coexist without the solver knowing either exists. Nothing here carries a coordinate – computing those is the solver’s job, and keeping them out is what makes a panel reusable at any size.
.. attribute:: panel
Dimensions and margin.
.. attribute:: camera
Framing and angle.
.. attribute:: cast
Actor id to character. Declaration order is not significant;
stagingdecides left-to-right order... attribute:: staging
Spatial and attentional relations between actors.
.. attribute:: script
Dialogue, in reading order.
Validation is strict and total: unknown keys are rejected, every actor id mentioned in
stagingorscriptmust exist incast, and the ordering relations must not contain a cycle. A misspelled key that was silently ignored would produce a panel that is subtly wrong with no indication of why, which for a language meant to be precise is the worst possible failure... admonition:: Example
from scenet import parse_panel panel = parse_panel(“panel: {size: [800.0, 600.0]}”) panel.panel.width, panel.camera.shot.value (800.0, ‘medium_shot’)
.. seealso:: :func:
compile_ir <scenet.pipeline.compile_ir>, to turn one of these into geometry... py:attribute:: PanelIR.panel
- module:
scenet.ir
- type:
~scenet.ir.PanelSpec
.. py:attribute:: PanelIR.camera
- module:
scenet.ir
- type:
~scenet.ir.CameraSpec
.. py:attribute:: PanelIR.cast
- module:
scenet.ir
- type:
dict[str, ~scenet.ir.CastMember]
.. py:attribute:: PanelIR.staging
- module:
scenet.ir
- type:
tuple[~scenet.ir.Relation, …]
.. py:attribute:: PanelIR.script
- module:
scenet.ir
- type:
tuple[~scenet.ir.SayEvent, …]
.. py:method:: PanelIR.check_references_resolve()
- module:
scenet.ir
Every actor id mentioned anywhere must exist in the cast.
Caught here rather than in the solver so the error names the offending identifier while the source is still in view.
- rtype:
- sphinx_autodoc_typehints_type:
\:py\:class\:\~typing.Self``
.. py:method:: PanelIR.check_ordering_is_consistent()
- module:
scenet.ir
Horizontal ordering must not contain a cycle.
The layout engine is a linear constraint solver, so ordering has to be decided before it runs – see docs/reference/language.md. A cycle such as ‘a left_of b, b left_of a’ has no solution, and detecting it here produces a comprehensible message instead of an opaque solver failure.
- rtype:
- sphinx_autodoc_typehints_type:
\:py\:class\:\~typing.Self``
.. py:method:: PanelIR.ordering_constraints()
- module:
scenet.ir
Normalised (left, right) pairs from both left_of and right_of relations.
- rtype:
- sphinx_autodoc_typehints_type:
\:py\:class\:\tuple`\ \[:py:class:`tuple`\ \[:py:class:`str`, :py:class:`str`], :py:data:`…<Ellipsis>`]`
.. py:method:: PanelIR.gaze_targets()
- module:
scenet.ir
Who is looking at whom.
- rtype:
- sphinx_autodoc_typehints_type:
\:py\:class\:\dict`\ \[:py:class:`str`, :py:class:`str`]`
- returns:
A mapping from each looking actor to the actor they are looking at. An actor may look at only one target, so a later
looking_atrelation for the same subject replaces an earlier one.
.. admonition:: Example
from scenet import parse_panel panel = parse_panel( … “{cast: {a: {reference: alice}, b: {reference: bob}},” … “ staging: [a looking_at b]}” … ) panel.gaze_targets() {‘a’: ‘b’}
.. py:method:: PanelIR.ground_groups()
- module:
scenet.ir
Actors joined by ground_shared_with, as connected components.
Union-find rather than pairwise handling, so that ‘a with b’ plus ‘b with c’ puts all three on one ground line without the author saying ‘a with c’.
- rtype:
- sphinx_autodoc_typehints_type:
\:py\:class\:\tuple`\ \[:py:class:`frozenset`\ \[:py:class:`str`], :py:data:`…<Ellipsis>`]`
.. py:class:: PanelSpec
- module:
scenet.ir
Bases: :py:class:
~scenet.ir.StrictThe panel’s own dimensions.
.. attribute:: size
(width, height)in panel units. Everything else in the language is expressed relative to these, so they set what a unit means... attribute:: margin
Inset on all four sides. Balloons are kept inside it; actors may bleed past it, which is ordinary comics practice.
.. admonition:: Example
from scenet import PanelSpec PanelSpec(size=(1200.0, 600.0)).width 1200.0
.. py:attribute:: PanelSpec.size
- module:
scenet.ir
- type:
tuple[float, float]
.. py:attribute:: PanelSpec.margin
- module:
scenet.ir
- type:
float
.. py:property:: PanelSpec.width
- module:
scenet.ir
- type:
float
Panel width in panel units.
.. py:property:: PanelSpec.height
- module:
scenet.ir
- type:
float
Panel height in panel units.
.. py:method:: PanelSpec.check_positive()
- module:
scenet.ir
Reject a panel with no usable area.
- rtype:
:sphinx_autodoc_typehints_type:
\:py\:class\:\~typing.Self``- returns:
The validated spec.
- raises ValueError:
A dimension is zero or negative, or the margins meet in the middle leaving nothing to compose in.
.. py:class:: PlacementZone
- module:
scenet.ir
Bases: :py:class:
~enum.StrEnumWhere in the panel a balloon would prefer to sit.
Two-dimensional, unlike
AnchorX: an actor is placed along the ground line and so only needs a horizontal anchor, whereas a balloon floats and needs both axes. These are hints of the weakest priority – occlusion and reading order override them freely... py:attribute:: PlacementZone.TOP_LEFT
- module:
scenet.ir
- value:
‘top_left’
.. py:attribute:: PlacementZone.TOP_CENTRE
- module:
scenet.ir
- value:
‘top_center’
.. py:attribute:: PlacementZone.TOP_RIGHT
- module:
scenet.ir
- value:
‘top_right’
.. py:attribute:: PlacementZone.MIDDLE_LEFT
- module:
scenet.ir
- value:
‘middle_left’
.. py:attribute:: PlacementZone.MIDDLE_CENTRE
- module:
scenet.ir
- value:
‘middle_center’
.. py:attribute:: PlacementZone.MIDDLE_RIGHT
- module:
scenet.ir
- value:
‘middle_right’
.. py:attribute:: PlacementZone.BOTTOM_LEFT
- module:
scenet.ir
- value:
‘bottom_left’
.. py:attribute:: PlacementZone.BOTTOM_CENTRE
- module:
scenet.ir
- value:
‘bottom_center’
.. py:attribute:: PlacementZone.BOTTOM_RIGHT
- module:
scenet.ir
- value:
‘bottom_right’
.. py:property:: PlacementZone.fractions
- module:
scenet.ir
- type:
tuple[float, float]
The zone’s centre as a fraction of panel width and height.
.. py:method:: PlacementZone.new(value)
- module:
scenet.ir
.. py:class:: Predicate
- module:
scenet.ir
Bases: :py:class:
~enum.StrEnumHow one actor stands in relation to another.
Drawn from the spatial subset of the Visual Genome vocabulary rather than invented, so a scene stays convertible to and from the scene-graph representations used elsewhere in computer vision.
left_ofandright_ofare the load-bearing ones: they are resolved at parse time into a linear ordering, because Cassowary cannot express the disjunction “A left of B or B left of A”... py:attribute:: Predicate.LEFT_OF
- module:
scenet.ir
- value:
‘left_of’
.. py:attribute:: Predicate.RIGHT_OF
- module:
scenet.ir
- value:
‘right_of’
.. py:attribute:: Predicate.IN_FRONT_OF
- module:
scenet.ir
- value:
‘in_front_of’
.. py:attribute:: Predicate.BEHIND
- module:
scenet.ir
- value:
‘behind’
.. py:attribute:: Predicate.LOOKING_AT
- module:
scenet.ir
- value:
‘looking_at’
.. py:attribute:: Predicate.GROUND_SHARED_WITH
- module:
scenet.ir
- value:
‘ground_shared_with’
.. py:method:: Predicate.new(value)
- module:
scenet.ir
.. py:class:: Relation
- module:
scenet.ir
Bases: :py:class:
~scenet.ir.StrictOne staging fact, written as a sentence.
.. attribute:: subject
Actor id the sentence is about.
.. attribute:: predicate
What relation holds.
.. attribute:: object
The other actor id.
Authored as
alice left_of bobrather than a three-key mapping because staging is read far more often than it is written, and a sentence is legible at a glance.- raises pydantic.ValidationError:
The subject and object are the same actor. No predicate here is meaningful reflexively.
.. py:attribute:: Relation.subject
- module:
scenet.ir
- type:
str
.. py:attribute:: Relation.predicate
- module:
scenet.ir
- type:
~scenet.ir.Predicate
.. py:attribute:: Relation.object
- module:
scenet.ir
- type:
str
.. py:method:: Relation.check_not_reflexive()
- module:
scenet.ir
Reject a relation between an actor and itself.
- rtype:
:sphinx_autodoc_typehints_type:
\:py\:class\:\~typing.Self``- returns:
The validated relation.
- raises ValueError:
Subject and object are the same actor id. No predicate in the language means anything reflexively, so this is always a typo.
.. py:class:: SayEvent
- module:
scenet.ir
Bases: :py:class:
~scenet.ir.StrictOne line of dialogue.
.. attribute:: by
Actor id of the speaker; must be in the cast.
.. attribute:: text
What is said. Line breaking is the compiler’s job, so write it as one string and do not insert newlines yourself.
.. attribute:: prefer
Optional hint about where the balloon should sit. The weakest of all the placement terms – face avoidance and reading order override it.
.. attribute:: kind
Which sort of balloon carries it.
Script order is reading order, and reading order is a hard constraint. Reorder these and you reorder the panel.
.. py:attribute:: SayEvent.by
- module:
scenet.ir
- type:
str
.. py:attribute:: SayEvent.text
- module:
scenet.ir
- type:
str
.. py:attribute:: SayEvent.prefer
- module:
scenet.ir
- type:
~scenet.ir.PlacementZone | None
.. py:attribute:: SayEvent.kind
- module:
scenet.ir
- type:
~scenet.ir.BalloonKind
.. py:class:: ShotType
- module:
scenet.ir
Bases: :py:class:
~enum.StrEnumHow tightly the camera frames the cast.
Ordered from widest to tightest, and the order is enforced by a test: reading down the ladder, the figure never gets smaller.
A shot type is defined by two things in two different units. The crop landmark is anatomical – the waist, the chest, the shoulders – which is what stops a shot type baking in one body and one pose; naming a fraction of panel height instead would do exactly that. The headroom is a plain fraction of panel height, because it is about composition within the frame rather than anatomy.
docs/reference/shot_types.mdis normative.The requested shot is an upper bound on tightness, not a promise. If the cast cannot fit across the panel at that framing the camera retreats, and says so in
- attr:
CompileResult.notes <scenet.pipeline.CompileResult.notes>.
.. admonition:: Example
from scenet import ShotType ShotType(“close_up”) <ShotType.CLOSE_UP: ‘close_up’>
.. py:attribute:: ShotType.LONG_SHOT
- module:
scenet.ir
- value:
‘long_shot’
.. py:attribute:: ShotType.WIDE
- module:
scenet.ir
- value:
‘wide’
.. py:attribute:: ShotType.FULL_SHOT
- module:
scenet.ir
- value:
‘full_shot’
.. py:attribute:: ShotType.MEDIUM_FULL
- module:
scenet.ir
- value:
‘medium_full’
.. py:attribute:: ShotType.COWBOY
- module:
scenet.ir
- value:
‘cowboy’
.. py:attribute:: ShotType.MEDIUM_SHOT
- module:
scenet.ir
- value:
‘medium_shot’
.. py:attribute:: ShotType.MEDIUM_CLOSE_UP
- module:
scenet.ir
- value:
‘medium_close_up’
.. py:attribute:: ShotType.CLOSE_UP
- module:
scenet.ir
- value:
‘close_up’
.. py:attribute:: ShotType.BIG_CLOSE_UP
- module:
scenet.ir
- value:
‘big_close_up’
.. py:attribute:: ShotType.EXTREME_CLOSE_UP
- module:
scenet.ir
- value:
‘extreme_close_up’
.. py:method:: ShotType.new(value)
- module:
scenet.ir
.. py:class:: Strict
- module:
scenet.ir
Bases: :py:class:
~pydantic.main.BaseModelReject unknown keys everywhere.
A misspelled key that is silently ignored produces a panel that is subtly wrong with no indication of why, which is the worst possible failure for a language meant to be precise.