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 +155 -15
- package/dist/{chunk-T3GVZJ4F.js → chunk-6MOK6ZWB.js} +501 -33
- package/dist/chunk-6MOK6ZWB.js.map +1 -0
- package/dist/index.d.ts +129 -7
- package/dist/index.js +846 -437
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +27 -3
- package/dist/spark.js +156 -3
- package/dist/spark.js.map +1 -1
- package/dist/{types-oxbxOkAx.d.ts → types-BDB9QT6Z.d.ts} +42 -3
- package/dist/vite.js +8 -4
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ContactMarkers.tsx +8 -1
- package/src/components/Debug.tsx +154 -3
- package/src/components/DragInteraction.tsx +2 -0
- package/src/components/IkGizmo.tsx +5 -1
- package/src/components/SplatCollisionProxyPreview.tsx +350 -0
- package/src/components/VisualScenario.tsx +140 -41
- package/src/core/MujocoSimProvider.tsx +6 -6
- package/src/hooks/useMountedCameraSequenceRecorder.ts +54 -6
- package/src/index.ts +32 -0
- package/src/rendering/cameraFrameCapture.ts +259 -28
- package/src/rendering/cameraFrameSource.ts +382 -2
- package/src/spark.tsx +241 -1
- package/src/types.ts +45 -2
- package/src/vite.ts +9 -4
- package/dist/chunk-T3GVZJ4F.js.map +0 -1
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.
|
|
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
|
|
184
|
-
and ready
|
|
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
|
-
|
|
187
|
-
|
|
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
|
|
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
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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:
|