mujoco-react 9.5.0 → 10.0.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 +60 -62
- package/dist/{chunk-6MOK6ZWB.js → chunk-QTCAVQS6.js} +29 -29
- package/dist/chunk-QTCAVQS6.js.map +1 -0
- package/dist/index.d.ts +27 -8
- package/dist/index.js +146 -8
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +1 -1
- package/dist/spark.js +1 -1
- package/dist/{types-BDB9QT6Z.d.ts → types-BaSMqJHT.d.ts} +88 -34
- package/dist/vite.js +21 -21
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/core/SceneLoader.ts +1 -1
- package/src/core/createController.tsx +2 -2
- package/src/hooks/useIkController.ts +8 -1
- package/src/hooks/useJointState.ts +18 -7
- package/src/hooks/useKeyboardIkTarget.ts +170 -0
- package/src/index.ts +21 -13
- package/src/rendering/Reflector.ts +7 -5
- package/src/types.ts +129 -56
- package/src/vite.ts +22 -22
- package/dist/chunk-6MOK6ZWB.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
# mujoco-react
|
|
6
6
|
|
|
7
|
-
> **Beta** — This library is under active development. The API may change between minor versions until 10.0.
|
|
8
|
-
|
|
9
7
|
Composable [React Three Fiber](https://docs.pmnd.rs/react-three-fiber) wrapper around the official [@mujoco/mujoco](https://www.npmjs.com/package/@mujoco/mujoco) WASM bindings. Load any MuJoCo model, step physics, render bodies, and write controllers as React components.
|
|
10
8
|
|
|
11
9
|
[](https://www.npmjs.com/package/mujoco-react)
|
|
@@ -20,7 +18,7 @@ npm install mujoco-react three @react-three/fiber @react-three/drei
|
|
|
20
18
|
|
|
21
19
|
## Vite Plugin and Type-Safe Names
|
|
22
20
|
|
|
23
|
-
Use the Vite plugin to generate
|
|
21
|
+
Use the Vite plugin to generate declaration merging for actuator, sensor, body, joint, site, geom, and keyframe names:
|
|
24
22
|
|
|
25
23
|
```ts
|
|
26
24
|
// vite.config.ts
|
|
@@ -44,9 +42,9 @@ The plugin writes `src/mujoco-register.gen.ts` during dev and build. Commit that
|
|
|
44
42
|
// src/mujoco-register.gen.ts
|
|
45
43
|
// Auto-generated by mujoco-react. Do not edit.
|
|
46
44
|
|
|
47
|
-
import {
|
|
45
|
+
import { registerModelResources } from "mujoco-react";
|
|
48
46
|
|
|
49
|
-
const
|
|
47
|
+
const generatedModelResources = {
|
|
50
48
|
franka: {
|
|
51
49
|
actuators: { joint1: "joint1", joint2: "joint2", joint3: "joint3", gripper: "gripper" },
|
|
52
50
|
sensors: { force_sensor: "force_sensor", torque_sensor: "torque_sensor" },
|
|
@@ -58,11 +56,11 @@ const generatedRobotResources = {
|
|
|
58
56
|
},
|
|
59
57
|
};
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
registerModelResources(generatedModelResources);
|
|
62
60
|
|
|
63
61
|
declare module "mujoco-react" {
|
|
64
62
|
interface Register {
|
|
65
|
-
|
|
63
|
+
models: {
|
|
66
64
|
franka: {
|
|
67
65
|
actuators: "joint1" | "joint2" | "joint3" | "gripper";
|
|
68
66
|
sensors: "force_sensor" | "torque_sensor";
|
|
@@ -80,7 +78,7 @@ declare module "mujoco-react" {
|
|
|
80
78
|
}
|
|
81
79
|
```
|
|
82
80
|
|
|
83
|
-
Once generated, hooks like `useCtrl`, `useSensor`, `useBodyState`, and API methods like `setCtrl`, `applyForce`, `getSensorData` accept the global union. For
|
|
81
|
+
Once generated, hooks like `useCtrl`, `useSensor`, `useBodyState`, and API methods like `setCtrl`, `applyForce`, `getSensorData` accept the global union. For model-scoped code, use package exports such as `ModelActuators.franka.gripper`, `ModelSites.franka.tcp`, and `ModelBodies.franka.hand`. Generic type helpers like `ModelActuators<"franka">` are still available for reusable library code. When no `Register` augmentation is present, names fall back to `string`.
|
|
84
82
|
|
|
85
83
|
Non-Vite projects can generate the same file with:
|
|
86
84
|
|
|
@@ -117,10 +115,10 @@ export function App() {
|
|
|
117
115
|
|
|
118
116
|
```tsx
|
|
119
117
|
import { OrbitControls } from "@react-three/drei";
|
|
120
|
-
import { IkGizmo,
|
|
118
|
+
import { IkGizmo, ModelSites, useIkController } from "mujoco-react";
|
|
121
119
|
|
|
122
120
|
function PandaTools() {
|
|
123
|
-
const ik = useIkController({ siteName:
|
|
121
|
+
const ik = useIkController({ siteName: ModelSites.franka.tcp });
|
|
124
122
|
|
|
125
123
|
return (
|
|
126
124
|
<>
|
|
@@ -311,10 +309,10 @@ Tune live rendering and snapshots separately with `renderTuning` and
|
|
|
311
309
|
## Write Controllers
|
|
312
310
|
|
|
313
311
|
```tsx
|
|
314
|
-
import {
|
|
312
|
+
import { ModelActuators, useBeforePhysicsStep, useCtrl } from "mujoco-react";
|
|
315
313
|
|
|
316
314
|
function MyController() {
|
|
317
|
-
const shoulder = useCtrl(
|
|
315
|
+
const shoulder = useCtrl(ModelActuators.franka.actuator1);
|
|
318
316
|
|
|
319
317
|
useBeforePhysicsStep(({ data }) => {
|
|
320
318
|
shoulder.write(Math.sin(data.time));
|
|
@@ -329,29 +327,29 @@ Controllers are just React children that read sensors, write named controls, app
|
|
|
329
327
|
With generated resource values, reusable controllers can be scoped to one robot without hand-typing names:
|
|
330
328
|
|
|
331
329
|
```tsx
|
|
332
|
-
import {
|
|
330
|
+
import { ModelActuators, ModelJoints, ModelSites, useCtrl, useIkController } from "mujoco-react";
|
|
333
331
|
|
|
334
332
|
function FrankaTypedControls() {
|
|
335
|
-
const gripper = useCtrl(
|
|
333
|
+
const gripper = useCtrl(ModelActuators.franka.gripper);
|
|
336
334
|
const ik = useIkController({
|
|
337
|
-
siteName:
|
|
335
|
+
siteName: ModelSites.franka.tcp,
|
|
338
336
|
joints: [
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
337
|
+
ModelJoints.franka.joint1,
|
|
338
|
+
ModelJoints.franka.joint2,
|
|
339
|
+
ModelJoints.franka.joint3,
|
|
340
|
+
ModelJoints.franka.joint4,
|
|
341
|
+
ModelJoints.franka.joint5,
|
|
342
|
+
ModelJoints.franka.joint6,
|
|
343
|
+
ModelJoints.franka.joint7,
|
|
346
344
|
],
|
|
347
345
|
actuators: [
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
346
|
+
ModelActuators.franka.actuator1,
|
|
347
|
+
ModelActuators.franka.actuator2,
|
|
348
|
+
ModelActuators.franka.actuator3,
|
|
349
|
+
ModelActuators.franka.actuator4,
|
|
350
|
+
ModelActuators.franka.actuator5,
|
|
351
|
+
ModelActuators.franka.actuator6,
|
|
352
|
+
ModelActuators.franka.actuator7,
|
|
355
353
|
],
|
|
356
354
|
});
|
|
357
355
|
|
|
@@ -378,14 +376,14 @@ Use control groups when a robot's actuator order does not match a simple `qpos[0
|
|
|
378
376
|
|
|
379
377
|
```tsx
|
|
380
378
|
import { useRef } from "react";
|
|
381
|
-
import { resolveControlGroup,
|
|
379
|
+
import { resolveControlGroup, ModelSites, useBeforePhysicsStep } from "mujoco-react";
|
|
382
380
|
import type { ControlGroupInfo } from "mujoco-react";
|
|
383
381
|
|
|
384
382
|
function HoldTcpPose() {
|
|
385
383
|
const armRef = useRef<ControlGroupInfo | null>(null);
|
|
386
384
|
|
|
387
385
|
useBeforePhysicsStep(({ model, data }) => {
|
|
388
|
-
armRef.current ??= resolveControlGroup(model, { siteName:
|
|
386
|
+
armRef.current ??= resolveControlGroup(model, { siteName: ModelSites.franka.tcp });
|
|
389
387
|
if (!armRef.current) return;
|
|
390
388
|
|
|
391
389
|
armRef.current.writeCtrl(data, armRef.current.readQpos(data));
|
|
@@ -489,7 +487,7 @@ For reusable controllers with typed config, default merging, and children, use t
|
|
|
489
487
|
|
|
490
488
|
```tsx
|
|
491
489
|
import {
|
|
492
|
-
|
|
490
|
+
ModelActuators,
|
|
493
491
|
createController,
|
|
494
492
|
useBeforePhysicsStep,
|
|
495
493
|
useCtrl,
|
|
@@ -498,7 +496,7 @@ import {
|
|
|
498
496
|
export const MyController = createController<{ gain: number }>(
|
|
499
497
|
{ name: "MyController", defaultConfig: { gain: 1.0 } },
|
|
500
498
|
({ config, children }) => {
|
|
501
|
-
const shoulder = useCtrl(
|
|
499
|
+
const shoulder = useCtrl(ModelActuators.franka.actuator1);
|
|
502
500
|
|
|
503
501
|
useBeforePhysicsStep(({ data }) => {
|
|
504
502
|
shoulder.write(config.gain * Math.sin(data.time));
|
|
@@ -535,14 +533,14 @@ A `createControllerHook` factory is also available for the hook equivalent — s
|
|
|
535
533
|
The built-in `useIkController()` uses Damped Least-Squares. Pass `ikSolveFn` to swap in your own solver (analytical, learned, etc.):
|
|
536
534
|
|
|
537
535
|
```tsx
|
|
538
|
-
import {
|
|
536
|
+
import { ModelSites } from "mujoco-react";
|
|
539
537
|
import type { IKSolveFn } from "mujoco-react";
|
|
540
538
|
|
|
541
539
|
const myIK: IKSolveFn = ({ position, currentQ }) => {
|
|
542
540
|
return myAnalyticalSolver(position, currentQ); // return joint angles or null
|
|
543
541
|
};
|
|
544
542
|
|
|
545
|
-
const ik = useIkController({ siteName:
|
|
543
|
+
const ik = useIkController({ siteName: ModelSites.franka.tcp, ikSolveFn: myIK });
|
|
546
544
|
```
|
|
547
545
|
|
|
548
546
|
### `useIkController(config | null)`
|
|
@@ -550,9 +548,9 @@ const ik = useIkController({ siteName: RobotSites.franka.tcp, ikSolveFn: myIK })
|
|
|
550
548
|
Hook for interactive end-effector control. Pass `null` to disable IK (safe to call unconditionally):
|
|
551
549
|
|
|
552
550
|
```tsx
|
|
553
|
-
import { IkGizmo,
|
|
551
|
+
import { IkGizmo, ModelSites, useIkController } from "mujoco-react";
|
|
554
552
|
|
|
555
|
-
const ik = useIkController({ siteName:
|
|
553
|
+
const ik = useIkController({ siteName: ModelSites.franka.tcp });
|
|
556
554
|
return ik ? <IkGizmo controller={ik} /> : null;
|
|
557
555
|
```
|
|
558
556
|
|
|
@@ -573,31 +571,31 @@ Pass the returned value to `<IkGizmo controller={ik} />` or to your own controll
|
|
|
573
571
|
By default the controller infers scalar hinge/slide joints by walking from the site body toward the model root. For robots where the MJCF control layout is not a simple chain, pass explicit names:
|
|
574
572
|
|
|
575
573
|
```tsx
|
|
576
|
-
import {
|
|
574
|
+
import { ModelActuators, ModelJoints, ModelSites } from "mujoco-react";
|
|
577
575
|
|
|
578
576
|
const leftArmIk = useIkController({
|
|
579
|
-
siteName:
|
|
577
|
+
siteName: ModelSites.franka.tcp,
|
|
580
578
|
joints: [
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
579
|
+
ModelJoints.franka.joint1,
|
|
580
|
+
ModelJoints.franka.joint2,
|
|
581
|
+
ModelJoints.franka.joint3,
|
|
582
|
+
ModelJoints.franka.joint4,
|
|
583
|
+
ModelJoints.franka.joint5,
|
|
584
|
+
ModelJoints.franka.joint6,
|
|
585
|
+
ModelJoints.franka.joint7,
|
|
588
586
|
],
|
|
589
587
|
});
|
|
590
588
|
|
|
591
589
|
const gripperIk = useIkController({
|
|
592
|
-
siteName:
|
|
590
|
+
siteName: ModelSites.franka.tcp,
|
|
593
591
|
actuators: [
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
592
|
+
ModelActuators.franka.actuator1,
|
|
593
|
+
ModelActuators.franka.actuator2,
|
|
594
|
+
ModelActuators.franka.actuator3,
|
|
595
|
+
ModelActuators.franka.actuator4,
|
|
596
|
+
ModelActuators.franka.actuator5,
|
|
597
|
+
ModelActuators.franka.actuator6,
|
|
598
|
+
ModelActuators.franka.actuator7,
|
|
601
599
|
],
|
|
602
600
|
});
|
|
603
601
|
```
|
|
@@ -934,9 +932,9 @@ if (mujoco) {
|
|
|
934
932
|
Run logic **before** `mj_step` each frame. Write to `data.ctrl`, apply forces, drive automation.
|
|
935
933
|
|
|
936
934
|
```tsx
|
|
937
|
-
import {
|
|
935
|
+
import { ModelActuators, useBeforePhysicsStep, useCtrl } from "mujoco-react";
|
|
938
936
|
|
|
939
|
-
const shoulder = useCtrl(
|
|
937
|
+
const shoulder = useCtrl(ModelActuators.franka.actuator1);
|
|
940
938
|
|
|
941
939
|
useBeforePhysicsStep(({ data }) => {
|
|
942
940
|
shoulder.write(Math.sin(data.time));
|
|
@@ -971,9 +969,9 @@ await moveCameraTo(
|
|
|
971
969
|
Read sensor values by name. Returns a `SensorHandle` with `read()`, `dim`, and `name`:
|
|
972
970
|
|
|
973
971
|
```tsx
|
|
974
|
-
import {
|
|
972
|
+
import { ModelSensors, useSensor } from "mujoco-react";
|
|
975
973
|
|
|
976
|
-
const imu = useSensor(
|
|
974
|
+
const imu = useSensor(ModelSensors.g1["imu-torso-angular-velocity"]);
|
|
977
975
|
// imu.read() -> Float64Array, imu.dim -> number
|
|
978
976
|
```
|
|
979
977
|
|
|
@@ -998,9 +996,9 @@ const { position, velocity } = useJointState("joint1");
|
|
|
998
996
|
Read/write actuator control by name. Returns a `CtrlHandle` with `read()`, `write()`, `name`, and `range`:
|
|
999
997
|
|
|
1000
998
|
```tsx
|
|
1001
|
-
import {
|
|
999
|
+
import { ModelActuators, useCtrl } from "mujoco-react";
|
|
1002
1000
|
|
|
1003
|
-
const gripper = useCtrl(
|
|
1001
|
+
const gripper = useCtrl(ModelActuators.franka.gripper);
|
|
1004
1002
|
// gripper.read() -> number, gripper.write(0.04), gripper.range -> [min, max]
|
|
1005
1003
|
```
|
|
1006
1004
|
|
|
@@ -1020,13 +1018,13 @@ useContactEvents("block_1", {
|
|
|
1020
1018
|
Map keyboard keys to actuators:
|
|
1021
1019
|
|
|
1022
1020
|
```tsx
|
|
1023
|
-
import {
|
|
1021
|
+
import { ModelActuators, useKeyboardTeleop } from "mujoco-react";
|
|
1024
1022
|
|
|
1025
1023
|
useKeyboardTeleop({
|
|
1026
1024
|
bindings: {
|
|
1027
1025
|
"w": { actuator: "forward", delta: 0.1 },
|
|
1028
1026
|
"s": { actuator: "forward", delta: -0.1 },
|
|
1029
|
-
"v": { actuator:
|
|
1027
|
+
"v": { actuator: ModelActuators.franka.gripper, toggle: [0, 0.04] },
|
|
1030
1028
|
},
|
|
1031
1029
|
});
|
|
1032
1030
|
```
|
|
@@ -4,7 +4,7 @@ import { useMemo, useEffect } from 'react';
|
|
|
4
4
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
// src/types.ts
|
|
7
|
-
var
|
|
7
|
+
var runtimeModelResources = {};
|
|
8
8
|
var REGISTER_RESOURCE_KEYS = ["actuators", "sensors", "bodies", "joints", "sites", "geoms", "keyframes", "cameras"];
|
|
9
9
|
function createEmptyRuntimeResources() {
|
|
10
10
|
return {
|
|
@@ -18,51 +18,51 @@ function createEmptyRuntimeResources() {
|
|
|
18
18
|
cameras: {}
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
function
|
|
22
|
-
for (const [
|
|
23
|
-
const existing =
|
|
21
|
+
function registerModelResources(resources) {
|
|
22
|
+
for (const [model, modelResources] of Object.entries(resources)) {
|
|
23
|
+
const existing = runtimeModelResources[model] ?? createEmptyRuntimeResources();
|
|
24
24
|
for (const key of REGISTER_RESOURCE_KEYS) {
|
|
25
|
-
existing[key] = { ...existing[key], ...
|
|
25
|
+
existing[key] = { ...existing[key], ...modelResources[key] ?? {} };
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
runtimeModelResources[model] = existing;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
function createResourceCategory(key) {
|
|
31
31
|
return new Proxy({}, {
|
|
32
|
-
get(_target,
|
|
33
|
-
if (typeof
|
|
34
|
-
return
|
|
32
|
+
get(_target, model) {
|
|
33
|
+
if (typeof model !== "string") return void 0;
|
|
34
|
+
return runtimeModelResources[model]?.[key] ?? {};
|
|
35
35
|
},
|
|
36
36
|
ownKeys() {
|
|
37
|
-
return Reflect.ownKeys(
|
|
37
|
+
return Reflect.ownKeys(runtimeModelResources);
|
|
38
38
|
},
|
|
39
|
-
getOwnPropertyDescriptor(_target,
|
|
40
|
-
if (typeof
|
|
39
|
+
getOwnPropertyDescriptor(_target, model) {
|
|
40
|
+
if (typeof model !== "string" || !(model in runtimeModelResources)) return void 0;
|
|
41
41
|
return { enumerable: true, configurable: true };
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
var
|
|
46
|
-
get(target,
|
|
47
|
-
if (typeof
|
|
48
|
-
return target[
|
|
45
|
+
var ModelResources = new Proxy(runtimeModelResources, {
|
|
46
|
+
get(target, model) {
|
|
47
|
+
if (typeof model !== "string") return void 0;
|
|
48
|
+
return target[model] ?? createEmptyRuntimeResources();
|
|
49
49
|
},
|
|
50
50
|
ownKeys(target) {
|
|
51
51
|
return Reflect.ownKeys(target);
|
|
52
52
|
},
|
|
53
|
-
getOwnPropertyDescriptor(target,
|
|
54
|
-
if (typeof
|
|
53
|
+
getOwnPropertyDescriptor(target, model) {
|
|
54
|
+
if (typeof model !== "string" || !(model in target)) return void 0;
|
|
55
55
|
return { enumerable: true, configurable: true };
|
|
56
56
|
}
|
|
57
57
|
});
|
|
58
|
-
var
|
|
59
|
-
var
|
|
60
|
-
var
|
|
61
|
-
var
|
|
62
|
-
var
|
|
63
|
-
var
|
|
64
|
-
var
|
|
65
|
-
var
|
|
58
|
+
var ModelActuators = createResourceCategory("actuators");
|
|
59
|
+
var ModelSensors = createResourceCategory("sensors");
|
|
60
|
+
var ModelBodies = createResourceCategory("bodies");
|
|
61
|
+
var ModelJoints = createResourceCategory("joints");
|
|
62
|
+
var ModelSites = createResourceCategory("sites");
|
|
63
|
+
var ModelGeoms = createResourceCategory("geoms");
|
|
64
|
+
var ModelKeyframes = createResourceCategory("keyframes");
|
|
65
|
+
var ModelCameras = createResourceCategory("cameras");
|
|
66
66
|
function getContact(contacts, i) {
|
|
67
67
|
try {
|
|
68
68
|
return contacts.get(i);
|
|
@@ -1077,6 +1077,6 @@ function clamp01(value) {
|
|
|
1077
1077
|
* Offscreen camera-frame capture for R3F/MuJoCo scenes.
|
|
1078
1078
|
*/
|
|
1079
1079
|
|
|
1080
|
-
export { CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY,
|
|
1081
|
-
//# sourceMappingURL=chunk-
|
|
1082
|
-
//# sourceMappingURL=chunk-
|
|
1080
|
+
export { CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY, CAPTURE_EXCLUDE_KEY, ModelActuators, ModelBodies, ModelCameras, ModelGeoms, ModelJoints, ModelKeyframes, ModelResources, ModelSensors, ModelSites, ScenarioLighting, SplatEnvironment, SplatEnvironmentReadinessStatus, VisualScenarioEffects, captureCameraFrame, captureCameraFrameBlob, createCameraFrameCaptureSession, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, createSplatSceneConfig, createVisualScenarioExecutionContext, getContact, getScenarioBackground, getScenarioCameraPosition, getSplatEnvironmentReadiness, registerModelResources, renderCameraFrameToCanvas, useSplatEnvironment, useSplatSceneConfig, useVisualScenarioEffects, useVisualScenarioExecutionContext, withContacts, withSplatEnvironment };
|
|
1081
|
+
//# sourceMappingURL=chunk-QTCAVQS6.js.map
|
|
1082
|
+
//# sourceMappingURL=chunk-QTCAVQS6.js.map
|