Skip to main content

threepp for Python

threepp is a cross-platform C++ 3D library with the high-level API of three.js. This package exposes that high-level API to Python through a single pybind11 extension module, threepp.

You get the three.js scene graph — Scene, Mesh, geometries, materials, cameras, lights, OrbitControls, an animation loop — plus headless render-to-numpy for ML data generation, robotics cameras and machine vision.

import threepp as tp

scene = tp.Scene()
scene.add(tp.Mesh(tp.BoxGeometry(), tp.MeshStandardMaterial()))
scene.add(tp.AmbientLight())

camera = tp.PerspectiveCamera(75, 1.0, 0.1, 100)
camera.position.z = 5

canvas = tp.Canvas("demo")
renderer = tp.GLRenderer(canvas)
canvas.animate(lambda: renderer.render(scene, camera))

Install

Status: pre-1.0 / alpha. This is a young package and the API is still moving — it may change between releases. Releases are dated (a YYYY-MM-DD tag builds a YYYY.MM.DD wheel); pin the exact release you tested and check the release notes before upgrading.

Prebuilt wheels (Windows / Linux, CPython 3.10–3.14) are on PyPI — no build tools or system libraries needed:

pip install threepp

They ship the GL renderer, CPU PhysX physics (PhysxWorld, articulations, proprioceptive sensors — tp.HAS_PHYSX == True) and the Vulkan deferred renderer with its G-buffer AOVs (tp.HAS_VULKAN == True). Vulkan needs a Vulkan-capable GPU driver at runtime — on machines without one the wheel still imports and renders GL, and tp.vulkan_available() tells you which world you're in. macOS has no prebuilt wheel — pip install builds it from source there (GL-only; a C++ compiler and CMake are all it needs).

The scene editor is its own package — author physics-ready scenes, robots and sensors visually, then drive them from Python:

pip install threepp-editor      # or: pip install threepp[editor]  (Windows)
threepp-editor

(threepp[full] = [rl,editor]. The editor ships Windows wheels only; the extra is a no-op elsewhere, and pip install threepp-editor states the platform requirement honestly.)

Or build from source (needs a C++ compiler + CMake ≥ 3.21; pybind11 is fetched automatically):

pip install "git+https://github.com/markaren/threepp"

Physics in the wheel is CPU-only — the GPU runtime (PhysXGpu_64.dll) is 238 MB and stays out. Everything PhysxWorld does by default works. The GPU paths need the two GPU DLLs (PhysXGpu_64.dll + PhysXDevice64.dll) dropped next to the installed module (they are loaded lazily by name, never linked): that alone enables PhysxWorld(gpu_dynamics=True) and the direct-GPU RL batch (verified). Soft bodies additionally need a Python API the wheel does not bind yet — today they are an editor capability.

Build

The module is built as part of threepp's CMake project, gated behind THREEPP_WITH_PYTHON. pybind11 is fetched automatically; the host Python is auto-detected (override with -DPython_EXECUTABLE=...).

cmake -S . -B build -DTHREEPP_WITH_PYTHON=ON \
      -DTHREEPP_BUILD_EXAMPLES=OFF -DTHREEPP_BUILD_TESTS=OFF
cmake --build build --target threepp_py

(On Windows, CMake defaults to the multi-config Visual Studio generator; the build works under it, but -G Ninja gives a faster single-config build.)

The built module (threepp.*.pyd / threepp.*.so) is placed in this python/ directory, so scripts here can import threepp. The examples add this directory to sys.path automatically, so they run from anywhere.

This is a GL-only build — it needs no Vulkan SDK, PhysX or CUDA, and works on any laptop. Off-screen rendering uses a hidden GLFW window (a real GL context), so a display is only required for the on-screen examples.

Examples

Script What it shows
examples/hello_cube.py On-screen window: spinning, lit cubes you can orbit (OrbitControls + animation loop). Needs a display.
examples/pbr_showcase.py Photoreal PBR playground — metals + glowing gems under a procedurally generated HDR sky (no assets), ACES tone mapping, live ImGui controls. --shot out.png renders headless.
examples/text_overlay.py Text & SVG overlay — billboard TextSprite labels on 3D objects + a 2D HUD (Text2D + SVGLoader badge) via an OrthographicCamera overlay pass.
examples/headless_render.py Off-screen render straight into a (H, W, 3) uint8 numpy array; saves a PNG. No window.
examples/textured_box.py Load an image with TextureLoader and map it onto a mesh (headless).
examples/load_model.py python load_model.py model.glb — load a model with ModelLoader, auto-frame and render it.
examples/vulkan_aovs.py Vulkan deferred render → G-buffer AOVs (normals / segmentation / albedo / depth) as numpy. Needs a Vulkan build.
examples/vulkan_ocean.py The FFT-displaced Ocean — fancy water (waves, foam, transmission) in one line; orbit around it live. Needs a Vulkan build + display.
examples/ui_demo.py In-window Dear ImGui control panel (sliders/buttons) driving the scene live (GL). Needs a display.
examples/vulkan_ui.py The same ImGui control panel, over the Vulkan deferred renderer. Needs a Vulkan build + display.
examples/physics_demo.py A pile of boxes tumbling onto the floor — PhysxWorld rigid bodies driving the scene graph. Needs a PhysX build + display.
examples/imu_demo.py Headless IMU — a box dropped onto the floor with an Imu mounted off-CoM; prints the physics-truth table (free fall ~0, at rest ~+9.81). Needs a PhysX build; no display.
examples/spider/spider_demo.py Drive a physics hexapod with WASD — an articulated robot walking via a CPG tripod gait (no training). Needs a PhysX build + display.
examples/spider/train_vec.py / play.py Residual RL on the gait. train_vec.py runs K robots in one PhysX scene (one world.step() for all of them, ~7–35× faster than separate processes); play.py drives the trained policy with WASD. train.py is the slower SubprocVecEnv variant. Needs gymnasium, stable-baselines3.
examples/smoke_test.py Assertion-based regression test of the whole surface; prints ALL OK.
cd python
python examples/headless_render.py

What's exposed

  • Math (mutable value types): Vector2/3/4, Color, Euler, Quaternion, Matrix3/4, Box3. A hex int (0xff0000) or CSS/name string converts implicitly to a Color, so material.color = 0xff0000 just works.

  • Scene graph: Object3D, Scene, Group, Mesh, InstancedMesh, Points, Line, LineSegments, Sprite. obj.position.x = 1 mutates in place, exactly like three.js; scene.add(a, b, c), traverse, get_object_by_name, children all work.

  • Geometries: Box, Sphere, Plane, Cylinder, Cone, Capsule, Torus, TorusKnot, Circle, Ring, Icosahedron, Octahedron.

  • Materials: MeshStandard, MeshPhong, MeshLambert, MeshBasic, MeshNormal, Points, LineBasic, Sprite, Shadow — concrete fields, the shared base fields (opacity, transparent, side, …), and texture-map slots (map, normal_map, roughness_map, metalness_map, emissive_map, ao_map, alpha_map, …).

  • Cameras: PerspectiveCamera, OrthographicCamera.

  • Lights: Ambient, Directional, Point, Spot, Hemisphere, RectArea.

  • Textures: Texture, TextureLoader (load(path, color_space=...)), with TextureWrapping / Filter / ColorSpace enums.

  • Model loaders: ModelLoader (load(path)Group, dispatches by extension: .obj / .gltf / .glb / .stl / .dae), plus OBJLoader, STLLoader, and GLTFLoader (load(path)GLTFResult with .scene and .animations). All first-party — no Assimp/FBX/USD needed.

  • Animation: AnimationMixer, AnimationClip, AnimationAction, the KeyframeTrack family (Vector/Quaternion/Number/Color), and the Loop / AnimationBlendMode / Interpolation enums — three.js' animation system. Play clips loaded from glTF, or build them procedurally:

    result = tp.GLTFLoader().load("model.glb")
    scene.add(result.scene)
    mixer = tp.AnimationMixer(result.scene)
    mixer.clip_action(result.animations[0]).set_loop(tp.Loop.REPEAT).play()
    
    clock = tp.Clock()
    canvas.animate(lambda: (mixer.update(clock.get_delta()),
                            renderer.render(scene, camera)))
    
  • Rendering: Canvas (window / headless), GLRenderer (render, set_clear_color, read_pixels → numpy, save_frame, shadows, tone_mapping / tone_mapping_exposure), OrbitControls, Clock.

  • HDR image-based lighting: RGBELoader().load("env.hdr") → an equirect Texture; assign it to scene.environment (IBL on standard/physical materials) or scene.background. ToneMapping.ACESFilmic (etc.) keeps HDR highlights from clipping.

  • Camera intrinsics / extrinsics: camera.projection_matrix, matrix_world_inverse, obj.matrix_world, get_world_quaternion, Matrix4.to_numpy() (→ (4, 4)), and Vector3.project(camera) / unproject for 3D↔2D — the building blocks for 6-DoF pose ground truth and 2D annotation.

  • URDF robots: URDFLoader().load("robot.urdf") → a Robot (an Object3D) with set_joint_value(s), get_joint_range(s), num_dof, and forward kinematics (get_end_effector_transform() / compute_end_effector_transform).

  • 2D text & SVG: FontLoader().default_font() (embedded — no font file), Text2D / Text3D (text meshes), TextSprite (billboard labels), and SVGLoader().parse(...) → a Group of filled meshes. Pair with an OrthographicCamera + auto_clear=False for a HUD.

  • In-window UI: ImguiContext + the threepp.imgui submodule — Dear ImGui immediate-mode widgets (window/text/button/slider/checkbox/color/combo/…) for control panels. Works on both the GL and Vulkan renderers (tp.HAS_IMGUI).

  • 3D Gaussian Splatting: SplatLoader.load_ply() reads the .ply files 3DGS optimisers emit (any SH degree, header-driven) and SplatCloud puts the result in the scene graph like any mesh — rendered by both the GL and Vulkan backends. is_splat_ply() discriminates splat files from mesh PLYs; submit_ranges exposes the chunk-LOD/culling mechanism (Vulkan).

  • Rigid-body physics (when built with PhysX, see below): PhysxWorld + RigidBody — add Meshes as dynamic/static bodies (box/sphere/capsule, convex hull, or triangle mesh), step(dt), and the bound meshes follow the simulation (tp.HAS_PHYSX). Reduced-coordinate Articulations (URDF robots with joints, limits and drives) are bound too.

  • Proprioceptive sensors (PhysX builds): Imu (gyroscope + accelerometer), JointEncoder, ContactSensor and ForceTorqueSensor — attached to scene nodes, driven from the physics step loop, with configurable seeded NoiseModels. See Proprioceptive sensors below.

  • Vulkan deferred renderer + G-buffer AOVs (when built with Vulkan, see below): VulkanRenderer.render_aov(scene, camera, aov) returns a deferred G-buffer attachment as (H, W, 3) uint8 — 'rgb', 'normals', 'segmentation' (per-instance ids), 'albedo', 'motion' — and the lossless typed family reads the native attachments: read_depth (metric (H, W) float32), read_instance_ids (raw uint32 ids), read_normals_float, read_motion, read_aovs_typed. This is the "labels for free" path for synthetic-data generation.

  • FFT ocean (renders under the Vulkan backend): tp.Ocean(size=1000.0) is a ready-made 3-cascade Phillips/FFT-displaced water surface — waves, foam, and transmission. Add it to a scene and render. Tune ocean.params (wind / cascades / choppiness), pack vertex density toward any focus with warp_toward(x, z), add add_foam_disturbance(...) splats, and read the wave height on the CPU with sample_height(x, z) (lazy opt-in: the first call enables the GPU->CPU mirror and may return 0 — values are live from the next rendered frame). The low-level tp.DisplacedMesh (own geometry + material) is exposed too. Not tied to any "hero" object — see examples/vulkan_ocean.py.

Naming follows Python conventions (snake_case methods/properties), e.g. camera.update_projection_matrix(), renderer.set_clear_color(...).

Tests

A pytest suite under tests/ covers the whole surface — math, scene graph, geometries, materials, cameras, lights, textures, loaders and headless rendering — including regression tests that pin the pybind11 virtual-base workaround (they crash the interpreter if it ever regresses).

pip install pytest numpy pillow
cd python
pytest

examples/smoke_test.py is a dependency-light standalone alternative that prints ALL OK.

Type stubs (IDE autocomplete)

Stubs ship alongside the module as a PEP 561 stub package, mirroring the native module's own structure:

threepp/py.typed                 # PEP 561 marker (makes the types count once installed)
threepp/threepp/__init__.pyi     # the threepp.threepp extension module
threepp/threepp/imgui.pyi        # the threepp.threepp.imgui submodule
threepp/threepp/editor.pyi       # the threepp.threepp.editor submodule — HAND-MAINTAINED

This replaces the old flat threepp/threepp.pyi, which could not describe the imgui submodule. The threepp/threepp/ directory sits next to threepp.<abi>.pyd / .so but does not shadow it at import time: it has no __init__.py, so it is only a namespace-package candidate, and Python's import machinery prefers the extension-module loader within the same directory.

Regenerate after changing the bindings:

pip install -r python/requirements-stubs.txt
cmake --build build --target threepp_stubs

Or, once the module is built into the package dir, standalone:

python python/scripts/gen_stubs.py

pybind11-stubgen is pinned in requirements-stubs.txt because its output layout and formatting change between releases — an unpinned upgrade is what silently reshaped the stubs from a flat file into this package. Bump the pin deliberately, regenerate, and review the diff.

scripts/gen_stubs.py wraps the generator so the result is reproducible: it fixes the flags, repairs any keyword-named binding (see below), fails if the emitted stub does not parse, and fails if regeneration dropped symbols the committed stubs declare. The bare pybind11-stubgen CLI exits 0 even when it writes a stub no type checker can read, which is how the previous stubs went stale unnoticed.

Regenerate from a full-featured build

The stubs describe whatever the built module exposes, so regenerate from a build with the same features as the last one — Vulkan, PhysX, FSR. A default or GL-only rebuild drops VulkanRenderer, the PhysX world and every sensor binding, and the result still parses, so nothing else would catch it.

gen_stubs.py guards this: it snapshots the committed stubs' symbols before regenerating and fails if any disappeared, naming them. If a reduction is genuinely intended, say so:

python python/scripts/gen_stubs.py --allow-removals

editor.pyi is hand-maintained

Every other stub here is generated; editor.pyi is written by hand, and gen_stubs.py restores it verbatim after each run (HAND_MAINTAINED).

threepp.editor is served by two modules built from the same binding sources: this wheel, and the editor app's embedded interpreter. Only the editor compiles src/bind_editor_physics.cppRigidBody, SoftBody, Articulation and the three *_from_object lookups are handles onto a live PhysicsPlaySession, which nothing outside a running editor has, so the wheel does not bind them. The stub has to describe the union for a script author's completion to be right, and a stub generated from the wheel alone would delete the physics half (41 symbols).

So: change bind_editor_physics.cpp, and edit editor.pyi to match. The restore happens after the produced-files check, so a threepp.editor that disappeared from the module still fails the run rather than being papered over by the committed copy.

The union stays in the source tree for the editor's Pylance integration, but it does not ship in the wheel: the wheel carries py.typed, so an installed stub is certified to a type checker, and certifying 40-odd names that raise AttributeError on a pip install would be lying. The wheel instead installs wheel-stubs/editor.pyi — just SplinePath and spline_from_object, the two names bind_editor.cpp actually provides there. A name added to bind_editor.cpp belongs in both stubs; a name added to the editor-only TUs belongs only in the union.

Keyword-named bindings

A bound name that is a Python keyword cannot be written in Python source or in a stub, and one is enough to make the whole stub unparseable. Two such names have been renamed at the binding site:

Was Now Why
Blending.None Blending.NoBlending tp.Blending.None is a SyntaxError; NoBlending is also the three.js name
damp(..., lambda=...) damp(..., lambda_=...) lambda is unusable as a keyword argument; PEP 8's trailing underscore

No binding needs repair today. gen_stubs.py keeps a general repair pass as a standing guard, since pybind11 will emit the same breakage from any future C++ identifier that collides with a Python keyword.

A few signatures show ... instead of a real parameter type (e.g. Vector3.apply_matrix4). That is pybind11 baking a signature before the argument's C++ type was registered; the fix is binding declaration order, not the stub generator.

Vulkan deferred AOVs (synthetic data)

The deferred (RasterFirst) Vulkan renderer writes a full G-buffer every frame — world normals, optical flow, per-instance segmentation ids, albedo, depth. The binding exposes those attachments as numpy, so a scene authored in Python yields ground-truth labels directly from the render, with no manual annotation:

import threepp as tp
assert tp.HAS_VULKAN

canvas = tp.Canvas("aov", width=512, height=384, headless=True, vsync=False)
renderer = tp.VulkanRenderer(canvas)            # deferred / RasterFirst

# ... build scene + camera ...
out = renderer.render_aovs(scene, camera, ["rgb", "normals", "segmentation"])
rgb, normals, seg = out["rgb"], out["normals"], out["segmentation"]   # (H, W, 3) uint8
depth = renderer.read_depth(scene, camera)                           # (H, W) float32, metres

Enable the Vulkan backend at configure time (needs the Vulkan SDK + a Vulkan-capable GPU). The GL renderer stays available in the same module:

cmake -S . -B build -DTHREEPP_WITH_PYTHON=ON -DTHREEPP_WITH_VULKAN=ON
cmake --build build --target threepp_py

tp.HAS_VULKAN reports whether the backend was compiled in.

Two readback paths. The 8-bit visualisation AOVs (render_aov / render_aovs) come out as (H, W, 3) uint8 via the renderer's debug-resolve pass: normals as n*0.5+0.5, segmentation as per-id hashed colours, albedo as linear albedo (so it looks gamma-/hue-off as a viewed image, but the data is the real linear base colour). The lossless path copies the native G-buffer attachments straight to host memory: read_depthmetric (H, W) float32 (full-precision native D32 read, verified accurate against known distances), read_instance_idsraw (H, W) uint32 per-instance ids, read_normals_float, read_motion, and read_aovs_typed([...]) for several in one render. Driving is via the deferred frame-model under the hood (submit/present is deferred to the canvas frame-end callback, so each render* repeats a few frames to make the MAILBOX readback deterministic — tune with set_flush_frames).

In-window UI (Dear ImGui)

threepp integrates Dear ImGui; the binding exposes it as ImguiContext plus the threepp.imgui submodule of immediate-mode widgets. Issue widgets each frame inside a draw callback, and call ui.render(draw) in the animate loop after renderer.render(...):

ui = tp.ImguiContext(canvas)        # create AFTER the GLRenderer

def draw():
    tp.imgui.begin("Controls")
    changed, rough = tp.imgui.slider_float("roughness", material.roughness, 0.0, 1.0)
    if changed: material.roughness = rough
    _, material.wireframe = tp.imgui.checkbox("wireframe", material.wireframe)
    if tp.imgui.button("reset"): camera.position.set(0, 2, 6)
    tp.imgui.end()

def animate():
    controls.enabled = not ui.want_capture_mouse   # don't orbit while over the panel
    controls.update()
    renderer.render(scene, camera)
    ui.render(draw)

canvas.animate(animate)

Value-returning widgets give back (changed, new_value) tuples; button returns True on the clicked frame. tp.imgui.show_demo_window() opens the full ImGui gallery. tp.HAS_IMGUI reports availability.

It works on both renderers — pass the renderer so the backend matches: ImguiContext(canvas, gl_renderer) (or ImguiContext(canvas)) for GL, and ImguiContext(canvas, vulkan_renderer) for the Vulkan overlay (recorded into the deferred frame after the scene). Create the ImguiContext after the renderer. See examples/vulkan_ui.py for the Vulkan version.

One caveat: only one ImguiContext should be alive at a time (Dear ImGui has a single global context) — don't keep a GL and a Vulkan one simultaneously.

Rigid-body physics (PhysX)

threepp ships a scene-graph-integrated PhysX wrapper; the binding exposes it as PhysxWorld (+ a RigidBody handle). Add meshes as bodies, step(dt) each frame, and the bound meshes' transforms follow the simulation. It's pure CPU — no canvas or renderer required, so it works headless:

import threepp as tp
assert tp.HAS_PHYSX

world = tp.PhysxWorld(gravity=tp.Vector3(0, -9.81, 0))

floor = tp.Mesh(tp.BoxGeometry(20, 1, 20), tp.MeshStandardMaterial())
floor.position.y = -0.5
world.add_static(floor)                 # static collider

box = tp.Mesh(tp.BoxGeometry(1, 1, 1), tp.MeshStandardMaterial())
box.position.set(0, 5, 0)
body = world.add(box, density=200)      # dynamic body, auto-bound to the mesh
body.add_impulse(tp.Vector3(2, 0, 0))

for _ in range(120):
    world.step(1 / 60)                   # box.position now follows the sim

Shapes are inferred from Box/Sphere/Capsule geometry; add_dynamic_convex (convex hull), add_static_trimesh / add_static_trimesh_tree (exact triangles, e.g. an imported glTF environment), and add_instanced (one body per InstancedMesh instance) cover the rest. RigidBody exposes pose, linear/angular velocity, add_force / add_impulse, damping, and a kinematic mode (set_kinematic + set_kinematic_target). on_pre_substep / on_post_substep hooks fire around each fixed substep.

Enable PhysX at configure time. It comes from vcpkg (unofficial-omniverse-physx-sdk), so configure with the vcpkg toolchain:

cmake -S . -B build -DTHREEPP_WITH_PYTHON=ON \
      -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
cmake --build build --target threepp_py

(Or, to reuse an existing install without the toolchain, pass -Dunofficial-omniverse-physx-sdk_DIR=<vcpkg_installed>/x64-windows/share/unofficial-omniverse-physx-sdk; the build then stages the PhysX runtime DLLs next to the module.) tp.HAS_PHYSX reports whether it was compiled in. Reduced-coordinate articulations (robots) are bound too — Articulation / ArticulationLink, built from a URDF in one call. Soft bodies and vehicles (which need the CUDA/GPU path) are not exposed yet.

Combined with the Vulkan AOVs, this is the dynamic half of the synthetic-data story: physics gives you moving scenes, the G-buffer gives you per-frame segmentation / depth / optical-flow labels for free.

Proprioceptive sensors (IMU)

On top of the PhysX world, threepp exposes a proprioceptive sensor suite for robotics: Imu (gyroscope + accelerometer), JointEncoder, ContactSensor and ForceTorqueSensor, each with seeded noise models. The walkthrough below uses the Imu. A sensor rides the scene graph: you attach it to an Object3D, and that node's world frame is the measurement frame. Register it with the world and it is sampled from the physics step loop — one clean sample per fixed substep, timestamped with the accumulated sim time — so the sampling fidelity is right for lock-step co-simulation (e.g. a future ArduPilot SITL).

import threepp as tp
assert tp.HAS_PHYSX

world = tp.PhysxWorld(gravity=tp.Vector3(0, -9.81, 0), fixed_timestep=1/240)
world.add_static(floor_mesh)                 # top face at y=0
body = world.add(box_mesh, density=200)      # dynamic body

mount = tp.Group()                           # IMU node, offset from the CoM
mount.position.set(0.3, 0.2, 0.1)
box_mesh.add(mount)

imu = tp.Imu(mount)                          # rate_hz=0 -> sample every substep
world.register_sensor(imu)                   # AFTER adding the body it rides

for _ in range(720):
    world.step(1/240)

for s in imu.drain():                        # oldest-first; empties the buffer
    print(s.t, s.angular_velocity, s.linear_acceleration)
data = imu.drain_array()                     # or a (N, 7) numpy [t, gx,gy,gz, ax,ay,az]
last = imu.latest()                          # most recent sample (survives drain)

Contract. Attach to a node; set rate_hz (0 = every physics substep, else the sensor sub-samples). Read non-blocking: latest() is the most recent sample, drain(out) / drain_array() move everything accumulated since the last drain out of a bounded ring buffer (default 2048; oldest dropped on overflow). register_sensor resolves the rigid body by walking up from the attachment node to the nearest ancestor added to the world — it raises immediately if there is none.

IMU units & frames. Both readings are expressed in the sensor (node) frame:

Field Meaning Units
angular_velocity the body's angular velocity rad/s
linear_acceleration specific force f = R⁻¹·(a_point − g) m/s²

Specific force is what a real accelerometer reads (proper acceleration minus gravity, threepp is Y-up so g = (0, −9.81, 0)), so a level body at rest reads (0, +9.81, 0) and a body in free fall reads ~0. When the node is offset from the centre of mass, the lever-arm terms α × r + ω × (ω × r) are included (a_com and α come from finite-differencing the PhysX velocities across samples; the first sample after attach/reset() emits zero to avoid a start-up spike).

Noise. imu.gyro_noise and imu.accel_noise are NoiseModels with per-axis continuous-time densities — white_noise_density [X/√Hz], random_walk [X/(s·√Hz)] bias instability, and a constant constant_bias [X] — plus a seed (deterministic given seed + call sequence). Defaults are consumer MEMS-class; set every field to zero for a perfect sensor. Change the noise, then call imu.reset() (which also re-arms the finite-difference after an episode reset).

See examples/imu_demo.py for the full headless demo.

Notes for maintainers

threepp uses virtual inheritance for some classes (Mesh/Points/Line derive from Object3D virtually; concrete materials derive from Material virtually). pybind11 mishandles pointer adjustment across a virtual base, which corrupts memory. The bindings work around this by:

  • never letting pybind up-cast a derived Python object to a shared_ptr of a virtual base — as_object3d / as_material cast to the concrete type and let the C++ compiler do the up-cast;
  • binding the inherited Object3D / Material members on each concrete leaf with concrete member pointers (&T::field) and T& lambdas, instead of inheriting the (broken) base bindings.

See src/bind_objects.cpp (bind_object3d_api) and src/bind_materials.cpp (bind_material_base_fields).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

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

threepp-2026.8.8-cp314-cp314-win_amd64.whl (6.2 MB view details)

Uploaded CPython 3.14Windows x86-64

threepp-2026.8.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (8.9 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

threepp-2026.8.8-cp313-cp313-win_amd64.whl (6.0 MB view details)

Uploaded CPython 3.13Windows x86-64

threepp-2026.8.8-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (8.9 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

threepp-2026.8.8-cp312-cp312-win_amd64.whl (6.0 MB view details)

Uploaded CPython 3.12Windows x86-64

threepp-2026.8.8-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (8.9 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

threepp-2026.8.8-cp311-cp311-win_amd64.whl (6.0 MB view details)

Uploaded CPython 3.11Windows x86-64

threepp-2026.8.8-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (8.9 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

threepp-2026.8.8-cp310-cp310-win_amd64.whl (6.0 MB view details)

Uploaded CPython 3.10Windows x86-64

threepp-2026.8.8-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (8.9 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

File details

Details for the file threepp-2026.8.8-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: threepp-2026.8.8-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 6.2 MB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for threepp-2026.8.8-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 e748ee0dda9686c938b86e2d0fe84eb12b61f8953a30c4998f8ecc2c988a9fbd
MD5 5e6ee5b20386cee4f80c22fb18cc8ab4
BLAKE2b-256 00e5298483726e8731c924520cddc4eb23a616feece0b9014ea9aa48f33faef4

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp314-cp314-win_amd64.whl:

Publisher: wheels.yml on markaren/threepp

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

File details

Details for the file threepp-2026.8.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for threepp-2026.8.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 3851108c0c0962f0d73f605de51816c4629707b9c84cd0f39ae78ac99b711ab6
MD5 b033fc526eba2af7529d53766460b7b4
BLAKE2b-256 7739cdd53bcf033df39563c0abe633568693ca7b2580c0eab20cef582f47ae28

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: wheels.yml on markaren/threepp

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

File details

Details for the file threepp-2026.8.8-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: threepp-2026.8.8-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 6.0 MB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for threepp-2026.8.8-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 21f91107d31f3f21275fb1e524134b4e3c37e74560a3aa766100003849c10478
MD5 ef01cfba29d4349f4e4d99de42d7acea
BLAKE2b-256 c7535e33e0fcc53c5d4e4a1c1396420a3e6327ae6b4f4f042ab8a53bb3690975

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp313-cp313-win_amd64.whl:

Publisher: wheels.yml on markaren/threepp

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

File details

Details for the file threepp-2026.8.8-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for threepp-2026.8.8-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 495112fa0239a32a9f5793f9786d4a334a16170b11c60b1e6d991192dc5d47ae
MD5 88ba23c91243f59cc257f6125f0f5889
BLAKE2b-256 a45b222bd4dedeef5ad3a6801a8ea8138e2431dbd9094d2599bedb7a63faea66

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: wheels.yml on markaren/threepp

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

File details

Details for the file threepp-2026.8.8-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: threepp-2026.8.8-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 6.0 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for threepp-2026.8.8-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 d1a2136767b137b640f641151e4de864f61bc7deed1db66800226b5c6215185c
MD5 164c637b131e2536748868d41d9e6554
BLAKE2b-256 fcb38683d71950889bf421cc13deb9839afeab39790223db343c3b40b6f44374

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp312-cp312-win_amd64.whl:

Publisher: wheels.yml on markaren/threepp

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

File details

Details for the file threepp-2026.8.8-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for threepp-2026.8.8-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 a820283da7ffbe84030fa5fb00a31e83c2f10cb8d487edb89d22a19ec4a6ee36
MD5 29bbed928acb398051fac43062dca50a
BLAKE2b-256 31b6ba4bbbc7b22a5a4d6486f1d7ab6a783113fce242d941341035120e9726d3

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: wheels.yml on markaren/threepp

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

File details

Details for the file threepp-2026.8.8-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: threepp-2026.8.8-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 6.0 MB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for threepp-2026.8.8-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 36736fb757c0994484460212220ccd8e753f98527995120b225d74eb723df297
MD5 6ee055937e217ce1bc8da5dcd3303aa2
BLAKE2b-256 2e7a35538aafbbda6a22d5af31b634e34775bb8eabfae3fda27cbb7c2ef89ad9

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp311-cp311-win_amd64.whl:

Publisher: wheels.yml on markaren/threepp

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

File details

Details for the file threepp-2026.8.8-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for threepp-2026.8.8-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 bd2762133fd8403c36f0be132127fbabc7bddfa8db6c53c2341356ee88afb34a
MD5 f7fe9bd6b6a3e99dffd70aa28fc864b8
BLAKE2b-256 455e5844dddcc62aff8a8e5d5a2aad70deef2c2028a9bbcabc826157f081a7ff

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: wheels.yml on markaren/threepp

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

File details

Details for the file threepp-2026.8.8-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: threepp-2026.8.8-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 6.0 MB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for threepp-2026.8.8-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 56a911006fa6df25e4e6c5a70bd9d12946d7acd1f52c5cc2ff46b2096311fcec
MD5 714c796873c7ba66ae24c98e244b3c35
BLAKE2b-256 bc508a664465767ecc3829ae6859569923abd7ed99e8eabd94a155bf8c883cd9

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp310-cp310-win_amd64.whl:

Publisher: wheels.yml on markaren/threepp

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

File details

Details for the file threepp-2026.8.8-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for threepp-2026.8.8-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 78e77a4c3e28e9e26c91f6479db77c2175b7f9b9b29318078a6a835ffa366f12
MD5 839ec4a35441244074deeb4448e4fecb
BLAKE2b-256 93b2e7e5e456c59d2b3764bfb538e046904f7318f893a7a9bb1b56db9dcc860b

See more details on using hashes here.

Provenance

The following attestation bundles were made for threepp-2026.8.8-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: wheels.yml on markaren/threepp

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

Release history Release notifications | RSS feed

2026.8.21

11 files

2026.8.9

11 files

This release

2026.8.8 This release

10 files

Supported by

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