mujoco-react 9.3.0 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -143,7 +143,7 @@ Use it as a child of `<MujocoCanvas>`:
143
143
 
144
144
  ## Gaussian Splat Environments
145
145
 
146
- Gaussian splats are visual context; MuJoCo XML remains the source of physics, contacts, and task fixtures. Pair each splat asset with collision proxy metadata so scene variants, rollouts, and datasets preserve both sides of the environment.
146
+ Gaussian splats are visual context; MuJoCo XML remains the source of physics, contacts, and task fixtures. Use visual-only splats when you only need rendered environment context, and add collision proxy metadata when a workflow needs simplified contact geometry.
147
147
 
148
148
  Use `VisualScenarioEffects` when the same MuJoCo task should render under
149
149
  different camera exposure, fog/background, and deterministic material variants:
@@ -162,7 +162,9 @@ import { ScenarioLighting, VisualScenarioEffects } from "mujoco-react";
162
162
 
163
163
  Use the renderer-agnostic boundary from the main package. If your app stores
164
164
  visual scenarios as data, pass the scenario directly; the component resolves the
165
- splat asset and paired MJCF collision proxy metadata for you.
165
+ splat asset and any paired MJCF collision proxy metadata for you. Visual-only
166
+ splats are valid, and readiness tells you whether a collision proxy is required
167
+ for your training/physics handoff.
166
168
 
167
169
  ```tsx
168
170
  import { MujocoCanvas, SplatEnvironment, useSplatSceneConfig } from "mujoco-react";
@@ -178,13 +180,84 @@ const splat = useSplatSceneConfig({ sceneConfig, scenario });
178
180
  </MujocoCanvas>;
179
181
  ```
180
182
 
183
+ When a splat scenario includes paired MJCF collision proxy metadata, render a
184
+ generic wireframe preview from that XML with `SplatCollisionProxyPreview`. The
185
+ component parses MJCF primitives such as planes, boxes, spheres, capsules, and
186
+ mesh placeholders from any fetchable proxy XML; the tabletop example is just one
187
+ possible environment.
188
+
189
+ ```tsx
190
+ import {
191
+ SplatCollisionProxyPreview,
192
+ SplatEnvironment,
193
+ useSplatCollisionProxyGeoms,
194
+ useSplatSceneConfig,
195
+ } from "mujoco-react";
196
+
197
+ const splat = useSplatSceneConfig({ sceneConfig, scenario });
198
+ const proxy = splat.environment?.collisionProxy;
199
+
200
+ <SplatEnvironment
201
+ environment={splat.environment}
202
+ collisionProxy={
203
+ proxy ? <SplatCollisionProxyPreview collisionProxy={proxy} /> : undefined
204
+ }
205
+ />;
206
+ ```
207
+
208
+ Use `useSplatCollisionProxyGeoms()` when your app wants to inspect or style the
209
+ proxy primitives itself:
210
+
211
+ ```tsx
212
+ const proxyPreview = useSplatCollisionProxyGeoms({
213
+ collisionProxy: splat.environment?.collisionProxy,
214
+ });
215
+
216
+ proxyPreview.geoms.map((geom) => geom.type);
217
+ ```
218
+
181
219
  Use `splat.readiness` or `getSplatEnvironmentReadiness(scenario)` to gate
182
220
  authoring and import flows. The status distinguishes disabled scenarios,
183
- missing splat assets, missing MJCF collision proxies, unsupported Spark formats,
184
- and ready paired environments.
221
+ missing visual assets, missing collision proxies, unsupported renderer formats,
222
+ and ready environments.
223
+
224
+ Use `createSplatSceneConfig()` when the same scene composition needs to run
225
+ outside React, such as codegen, import validation, backend handoff metadata, or
226
+ tests:
227
+
228
+ ```tsx
229
+ import { createSplatSceneConfig } from "mujoco-react";
230
+
231
+ const splat = createSplatSceneConfig({
232
+ sceneConfig,
233
+ scenario,
234
+ renderer: "spark",
235
+ });
236
+ ```
237
+
238
+ Use `createVisualScenarioExecutionContext()` or
239
+ `useVisualScenarioExecutionContext()` when recording rollouts or exporting
240
+ LeRobot/HF Jobs handoff artifacts. It resolves the scenario seed, camera
241
+ exposure/noise/blur/jitter, material randomization, splat source, collision
242
+ proxy, and readiness into one serializable object.
243
+
244
+ ```tsx
245
+ import { createVisualScenarioExecutionContext } from "mujoco-react";
246
+
247
+ const visualContext = createVisualScenarioExecutionContext({
248
+ scenario,
249
+ renderer: "spark",
250
+ variantId,
251
+ });
185
252
 
186
- For MuJoCo + 3DGS composition, derive the collision environment from the same
187
- splat metadata and pass the resulting config to `<MujocoCanvas>`:
253
+ writeEpisodeManifest({
254
+ task,
255
+ visualExecutionContext: visualContext,
256
+ });
257
+ ```
258
+
259
+ For MuJoCo + 3DGS composition, derive the optional collision environment from
260
+ the same splat metadata and pass the resulting config to `<MujocoCanvas>`:
188
261
 
189
262
  ```tsx
190
263
  const sceneConfig = withSplatEnvironment(
@@ -224,6 +297,16 @@ function Scene() {
224
297
 
225
298
  `SparkSplatEnvironment` currently renders `.spz` assets. Use the renderer-agnostic
226
299
  `SplatEnvironment` for `.ply`/`.splat` metadata or when wiring a different renderer.
300
+ Tune live rendering and snapshots separately with `renderTuning` and
301
+ `captureTuning`:
302
+
303
+ ```tsx
304
+ <SparkSplatEnvironment
305
+ {...splat.props}
306
+ renderTuning={{ lodSplatScale: 0.75, minSortIntervalMs: 50 }}
307
+ captureTuning={{ lodSplatScale: 1.4, lodRenderScale: 0.45, maxWarmupFrames: 6 }}
308
+ />
309
+ ```
227
310
 
228
311
  ## Write Controllers
229
312
 
@@ -552,7 +635,7 @@ interface SceneConfig {
552
635
  }
553
636
  ```
554
637
 
555
- Use `environmentFiles` to compose reusable physics/collision layers with a robot model. For Gaussian splat scenes, keep the `.spz` as a parallel visual layer and point `environmentFiles` at the paired MJCF proxy scene:
638
+ Use `environmentFiles` to compose reusable physics/collision layers with a robot model. For Gaussian splat scenes, keep the `.spz` as a parallel visual layer and point `environmentFiles` at a paired MJCF proxy scene only when contact geometry is needed:
556
639
 
557
640
  ```tsx
558
641
  const kitchenRobot: SceneConfig = {
@@ -777,6 +860,7 @@ Visualization overlays:
777
860
  | `showSites` | `boolean?` | `false` | Site markers |
778
861
  | `showJoints` | `boolean?` | `false` | Joint axes |
779
862
  | `showContacts` | `boolean?` | `false` | Contact force vectors |
863
+ | `showCameras` | `boolean?` | `false` | MuJoCo camera positions, frustums, and forward rays |
780
864
  | `showCOM` | `boolean?` | `false` | Center of mass markers |
781
865
  | `showInertia` | `boolean?` | `false` | Inertia ellipsoids |
782
866
  | `showTendons` | `boolean?` | `false` | Tendon paths |
@@ -785,6 +869,8 @@ Visualization overlays:
785
869
  | `contactColor` | `string?` | `"#ff4444"` | Color for contact force arrows |
786
870
  | `comColor` | `string?` | `"#ff0000"` | Color for COM markers |
787
871
 
872
+ Camera debug overlays use the live MuJoCo `cam_xpos` / `cam_xmat` frame, so the frustum matches mounted camera captures and follows parent body motion.
873
+
788
874
  ### `<TendonRenderer />`
789
875
 
790
876
  Renders tendons as tube geometry from wrap paths.
@@ -1066,6 +1152,10 @@ const sequence = await recordMountedCameraFrameSequence(api, {
1066
1152
  sequence.readiness.ready; // true when every requested stream resolved
1067
1153
  sequence.plan.missingKeys; // unresolved task cameras, if requireAll is false
1068
1154
  sequence.cameraSummaries.head.source; // mounted source provenance
1155
+
1156
+ const manifest = createMountedCameraFrameSequenceManifest(sequence);
1157
+ manifest.streamSummaries.head.complete; // per-camera frame coverage
1158
+ manifest.status; // "complete", "partial", or "missing"
1069
1159
  ```
1070
1160
 
1071
1161
  `recordMountedCameraFrameSequence()` requires all requested `cameraKeys` by
@@ -1073,23 +1163,69 @@ default so dataset recording cannot silently omit a camera stream. Set
1073
1163
  `requireAll: false` only for exploratory tooling that can tolerate partial
1074
1164
  camera coverage.
1075
1165
 
1166
+ Use `createMountedCameraFrameSequenceManifest()` after recording to persist a
1167
+ stable dataset-facing manifest with readiness, source targets, dimensions,
1168
+ first/last frame indices, timestamps, and per-camera missing-frame counts.
1169
+
1076
1170
  Inside `<MujocoCanvas>` children, `useMountedCameraSequenceRecorder()` exposes
1077
- the same planning and recording surface with React status/error state.
1171
+ the same planning and recording surface with React status/error/result state.
1172
+ Use `checkReadiness()` before recording when the UI needs a preflight gate for
1173
+ LeRobot camera streams:
1174
+
1175
+ ```tsx
1176
+ function DatasetRecorder() {
1177
+ const recorder = useMountedCameraSequenceRecorder({
1178
+ defaults: { width: 640, height: 480, type: "image/png" },
1179
+ aliases: {
1180
+ head: { cameraName: "head" },
1181
+ left_wrist: { siteName: "left_wrist_camera_optical_frame" },
1182
+ right_wrist: { siteName: "right_wrist_camera_optical_frame" },
1183
+ },
1184
+ });
1185
+
1186
+ async function recordDatasetEpisode() {
1187
+ const cameraKeys = ["head", "left_wrist", "right_wrist"];
1188
+ const readiness = recorder.checkReadiness(cameraKeys);
1189
+ if (!readiness.ready) return;
1190
+
1191
+ await recorder.record({
1192
+ cameraKeys,
1193
+ frames: 16,
1194
+ retainFrames: false,
1195
+ onFrame: ({ frameIndex, cameras }) => {
1196
+ queueLeRobotImages(frameIndex, cameras);
1197
+ },
1198
+ });
1199
+ }
1200
+
1201
+ return (
1202
+ <button disabled={recorder.isRecording} onClick={recordDatasetEpisode}>
1203
+ Record camera streams
1204
+ </button>
1205
+ );
1206
+ }
1207
+ ```
1208
+
1209
+ `recorder.readiness` keeps the latest preflight result, and
1210
+ `recorder.result?.readiness` keeps the readiness that shipped with the most
1211
+ recent recording.
1078
1212
 
1079
1213
  Use `resolveMountedCameraFrameSource()` when dataset feature names need to map
1080
- to named MuJoCo cameras, sites, or bodies before recording. The helper accepts
1081
- the model resource lists plus app-level aliases and returns both the capture
1082
- selector and the mounted-source provenance that should be stored beside the
1083
- dataset:
1214
+ to named MuJoCo cameras, sites, or bodies before recording. The helper honors
1215
+ aliases first, then prefers camera matches over sites and bodies, then falls
1216
+ back to exact body/site names. This keeps streams such as `wrist` mapped to a
1217
+ real `<camera name="wrist_cam">` even when the model also has a body named
1218
+ `wrist`. It also handles normalized/prefix/suffix matches such as
1219
+ `left_wrist` -> `left_wrist_camera_optical_frame`, and token-contained
1220
+ imported-model names such as `observation.images.head` ->
1221
+ `robot_head_camera`. It returns both the capture selector and the mounted-source
1222
+ provenance that should be stored beside the dataset:
1084
1223
 
1085
1224
  ```tsx
1086
1225
  const resolved = resolveMountedCameraFrameSource("head", {
1087
1226
  cameras: api.getCameras(),
1088
1227
  sites: api.getSites(),
1089
1228
  bodies: api.getBodies(),
1090
- aliases: {
1091
- head: [{ siteName: "head_camera_rgb_optical_frame" }],
1092
- },
1093
1229
  });
1094
1230
 
1095
1231
  if (!resolved) throw new Error("head does not resolve to a MuJoCo source");
@@ -1101,6 +1237,10 @@ await api.recordCameraSequence({
1101
1237
  });
1102
1238
  ```
1103
1239
 
1240
+ Pass `aliases` when multiple MuJoCo resources could match a dataset stream key
1241
+ or when the model uses names that do not share a normalized prefix/suffix with
1242
+ the LeRobot camera feature.
1243
+
1104
1244
  ### `useCtrlNoise(config)`
1105
1245
 
1106
1246
  Apply Gaussian noise to controls for robustness testing: