Metadata-Version: 2.4
Name: body-models-viser
Version: 0.3.5
Summary: Browser-side body model evaluation for viser
Requires-Python: >=3.12
Requires-Dist: body-models>=0.18.11
Requires-Dist: jaxtyping
Requires-Dist: nodeenv
Requires-Dist: numpy
Requires-Dist: viser<1.1,>=1.0.29
Description-Content-Type: text/markdown

# body-models-viser

Browser-side body model evaluation for viser.

Python owns the body model asset loaders and the `prepare_identity()` /
`prepare_pose()` calls. TypeScript owns the browser model lifecycle and WASM
buffers. Rust owns the stateless `forward_vertices()` kernel.

## Usage

### Skinned Body Models

Use `add_body_model()` for non-rigid skinned body models such as SMPL, SMPL-X,
MANO, FLAME, MHR, SKEL, ANNY, GarmentMeasurements, and SOMA.

```python
import body_models_viser as bmv
import viser
from body_models.smpl.numpy import SMPL

server = viser.ViserServer()
model = SMPL(gender="neutral")
handle = bmv.add_body_model(server.scene, "/smpl", model, color=(173, 216, 230))

pose = handle.body_pose.copy()
pose[2, 0] = 0.5
handle.body_pose = pose

translation = handle.global_translation.copy()
translation[1] = 0.25
handle.set_transform(global_translation=translation)

shape = handle.shape.copy()
shape[0] = 1.0
handle.set_identity(shape=shape)
```

`add_body_model()` returns a generic handle with `set_identity(...)`,
`set_pose(...)`, `set_transform(...)`, `remove()`, `global_rotation`,
`global_translation`, and the parameter properties declared by the model.

### Skeletons

Use `add_skeleton()` for a standalone clickable skeleton. It takes joint
positions and a parent index for each joint.

```python
import body_models_viser as bmv

pose = model.get_rest_pose()
skeleton = model.forward_skeleton(**pose)
joint_positions = skeleton[:, :3, 3]

handle = bmv.add_skeleton(
    server.scene,
    "/skeleton",
    joint_positions,
    model.parents,
    joint_names=tuple(model.joint_names),
)

handle.visible = True
handle.joint_positions = joint_positions
```

### Rigid Body Models

Use `add_rigid_body_model()` for any `body_models.RigidBodyModel`, such as G1,
BrainCo, SmplHumanoid, and MyoFullBody.

```python
import body_models_viser as bmv
from body_models import create_model

model = create_model("g1")
handle = bmv.add_rigid_body_model(server.scene, "/robot", model)
handle.set_pose(body_pose=handle.pose["body_pose"])
```

The rigid-body helper bakes one static link-local mesh per link from the rest
pose (`forward_meshes()` sliced with the model's link metadata), then only
updates link transforms from `forward_links()` when the pose changes.

## Runtime

`bmv.add_body_model(scene, name, model)` does three things:

1. Injects `body-models-viser.js` and `body-models-viser.wasm`.
2. Sends faces, material props, current identity, and current pose as
   little-endian binary buffers.
3. Returns a body model handle.

`handle.set_identity(...)` recomputes `prepare_identity()`, then prepares and
sends updated skinning data. `handle.set_pose(...)` reuses the cached identity
and sends updated pose-dependent skinning data. `handle.set_transform(...)`
updates only global transform buffers. TypeScript copies changed buffers into
persistent WASM memory, calls the Rust `forward_vertices()` kernel, and forwards
the resulting vertex buffer to viser as a regular mesh message.

The browser protocol is model-agnostic: Python converts every `SkinnedModel` to
a shared runtime payload containing skinning weights, rest vertices, skinning
transforms, and pose offsets. Models that implement the standard preparation API
and parameter-key metadata require no viser-specific adapter.

## viser compatibility

This package patches viser private internals (message serializer, websock
client state, and the client React tree) to inject its runtime. The supported
viser range is pinned in `pyproject.toml`; when raising the ceiling, run
`uv run pytest` and `uv run scripts/visualize_models.py` against the new
version and check a browser actually renders.

## Development

Build the Rust crate:

```sh
cargo test
```

Build the browser bundle and WASM:

```sh
cd client
npm test
```

The browser build requires a Rust toolchain with `wasm32-unknown-unknown`
installed, for example:

```sh
rustup target add wasm32-unknown-unknown
```

Run the small visualizer:

```sh
uv run --no-sync scripts/visualize_models.py
```

Check NumPy/WASM vertex parity for all supported models:

```sh
uv run scripts/check_model_parity.py
```
