mujoco-react 9.3.0 → 9.4.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.
@@ -26,6 +26,8 @@ import type {
26
26
  ScenarioLightingProps,
27
27
  SplatEnvironmentProps,
28
28
  VisualScenarioConfig,
29
+ VisualScenarioExecutionContext,
30
+ VisualScenarioExecutionContextInput,
29
31
  VisualScenarioEffectsProps,
30
32
  } from '../types';
31
33
 
@@ -114,6 +116,84 @@ export function getScenarioCameraPosition(
114
116
  ];
115
117
  }
116
118
 
119
+ export function useVisualScenarioExecutionContext({
120
+ scenario,
121
+ environment,
122
+ renderer,
123
+ variantId,
124
+ enabled,
125
+ }: VisualScenarioExecutionContextInput): VisualScenarioExecutionContext {
126
+ return useMemo(
127
+ () =>
128
+ createVisualScenarioExecutionContext({
129
+ scenario,
130
+ environment,
131
+ renderer,
132
+ variantId,
133
+ enabled,
134
+ }),
135
+ [enabled, environment, renderer, scenario, variantId]
136
+ );
137
+ }
138
+
139
+ export function createVisualScenarioExecutionContext({
140
+ scenario,
141
+ environment,
142
+ renderer,
143
+ variantId,
144
+ enabled = true,
145
+ }: VisualScenarioExecutionContextInput): VisualScenarioExecutionContext {
146
+ const pairedEnvironment =
147
+ environment ??
148
+ (scenario ? createPairedSplatEnvironment(scenario, { renderer }) : undefined);
149
+ const splat = scenario?.splat;
150
+ const collisionProxy =
151
+ pairedEnvironment?.collisionProxy ?? splat?.collisionProxy ?? undefined;
152
+ const readiness = getSplatEnvironmentReadiness({
153
+ environment: pairedEnvironment,
154
+ scenario,
155
+ renderer,
156
+ enabled,
157
+ });
158
+ const format =
159
+ pairedEnvironment?.splat.format ?? splat?.format ?? readiness.format ?? 'spz';
160
+
161
+ return {
162
+ scenarioId: scenario?.id ?? pairedEnvironment?.id ?? 'visual-scenario',
163
+ scenarioLabel:
164
+ scenario?.label ?? pairedEnvironment?.label ?? 'Visual scenario',
165
+ variantId,
166
+ seed: scenario?.seed ?? 0,
167
+ lighting: scenario?.lighting ?? 'studio',
168
+ environment: scenario?.environment,
169
+ camera: {
170
+ jitter: scenario?.camera?.jitter ?? 0,
171
+ exposure: scenario?.camera?.exposure ?? 1,
172
+ noise: scenario?.camera?.noise ?? 0,
173
+ blur: scenario?.camera?.blur ?? 0,
174
+ },
175
+ materials: {
176
+ randomizeObjectColors: Boolean(
177
+ scenario?.materials?.randomizeObjectColors
178
+ ),
179
+ randomizeTableMaterial: Boolean(
180
+ scenario?.materials?.randomizeTableMaterial
181
+ ),
182
+ roughness: scenario?.materials?.roughness,
183
+ metalness: scenario?.materials?.metalness,
184
+ },
185
+ splatEnabled: Boolean(splat?.enabled || pairedEnvironment),
186
+ splatSrc: pairedEnvironment?.splat.src ?? splat?.src,
187
+ splatFormat: format,
188
+ splatRenderer: renderer ?? pairedEnvironment?.splat.renderer,
189
+ collisionProxyXmlPath: collisionProxy?.xmlPath,
190
+ collisionProxyStatus: collisionProxy?.status,
191
+ collisionProxyPrimitives: collisionProxy?.primitives ?? [],
192
+ readiness,
193
+ transformSource: 'visualScenario.camera',
194
+ };
195
+ }
196
+
117
197
  export function VisualScenarioEffects(props: VisualScenarioEffectsProps) {
118
198
  useVisualScenarioEffects(props);
119
199
  return null;
@@ -341,43 +421,55 @@ export function useSplatSceneConfig({
341
421
  enabled = true,
342
422
  renderer,
343
423
  }: SplatSceneConfigInput): SplatSceneConfigState {
344
- const resolvedEnvironment = useMemo(
345
- () =>
346
- enabled
347
- ? environment ??
348
- (scenario
349
- ? createPairedSplatEnvironment(scenario, { renderer })
350
- : undefined)
351
- : undefined,
352
- [enabled, environment, renderer, scenario]
353
- );
354
- const readiness = useMemo(
424
+ return useMemo(
355
425
  () =>
356
- getSplatEnvironmentReadiness({
357
- environment: resolvedEnvironment,
426
+ createSplatSceneConfig({
427
+ sceneConfig,
358
428
  scenario,
359
- renderer,
429
+ environment,
360
430
  enabled,
431
+ renderer,
361
432
  }),
362
- [enabled, renderer, resolvedEnvironment, scenario]
363
- );
364
- const resolvedSceneConfig = useMemo(
365
- () =>
366
- resolvedEnvironment
367
- ? withSplatEnvironment(sceneConfig, resolvedEnvironment)
368
- : sceneConfig,
369
- [resolvedEnvironment, sceneConfig]
433
+ [enabled, environment, renderer, scenario, sceneConfig]
370
434
  );
435
+ }
371
436
 
372
- return useMemo(
373
- () => ({
374
- environment: resolvedEnvironment,
375
- sceneConfig: resolvedSceneConfig,
376
- enabled: enabled && readiness.status !== SplatEnvironmentReadinessStatus.Disabled,
377
- readiness,
378
- }),
379
- [enabled, readiness, resolvedEnvironment, resolvedSceneConfig]
380
- );
437
+ /**
438
+ * Resolve a visual scenario's paired splat environment without requiring React.
439
+ *
440
+ * Use this in codegen, import validators, backend handoff metadata, or app code
441
+ * that needs the same behavior as `useSplatSceneConfig` outside a component.
442
+ */
443
+ export function createSplatSceneConfig({
444
+ sceneConfig,
445
+ scenario,
446
+ environment,
447
+ enabled = true,
448
+ renderer,
449
+ }: SplatSceneConfigInput): SplatSceneConfigState {
450
+ const resolvedEnvironment = enabled
451
+ ? environment ??
452
+ (scenario
453
+ ? createPairedSplatEnvironment(scenario, { renderer })
454
+ : undefined)
455
+ : undefined;
456
+ const readiness = getSplatEnvironmentReadiness({
457
+ environment: resolvedEnvironment,
458
+ scenario,
459
+ renderer,
460
+ enabled,
461
+ });
462
+ const resolvedSceneConfig = resolvedEnvironment
463
+ ? withSplatEnvironment(sceneConfig, resolvedEnvironment, { renderer })
464
+ : sceneConfig;
465
+
466
+ return {
467
+ environment: resolvedEnvironment,
468
+ sceneConfig: resolvedSceneConfig,
469
+ enabled:
470
+ enabled && readiness.status !== SplatEnvironmentReadinessStatus.Disabled,
471
+ readiness,
472
+ };
381
473
  }
382
474
 
383
475
  export function getSplatEnvironmentReadiness({
@@ -468,9 +560,9 @@ export function getSplatEnvironmentReadiness({
468
560
  }
469
561
 
470
562
  /**
471
- * Convert a generic visual scenario splat block into a paired visual/physics
472
- * environment config. Returns undefined until both the splat asset and MJCF
473
- * collision proxy are present.
563
+ * Convert a generic visual scenario splat block into a composable splat
564
+ * environment config. Visual-only splats are valid; readiness reports whether
565
+ * a paired MJCF collision proxy is required before training/physics handoff.
474
566
  */
475
567
  export function createPairedSplatEnvironment(
476
568
  scenario: Pick<VisualScenarioConfig, 'id' | 'label' | 'environment' | 'splat'>,
@@ -484,7 +576,7 @@ export function createPairedSplatEnvironment(
484
576
  const splat = scenario.splat;
485
577
  const collisionProxy = splat?.collisionProxy;
486
578
 
487
- if (!splat?.enabled || !splat.src || !collisionProxy?.xmlPath) {
579
+ if (!splat?.enabled || !splat.src) {
488
580
  return undefined;
489
581
  }
490
582
 
@@ -501,15 +593,22 @@ export function createPairedSplatEnvironment(
501
593
  format: splat.format ?? 'spz',
502
594
  renderer: options.renderer,
503
595
  },
504
- collisionProxy: {
505
- ...collisionProxy,
506
- xmlPath: collisionProxy.xmlPath,
507
- },
596
+ collisionProxy: collisionProxy?.xmlPath
597
+ ? {
598
+ ...collisionProxy,
599
+ xmlPath: collisionProxy.xmlPath,
600
+ }
601
+ : undefined,
508
602
  };
509
603
  }
510
604
 
511
605
  function isPairedSplatEnvironment(input: SplatSceneInput): input is PairedSplatEnvironmentConfig {
512
- return !!input && 'collisionProxy' in input && 'splat' in input;
606
+ return (
607
+ !!input &&
608
+ 'splat' in input &&
609
+ !!input.splat &&
610
+ !('enabled' in input.splat)
611
+ );
513
612
  }
514
613
 
515
614
  function sceneRelativePath(sceneConfig: SceneConfig, path: string): string {
@@ -549,7 +648,7 @@ export function withSplatEnvironment(
549
648
  : input
550
649
  ? createPairedSplatEnvironment(input, options)
551
650
  : undefined;
552
- const xmlPath = environment?.collisionProxy.xmlPath;
651
+ const xmlPath = environment?.collisionProxy?.xmlPath;
553
652
  if (!xmlPath) return sceneConfig;
554
653
 
555
654
  return {
@@ -1471,7 +1471,7 @@ export function MujocoSimProvider({
1471
1471
 
1472
1472
  const frame = {
1473
1473
  frameIndex,
1474
- time: getTime(),
1474
+ time: data.time,
1475
1475
  cameras: cameraFrames,
1476
1476
  };
1477
1477
  if (retainFrames) {
@@ -8,10 +8,12 @@
8
8
  import { useCallback, useState } from 'react';
9
9
  import { useMujoco } from '../core/MujocoSimProvider';
10
10
  import {
11
+ createMountedCameraFrameSequenceReadiness,
11
12
  createMountedCameraFrameSequencePlanFromApi,
12
13
  recordMountedCameraFrameSequence,
13
14
  type MountedCameraFrameSequencePlan,
14
15
  type MountedCameraFrameSequencePlanOptions,
16
+ type MountedCameraFrameSequenceReadiness,
15
17
  type MountedCameraFrameSequenceRecordOptions,
16
18
  type MountedCameraFrameSequenceRecordResult,
17
19
  } from '../rendering/cameraFrameSource';
@@ -26,13 +28,22 @@ export type MountedCameraSequenceRecordOptions =
26
28
  MountedCameraFrameSequenceRecordOptions;
27
29
  export type MountedCameraSequenceRecordResult =
28
30
  MountedCameraFrameSequenceRecordResult;
31
+ export type MountedCameraSequenceReadiness =
32
+ MountedCameraFrameSequenceReadiness;
29
33
 
30
34
  export interface MountedCameraSequenceRecorderAPI
31
35
  extends Omit<CameraFrameSequenceRecorderAPI, 'record'> {
36
+ plan: MountedCameraFrameSequencePlan | null;
37
+ readiness: MountedCameraSequenceReadiness | null;
38
+ result: MountedCameraSequenceRecordResult | null;
32
39
  createPlan: (
33
40
  cameraKeys: readonly string[],
34
41
  options?: MountedCameraSequencePlanOptions
35
42
  ) => MountedCameraFrameSequencePlan;
43
+ checkReadiness: (
44
+ cameraKeys: readonly string[],
45
+ options?: MountedCameraSequencePlanOptions
46
+ ) => MountedCameraSequenceReadiness;
36
47
  record: (
37
48
  options: MountedCameraSequenceRecordOptions
38
49
  ) => Promise<MountedCameraSequenceRecordResult>;
@@ -44,10 +55,19 @@ export function useMountedCameraSequenceRecorder(
44
55
  const mujoco = useMujoco();
45
56
  const [status, setStatus] = useState<FrameCaptureStatus>('idle');
46
57
  const [error, setError] = useState<Error | null>(null);
58
+ const [plan, setPlan] = useState<MountedCameraFrameSequencePlan | null>(null);
59
+ const [readiness, setReadiness] =
60
+ useState<MountedCameraSequenceReadiness | null>(null);
61
+ const [result, setResult] = useState<MountedCameraSequenceRecordResult | null>(
62
+ null
63
+ );
47
64
 
48
65
  const reset = useCallback(() => {
49
66
  setStatus('idle');
50
67
  setError(null);
68
+ setPlan(null);
69
+ setReadiness(null);
70
+ setResult(null);
51
71
  }, []);
52
72
 
53
73
  const createPlan = useCallback(
@@ -59,14 +79,34 @@ export function useMountedCameraSequenceRecorder(
59
79
  throw new Error('MuJoCo scene is not ready for mounted camera sequence planning.');
60
80
  }
61
81
 
62
- return createMountedCameraFrameSequencePlanFromApi(mujoco.api, cameraKeys, {
63
- ...defaultOptions,
64
- ...options,
65
- });
82
+ const nextPlan = createMountedCameraFrameSequencePlanFromApi(
83
+ mujoco.api,
84
+ cameraKeys,
85
+ {
86
+ ...defaultOptions,
87
+ ...options,
88
+ }
89
+ );
90
+ setPlan(nextPlan);
91
+ setReadiness(null);
92
+ return nextPlan;
66
93
  },
67
94
  [defaultOptions, mujoco.api]
68
95
  );
69
96
 
97
+ const checkReadiness = useCallback(
98
+ (
99
+ cameraKeys: readonly string[],
100
+ options: MountedCameraSequencePlanOptions = {}
101
+ ) => {
102
+ const nextPlan = createPlan(cameraKeys, options);
103
+ const nextReadiness = createMountedCameraFrameSequenceReadiness(nextPlan);
104
+ setReadiness(nextReadiness);
105
+ return nextReadiness;
106
+ },
107
+ [createPlan]
108
+ );
109
+
70
110
  const record = useCallback(
71
111
  async (options: MountedCameraSequenceRecordOptions) => {
72
112
  if (!mujoco.api) {
@@ -75,14 +115,18 @@ export function useMountedCameraSequenceRecorder(
75
115
 
76
116
  setStatus('capturing');
77
117
  setError(null);
118
+ setResult(null);
78
119
 
79
120
  try {
80
- const result = await recordMountedCameraFrameSequence(mujoco.api, {
121
+ const nextResult = await recordMountedCameraFrameSequence(mujoco.api, {
81
122
  ...defaultOptions,
82
123
  ...options,
83
124
  });
125
+ setPlan(nextResult.plan);
126
+ setReadiness(nextResult.readiness);
127
+ setResult(nextResult);
84
128
  setStatus('captured');
85
- return result;
129
+ return nextResult;
86
130
  } catch (nextError) {
87
131
  const error =
88
132
  nextError instanceof Error
@@ -99,8 +143,12 @@ export function useMountedCameraSequenceRecorder(
99
143
  return {
100
144
  status,
101
145
  error,
146
+ plan,
147
+ readiness,
148
+ result,
102
149
  isRecording: status === 'capturing',
103
150
  createPlan,
151
+ checkReadiness,
104
152
  record,
105
153
  reset,
106
154
  };
package/src/index.ts CHANGED
@@ -48,14 +48,32 @@ export {
48
48
  createPairedSplatEnvironment,
49
49
  createSparkSplatViewerUrl,
50
50
  createSplatEnvironmentUserData,
51
+ createSplatSceneConfig,
52
+ createVisualScenarioExecutionContext,
51
53
  getSplatEnvironmentReadiness,
52
54
  getScenarioBackground,
53
55
  getScenarioCameraPosition,
54
56
  useSplatEnvironment,
55
57
  useSplatSceneConfig,
58
+ useVisualScenarioExecutionContext,
56
59
  useVisualScenarioEffects,
57
60
  withSplatEnvironment,
58
61
  } from './components/VisualScenario';
62
+ export {
63
+ canFetchSplatCollisionProxyXml,
64
+ fetchSplatCollisionProxyXml,
65
+ parseSplatCollisionProxyGeoms,
66
+ SplatCollisionProxyPreview,
67
+ useSplatCollisionProxyGeoms,
68
+ } from './components/SplatCollisionProxyPreview';
69
+ export type {
70
+ SplatCollisionProxyGeomPreview,
71
+ SplatCollisionProxyGeomsState,
72
+ SplatCollisionProxyPreviewProps,
73
+ SplatCollisionProxyPreviewStatus,
74
+ SplatCollisionProxyPreviewVector3,
75
+ UseSplatCollisionProxyGeomsOptions,
76
+ } from './components/SplatCollisionProxyPreview';
59
77
  export { Debug } from './components/Debug';
60
78
  export { TendonRenderer } from './components/TendonRenderer';
61
79
  export { FlexRenderer } from './components/FlexRenderer';
@@ -90,6 +108,7 @@ export { useMountedCameraSequenceRecorder } from './hooks/useMountedCameraSequen
90
108
  export type {
91
109
  MountedCameraSequencePlanOptions,
92
110
  MountedCameraSequenceRecorderAPI,
111
+ MountedCameraSequenceReadiness,
93
112
  MountedCameraSequenceRecordOptions,
94
113
  MountedCameraSequenceRecordResult,
95
114
  } from './hooks/useMountedCameraSequenceRecorder';
@@ -100,8 +119,12 @@ export {
100
119
  renderCameraFrameToCanvas,
101
120
  } from './rendering/cameraFrameCapture';
102
121
  export {
122
+ createMountedCameraFrameSequenceManifest,
103
123
  createMountedCameraFrameSequenceReadiness,
124
+ createMountedCameraFrameSourceSuggestions,
125
+ MountedCameraFrameSequenceManifestStatus,
104
126
  MountedCameraFrameSequenceReadinessStatus,
127
+ MountedCameraFrameSourceSuggestionMatch,
105
128
  createMountedCameraFrameSequencePlanFromApi,
106
129
  createMountedCameraFrameSequencePlan,
107
130
  getCameraFrameCaptureSourceTarget,
@@ -112,8 +135,10 @@ export {
112
135
  } from './rendering/cameraFrameSource';
113
136
  export type {
114
137
  CameraFrameMountSelector,
138
+ CreateMountedCameraFrameSequenceManifestOptions,
115
139
  CreateMountedCameraFrameSequencePlanOptions,
116
140
  MountedCameraFrameCaptureSource,
141
+ MountedCameraFrameSequenceManifest,
117
142
  MountedCameraFrameSequencePlanOptions,
118
143
  MountedCameraFrameSequenceRecorderTarget,
119
144
  MountedCameraFrameSequenceCameraOptions,
@@ -123,6 +148,8 @@ export type {
123
148
  MountedCameraFrameSequenceRecordOptions,
124
149
  MountedCameraFrameSequenceRecordResult,
125
150
  MountedCameraFrameSequenceSourceReadiness,
151
+ MountedCameraFrameSequenceStreamSummary,
152
+ MountedCameraFrameSourceSuggestion,
126
153
  NamedCameraFrameResource,
127
154
  ResolveMountedCameraFrameSourceOptions,
128
155
  ResolvedMountedCameraFrameSource,
@@ -214,6 +241,10 @@ export type {
214
241
  SplatEnvironmentReadiness,
215
242
  SplatEnvironmentMetadataInput,
216
243
  SplatEnvironmentMetadata,
244
+ VisualScenarioExecutionContext,
245
+ VisualScenarioExecutionContextInput,
246
+ ResolvedScenarioCameraConfig,
247
+ ResolvedScenarioMaterialConfig,
217
248
  SplatSceneConfigInput,
218
249
  SplatSceneConfigState,
219
250
  SplatSceneInput,