cutan.characters.mouth_set

Generate the 9-shape default mouth set as parametric SVGs.

Produces drop-in art for mouth_a … mouth_h plus mouth_x, with shape parameters cribbed from Daniel Wolf’s Rhubarb README (see research §2.2). Every shape is rendered into the same per-mouth canvas (DEFAULT_MOUTH_VIEWBOX, default 256×128) with a centered anchor, so swapping attachments at runtime needs no per-shape offset.

The set is deliberately stylized — flat colors, bold strokes — so it reads at the small sizes a typical cutout puppet uses (~30-40 px tall on a 1080p frame). It’s not meant to compete with hand-drawn art; it’s the “always works” fallback.

>>> svgs = generate_default_mouths()
>>> sorted(svgs.keys())
['mouth_a', 'mouth_b', 'mouth_c', 'mouth_d', 'mouth_e', 'mouth_f', 'mouth_g', 'mouth_h', 'mouth_x']
>>> len(svgs['mouth_a']) > 100
True

Module Attributes

DEFAULT_MOUTH_VIEWBOX

Mouth canvas viewBox (width, height) — small per-shape and centered so the anchor is always (0.5, 0.5).

SMILE_CURVE_GAIN

How far a variant's smile lifts the mouth's corners, as a fraction of the shape's half width per unit of smile (an#253).

LIP_STROKE

wide enough that a form (a smile, a frown) still reads at full-figure framing (cutan#61; was 2).

DEFAULT_MOUTH_VARIANTS

a viseme@<form> set per entry, its shapes drawn with this corner upturn added (an#98).

Functions

generate_default_mouths(*[, canvas, ...])

Return {"mouth_<letter>[_<form>]": <svg-string>, ...} for every shape.

mouth_attachment_name(shape[, form])

The attachment (and file stem) of one mouth drawing.

write_default_mouths(out_dir, *[, canvas, ...])

Write the default mouth SVGs into out_dir (created if missing), plus one mouth_<shape>_<form>.svg per shape for every variants entry ({form: smile offset}; None = none).

cutan.characters.mouth_set.DEFAULT_MOUTH_VARIANTS: dict[str, float] = {'angry': -0.25, 'happy': 0.35, 'sad': -0.35}

a viseme@<form> set per entry, its shapes drawn with this corner upturn added (an#98). Every preset that prefers a form the character lacks falls back to viseme with a warning, so the default covers the two forms the most-authored presets (happy/amused, sad, angry: cutan#61) ask for.

Type:

The mouth-form variants a synthesized character gets by default

cutan.characters.mouth_set.DEFAULT_MOUTH_VIEWBOX: tuple[int, int] = (256, 128)

Mouth canvas viewBox (width, height) — small per-shape and centered so the anchor is always (0.5, 0.5).

cutan.characters.mouth_set.LIP_STROKE: float = 3.0

wide enough that a form (a smile, a frown) still reads at full-figure framing (cutan#61; was 2).

Type:

The lip line’s width in the mouth canvas

cutan.characters.mouth_set.SMILE_CURVE_GAIN: float = 1.0

How far a variant’s smile lifts the mouth’s corners, as a fraction of the shape’s half width per unit of smile (an#253). The corners move against the middle, so the line curves: a viseme@happy rest mouth is a closed smile, viseme@sad a frown. At DEFAULT_MOUTH_VARIANTS’ 0.35 the corners of the idle line rise about a sixth of its half width.

cutan.characters.mouth_set.generate_default_mouths(*, canvas=(256, 128), palette=None, shapes=('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'x'), smile=0.0, form=None)[source]

Return {"mouth_<letter>[_<form>]": <svg-string>, ...} for every shape.

>>> svgs = generate_default_mouths()
>>> 'mouth_x' in svgs and 'viewBox' in svgs['mouth_x']
True
>>> sorted(generate_default_mouths(shapes=["a"], smile=0.35, form="happy"))
['mouth_a_happy']
Return type:

dict[str, str]

cutan.characters.mouth_set.mouth_attachment_name(shape, form=None)[source]

The attachment (and file stem) of one mouth drawing.

>>> mouth_attachment_name("a"), mouth_attachment_name("a", "happy")
('mouth_a', 'mouth_a_happy')
Return type:

str

cutan.characters.mouth_set.write_default_mouths(out_dir, *, canvas=(256, 128), palette=None, shapes=('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'x'), variants=None)[source]

Write the default mouth SVGs into out_dir (created if missing), plus one mouth_<shape>_<form>.svg per shape for every variants entry ({form: smile offset}; None = none).

Returns the list of paths written: the neutral set in shape order, then each variant’s.

Return type:

list[Path]