Changes since version 0.10.0¶
New Features¶
- Scene-graph group containers (
VizGroup) — create parent/child node hierarchies and render groups as emptyTHREE.Groupnodes in the live and static viewers. VizObjectRefconvenience 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 derivedLineStylethat rendersLineentities as solid 3D cylinders whosethicknessis a world-unit radius, alongside the default screen-spaceLine2fat-line rendering. Example:demo_act_point.py.contentaspect for in-place entity updates — replacing an entity's geometry (same kind) now emits acontentpatch 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 byalong(a scalar or 2-/3-tuple of fractions along the entity's extent): e.g.0.5places 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 at0.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 viavalue_style, while the axis name label (Axis.label, e.g."X") is styled vialabel_styleand 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 targetfps(default 60;fps=0disables pacing), and guarantees a cleanstop()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-copyableVizStylesholder.viz.stylestargets the main scene (mutations take effect),viz.global_stylesis the master template that new scenes copy from, andviz.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-basedtransform(Rotor/Motor/Translator/Dilator) with aspect-scopedfull/style/transformupdates 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.pyconvertsRotor,GeneralRotor,Motor,Translator,Dilator,Point, andDirectionto 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 byopen_snapshot(), which opens a full-page static snapshot of the current scene.Visualizer(port=..., host=...)is deprecated — pass them tostart_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, anddefault_tex_label_styleare gone; useviz.styles.kind/viz.styles.label_base/viz.styles.label_kind/viz.styles.annotation/viz.styles.act_point/viz.styles.tex_label_kindinstead.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). Axislabel parameters renamed and split —AxisStyle.label_stylenow styles the axis name label (previously it styled the value labels, which move to the newAxisStyle.value_style);Axis.label_at_major→Axis.show_value_labels;Axis.label_format→Axis.value_format;Axis.label_sizeremoved (useAxisStyle.value_stylewithfont_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.htmland the JS modules) are now served withCache-Control: no-cacheso 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; passport=...to override orport=0to auto-pick. - Server binds to both IPv4 and IPv6 loopback — previously it bound only to
127.0.0.1, so on Windows (where Firefox resolveslocalhostto::1for WebSocket connections) the upgrade never reached the handler and the connection hung inCONNECTING. - WebSocket shutdown is now graceful —
Visualizer.stop()sends a clean1001close frame before cancelling handler tasks (previously clients saw an abnormal1006reset), and anatexithook shuts the server down cleanly even if the script forgets to callstop(). - Unicode-safe console/log output on Windows — the "✓ Browser connected"
message raised
UnicodeEncodeErroron 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. Linesegments respectfrom_pointslength again — the scene-graph node serializer no longer lets the canonicalLineStyle.lengthdefault (20.0) clobber the explicit segment length.Linerendered as a screen-space fat line — the line renderer now uses the three.jsLine2fat-line path with a pixel-basedthicknessdefault of1, instead of a world-unit cylinder radius.- Labels parent correctly on full-state sync — the
scene_update/object_updatehandlers now await each object in order, so overlay labels find theirattach_toscene node instead of falling back to the origin.
Refactor¶
- Node hierarchy replaces the flat registry —
VizSceneObject/VizOverlayObject/VizGroupbecome the authoritative source of truth, with serialization moved into nodeserialize()and updates emitted asobject_updateaspect patches. - Per-scene style snapshots — default styles moved into a copyable
VizStyleDefaultsholder 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=Nonereturns a string), andexport_glb()for glTF. - Animated export via
animation=—export_snapshot(..., animation=rec)andexport_figure(..., animation=rec)replaceexport_animated_html()/export_animated_figure(); recording starts viastart_animation_recording(). SceneExporterdeprecated — export methods now live onVisualizer/VizSceneHandle.display_row(mode="static")— stack serverless snapshots side by side in Jupyter.Linelength is a content field — a line's explicit length (fromLine.from_points) is serialized as the contentlength, with0marking an infinite line; the frontend falls back toLineStyle.lengthonly when the content length is0.- 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.jsmodule, so node-transform wrapping,parent_idparenting, and labelattach_tohandling are implemented once. Exporters now consumeScene.full_state()directly, and the legacy_serialize_labels()path is removed. - Single frontend registry (
sceneObjects) — removed the legacyentityMeshes/entityData/labelObjectsmaps and the old flat-entity path, so all updates flow through the aspect-scopedobject_updatehandler 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.