Loading editor...

pygeomatic by example

A tour of the features. Every symbol here is a placeholder (, , , ) and the scene is deliberately empty of meaning: a point, a segment, a circle. Each bullet states one thing pygeomatic can do and demonstrates it with the smallest live example that shows the mechanism.

The pygeomatic fence

  • Python that computes and records. A fence runs real Python at compile time; each gm. call both evaluates numerically and appends one DSL line, so the reader receives deterministic command text, never Python.
  • One call, one line. gm.mul(k, 2) is exactly x = \mul k 2. Repetition is ordinary Python loops, not a batch API.
  • Arithmetic. Infix works on Scalar / Complex / Array nodes (k + 1 records \add), but the explicit form is always available and is the only option elsewhere: gm.add(a, b), gm.pow_(a, 2), and nothing infix on a Point or a Circle. In-place += raises, so assign a new name instead.
  • Id grammar. Letters, digits and dashes only. An assignment target becomes the id with underscores turned into dashes, so sum_ab above is the node sum-ab; out="my-id" overrides, and engine shapes like num0 are refused.
  • System nodes are already there: gm.p0 is the origin used above as the segment's first endpoint, unit is the zoom, gm.T / gm.F are the literals.
  • Colors come from gm.load_colors() once, then c.BLUE, c.AMBER, or c["COLOR-TEAL-LIGHT"] for the hyphenated ramp names.

Reveal beats

A with group(...) block is one beat; a prose span whose target is ref:name reveals it, so the figure builds click by click and each click lands on a fully set-up scene.

  • at .
  • from the origin to it.
  • of radius .
  • An whose text reads its value back live.
  • A on the point.
  • A that records where the point has been.

Objects built as scaffolding are hidden at build time with gm.hide and brought back with gm.show inside the beat that earns them.

Inline CommandLinks and reactivity

An inline span carrying a Python statement is a one-off command the reader triggers. Article mode is last-write-wins, so re-binding a name is a reassignment.

  • Reassign: or .
  • Animate: walks the value there frame by frame.
  • Everything downstream recomputes on its own: moves the point, the circle radius follows , and the caption interpolates ${k} in place.
  • Any command works inline, not only assignment: .

texatlas: value binding

Give a $$…$$ formula an id with a %id: first line, take a handle with gm.tex(...), and bind a store node into a slot. Binding replaces content, so every slot needs a placeholder symbol already written in the LaTeX.

  • Legal families and slots come from the SCHEMA in src/pygeomatic/tex.py: int / sum / prod take lower, upper, body; frac takes num and denom; sqrt takes body; underbrace / overbrace are reveal-only.
  • fmt is ".2f" or "d", or omit it to trim to four decimals, and show="symbol" links a slot without substituting its glyph.
  • Driving the bound node drives the formula: .

texatlas: matrix highlight

A highlight is a predicate over a cell's grid position, never its content.

  • Axes are gm.rows, gm.cols, gm.dim(i); compare them with ==, <, > against an int or a node, and combine regions with & and |.
  • Move the row band: or .
  • Open the gated highlight: .
  • matrix=N picks the -th matrix in a multi-matrix formula, counting real matrices in source order and skipping aligned-style layout blocks.

texatlas: reveal

The same selector machine, painting opacity instead of color. Use strict < so the gate counts how many parts show and shows nothing.

  • Step the derivation: , , , or .
  • Flip the brace on: .
  • Matrix reveal is fade-only; mode="collapse" is available for lines and slots but would break a grid.

Compiling

Bindings never become DSL: they ride a separate channel and land in a trailing <!-- texatlas:v1 … --> comment. The compiler replays the whole document through parse_dsl in order, so a broken article fails the build, not the reader:

uv run python scripts/compile_article.py examples/agent-output.md