mujoco-react 8.4.2 → 8.6.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 +128 -14
- package/bin/mujoco-react-codegen.mjs +3 -0
- package/bin/mujoco-react.mjs +86 -0
- package/dist/index.d.ts +100 -3
- package/dist/index.js +548 -76
- package/dist/index.js.map +1 -1
- package/dist/vite.d.ts +47 -0
- package/dist/vite.js +162 -0
- package/dist/vite.js.map +1 -0
- package/package.json +13 -1
- package/src/components/InstancedGeomRenderer.tsx +158 -0
- package/src/components/SceneRenderer.tsx +51 -12
- package/src/core/MujocoCanvas.tsx +2 -0
- package/src/core/MujocoPhysics.tsx +2 -0
- package/src/core/MujocoSimProvider.tsx +138 -10
- package/src/core/ObservationBuilder.ts +120 -0
- package/src/core/SceneLoader.ts +190 -60
- package/src/hooks/useObservation.ts +38 -0
- package/src/index.ts +9 -0
- package/src/types.ts +64 -1
- package/src/vite.ts +223 -0
package/src/core/SceneLoader.ts
CHANGED
|
@@ -10,6 +10,8 @@ import type {
|
|
|
10
10
|
ControlGroupSelector,
|
|
11
11
|
ControlJointInfo,
|
|
12
12
|
JointInfo,
|
|
13
|
+
LoadFromFilesOptions,
|
|
14
|
+
LocalMujocoFile,
|
|
13
15
|
MujocoData,
|
|
14
16
|
MujocoModel,
|
|
15
17
|
MujocoModule,
|
|
@@ -505,6 +507,170 @@ function loadModelFromPath(mujoco: MujocoModule, path: string): MujocoModel {
|
|
|
505
507
|
throw new Error('MuJoCo WASM module does not expose an XML path loader');
|
|
506
508
|
}
|
|
507
509
|
|
|
510
|
+
function isModelTextFile(fname: string): boolean {
|
|
511
|
+
const lower = fname.toLowerCase();
|
|
512
|
+
return lower.endsWith('.xml') || lower.endsWith('.urdf') || lower.endsWith('.mjcf');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function normalizeVfsPath(path: string): string {
|
|
516
|
+
const parts = path.replace(/\\/g, '/').split('/');
|
|
517
|
+
const norm: string[] = [];
|
|
518
|
+
for (const part of parts) {
|
|
519
|
+
if (!part || part === '.') continue;
|
|
520
|
+
if (part === '..') norm.pop();
|
|
521
|
+
else norm.push(part);
|
|
522
|
+
}
|
|
523
|
+
return norm.join('/');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function localFilePath(file: LocalMujocoFile): string {
|
|
527
|
+
return normalizeVfsPath(file.webkitRelativePath || file.name);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function inferSceneFile(files: readonly LocalMujocoFile[], options?: LoadFromFilesOptions): string {
|
|
531
|
+
if (options?.sceneFile) return normalizeVfsPath(options.sceneFile);
|
|
532
|
+
|
|
533
|
+
const paths = files.map(localFilePath);
|
|
534
|
+
const preferred = ['scene.xml', 'model.xml', 'robot.xml', 'scene.urdf', 'model.urdf', 'robot.urdf'];
|
|
535
|
+
for (const name of preferred) {
|
|
536
|
+
const match = paths.find((path) => path.endsWith(name));
|
|
537
|
+
if (match) return match;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const firstModel = paths.find(isModelTextFile);
|
|
541
|
+
if (!firstModel) throw new Error('No MJCF XML or URDF file found in FileList');
|
|
542
|
+
return firstModel;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export function createSceneConfigFromFiles(
|
|
546
|
+
files: FileList | readonly LocalMujocoFile[],
|
|
547
|
+
options: LoadFromFilesOptions = {}
|
|
548
|
+
): SceneConfig {
|
|
549
|
+
const fileArray = Array.from(files) as LocalMujocoFile[];
|
|
550
|
+
return {
|
|
551
|
+
src: '',
|
|
552
|
+
sceneFile: inferSceneFile(fileArray, options),
|
|
553
|
+
files: fileArray,
|
|
554
|
+
homeJoints: options.homeJoints,
|
|
555
|
+
xmlPatches: options.xmlPatches,
|
|
556
|
+
sceneObjects: options.sceneObjects,
|
|
557
|
+
onReset: options.onReset,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function applyXmlPatches(text: string, fname: string, config: SceneConfig): string {
|
|
562
|
+
let result = text;
|
|
563
|
+
for (const patch of config.xmlPatches ?? []) {
|
|
564
|
+
if (fname.endsWith(patch.target) || fname === patch.target) {
|
|
565
|
+
if (patch.replace) {
|
|
566
|
+
const [from, to] = patch.replace;
|
|
567
|
+
if (result.includes(from)) {
|
|
568
|
+
result = result.replace(from, to);
|
|
569
|
+
} else {
|
|
570
|
+
const preview = from.length > 80 ? `${from.slice(0, 80)}...` : from;
|
|
571
|
+
console.warn(`XML patch replace pattern not found in ${fname}: "${preview}"`);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (patch.inject && patch.injectAfter) {
|
|
575
|
+
const idx = result.indexOf(patch.injectAfter);
|
|
576
|
+
if (idx !== -1) {
|
|
577
|
+
const tagEnd = result.indexOf('>', idx + patch.injectAfter.length);
|
|
578
|
+
if (tagEnd !== -1) {
|
|
579
|
+
result = result.slice(0, tagEnd + 1) + patch.inject + result.slice(tagEnd + 1);
|
|
580
|
+
} else {
|
|
581
|
+
console.warn(`XML patch inject failed in ${fname}: could not find tag end after "${patch.injectAfter}"`);
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
const preview = patch.injectAfter.length > 80
|
|
585
|
+
? `${patch.injectAfter.slice(0, 80)}...`
|
|
586
|
+
: patch.injectAfter;
|
|
587
|
+
console.warn(`XML patch inject anchor not found in ${fname}: "${preview}"`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (fname === config.sceneFile && config.sceneObjects?.length && result.includes('</worldbody>')) {
|
|
594
|
+
const xml = config.sceneObjects.map((obj) => sceneObjectToXml(obj)).join('');
|
|
595
|
+
result = result.replace('</worldbody>', xml + '</worldbody>');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
return result;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function loadSceneFromFiles(
|
|
602
|
+
mujoco: MujocoModule,
|
|
603
|
+
config: SceneConfig,
|
|
604
|
+
onProgress?: (msg: string) => void
|
|
605
|
+
): Promise<LoadResult> {
|
|
606
|
+
const files = config.files ?? [];
|
|
607
|
+
if (!files.length) throw new Error('loadFromFiles requires at least one File');
|
|
608
|
+
|
|
609
|
+
try { mujoco.FS.unmount('/working'); } catch { /* ignore */ }
|
|
610
|
+
try { mujoco.FS.mkdir('/working'); } catch { /* ignore */ }
|
|
611
|
+
|
|
612
|
+
const parser = new DOMParser();
|
|
613
|
+
const byPath = new Map<string, LocalMujocoFile>();
|
|
614
|
+
const byBasename = new Map<string, LocalMujocoFile>();
|
|
615
|
+
const written = new Set<string>();
|
|
616
|
+
const textByPath = new Map<string, string>();
|
|
617
|
+
|
|
618
|
+
for (const file of files) {
|
|
619
|
+
const path = localFilePath(file);
|
|
620
|
+
byPath.set(path, file);
|
|
621
|
+
byBasename.set(path.split('/').pop() ?? path, file);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
for (const [path, file] of byPath) {
|
|
625
|
+
onProgress?.(`Reading ${path}...`);
|
|
626
|
+
ensureDir(mujoco, path);
|
|
627
|
+
if (isModelTextFile(path)) {
|
|
628
|
+
const text = applyXmlPatches(await file.text(), path, config);
|
|
629
|
+
textByPath.set(path, text);
|
|
630
|
+
mujoco.FS.writeFile(`/working/${path}`, text);
|
|
631
|
+
} else {
|
|
632
|
+
mujoco.FS.writeFile(`/working/${path}`, new Uint8Array(await file.arrayBuffer()));
|
|
633
|
+
}
|
|
634
|
+
written.add(path);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
for (const [path, text] of textByPath) {
|
|
638
|
+
const deps = collectDependencyPaths(text, path, parser);
|
|
639
|
+
for (const dep of deps) {
|
|
640
|
+
if (written.has(dep)) continue;
|
|
641
|
+
const file = byPath.get(dep) ?? byBasename.get(dep.split('/').pop() ?? dep);
|
|
642
|
+
if (!file) continue;
|
|
643
|
+
ensureDir(mujoco, dep);
|
|
644
|
+
if (isModelTextFile(dep)) {
|
|
645
|
+
mujoco.FS.writeFile(`/working/${dep}`, applyXmlPatches(await file.text(), dep, config));
|
|
646
|
+
} else {
|
|
647
|
+
mujoco.FS.writeFile(`/working/${dep}`, new Uint8Array(await file.arrayBuffer()));
|
|
648
|
+
}
|
|
649
|
+
written.add(dep);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
onProgress?.('Loading model...');
|
|
654
|
+
const mjModel = loadModelFromPath(mujoco, `/working/${config.sceneFile}`);
|
|
655
|
+
const mjData = new mujoco.MjData(mjModel);
|
|
656
|
+
applyInitialPose(mjModel, mjData, config);
|
|
657
|
+
mujoco.mj_forward(mjModel, mjData);
|
|
658
|
+
|
|
659
|
+
return { mjModel, mjData };
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function applyInitialPose(mjModel: MujocoModel, mjData: MujocoData, config: SceneConfig) {
|
|
663
|
+
if (!config.homeJoints) return;
|
|
664
|
+
const homeCount = Math.min(config.homeJoints.length, Math.max(mjModel.nu, mjModel.nq));
|
|
665
|
+
for (let i = 0; i < homeCount; i++) {
|
|
666
|
+
if (i < mjModel.nu) mjData.ctrl[i] = config.homeJoints[i];
|
|
667
|
+
if (i < mjModel.nq) {
|
|
668
|
+
const qposAdr = i < mjModel.nu ? getActuatedScalarQposAdr(mjModel, i) : -1;
|
|
669
|
+
mjData.qpos[qposAdr !== -1 ? qposAdr : i] = config.homeJoints[i];
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
508
674
|
/**
|
|
509
675
|
* Config-driven scene loader — replaces the old RobotLoader + patchSingleRobot approach.
|
|
510
676
|
*/
|
|
@@ -513,6 +679,10 @@ export async function loadScene(
|
|
|
513
679
|
config: SceneConfig,
|
|
514
680
|
onProgress?: (msg: string) => void
|
|
515
681
|
): Promise<LoadResult> {
|
|
682
|
+
if (config.files?.length) {
|
|
683
|
+
return loadSceneFromFiles(mujoco, config, onProgress);
|
|
684
|
+
}
|
|
685
|
+
|
|
516
686
|
// 1. Clean up virtual filesystem
|
|
517
687
|
try { mujoco.FS.unmount('/working'); } catch { /* ignore */ }
|
|
518
688
|
try { mujoco.FS.mkdir('/working'); } catch { /* ignore */ }
|
|
@@ -530,7 +700,7 @@ export async function loadScene(
|
|
|
530
700
|
if (downloaded.has(fname)) continue;
|
|
531
701
|
downloaded.add(fname);
|
|
532
702
|
|
|
533
|
-
if (!fname
|
|
703
|
+
if (!isModelTextFile(fname)) {
|
|
534
704
|
// Non-XML discovered during XML scan — collect for parallel download
|
|
535
705
|
assetFiles.push(fname);
|
|
536
706
|
continue;
|
|
@@ -544,44 +714,7 @@ export async function loadScene(
|
|
|
544
714
|
continue;
|
|
545
715
|
}
|
|
546
716
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
// 3. Apply XML patches from config
|
|
550
|
-
for (const patch of config.xmlPatches ?? []) {
|
|
551
|
-
if (fname.endsWith(patch.target) || fname === patch.target) {
|
|
552
|
-
if (patch.replace) {
|
|
553
|
-
const [from, to] = patch.replace;
|
|
554
|
-
if (text.includes(from)) {
|
|
555
|
-
text = text.replace(from, to);
|
|
556
|
-
} else {
|
|
557
|
-
const preview = from.length > 80 ? `${from.slice(0, 80)}...` : from;
|
|
558
|
-
console.warn(`XML patch replace pattern not found in ${fname}: "${preview}"`);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
if (patch.inject && patch.injectAfter) {
|
|
562
|
-
const idx = text.indexOf(patch.injectAfter);
|
|
563
|
-
if (idx !== -1) {
|
|
564
|
-
const tagEnd = text.indexOf('>', idx + patch.injectAfter.length);
|
|
565
|
-
if (tagEnd !== -1) {
|
|
566
|
-
text = text.slice(0, tagEnd + 1) + patch.inject + text.slice(tagEnd + 1);
|
|
567
|
-
} else {
|
|
568
|
-
console.warn(`XML patch inject failed in ${fname}: could not find tag end after "${patch.injectAfter}"`);
|
|
569
|
-
}
|
|
570
|
-
} else {
|
|
571
|
-
const preview = patch.injectAfter.length > 80
|
|
572
|
-
? `${patch.injectAfter.slice(0, 80)}...`
|
|
573
|
-
: patch.injectAfter;
|
|
574
|
-
console.warn(`XML patch inject anchor not found in ${fname}: "${preview}"`);
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// 4. Inject scene objects into the scene file
|
|
581
|
-
if (fname === config.sceneFile && config.sceneObjects?.length) {
|
|
582
|
-
const xml = config.sceneObjects.map((obj) => sceneObjectToXml(obj)).join('');
|
|
583
|
-
text = text.replace('</worldbody>', xml + '</worldbody>');
|
|
584
|
-
}
|
|
717
|
+
const text = applyXmlPatches(await res.text(), fname, config);
|
|
585
718
|
|
|
586
719
|
ensureDir(mujoco, fname);
|
|
587
720
|
mujoco.FS.writeFile(`/working/${fname}`, text);
|
|
@@ -617,16 +750,7 @@ export async function loadScene(
|
|
|
617
750
|
|
|
618
751
|
// 6. Set initial pose — set both ctrl and qpos so robot starts at home.
|
|
619
752
|
// If homeJoints is not provided, keep raw MuJoCo defaults.
|
|
620
|
-
|
|
621
|
-
const homeCount = Math.min(config.homeJoints.length, mjModel.nu);
|
|
622
|
-
for (let i = 0; i < homeCount; i++) {
|
|
623
|
-
mjData.ctrl[i] = config.homeJoints[i];
|
|
624
|
-
const qposAdr = getActuatedScalarQposAdr(mjModel, i);
|
|
625
|
-
if (qposAdr !== -1) {
|
|
626
|
-
mjData.qpos[qposAdr] = config.homeJoints[i];
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
753
|
+
applyInitialPose(mjModel, mjData, config);
|
|
630
754
|
|
|
631
755
|
mujoco.mj_forward(mjModel, mjData);
|
|
632
756
|
|
|
@@ -643,6 +767,16 @@ function scanDependencies(
|
|
|
643
767
|
downloaded: Set<string>,
|
|
644
768
|
queue: string[]
|
|
645
769
|
) {
|
|
770
|
+
for (const fullPath of collectDependencyPaths(xmlString, currentFile, parser)) {
|
|
771
|
+
if (!downloaded.has(fullPath)) queue.push(fullPath);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function collectDependencyPaths(
|
|
776
|
+
xmlString: string,
|
|
777
|
+
currentFile: string,
|
|
778
|
+
parser: DOMParser
|
|
779
|
+
): string[] {
|
|
646
780
|
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
|
|
647
781
|
|
|
648
782
|
const compiler = xmlDoc.querySelector('compiler');
|
|
@@ -653,9 +787,11 @@ function scanDependencies(
|
|
|
653
787
|
? currentFile.substring(0, currentFile.lastIndexOf('/') + 1)
|
|
654
788
|
: '';
|
|
655
789
|
|
|
656
|
-
|
|
657
|
-
|
|
790
|
+
const paths: string[] = [];
|
|
791
|
+
xmlDoc.querySelectorAll('[file], [filename]').forEach((el) => {
|
|
792
|
+
const fileAttr = el.getAttribute('file') ?? el.getAttribute('filename');
|
|
658
793
|
if (!fileAttr) return;
|
|
794
|
+
if (/^[a-z]+:\/\//i.test(fileAttr) || fileAttr.startsWith('package://')) return;
|
|
659
795
|
|
|
660
796
|
let prefix = '';
|
|
661
797
|
if (el.tagName.toLowerCase() === 'mesh') {
|
|
@@ -664,15 +800,9 @@ function scanDependencies(
|
|
|
664
800
|
prefix = textureDir ? textureDir + '/' : '';
|
|
665
801
|
}
|
|
666
802
|
|
|
667
|
-
|
|
668
|
-
const parts = fullPath.split('/');
|
|
669
|
-
const norm: string[] = [];
|
|
670
|
-
for (const p of parts) {
|
|
671
|
-
if (p === '..') norm.pop();
|
|
672
|
-
else if (p !== '.') norm.push(p);
|
|
673
|
-
}
|
|
674
|
-
fullPath = norm.join('/');
|
|
803
|
+
const fullPath = normalizeVfsPath(currentDir + prefix + fileAttr);
|
|
675
804
|
|
|
676
|
-
|
|
805
|
+
paths.push(fullPath);
|
|
677
806
|
});
|
|
807
|
+
return paths;
|
|
678
808
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useMemo, useRef } from 'react';
|
|
7
|
+
import { useMujocoContext } from '../core/MujocoSimProvider';
|
|
8
|
+
import { buildObservation } from '../core/ObservationBuilder';
|
|
9
|
+
import type { ObservationConfig, ObservationHandle, ObservationResult } from '../types';
|
|
10
|
+
|
|
11
|
+
const EMPTY_OBSERVATION: ObservationResult = {
|
|
12
|
+
values: new Float32Array(0),
|
|
13
|
+
layout: [],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Live observation reader for policy loops and telemetry.
|
|
18
|
+
*
|
|
19
|
+
* The handle is stable; call `read()` inside callbacks to sample the latest
|
|
20
|
+
* MuJoCo model/data state without forcing React renders.
|
|
21
|
+
*/
|
|
22
|
+
export function useObservation(config: ObservationConfig): ObservationHandle {
|
|
23
|
+
const { mjModelRef, mjDataRef } = useMujocoContext();
|
|
24
|
+
const configRef = useRef(config);
|
|
25
|
+
configRef.current = config;
|
|
26
|
+
|
|
27
|
+
return useMemo(() => ({
|
|
28
|
+
read() {
|
|
29
|
+
const model = mjModelRef.current;
|
|
30
|
+
const data = mjDataRef.current;
|
|
31
|
+
if (!model || !data) return EMPTY_OBSERVATION;
|
|
32
|
+
return buildObservation(model, data, configRef.current);
|
|
33
|
+
},
|
|
34
|
+
readValues() {
|
|
35
|
+
return this.read().values;
|
|
36
|
+
},
|
|
37
|
+
}), [mjDataRef, mjModelRef]);
|
|
38
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export {
|
|
|
26
26
|
resolveControlGroup,
|
|
27
27
|
createContiguousControlGroup,
|
|
28
28
|
} from './core/SceneLoader';
|
|
29
|
+
export { buildObservation } from './core/ObservationBuilder';
|
|
29
30
|
|
|
30
31
|
// Controller factory
|
|
31
32
|
export { createController, createControllerHook } from './core/createController';
|
|
@@ -43,6 +44,7 @@ export { SceneLights } from './components/SceneLights';
|
|
|
43
44
|
export { Debug } from './components/Debug';
|
|
44
45
|
export { TendonRenderer } from './components/TendonRenderer';
|
|
45
46
|
export { FlexRenderer } from './components/FlexRenderer';
|
|
47
|
+
export { InstancedGeomRenderer } from './components/InstancedGeomRenderer';
|
|
46
48
|
export { ContactListener } from './components/ContactListener';
|
|
47
49
|
export { TrajectoryPlayer } from './components/TrajectoryPlayer';
|
|
48
50
|
|
|
@@ -57,6 +59,7 @@ export { useCtrl } from './hooks/useCtrl';
|
|
|
57
59
|
export { useContacts, useContactEvents } from './hooks/useContacts';
|
|
58
60
|
export { useKeyboardTeleop } from './hooks/useKeyboardTeleop';
|
|
59
61
|
export { usePolicy } from './hooks/usePolicy';
|
|
62
|
+
export { useObservation } from './hooks/useObservation';
|
|
60
63
|
export { useTrajectoryPlayer } from './hooks/useTrajectoryPlayer';
|
|
61
64
|
export { useTrajectoryRecorder } from './hooks/useTrajectoryRecorder';
|
|
62
65
|
export { useGamepad } from './hooks/useGamepad';
|
|
@@ -112,6 +115,12 @@ export type {
|
|
|
112
115
|
KeyboardTeleopConfig,
|
|
113
116
|
// Policy
|
|
114
117
|
PolicyConfig,
|
|
118
|
+
// Observations
|
|
119
|
+
ObservationConfig,
|
|
120
|
+
ObservationHandle,
|
|
121
|
+
ObservationLayoutItem,
|
|
122
|
+
ObservationOutput,
|
|
123
|
+
ObservationResult,
|
|
115
124
|
// Component props
|
|
116
125
|
BodyProps,
|
|
117
126
|
IkGizmoProps,
|
package/src/types.ts
CHANGED
|
@@ -331,11 +331,24 @@ export interface XmlPatch {
|
|
|
331
331
|
replace?: [string, string];
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
export type LocalMujocoFile = File;
|
|
335
|
+
|
|
336
|
+
export interface LoadFromFilesOptions {
|
|
337
|
+
/** Entry MJCF/URDF file. Inferred from scene.xml, model.xml, robot.xml, or the first XML/URDF file when omitted. */
|
|
338
|
+
sceneFile?: string;
|
|
339
|
+
homeJoints?: number[];
|
|
340
|
+
xmlPatches?: XmlPatch[];
|
|
341
|
+
sceneObjects?: SceneObject[];
|
|
342
|
+
onReset?: (model: MujocoModel, data: MujocoData) => void;
|
|
343
|
+
}
|
|
344
|
+
|
|
334
345
|
export interface SceneConfig {
|
|
335
346
|
/** Base URL for fetching model files. The loader fetches `src + sceneFile` and follows dependencies. */
|
|
336
347
|
src: string;
|
|
337
|
-
/** Entry MJCF XML file name, e.g. 'scene.xml'. */
|
|
348
|
+
/** Entry MJCF XML or URDF file name, e.g. 'scene.xml' or 'robot.urdf'. */
|
|
338
349
|
sceneFile: string;
|
|
350
|
+
/** Browser-selected files for local MJCF/URDF loading. Preserves webkitRelativePath when available. */
|
|
351
|
+
files?: readonly LocalMujocoFile[];
|
|
339
352
|
sceneObjects?: SceneObject[];
|
|
340
353
|
homeJoints?: number[];
|
|
341
354
|
xmlPatches?: XmlPatch[];
|
|
@@ -595,6 +608,51 @@ export interface PolicyConfig {
|
|
|
595
608
|
onAction: (action: Float32Array | Float64Array | number[], model: MujocoModel, data: MujocoData) => void;
|
|
596
609
|
}
|
|
597
610
|
|
|
611
|
+
// ---- Observation Builder ----
|
|
612
|
+
|
|
613
|
+
export type ObservationOutput = 'float32' | 'float64';
|
|
614
|
+
|
|
615
|
+
export interface ObservationConfig {
|
|
616
|
+
/** Include scalar simulation time. */
|
|
617
|
+
time?: boolean;
|
|
618
|
+
/** Include all qpos values. */
|
|
619
|
+
qpos?: boolean;
|
|
620
|
+
/** Include all qvel values. */
|
|
621
|
+
qvel?: boolean;
|
|
622
|
+
/** Include all ctrl values. */
|
|
623
|
+
ctrl?: boolean;
|
|
624
|
+
/** Include all actuator activation values. */
|
|
625
|
+
act?: boolean;
|
|
626
|
+
/** Include all raw sensordata values. */
|
|
627
|
+
sensordata?: boolean;
|
|
628
|
+
/** Include named sensor values in the configured order. */
|
|
629
|
+
sensors?: readonly Sensors[];
|
|
630
|
+
/** Include named site world positions in the configured order. */
|
|
631
|
+
sites?: readonly Sites[];
|
|
632
|
+
/** Include world gravity projected into each named body's local frame. */
|
|
633
|
+
projectedGravity?: Bodies | readonly Bodies[];
|
|
634
|
+
/** Output array type. Defaults to Float32Array. */
|
|
635
|
+
output?: ObservationOutput;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export interface ObservationLayoutItem {
|
|
639
|
+
name: string;
|
|
640
|
+
start: number;
|
|
641
|
+
size: number;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export interface ObservationResult {
|
|
645
|
+
values: Float32Array | Float64Array;
|
|
646
|
+
layout: ObservationLayoutItem[];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export interface ObservationHandle {
|
|
650
|
+
/** Read a fresh observation from the current live MuJoCo model/data refs. */
|
|
651
|
+
read(): ObservationResult;
|
|
652
|
+
/** Read just the vector values for policy inference. */
|
|
653
|
+
readValues(): Float32Array | Float64Array;
|
|
654
|
+
}
|
|
655
|
+
|
|
598
656
|
// ---- Debug Component (spec 6.1) ----
|
|
599
657
|
|
|
600
658
|
export interface DebugProps {
|
|
@@ -733,6 +791,10 @@ export interface MujocoSimAPI {
|
|
|
733
791
|
|
|
734
792
|
// Model loading (spec 9.1)
|
|
735
793
|
loadScene(newConfig: SceneConfig): Promise<void>;
|
|
794
|
+
loadFromFiles(files: FileList | readonly LocalMujocoFile[], options?: LoadFromFilesOptions): Promise<void>;
|
|
795
|
+
addBody(body: SceneObject): Promise<void>;
|
|
796
|
+
removeBody(name: Bodies): Promise<void>;
|
|
797
|
+
recompile(patches?: XmlPatch[]): Promise<void>;
|
|
736
798
|
|
|
737
799
|
// Canvas
|
|
738
800
|
getCanvasSnapshot(width?: number, height?: number, mimeType?: string): string;
|
|
@@ -767,6 +829,7 @@ export type MujocoCanvasProps = Omit<CanvasProps, 'onError'> & {
|
|
|
767
829
|
substeps?: number;
|
|
768
830
|
paused?: boolean;
|
|
769
831
|
speed?: number;
|
|
832
|
+
interpolate?: boolean;
|
|
770
833
|
};
|
|
771
834
|
|
|
772
835
|
// ---- Hook Return Types ----
|