pygeomatic 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/PKG-INFO +11 -1
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/README.md +10 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/pyproject.toml +1 -1
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/registry.py +6 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/tex.py +149 -19
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/.gitignore +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/LICENSE +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/__init__.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/article.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/coercions.json +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/coercions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/emit.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/extensions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/__init__.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/helpers.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/__init__.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/annotation_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/array.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/autograd_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/basic_figures.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/boolean_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/complex_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/curve_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/intersections.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/ode_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/planar_geometry.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/polygons.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/property_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/rotation_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/scalar_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/special_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/tangent_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/tensor_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/translation_functions.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/__init__.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/abs.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/add.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/create_overload.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/div.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/exp.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/log.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/mul.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/neg.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/pow.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/sqrt.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/overloads/sub.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/generate.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/inference.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/macros.json +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/macros.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/nodes.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/palette.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/parse.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/prompting.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/runner.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/store.py +0 -0
- {pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/system_nodes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pygeomatic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Python mirror of the geomatic DSL function library; records calls for deterministic DSL emission
|
|
5
5
|
Project-URL: Homepage, https://github.com/TinyVolt/pygeomatic
|
|
6
6
|
Project-URL: Repository, https://github.com/TinyVolt/pygeomatic
|
|
@@ -227,6 +227,16 @@ Move it: {row 1}(r = gm.scalar(1)) · {row 2}(r = gm.scalar(2))
|
|
|
227
227
|
behind a click. Palette names (`"pink"`, `"BLUE"`) resolve to hex; raw
|
|
228
228
|
`"#f472b6"` / CSS names pass through. Full reference:
|
|
229
229
|
[docs/tex-highlight-ergonomics.md](docs/tex-highlight-ergonomics.md).
|
|
230
|
+
- **Reveal** (fade a part in): same selector machine, painting **opacity**
|
|
231
|
+
instead of color, so changing a gate node — reassigning it (`b = gm.bool_(True)`,
|
|
232
|
+
`k = gm.scalar(1)`) or animating it (`gm.animate(k, 3)`) in a CommandLink —
|
|
233
|
+
makes a piece of the formula appear. Three targets — an over/underbrace + label
|
|
234
|
+
(`t.underbrace.reveal(b)`, body stays visible; `.label` / `.body` for just
|
|
235
|
+
that part), a derivation line-by-line (`d.rows().reveal(rows < k)` — `k` lines
|
|
236
|
+
shown), or a matrix's rows/columns (`M.reveal(M.cols() < k)`). A bare gate
|
|
237
|
+
node/bool is the all-or-nothing case (`gm.bool_(False)` makes a false gate); a
|
|
238
|
+
scalar gives an animatable sweep. Full reference:
|
|
239
|
+
[docs/tex-reveal.md](docs/tex-reveal.md).
|
|
230
240
|
|
|
231
241
|
Two rules that will bite otherwise:
|
|
232
242
|
|
|
@@ -206,6 +206,16 @@ Move it: {row 1}(r = gm.scalar(1)) · {row 2}(r = gm.scalar(2))
|
|
|
206
206
|
behind a click. Palette names (`"pink"`, `"BLUE"`) resolve to hex; raw
|
|
207
207
|
`"#f472b6"` / CSS names pass through. Full reference:
|
|
208
208
|
[docs/tex-highlight-ergonomics.md](docs/tex-highlight-ergonomics.md).
|
|
209
|
+
- **Reveal** (fade a part in): same selector machine, painting **opacity**
|
|
210
|
+
instead of color, so changing a gate node — reassigning it (`b = gm.bool_(True)`,
|
|
211
|
+
`k = gm.scalar(1)`) or animating it (`gm.animate(k, 3)`) in a CommandLink —
|
|
212
|
+
makes a piece of the formula appear. Three targets — an over/underbrace + label
|
|
213
|
+
(`t.underbrace.reveal(b)`, body stays visible; `.label` / `.body` for just
|
|
214
|
+
that part), a derivation line-by-line (`d.rows().reveal(rows < k)` — `k` lines
|
|
215
|
+
shown), or a matrix's rows/columns (`M.reveal(M.cols() < k)`). A bare gate
|
|
216
|
+
node/bool is the all-or-nothing case (`gm.bool_(False)` makes a false gate); a
|
|
217
|
+
scalar gives an animatable sweep. Full reference:
|
|
218
|
+
[docs/tex-reveal.md](docs/tex-reveal.md).
|
|
209
219
|
|
|
210
220
|
Two rules that will bite otherwise:
|
|
211
221
|
|
|
@@ -217,6 +217,12 @@ def _resolve_arg(fdef: FunctionDef, p: P, arg: Any, store: Store) -> tuple[ArgTo
|
|
|
217
217
|
if p.type == "Bool":
|
|
218
218
|
node = _implicit_bool(arg, store)
|
|
219
219
|
return node.ref, node
|
|
220
|
+
if p.type == "Any":
|
|
221
|
+
# A bare Python bool in an untyped slot is just its 0/1 value (bool
|
|
222
|
+
# is an int subclass), like passing 1/0 — e.g. `gm.bool_(True)`
|
|
223
|
+
# records `\bool 1`. Scalar/Text params stay strict (they reject a
|
|
224
|
+
# bool below), so this only relaxes genuinely untyped parameters.
|
|
225
|
+
return int(arg), int(arg)
|
|
220
226
|
raise TypeError(
|
|
221
227
|
f"\\{fdef.keyword}: parameter '{p.name}' expects {p.type}, got bool"
|
|
222
228
|
)
|
|
@@ -2,10 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
A `$$…$$` formula in a markdown article (given an id by a `%id:` line as its
|
|
4
4
|
first line) is made addressable and reactive
|
|
5
|
-
NOT by any DSL command but by declarative *bindings* recorded here
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
NOT by any DSL command but by declarative *bindings* recorded here. Three
|
|
6
|
+
effects, all driven by *changing a store node* (reassigning it — `b = gm.bool_(True)`,
|
|
7
|
+
`k = gm.scalar(1)` — or `gm.animate(k, 3)` — in a CommandLink):
|
|
8
|
+
|
|
9
|
+
- **value** — a schema slot shows a store node's live value
|
|
10
|
+
(`energy.int.upper.bind(a)`);
|
|
11
|
+
- **highlight** — matrix cells painted a color by a selector over store nodes
|
|
12
|
+
(`M.highlight(M.rows().eq(r), color="pink")`);
|
|
13
|
+
- **reveal** — a part of the formula fades in when a node says so: an
|
|
14
|
+
over/underbrace (`t.underbrace.reveal(b)`), a derivation line-by-line
|
|
15
|
+
(`d.rows().reveal(rows < k)`), or a matrix's rows/columns
|
|
16
|
+
(`M.reveal(M.cols() < k)`). Same selector machine as highlight, painting
|
|
17
|
+
opacity instead of color.
|
|
9
18
|
|
|
10
19
|
Three properties define the shape (see the texatlas design memo):
|
|
11
20
|
|
|
@@ -80,6 +89,13 @@ def _register_builtin_schema() -> None:
|
|
|
80
89
|
register_tex_schema("prod", ("lower", "upper", "body"))
|
|
81
90
|
register_tex_schema("frac", ("num", "denom"))
|
|
82
91
|
register_tex_schema("sqrt", ("body",))
|
|
92
|
+
# Over/underbrace are one browser family (`horizBrace`, keyed off `isOver`);
|
|
93
|
+
# here they are two named families with the same slots. The BARE family
|
|
94
|
+
# address (`t.underbrace`) is the annotation — brace glyph + label; the body
|
|
95
|
+
# stays visible. `.body` / `.label` address just those parts. Used for the
|
|
96
|
+
# reveal effect (`t.underbrace.reveal(gate)`), not for value binding.
|
|
97
|
+
register_tex_schema("underbrace", ("body", "label"))
|
|
98
|
+
register_tex_schema("overbrace", ("body", "label"))
|
|
83
99
|
|
|
84
100
|
|
|
85
101
|
_register_builtin_schema()
|
|
@@ -143,6 +159,40 @@ def _highlight_entry(selector_json: dict, color: str, matrix: int) -> dict:
|
|
|
143
159
|
return entry
|
|
144
160
|
|
|
145
161
|
|
|
162
|
+
def _align_index(align: int) -> int:
|
|
163
|
+
"""Validate an author-supplied align occurrence index (a non-negative int).
|
|
164
|
+
|
|
165
|
+
This is which equation-layout array (`aligned`/`align`/`split`/`alignat`/
|
|
166
|
+
`gather`/`CD`) in the formula a reveal fades line-by-line, counted in source
|
|
167
|
+
order — the arrays that `matrix`/highlight deliberately SKIP. Python does not
|
|
168
|
+
parse the LaTeX; the author supplies the integer (default 0)."""
|
|
169
|
+
if isinstance(align, bool) or not isinstance(align, int) or align < 0:
|
|
170
|
+
raise TexError(f"align index must be a non-negative int, got {align!r}")
|
|
171
|
+
return align
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _reveal_mode(mode: str) -> str:
|
|
175
|
+
"""Validate a reveal paint mode. `"fade"` (default) paints opacity and keeps
|
|
176
|
+
the layout; `"collapse"` also removes the slot's space (avoid it for matrix
|
|
177
|
+
cells — it breaks the grid/brackets)."""
|
|
178
|
+
if mode not in ("fade", "collapse"):
|
|
179
|
+
raise TexError(f"reveal mode must be 'fade' or 'collapse', got {mode!r}")
|
|
180
|
+
return mode
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _reveal_entry(target: dict, selector: "Selector", mode: str) -> dict:
|
|
184
|
+
"""A `RevealBinding` wire entry: exactly one target descriptor
|
|
185
|
+
(`slot` | `align` | `matrix`) plus a selector giving each cell an opacity
|
|
186
|
+
weight in [0, 1]. `mode` is omitted when `"fade"` (the default) so the wire
|
|
187
|
+
JSON stays minimal. A bare gate node/bool is accepted as the selector (the
|
|
188
|
+
`{node}` leaf — the degenerate all-or-nothing case)."""
|
|
189
|
+
entry: dict = dict(target)
|
|
190
|
+
entry["selector"] = _as_selector(selector)
|
|
191
|
+
if _reveal_mode(mode) != "fade":
|
|
192
|
+
entry["mode"] = mode
|
|
193
|
+
return entry
|
|
194
|
+
|
|
195
|
+
|
|
146
196
|
def _resolve_color(color: str) -> str:
|
|
147
197
|
"""Resolve a palette name to its hex before it crosses the wire; pass any
|
|
148
198
|
other CSS color string (`#f472b6`, `pink`, `rgb(...)`) through unchanged."""
|
|
@@ -279,6 +329,33 @@ rows: AxisExpr = _NamedAxis("row")
|
|
|
279
329
|
cols: AxisExpr = _NamedAxis("col")
|
|
280
330
|
|
|
281
331
|
|
|
332
|
+
class _TexAxis(_NamedAxis):
|
|
333
|
+
"""A row/col axis that remembers its formula, returned by `t.rows()` /
|
|
334
|
+
`t.cols()`. It behaves exactly like the free `rows`/`cols` axes for building
|
|
335
|
+
selectors (`t.rows().eq(r)`), and additionally carries `.reveal(...)` to fade
|
|
336
|
+
in an equation-layout / derivation block line-by-line (the `align` target).
|
|
337
|
+
The axis identity does not enter the align target — the whole aligned block
|
|
338
|
+
is the target and the selector (e.g. `rows < k`) picks which lines show."""
|
|
339
|
+
|
|
340
|
+
def __init__(self, tex_id: str, axis: str) -> None:
|
|
341
|
+
super().__init__(axis)
|
|
342
|
+
self._tex_id = tex_id
|
|
343
|
+
|
|
344
|
+
def reveal(
|
|
345
|
+
self, selector: "Selector", *, align: int = 0, mode: str = "fade"
|
|
346
|
+
) -> None:
|
|
347
|
+
"""Progressively reveal the lines of an equation-layout / derivation
|
|
348
|
+
block: `d.rows().reveal(rows < k)` shows `k` lines (`k = 0` shows none).
|
|
349
|
+
|
|
350
|
+
`align` is the 0-based occurrence index of which equation-layout array to
|
|
351
|
+
reveal (source order, counting only `aligned`/`align`/`split`/`alignat`/
|
|
352
|
+
`gather`/`CD` — the arrays highlight/`matrix` skip); default 0. `mode` is
|
|
353
|
+
`"fade"` (opacity, keeps layout) or `"collapse"` (also removes space)."""
|
|
354
|
+
_bindings(self._tex_id)["reveals"].append(
|
|
355
|
+
_reveal_entry({"align": _align_index(align)}, selector, mode)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
282
359
|
class Selector:
|
|
283
360
|
"""A highlight selector: `(cell, env) -> weight`. Combine with `and_`/`or_`
|
|
284
361
|
(min/max) or fade the whole selection with `scale`."""
|
|
@@ -302,10 +379,18 @@ class Selector:
|
|
|
302
379
|
return self._json_
|
|
303
380
|
|
|
304
381
|
|
|
305
|
-
def _as_selector(value: "Selector") -> dict:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
382
|
+
def _as_selector(value: Union["Selector", GNode, str]) -> dict:
|
|
383
|
+
"""The selector JSON for a value that is either a built `Selector` or a bare
|
|
384
|
+
gate node/id. A bare node lowers to the `{node}` `SelectorExpr` leaf
|
|
385
|
+
(`weight = clamp(0, v, 1)`), so a bool gate is expressible without a dummy
|
|
386
|
+
comparison — e.g. `t.underbrace.reveal(b)` or `(rows == 2) & b`."""
|
|
387
|
+
if isinstance(value, Selector):
|
|
388
|
+
return value._json_
|
|
389
|
+
if isinstance(value, (GNode, str)):
|
|
390
|
+
return {"node": _node_id(value, what="selector gate")}
|
|
391
|
+
raise TexError(
|
|
392
|
+
f"expected a selector or a gate node, got {type(value).__name__}"
|
|
393
|
+
)
|
|
309
394
|
|
|
310
395
|
|
|
311
396
|
class _Region(Selector):
|
|
@@ -376,6 +461,18 @@ class _Slot:
|
|
|
376
461
|
entry["fmt"] = fmt
|
|
377
462
|
_bindings(self._tex_id)["values"].append(entry)
|
|
378
463
|
|
|
464
|
+
def reveal(self, selector: "Selector", *, mode: str = "fade") -> None:
|
|
465
|
+
"""Fade this slot in when the `selector` says so (opacity from its weight
|
|
466
|
+
in [0, 1]). A bare gate node/bool is the all-or-nothing case
|
|
467
|
+
(`t.underbrace.label.reveal(b)`); pass a real selector for a progressive
|
|
468
|
+
sweep. `mode` is `"fade"` (default, keeps layout) or `"collapse"` (also
|
|
469
|
+
removes the slot's space). For a bare over/underbrace address
|
|
470
|
+
(`t.underbrace.reveal(b)`) the browser reveals the brace glyph + label
|
|
471
|
+
while the body stays visible; `.label` / `.body` address just that part."""
|
|
472
|
+
_bindings(self._tex_id)["reveals"].append(
|
|
473
|
+
_reveal_entry({"slot": self._address}, selector, mode)
|
|
474
|
+
)
|
|
475
|
+
|
|
379
476
|
|
|
380
477
|
class _Family:
|
|
381
478
|
"""A LaTeX command family occurrence (`tex.int`, `tex.ints[1]`). Bind the
|
|
@@ -393,6 +490,11 @@ class _Family:
|
|
|
393
490
|
def bind(self, node: Union[GNode, str], *, show: str = "value", fmt: Optional[str] = None) -> None:
|
|
394
491
|
_Slot(self._tex_id, self._base).bind(node, show=show, fmt=fmt)
|
|
395
492
|
|
|
493
|
+
def reveal(self, selector: "Selector", *, mode: str = "fade") -> None:
|
|
494
|
+
"""Reveal this whole family occurrence (the bare address, e.g. a brace's
|
|
495
|
+
glyph + label — see `_Slot.reveal`)."""
|
|
496
|
+
_Slot(self._tex_id, self._base).reveal(selector, mode=mode)
|
|
497
|
+
|
|
396
498
|
def __getattr__(self, name: str) -> _Slot:
|
|
397
499
|
# Only slot names reach here (real methods/attrs resolve first). Let
|
|
398
500
|
# Python's own dunder probing (copy, pickling, ...) miss cleanly.
|
|
@@ -448,13 +550,15 @@ class Tex:
|
|
|
448
550
|
|
|
449
551
|
# -- matrix highlights --------------------------------------------------
|
|
450
552
|
|
|
451
|
-
def rows(self) ->
|
|
452
|
-
"""The cell's row index, as an axis expression.
|
|
453
|
-
|
|
553
|
+
def rows(self) -> _TexAxis:
|
|
554
|
+
"""The cell's row index, as an axis expression. Also carries `.reveal()`
|
|
555
|
+
to fade in a derivation / equation-layout block line-by-line
|
|
556
|
+
(`d.rows().reveal(rows < k)`)."""
|
|
557
|
+
return _TexAxis(self.id, "row")
|
|
454
558
|
|
|
455
|
-
def cols(self) ->
|
|
559
|
+
def cols(self) -> _TexAxis:
|
|
456
560
|
"""The cell's column index, as an axis expression."""
|
|
457
|
-
return
|
|
561
|
+
return _TexAxis(self.id, "col")
|
|
458
562
|
|
|
459
563
|
def highlight(
|
|
460
564
|
self, selector: Selector, *, color: str = "COLOR-YELLOW", matrix: int = 0
|
|
@@ -484,6 +588,28 @@ class Tex:
|
|
|
484
588
|
_highlight_entry(_as_selector(selector), color, matrix)
|
|
485
589
|
)
|
|
486
590
|
|
|
591
|
+
def reveal(
|
|
592
|
+
self, selector: Selector, *, matrix: int = 0, mode: str = "fade"
|
|
593
|
+
) -> None:
|
|
594
|
+
"""Progressively reveal the rows/columns of a matrix — the same selector
|
|
595
|
+
machine as `highlight`, painting opacity instead of color:
|
|
596
|
+
`M.reveal(M.cols() < k)` shows `k` columns (`k = 0` shows none). A bare
|
|
597
|
+
gate node/bool reveals the whole matrix all-or-nothing.
|
|
598
|
+
|
|
599
|
+
`matrix` is the 0-based occurrence index of which matrix to reveal (same
|
|
600
|
+
source-order counting rule as `highlight`'s `matrix=`; unlike highlight
|
|
601
|
+
it is always written to the wire — it is the target discriminator). Only
|
|
602
|
+
`mode="fade"` is supported: `collapse` would break the grid and brackets,
|
|
603
|
+
so opacity keeps the shape stable while cells fade in."""
|
|
604
|
+
if _reveal_mode(mode) == "collapse":
|
|
605
|
+
raise TexError(
|
|
606
|
+
"matrix reveal supports only mode='fade' — collapse would remove "
|
|
607
|
+
"cell space and break the grid/brackets; opacity keeps the shape"
|
|
608
|
+
)
|
|
609
|
+
_bindings(self.id)["reveals"].append(
|
|
610
|
+
_reveal_entry({"matrix": _matrix_index(matrix)}, selector, mode)
|
|
611
|
+
)
|
|
612
|
+
|
|
487
613
|
def __getitem__(self, key) -> _Region:
|
|
488
614
|
"""numpy-style cell region: `M[3:, 4:]`, `M[:, c]`, `M[r, ...]`.
|
|
489
615
|
|
|
@@ -571,16 +697,18 @@ def tex(tex_id: str) -> Tex:
|
|
|
571
697
|
|
|
572
698
|
|
|
573
699
|
def _bindings(tex_id: str) -> dict:
|
|
574
|
-
return current_store().tex_bindings.setdefault(
|
|
700
|
+
return current_store().tex_bindings.setdefault(
|
|
701
|
+
tex_id, {"values": [], "highlights": [], "reveals": []}
|
|
702
|
+
)
|
|
575
703
|
|
|
576
704
|
|
|
577
705
|
def harvest_tex_bindings(store=None) -> dict:
|
|
578
706
|
"""The session's recorded texatlas bindings as the wire manifest
|
|
579
|
-
`{ texId: { "values": [...], "highlights": [...] } }` — the
|
|
580
|
-
TypeScript runtime consumes (empty arrays and formulas with no
|
|
581
|
-
dropped). This is how the publish compiler snapshots bindings
|
|
582
|
-
article without the author calling any emit; it is the tex analogue
|
|
583
|
-
`gm.emit()` for the command tape."""
|
|
707
|
+
`{ texId: { "values": [...], "highlights": [...], "reveals": [...] } }` — the
|
|
708
|
+
exact JSON the TypeScript runtime consumes (empty arrays and formulas with no
|
|
709
|
+
bindings are dropped). This is how the publish compiler snapshots bindings
|
|
710
|
+
into an article without the author calling any emit; it is the tex analogue
|
|
711
|
+
of `gm.emit()` for the command tape."""
|
|
584
712
|
store = store or current_store()
|
|
585
713
|
manifest: dict = {}
|
|
586
714
|
for tex_id, b in store.tex_bindings.items():
|
|
@@ -589,6 +717,8 @@ def harvest_tex_bindings(store=None) -> dict:
|
|
|
589
717
|
entry["values"] = b["values"]
|
|
590
718
|
if b["highlights"]:
|
|
591
719
|
entry["highlights"] = b["highlights"]
|
|
720
|
+
if b.get("reveals"):
|
|
721
|
+
entry["reveals"] = b["reveals"]
|
|
592
722
|
if entry:
|
|
593
723
|
manifest[tex_id] = entry
|
|
594
724
|
return manifest
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/autograd_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/basic_figures.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/boolean_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/complex_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/curve_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/intersections.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/ode_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/planar_geometry.py
RENAMED
|
File without changes
|
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/property_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/rotation_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/scalar_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/special_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/tangent_functions.py
RENAMED
|
File without changes
|
{pygeomatic-0.2.1 → pygeomatic-0.2.2}/src/pygeomatic/functions/implementations/tensor_functions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|