Skip to main content

Lightweight Three.js viewer controlled from Python via WebSocket

Project description

threejs-viewer

Lightweight Three.js viewer controlled from Python via WebSocket.

Toolpath demo

A Python client runs a WebSocket server that a browser-based Three.js viewer connects to. Designed for robotics visualization, scientific computing, and interactive 3D exploration.

VS Code integration

Features

  • Simple API: Add primitives, load models, update transforms
  • PBR materials: Roughness, metalness, transparency and environment reflections on all objects
  • GLB/PBR support: Load GLB models with PBR materials, embedded skeletal/morph animations
  • Animation support: Pre-compute animations, scrub timeline, adjust playback speed
  • Binary channels: Efficient transfer of large animations (100k+ objects × frames)
  • Toolpath visualization: Extruded bead tubes with per-point colors, draw_range animation, and automatic LOD — handles 1M+ point toolpaths at 60 fps with smooth camera-distance-adaptive simplification via Web Worker
  • Auto-reconnect: Browser reconnects automatically, animations persist
  • Z-up coordinates: Robotics convention (matches ROS, URDF)
  • No build step: Self-contained HTML viewer, just open in browser

Installation

pip install threejs-viewer

Quick Start

from threejs_viewer import viewer

# Start server and wait for browser to connect
v = viewer()

# Add objects
v.add_sphere("ball", radius=0.3, color=0xFF0000, position=[0, 0, 0.5])
v.add_box("ground", width=5, height=5, depth=0.1, color=0x444444)

# Keep running
input("Press Enter to exit")

The viewer opens automatically in your default browser. To open it manually:

threejs-viewer open
# Or: threejs-viewer path  (prints path to viewer.html)

Usage

Objects

from threejs_viewer import Toolpath, viewer

# Primitives with PBR materials
v.add_box("box1", width=1, height=2, depth=0.5, color=0x4A90D9, roughness=0.5, metalness=0.1)
v.add_sphere("ball", radius=0.5, position=[2, 0, 0], roughness=0.3, metalness=0.7)
v.add_cylinder("cyl1", radius_top=0.3, radius_bottom=0.5, height=1)

# 3D models (binary transfer)
v.add_model_binary("robot", "robot.stl", format="stl")

# Polylines with colormaps
v.add_polyline("path", points, colors=z_values, colormap="viridis", line_width=3)

# Bead toolpath (parametric tube, built client-side)
tp = Toolpath.from_points(points, bead_width=0.3, bead_height=0.08)
tp.colorize(per_point_rgb)
v.add_toolpath("bead", tp)

# Transparency
v.set_opacity("box1", 0.5)
v.set_color("ball", 0xFF0000, opacity=0.3)

Transforms

# Single object
v.set_matrix("box1", matrix_4x4.flatten().tolist())

# Batch update (efficient for 60fps)
v.batch_update({
    "link1": {"position": [1, 2, 0.5]},
    "link2": {"position": [3, 0, 1], "rotation": [0, 0, 1.57]},
})

Picking points on lines & beads

# Click a point anywhere along any polyline or parametric tube; the pick
# travels back to Python. Hovering glides a marker to the closest point.
def on_pick(pick):
    print(f"{pick['kind']} {pick['id']}: {pick['fraction']:.1%} along  at {pick['point']}")

v.on_polyline_pick(on_pick)   # also enables picking

Each click sends {id, kind, fraction, point, local_point, segment, t} where kind is "line" or "tube". Movement is continuous — the marker never snaps to vertices. Picking is opt-out per object — pass pickable=False to add_polyline / add_parametric_tube to exclude one from picking (it stays rendered); adding objects costs nothing when picking is never enabled. For a tube, segment indexes the full-resolution spine 1:1 with the per-point arrays you built it from, so it doubles as a lookup key for other per-point data at the picked point. A browser embedder can subscribe in JS instead — viewer.onPolylinePick(cb) (click) / viewer.onPolylineHover(cb) (every hover move) — for a live readout with no Python round-trip. See examples/22_polyline_picking.py.

Animations

from threejs_viewer import Animation

animation = Animation(loop=True)
for t in times:
    animation.add_frame(
        time=t,
        transforms=compute_transforms(t),
        colors={"robot": 0xFF0000 if collision else 0x00FF00},
        clip_times={"glb_model": t},  # drive embedded GLTF animations
    )
animation.add_marker(3.5, "Collision detected")

client.load_animation(animation)

Viewer controls: Space (play/pause), Arrow keys (step frames), 1-5 (speed), L (loop)

Binary Channels (Large Animations)

For animations with many objects or frames, use binary channels instead of Frame dicts for much faster serialization and transfer:

import numpy as np
from threejs_viewer import Animation

animation = Animation(loop=True)
animation.set_frame_times(np.arange(n_frames) / 60.0)

# Transforms: (n_frames, n_objects, 16) float32
animation.set_transform_data(object_ids, transform_array)

# Draw ranges: (n_frames, n_objects) float32
animation.set_draw_range_data(object_ids, draw_range_array)

# Clip times for embedded GLTF animations: (n_frames, n_objects) float32
animation.set_clip_time_data(object_ids, clip_time_array)

# Colors with indexed colormap: (n_frames, n_objects) uint8
animation.add_channel("colors", object_ids, color_indices, dtype="uint8",
                       metadata={"colormap": [0x44AA44, 0xFF3333]})

# Visibility: (n_frames, n_objects) uint8 (0=hidden, 1=visible)
animation.add_channel("visibility", object_ids, vis_data, dtype="uint8")

client.load_animation(animation)

Binary channels and Frame-based JSON can be mixed. A binary channel supersedes the same-named Frame field.

GLB Models with Embedded Animations

# Load a GLB with embedded animations (skeletal, morph targets)
client.add_model_binary("fox", "fox.glb", format="glb")

# Seek embedded animation to a specific time (seconds)
client.set_clip_time("fox", 1.5)

Documentation

CLI

threejs-viewer path    # Print path to viewer.html
threejs-viewer open    # Open in default browser
threejs-viewer code    # Open in VS Code (use "Show Preview" for docked view)

License

MIT

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

threejs_viewer-0.0.38.tar.gz (307.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

threejs_viewer-0.0.38-py3-none-any.whl (309.5 kB view details)

Uploaded Python 3

File details

Details for the file threejs_viewer-0.0.38.tar.gz.

File metadata

  • Download URL: threejs_viewer-0.0.38.tar.gz
  • Upload date:
  • Size: 307.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for threejs_viewer-0.0.38.tar.gz
Algorithm Hash digest
SHA256 a509872cb04ab0fa7db741157a918fdc1f31914a1860683ea525a523711586e4
MD5 4c2d4eb4977bf727dc91191a5cf965fb
BLAKE2b-256 9408db2f9720c6098204ed708e8b6878787b62232a7f22dffb421cdf29629b99

See more details on using hashes here.

Provenance

The following attestation bundles were made for threejs_viewer-0.0.38.tar.gz:

Publisher: build-and-release.yml on CEAD-group/threejs-viewer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file threejs_viewer-0.0.38-py3-none-any.whl.

File metadata

  • Download URL: threejs_viewer-0.0.38-py3-none-any.whl
  • Upload date:
  • Size: 309.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for threejs_viewer-0.0.38-py3-none-any.whl
Algorithm Hash digest
SHA256 d1d55992aa25be47c744b0b49af96b1d6114029601b2e9d1db54cbfc46a24dec
MD5 58a6b4344396da85034d28c3eb0c49ff
BLAKE2b-256 9c3c4c2e1bf7cf37326235014ebd146e079d585816180733f69ea208b76635f0

See more details on using hashes here.

Provenance

The following attestation bundles were made for threejs_viewer-0.0.38-py3-none-any.whl:

Publisher: build-and-release.yml on CEAD-group/threejs-viewer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page