Python mirror of the geomatic DSL function library; records calls for deterministic DSL emission
Project description
pygeomatic
Python mirror of the geomatic DSL. Intended to be used in Nova editor.
Every public function maps 1:1 to a geomatic command; calling it computes numeric values (numpy) where
possible and records the call onto a tape, from which emit() produces
geomatic DSL lines deterministically.
Naming
Every node has an id. You control it in three ways.
Programmatically — pass out= with an f-string (or any computed string),
so ids can be generated in a loop:
>>> for i in range(5):
... gm.point(i-3, 0, out=f'point{i}')
...
Point(id='point0', x=-3.0, y=0.0)
Point(id='point1', x=-2.0, y=0.0)
Point(id='point2', x=-1.0, y=0.0)
Point(id='point3', x=0.0, y=0.0)
Point(id='point4', x=1.0, y=0.0)
By assignment — the assignment target names the output (underscores become
dashes). Without a target, the id is auto-assigned a dashed form (p-0,
num-1):
>>> my_p
Point(id='my-p', x=2.0, y=3.0)
>>> gm.point(3,2)
Point(id='p-0', x=3.0, y=2.0)
Assignment naming works at any arity — a, b, c = gm.scalar(1), gm.scalar(2), gm.scalar(3) names all three.
An explicit out="my-id" always overrides the
inferred name.
Anything ambiguous or unsafe (no assignment target, attribute targets, reusing
a name in a loop, a name shaped like an engine auto-name, an already-taken id)
silently falls back to a dashed auto-id. Explicit ids must match the DSL
grammar: start with a letter, then letters/digits/dashes, no underscores —
and must not look like an engine auto-name (num0, p3, text1). The engine
generates those for its own internal nodes, and a collision creates a reactive
cycle that hangs the tab; pygeomatic's dashed ids can never collide. Name
inference reads the caller's bytecode, not its source, so it behaves
identically everywhere: files, the REPL, python -c, notebooks, exec'd
strings.
Whichever way a node was named, gm.emit() renders the tape as DSL, one line
per call, using each node's resolved id. The examples above emit as:
>>> print(gm.emit())
point0 = \point -3 0
point1 = \point -2 0
point2 = \point -1 0
point3 = \point 0 0
point4 = \point 1 0
my-p = \point 2 3
p-0 = \point 3 2
Built-in variables
Every canvas — and every Store — starts with the engine's default nodes
already registered, so a scene may reference them by id without defining them
first. They record no commands (they exist implicitly on every canvas) and
resolve against the active store at access time. Access them as module
attributes with dashes turned into underscores (gm.p0, gm.learning_rate),
or by id with gm.node("learning-rate").
You may reassign any of them (gm.scalar(0.5, out="learning-rate")), matching
the engine's last-write-wins saveNode.
| Variable | Value | What it is / when to use it |
|---|---|---|
p0 |
(0, 0) |
The world origin. It is the default center/point2 for many commands (\circle, \ellipse, \square, \rectangle, \reflect-point, …), so reference it for a fixed origin instead of re-declaring \point 0 0. |
learning-rate |
0.01 |
Gradient-descent step size, read by \gradient-descent-step and \minimize. Reassign before a descent step to tune training speed. |
animation-speed |
0.001 |
Per-frame step size for \animate. Reassign larger to make animations run faster, smaller to slow them. |
grid-points |
Point array | Every integer-lattice Point currently on the canvas (built from the live canvas bounds, so numerically unknown here). Reference it to act on the whole background grid at once, e.g. apply a linear map / \translate-array to every grid point. |
unit |
50 |
Zoom: pixels per world unit (unitX == unitY == unit). Reassign to zoom (larger = more zoomed in). |
grid-opacity |
1 |
Opacity of the grid lines and axes. Set to 0 to hide the grid. |
grid-bg-color |
"" |
Solid fill painted behind the grid (empty = transparent). Reassign to a color (grid-bg-color = COLOR-BLACK) to give the canvas a background. |
grid-origin |
(0, 0) |
Where the world origin sits on the canvas, in world units ((0, 0) = centered). Reassign to pan the view. |
T |
true |
The boolean literal true, referenceable wherever a Bool argument is expected. |
F |
false |
The boolean literal false, referenceable wherever a Bool argument is expected. |
gm.line(gm.p0, gm.point(1, 1)) # p0 by attribute
gm.scalar(0.5, out="learning-rate") # reassign a default (last-write-wins)
gm.node("unit") # the string-keyed equivalent
Authoring articles with Nova
Write geomatic articles as markdown with pygeomatic Python instead of raw DSL;
compile_article turns them into the {label}(command) span format. The Python
runs once at compile time — readers only ever receive deterministic DSL text.
```pygeomatic
origin = gm.p0 # top-level code → hidden {}(...) setup spans
a = gm.point(3, 0)
walk = gm.line(origin, a)
gm.hide(walk)
with group("walk-x"): # a named run of commands for prose to reveal
gm.highlight(walk)
gm.show(walk) # last command gets the visible label
```
Reach the point by {moving a distance}(ref:walk-x) of $3$ units.
Or reset it inline: {set scale to 1}(scale = gm.scalar(1)).
- One store per article: all fences and inline spans run in document order sharing state, so ids and auto-names stay consistent.
- Ref expansion: every command of a group but the last becomes a hidden
{}()span before the visible one — a click always lands on a fully set-up scene. - Inline spans (
{label}(python statement)) are the escape hatch for one-offs; article mode is last-write-wins, sos1 = gm.scalar(1)reassigns like the DSL line it becomes. - Round-trip gate: the compiled document is replayed with
parse_dslin document order; broken ordering or invalid DSL fails the compile, not the reader. - Regular code fences and
$...$math are never scanned for spans.
uv run python scripts/compile_article.py article.md -o compiled.md # one file
uv run python scripts/compile_articles.py articles/ dist/ # a tree
(equivalently gm.compile_article(md) in-process or gm.run_article(md) in a
subprocess; both take extensions= / macros= / allow_coercions=.)
Publishing from a content repo (GitHub Action)
A content repo publishes compiled articles with one workflow file; a compile error in any article fails the push and nothing is published:
name: Publish articles
on:
push: {branches: [main]}
jobs:
publish:
permissions: {contents: write}
uses: TinyVolt/pygeomatic/.github/workflows/publish-articles.yml@main
This compiles articles/ and force-pushes the result (plus any non-markdown
assets) to a dist branch, which raw.githubusercontent.com serves with CORS.
Inputs: source, publish-branch, extensions, macros, allow-coercions,
and pygeomatic-ref — pin the latter to a tag or SHA for reproducible output.
For custom pipelines, the composite action TinyVolt/pygeomatic@<ref> runs just
the compile step (the <ref> you pin is also the exact pygeomatic version
used).
Reading published articles
Once the dist branch exists, the article is live — readers open it at
https://www.tinyvolt.com/nova/<username>/<repo>/<article>
where <article> is the markdown file's path within dist (the .md suffix
is optional). E.g. articles/intro.md compiled from the repo alice/vectors
is read at /nova/alice/vectors/intro.
If the article uses extension commands or macros, bake their URLs into the link you share so readers never load anything manually:
?ext=<manifest-url>— extension manifest, loaded (sandboxed, from whitelisted domains only) before the article renders.?esm=<url>— macro definitions, fetched and registered on page load.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pygeomatic-0.2.tar.gz.
File metadata
- Download URL: pygeomatic-0.2.tar.gz
- Upload date:
- Size: 81.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
24eb0758041fd81cbad99582429d7466bc191c6b122777722fbdefde62cb060d
|
|
| MD5 |
af138cfc6883583a9fc27b4a82edaa2b
|
|
| BLAKE2b-256 |
da8c703d1f578f20a749c4c894998e845ada694f998b6462f7f2ed61538d0f61
|
Provenance
The following attestation bundles were made for pygeomatic-0.2.tar.gz:
Publisher:
release.yml on TinyVolt/pygeomatic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pygeomatic-0.2.tar.gz -
Subject digest:
24eb0758041fd81cbad99582429d7466bc191c6b122777722fbdefde62cb060d - Sigstore transparency entry: 2206398740
- Sigstore integration time:
-
Permalink:
TinyVolt/pygeomatic@fc2a43720a858eb85ce801ecd8612ef34821aff3 -
Branch / Tag:
refs/tags/v0.2 - Owner: https://github.com/TinyVolt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fc2a43720a858eb85ce801ecd8612ef34821aff3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pygeomatic-0.2-py3-none-any.whl.
File metadata
- Download URL: pygeomatic-0.2-py3-none-any.whl
- Upload date:
- Size: 103.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7ddc4164e521f42063e92a9737099b80f95016f392e130536de95576428ab74
|
|
| MD5 |
5da6d44cbab39c28fe2bf4faae00d8a3
|
|
| BLAKE2b-256 |
9d9749b7745cb952f5f70b84ba3079ffd09f3441f974ac3651e7b2595a0f7a79
|
Provenance
The following attestation bundles were made for pygeomatic-0.2-py3-none-any.whl:
Publisher:
release.yml on TinyVolt/pygeomatic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pygeomatic-0.2-py3-none-any.whl -
Subject digest:
e7ddc4164e521f42063e92a9737099b80f95016f392e130536de95576428ab74 - Sigstore transparency entry: 2206398826
- Sigstore integration time:
-
Permalink:
TinyVolt/pygeomatic@fc2a43720a858eb85ce801ecd8612ef34821aff3 -
Branch / Tag:
refs/tags/v0.2 - Owner: https://github.com/TinyVolt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@fc2a43720a858eb85ce801ecd8612ef34821aff3 -
Trigger Event:
push
-
Statement type: