Panel Core#

The resolved intermediate format: fully numeric, still named, and a real file format rather than a private data structure.

scenet.core#

.. py:module:: scenet.core

Panel Core: the resolved intermediate format.

Every position is absolute and numeric, but identifiers survive – which is what separates this from SVG. A Core document can be read, diffed, hand-adjusted and re-emitted.

Golden-file tests target this tier rather than the SVG, because it changes only when layout genuinely changes. Diffing SVG text is brittle: a reordered attribute or a different path-rounding convention produces a huge diff that means nothing.

.. py:class:: Blob

module:

scenet.core

Bases: :py:class:~scenet.core.CoreModel

A rounded mass – a head, a hand – drawn as a filled circle.

.. attribute:: centre

(x, y) of the centre.

.. attribute:: radius

Radius in panel units.

.. py:attribute:: Blob.centre

module:

scenet.core

type:

tuple[float, float]

.. py:attribute:: Blob.radius

module:

scenet.core

type:

float

.. py:class:: Box

module:

scenet.core

Bases: :py:class:~scenet.core.CoreModel

A rectangle, as stored in a Core document.

The serialisable twin of :class:BBox <scenet.geom.BBox>. The geometry code works in BBox; this is what gets written to JSON, with every value already rounded so the file is byte-identical across platforms.

.. attribute:: x

Left edge.

.. attribute:: y

Top edge.

.. attribute:: width

Extent rightward.

.. attribute:: height

Extent downward.

.. py:attribute:: Box.x

module:

scenet.core

type:

float

.. py:attribute:: Box.y

module:

scenet.core

type:

float

.. py:attribute:: Box.width

module:

scenet.core

type:

float

.. py:attribute:: Box.height

module:

scenet.core

type:

float

.. py:property:: Box.right

module:

scenet.core

type:

float

The right edge, x + width.

.. py:property:: Box.bottom

module:

scenet.core

type:

float

The bottom edge, y + height.

.. py:method:: Box.of(bbox)

module:

scenet.core

classmethod:
Build a `Box` from a geometry `BBox`, rounding for emission.

:type bbox: :sphinx_autodoc_typehints_type:`\:py\:class\:\`\~scenet.geom.BBox\``
:param bbox: The box to convert.

:rtype: :sphinx_autodoc_typehints_type:`\:py\:class\:\`\~typing.Self\``
:returns: The serialisable equivalent, with all four values rounded.

.. py:method:: Box.as_bbox() :module: scenet.core

Convert back to a geometry `BBox` for further computation.

:rtype: :sphinx_autodoc_typehints_type:`\:py\:class\:\`\~scenet.geom.BBox\``

.. py:class:: Capsule

module:

scenet.core

Bases: :py:class:~scenet.core.CoreModel

A limb segment, as a thick line with rounded ends.

.. py:attribute:: Capsule.start

module:

scenet.core

type:

tuple[float, float]

.. py:attribute:: Capsule.end

module:

scenet.core

type:

tuple[float, float]

.. py:attribute:: Capsule.width

module:

scenet.core

type:

float

.. py:class:: CoreActor

module:

scenet.core

Bases: :py:class:~scenet.core.CoreModel

One character, fully resolved: placed, posed, scaled and measured.

This is the geometric contract in its final form. Everything the emitter needs to draw the figure, and everything the balloon placer needed to avoid it, with no reference to artwork of any kind.

.. attribute:: id

The actor id from the panel source.

.. attribute:: reference

Which puppet was used. Two actors may share one.

.. attribute:: pose

Which named pose was applied.

.. attribute:: transform

Where the root joint landed, and the scale and mirroring applied.

.. attribute:: anchors

Named attachment points – mouth, eyes, and whatever else the puppet declared – already in panel coordinates.

.. attribute:: face_exclusion

The disc no balloon may overlap.

.. attribute:: gaze

Unit direction the character is looking, (dx, dy).

.. attribute:: hull

Convex silhouette, used for the soft occlusion cost.

.. attribute:: capsules

Limb segments, as thick rounded lines.

.. attribute:: blobs

Rounded masses such as the head.

.. attribute:: depth

Painter’s order. Lower is drawn first, so higher sits in front.

.. py:attribute:: CoreActor.id

module:

scenet.core

type:

str

.. py:attribute:: CoreActor.reference

module:

scenet.core

type:

str

.. py:attribute:: CoreActor.pose

module:

scenet.core

type:

str

.. py:attribute:: CoreActor.transform

module:

scenet.core

type:

~scenet.core.Transform

.. py:attribute:: CoreActor.anchors

module:

scenet.core

type:

dict[str, tuple[float, float]]

.. py:attribute:: CoreActor.face_exclusion

module:

scenet.core

type:

~scenet.core.Disc

.. py:attribute:: CoreActor.gaze

module:

scenet.core

type:

tuple[float, float]

.. py:attribute:: CoreActor.hull

module:

scenet.core

type:

tuple[tuple[float, float], …]

.. py:attribute:: CoreActor.capsules

module:

scenet.core

type:

tuple[~scenet.core.Capsule, …]

.. py:attribute:: CoreActor.blobs

module:

scenet.core

type:

tuple[~scenet.core.Blob, …]

.. py:attribute:: CoreActor.depth

module:

scenet.core

type:

int

.. py:property:: CoreActor.bounds

module:

scenet.core

type:

~scenet.geom.BBox

Axis-aligned bounds of the silhouette.

.. py:class:: CoreBalloon

module:

scenet.core

Bases: :py:class:~scenet.core.CoreModel

One balloon, placed and with its lettering already broken into lines.

.. attribute:: id

Stable identifier, b0, b1, … in script order.

.. attribute:: speaker

Actor id of whoever is talking.

.. attribute:: order

Position in reading order, counting from zero.

.. attribute:: kind

Which sort of balloon to draw.

.. attribute:: box

Where it sits.

.. attribute:: lines

The resolved line breaking, not the source string.

.. attribute:: font_size

Type size in panel units.

.. attribute:: line_height

Baseline-to-baseline distance in panel units.

.. attribute:: tail

The pointer to the speaker’s mouth.

Storing broken lines rather than the original string is deliberate. Wrapping is decided during compilation against real font metrics; if the emitter re-measured, it could disagree with the solver about how wide the balloon needed to be, and the text would overflow the shape drawn for it.

.. py:attribute:: CoreBalloon.id

module:

scenet.core

type:

str

.. py:attribute:: CoreBalloon.speaker

module:

scenet.core

type:

str

.. py:attribute:: CoreBalloon.order

module:

scenet.core

type:

int

.. py:attribute:: CoreBalloon.kind

module:

scenet.core

type:

~scenet.ir.BalloonKind

.. py:attribute:: CoreBalloon.box

module:

scenet.core

type:

~scenet.core.Box

.. py:attribute:: CoreBalloon.lines

module:

scenet.core

type:

tuple[str, …]

.. py:attribute:: CoreBalloon.font_size

module:

scenet.core

type:

float

.. py:attribute:: CoreBalloon.line_height

module:

scenet.core

type:

float

.. py:attribute:: CoreBalloon.tail

module:

scenet.core

type:

~scenet.core.Tail

.. py:class:: Disc

module:

scenet.core

Bases: :py:class:~scenet.core.CoreModel

A circle, as stored in a Core document.

In practice always a face exclusion zone – the region no balloon may cover.

.. attribute:: cx

Centre x.

.. attribute:: cy

Centre y.

.. attribute:: r

Radius.

.. py:attribute:: Disc.cx

module:

scenet.core

type:

float

.. py:attribute:: Disc.cy

module:

scenet.core

type:

float

.. py:attribute:: Disc.r

module:

scenet.core

type:

float

.. py:method:: Disc.of(circle)

module:

scenet.core

classmethod:
Build a `Disc` from a geometry `Circle`, rounding for emission.

:type circle: :sphinx_autodoc_typehints_type:`\:py\:class\:\`\~scenet.geom.Circle\``
:param circle: The circle to convert.

:rtype: :sphinx_autodoc_typehints_type:`\:py\:class\:\`\~typing.Self\``
:returns: The serialisable equivalent.

.. py:method:: Disc.as_circle() :module: scenet.core

Convert back to a geometry `Circle` for further computation.

:rtype: :sphinx_autodoc_typehints_type:`\:py\:class\:\`\~scenet.geom.Circle\``

.. py:class:: PanelCore

module:

scenet.core

Bases: :py:class:~scenet.core.CoreModel

A fully resolved panel: numeric, named, and ready to emit.

The middle tier, and the architectural idea of the project. Every position here is absolute and final, but identifiers survive – which is exactly what separates this from SVG. You can read a Core document, see that alice sits at x=280 with her balloon top-left of her head, change one number, and emit it again.

The approach is borrowed from Vega-Lite, which compiles a high-level grammar into a lower-level one before emitting anything drawable.

.. attribute:: format_version

Bumped when the shape of this document changes incompatibly.

.. attribute:: width

Panel width in panel units.

.. attribute:: height

Panel height in panel units.

.. attribute:: actors

Resolved characters, in declaration order.

.. attribute:: balloons

Resolved balloons, in reading order.

Golden-file tests target this tier rather than the SVG, because it changes only when the layout genuinely changes. Diffing SVG text is brittle – a reordered attribute or a different path-rounding convention produces an enormous diff that means nothing.

.. admonition:: Example

from scenet import compile_source core = compile_source(“{cast: {a: {reference: alice}}}”).core core.width, core.height (1000.0, 1000.0) core.actor(“a”).reference ‘alice’ core.to_json().splitlines()[0] ‘{’

.. py:attribute:: PanelCore.format_version

module:

scenet.core

type:

int

.. py:attribute:: PanelCore.width

module:

scenet.core

type:

float

.. py:attribute:: PanelCore.height

module:

scenet.core

type:

float

.. py:attribute:: PanelCore.actors

module:

scenet.core

type:

tuple[~scenet.core.CoreActor, …]

.. py:attribute:: PanelCore.balloons

module:

scenet.core

type:

tuple[~scenet.core.CoreBalloon, …]

.. py:property:: PanelCore.bounds

module:

scenet.core

type:

~scenet.geom.BBox

The panel rectangle, origin at (0, 0).

.. py:method:: PanelCore.actor(actor_id)

module:

scenet.core

Look up one actor by id.

type actor_id:
sphinx_autodoc_typehints_type:

\:py\:class\:\str``

param actor_id:

The id used in the panel source.

rtype:
sphinx_autodoc_typehints_type:

\:py\:class\:\~scenet.core.CoreActor``

returns:

That actor.

raises KeyError:

No actor in this panel has that id.

.. py:method:: PanelCore.to_json()

module:

scenet.core

Serialise deterministically.

Keys are sorted and floats already rounded at construction, so the same input yields byte-identical output on any platform. A trailing newline keeps the file well-formed for line-oriented tools like git diff.

rtype:
sphinx_autodoc_typehints_type:

\:py\:class\:\str``

.. py:method:: PanelCore.from_json(text)

module:

scenet.core

classmethod:
Read a Core document back in.

The inverse of :meth:`to_json <scenet.core.PanelCore.to_json>`, and the reason Panel
Core is a real format rather than a private data structure: a layout can be
exported, adjusted by hand or by another tool, and read back for emission.

:type text: :sphinx_autodoc_typehints_type:`\:py\:class\:\`str\``
:param text: A Core document.

:rtype: :sphinx_autodoc_typehints_type:`\:py\:class\:\`\~typing.Self\``
:returns: The parsed panel.

:raises pydantic.ValidationError: The document is not a valid Core panel.
:raises json.JSONDecodeError: The text is not JSON at all.

.. py:class:: Tail

module:

scenet.core

Bases: :py:class:~scenet.core.CoreModel

The pointer from a balloon to its speaker’s mouth.

control is present only when the straight route was obstructed and the tail had to bend, which keeps the common case honest about being a simple straight line.

.. py:attribute:: Tail.start

module:

scenet.core

type:

tuple[float, float]

.. py:attribute:: Tail.end

module:

scenet.core

type:

tuple[float, float]

.. py:attribute:: Tail.control

module:

scenet.core

type:

tuple[float, float] | None

.. py:attribute:: Tail.width

module:

scenet.core

type:

float

.. py:property:: Tail.is_curved

module:

scenet.core

type:

bool

Whether this tail had to bend around an obstacle.

.. py:class:: Transform

module:

scenet.core

Bases: :py:class:~scenet.core.CoreModel

Where a puppet’s root joint lands, and how it is scaled and mirrored.

.. py:attribute:: Transform.x

module:

scenet.core

type:

float

.. py:attribute:: Transform.y

module:

scenet.core

type:

float

.. py:attribute:: Transform.scale

module:

scenet.core

type:

float

.. py:attribute:: Transform.mirrored

module:

scenet.core

type:

bool

.. py:function:: point_pair(point)

module:

scenet.core

Convert a point to the rounded (x, y) pair a Core document stores.

rtype:
sphinx_autodoc_typehints_type:

\:py\:class\:\tuple`\ \[:py:class:`float`, :py:class:`float`]`

.. py:function:: round_pairs(points)

module:

scenet.core

Convert a sequence of points – a hull, typically – to rounded pairs.

rtype:
sphinx_autodoc_typehints_type:

\:py\:class\:\tuple`\ \[:py:class:`tuple`\ \[:py:class:`float`, :py:class:`float`], :py:data:`…<Ellipsis>`]`

.. py:function:: vector_pair(vector)

module:

scenet.core

Convert a vector to the rounded (dx, dy) pair a Core document stores.

rtype:
sphinx_autodoc_typehints_type:

\:py\:class\:\tuple`\ \[:py:class:`float`, :py:class:`float`]`