Skip to content

Changes since version 0.13.0

New Features

  • show() works in Jupyter notebooks — Visualizer.show() and VizSceneHandle.show() now accept a jupyter option (None auto-detects, True forces the notebook display, False forces a browser tab) and, in Jupyter, delegate to display() so show() is the single entry point for showing a scene.
  • Idempotent display() / show() in Jupyter — within a single cell execution, repeated calls no longer open a second viewer; they flush the latest scene state into the already-open viewer, keyed by an optional viewer_name, the current notebook cell id (via the IPython pre_run_cell event), or the scene name.
  • Scene context managers — Visualizer and VizSceneHandle support with viz: and with viz.scene("name"):, which reset the scene (clearing entities but keeping the default axes/grid) and call show() on entry, so each flush() inside the block updates the live viewer (e.g. an animate() loop), then flush on exit.
  • animate(auto_clear=True) — Visualizer.animate() (and VizSceneHandle.animate()) gained an auto_clear flag: each frame flushes first, then removes every object added after the loop began (anything added before the loop persists), so per-frame add() calls no longer accumulate.
  • viz(...) shorthand for new() — Visualizer is now callable: viz(Point(1, 2, 3), color="...") returns a VizObjectRef (equivalent to viz.new(...)), making the pre-create + update animation pattern concise.
  • Visualizer docs restructured around use cases — added use-cases-scripts.md, use-cases-notebooks.md, and app.md, a use-case routing list above the topics table in docs/py/viz/index.md, and an export/ subsection (index.md, html.md, gltf.md, video-image.md) replacing the single export.md; docs/py/index.md now covers only the geometric algebra part and links to the Visualization section.
  • Jupyter notebook and multi-scene examples — added interactive.ipynb, animation.ipynb, and export.ipynb under py/examples/jupyter/, plus a py/examples/viz/demo_multi_scene.py script showing two named scenes in separate browser tabs.
  • Scene title in the browser tab — the scene title now also sets the browser tab title (in addition to the viewport overlay), truncated to 40 characters with an ellipsis when longer.
  • Browser-triggered full-server stop key — a new, opt-in per-scene binding (default Ctrl+Q) ends wait() and every animate() loop by setting the global shutdown event, mirroring a terminal Ctrl+C. Enabled via enable_server_stop_key(), the Visualizer(enable_server_stop_key=True) flag (main scene), or viz.scene("name", enable_server_stop_key=True); VizSceneHandle.enable_server_stop_key() scopes it per named scene. Disabled by default.
  • Operator visualizations reworked — GeneralRotor now renders with the same rotor visualization as Rotor (disc arc, torus, and axis line), displaced to its origin, and Motor renders a displaced rotation (general rotor) plus a translation arrow along the screw axis.

Breaking Changes

  • animate() no longer opens the viewer — Visualizer.animate() (and VizSceneHandle.animate()) previously opened a browser tab in scripts; it now only starts the server (headless) and never displays. Call show() first (or use with viz:) to open the viewer, then drive the loop with animate(). show() is now the single display entry point.
  • meet()/join() inverted for BasisPGA2/BasisPGA3 to match Gunn/Dorst — in the plane-based PGA models, MV.meet() is now the intersection (outer product) and MV.join() the union (regressive product); all other algebras (E2/E3/P2/P3/N2/N3) keep the previous semantics. Code that used a.meet(b) on PGA to join two points must switch to a.join(b). The outer/inner products (^, |) are unchanged. Incidence in PGA2/3 is now documented (A.dual() ^ B.dual() == 0; see docs/py/basis/basis_pga3.md and basis_pga2.md).
  • BasisPGA2/BasisPGA3 rename e0_inv to e0_recip — the null-vector reciprocal attribute e0_inv is renamed to e0_recip to make clear it is the reciprocal of e0 (satisfying e0 · e0_recip = 1), not its inverse.

Bug Fixes

  • display() hints when the server is not running in Jupyter — Visualizer.display() and VizSceneHandle.display() now print a hint to call start_server() (and skip the iframe) when invoked in a Jupyter notebook before the server is running, instead of rendering an empty frame.
  • PGA3 line offset sign — create_pga3._line_opns now builds its two defining planes with d = -(n·origin) (matching _plane_opns), and analysis_pga3._line_origin_from_planes drops the compensating double negation, so lines analyze to the correct side of the origin on both the create → analyze round-trip and the join → analyze path.
  • PGA meet/join use the outer product (not the span) — the PGA meet/join now follow Gunn/Dorst exactly: meet is the outer product (intersection) and join is the regressive product ⋆(⋆A ∧ ⋆B), so the join of incident elements vanishes (e.g. point.join(line) is zero when the point lies on the line).
  • Infinite lines render centered on their closest point — the serializer now emits the start point (closest − d̂·length/2) for infinite lines, so the line is drawn centered on the point of the line closest to the origin instead of stretching one-directionally from it. glTF lines now also honor their direction and are centered consistently. Planes were already centered.
  • Animation restarts after a browser q stop — animate() now clears the scene's per-scene interrupt event at the start of each loop, so re-running a cell after pressing the q stop key starts a fresh animation instead of immediately ending. The global Ctrl+C/SIGTERM shutdown is unaffected.
  • Per-scene titles no longer leak across scenes — SceneConfig.to_dict() now includes the scene name as a scene field, matching the scene_update/object_update message shape. Previously a broadcast title update (e.g. detail.set_title(...)) was applied by every open browser tab, overwriting the title of the main/other scenes; the frontend now filters scene_config messages per scene.
  • Three visualization pages were missing from the nav — texture-labels.md, object-interaction.md, and the active-elements/ folder were on disk but absent from the mkdocs.yml nav; the restructured Visualization nav now includes them.
  • Plane/space/reflection-plane extent style overrides had no effect — the serializer always emitted the builtin default as a flat extent field, and the scene-graph serializer mirrored that flat value back into the style, overwriting a per-call PlaneStyle(extent=...) (and SpaceStyle / ReflectionPlaneStyle). The Python side now resolves the extent up front (entity → style → canonical default) and the renderers read the resolved content field, matching the line-length behaviour.

Refactor

  • Visualization docs restructured into topic folders — the flat docs/py/viz/ pages were reorganized into scene-objects/, visualizer/, visualizerapp/, jupyter/, and styles/ (plus the existing export/), with new section overview pages, a compiled entities.ipynb notebook of entity styles with embedded static viewers, a dedicated operators.md, and the jupyter.md page split into live (markdown) and static (notebook) guides. mkdocs-jupyter was added to compile the notebooks at build time.
  • Motor normalized to a screw form — Motor now stores a GeneralRotor (rotation about a displaced axis) plus an axial Translator, computed on construction from the rotation angle/axis and the translation vector with pure vector arithmetic (u = t_∥, v = ½(t_⊥ + cot(φ/2)·â×t_⊥)). analyze_operator, create_motor, the serializer, the renderer, and _transforms all use this form, with the derivation documented in motor-decomposition.md.