Skip to content

Changes since version 0.10.0

New Features

  • Scene-graph group containers (VizGroup) — create parent/child node hierarchies and render groups as empty THREE.Group nodes in the live and static viewers.
  • VizObjectRef convenience wrapper — mutate nodes without tracking raw IDs: entity, style, color, opacity, texture_label, label access (label_ids, labels, update_label), and transform mutators.
  • CylinderLineStyle — a derived LineStyle that renders Line entities as solid 3D cylinders whose thickness is a world-unit radius, alongside the default screen-space Line2 fat-line rendering. Example: demo_act_point.py.
  • content aspect for in-place entity updates — replacing an entity's geometry (same kind) now emits a content patch that mutates the inner three.js mesh in place, preserving the node's transform, parent, style, and id mapping instead of a full destroy-and-rebuild. Examples: demo_animation_orbit.py, demo_point_path_trail.py (ref.entity = ...).
  • Per-entity label anchors (LabelStyle.along) — labels can now be anchored at a per-entity position parameterized by along (a scalar or 2-/3-tuple of fractions along the entity's extent): e.g. 0.5 places a line's label at its segment midpoint. Line length is resolved on the backend so the frontend always receives a valid value. Examples: demo_nested_groups.py, demo_double_pendulum.py (line labels at 0.5).
  • Screen-plane label rotation (LabelStyle.rotation) — labels (including coordinate-axis tick labels) can be rotated in the screen plane about their anchor, so longer labels don't overlap. Examples: demo_labels.py (entity labels), demo_axes_custom.py (axis tick labels).
  • Per-axis name and value label styles (AxisStyle.label_style / value_style) — the numeric value labels of an axis are now styled via value_style, while the axis name label (Axis.label, e.g. "X") is styled via label_style and is anchored at the axis midpoint, hanging below it by default (along=0.5, align=(0.5, 0), offset_2d=(0, 10)). Example: demo_axes_custom.py.
  • Visualizer.animate() frame loop — a generator that yields once per animation frame until Ctrl+C, paces the loop to a target fps (default 60; fps=0 disables pacing), and guarantees a clean stop() when the loop ends. The recommended way to drive scripted animations. Examples: demo_animation_orbit.py, demo_scene_graph.py.
  • Unified style holder (VizStyles) — all seven changeable default-style bundles now live in one deep-copyable VizStyles holder. viz.styles targets the main scene (mutations take effect), viz.global_styles is the master template that new scenes copy from, and viz.styles[Point] = PointStyle(...) is sugar for the per-kind entity/operator default. Examples: demo_custom_defaults.py, demo_act_point.py.
  • Per-object transforms — translate, rotate, scale_by, set_transform, and operator-based transform (Rotor/Motor/ Translator/Dilator) with aspect-scoped full/style/transform updates for cheap, in-place group animation.
  • Overlay attach_to — first-class overlay nodes (VizOverlayObject) that live-follow a referenced scene node in the CSS plane.
  • Operator/entity → matrix/TRS helpers — _transforms.py converts Rotor, GeneralRotor, Motor, Translator, Dilator, Point, and Direction to 4×4 matrices and TRS tuples.

  • Frontend version check — the browser viewer now verifies on every WebSocket connect that it is running the same frontend build as the backend (a content hash of the served templates) and prompts for a hard reload when a stale, cached copy is detected.

Breaking Changes

  • open_figure() → open_snapshot() — the standalone figure-window preview is replaced by open_snapshot(), which opens a full-page static snapshot of the current scene.
  • Visualizer(port=..., host=...) is deprecated — pass them to start_server(host=..., port=...) instead.
  • Removed the default_* style API — Visualizer.default_styles, default_label_style, default_label_styles, default_annotation_style, default_act_point_style, and default_tex_label_style are gone; use viz.styles.kind / viz.styles.label_base / viz.styles.label_kind / viz.styles.annotation / viz.styles.act_point / viz.styles.tex_label_kind instead. set_default_color() now targets the main scene's defaults. Examples: demo_custom_defaults.py (viz.styles / set_default_color), demo_camera_2d.py (viz.styles.kind.merge).
  • Axis label parameters renamed and split — AxisStyle.label_style now styles the axis name label (previously it styled the value labels, which move to the new AxisStyle.value_style); Axis.label_at_major → Axis.show_value_labels; Axis.label_format → Axis.value_format; Axis.label_size removed (use AxisStyle.value_style with font_size).

Bug Fixes

  • display_static() / display_snapshot() inline rendering — the static snapshot now renders inside an <iframe srcdoc> in Jupyter instead of injecting a full HTML document inline, which leaked global styles into the notebook page and could crash the kernel.
  • Stale cached viewer — frontend assets (viewer.html and the JS modules) are now served with Cache-Control: no-cache so browsers revalidate instead of keeping out-of-date copies.
  • Reconnection-friendly default port — start_server() / show() now default to port 8765 instead of auto-picking a new free port each time, so an already-open browser tab can reconnect after a server restart; pass port=... to override or port=0 to auto-pick.
  • Server binds to both IPv4 and IPv6 loopback — previously it bound only to 127.0.0.1, so on Windows (where Firefox resolves localhost to ::1 for WebSocket connections) the upgrade never reached the handler and the connection hung in CONNECTING.
  • WebSocket shutdown is now graceful — Visualizer.stop() sends a clean 1001 close frame before cancelling handler tasks (previously clients saw an abnormal 1006 reset), and an atexit hook shuts the server down cleanly even if the script forgets to call stop().
  • Unicode-safe console/log output on Windows — the "✓ Browser connected" message raised UnicodeEncodeError on cp1252 consoles (the default Windows console), crashing the WebSocket handler and dropping every connection; console printing now falls back gracefully and the →/—/… glyphs were replaced with ASCII so they are never lost in non-UTF-8 consoles or log files.
  • Line segments respect from_points length again — the scene-graph node serializer no longer lets the canonical LineStyle.length default (20.0) clobber the explicit segment length.
  • Line rendered as a screen-space fat line — the line renderer now uses the three.js Line2 fat-line path with a pixel-based thickness default of 1, instead of a world-unit cylinder radius.
  • Labels parent correctly on full-state sync — the scene_update / object_update handlers now await each object in order, so overlay labels find their attach_to scene node instead of falling back to the origin.

Refactor

  • Node hierarchy replaces the flat registry — VizSceneObject / VizOverlayObject / VizGroup become the authoritative source of truth, with serialization moved into node serialize() and updates emitted as object_update aspect patches.
  • Per-scene style snapshots — default styles moved into a copyable VizStyleDefaults holder snapshotted at scene creation.
  • Consolidated serving lifecycle — show() (serve + open a browser), wait() (block until Ctrl+C), start_server() / stop_server() / open_browser(). start() / stop() / run() remain as deprecated aliases.
  • Consolidated export API — export_snapshot() / open_snapshot() / display_snapshot() for static full-page snapshots, export_figure() (sized snippet; path=None returns a string), and export_glb() for glTF.
  • Animated export via animation= — export_snapshot(..., animation=rec) and export_figure(..., animation=rec) replace export_animated_html() / export_animated_figure(); recording starts via start_animation_recording().
  • SceneExporter deprecated — export methods now live on Visualizer / VizSceneHandle.
  • display_row(mode="static") — stack serverless snapshots side by side in Jupyter.
  • Line length is a content field — a line's explicit length (from Line.from_points) is serialized as the content length, with 0 marking an infinite line; the frontend falls back to LineStyle.length only when the content length is 0.
  • Unified live/export render pipeline — the live viewer and the static/animated HTML export now build the three.js scene through one shared scene-builder.js module, so node-transform wrapping, parent_id parenting, and label attach_to handling are implemented once. Exporters now consume Scene.full_state() directly, and the legacy _serialize_labels() path is removed.
  • Single frontend registry (sceneObjects) — removed the legacy entityMeshes/entityData/labelObjects maps and the old flat-entity path, so all updates flow through the aspect-scoped object_update handler against one {obj, mesh, data, layer, el?} registry.
  • WebSocket connection/reconnection tracing — restored detailed frontend console logging (connect/watchdog/open/close/reconnect/teardown transitions plus every sent and received message) and extended backend logging, so the browser transcript can be correlated with the server log to locate reconnect hangs.

Changed

  • Auto-reconnect is a fixed 2s interval for 60s, then stops — replaced the exponential backoff (1s → 30s) with a constant retry every 2 seconds for the first minute after a connection is lost, after which automatic reconnection stops entirely. Manual reconnection via the "Reconnect" button (and tab visibility) still works and restarts a fresh 60s window.