three-usd-robot 0.5.0 → 0.7.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.
@@ -1,5 +1,56 @@
1
1
  import { unzipSync } from 'fflate';
2
2
 
3
+ // src/parser/ast.ts
4
+ var Quat = class _Quat {
5
+ constructor(real, imaginary) {
6
+ this.real = real;
7
+ this.imaginary = imaginary;
8
+ }
9
+ real;
10
+ imaginary;
11
+ /** Components in Three.js order `[x, y, z, w]`. */
12
+ toXYZW() {
13
+ return [this.imaginary[0], this.imaginary[1], this.imaginary[2], this.real];
14
+ }
15
+ static identity() {
16
+ return new _Quat(1, [0, 0, 0]);
17
+ }
18
+ };
19
+ var UsdMatrix = class _UsdMatrix {
20
+ constructor(values, dim) {
21
+ this.values = values;
22
+ this.dim = dim;
23
+ }
24
+ values;
25
+ dim;
26
+ static identity4() {
27
+ return new _UsdMatrix([
28
+ 1,
29
+ 0,
30
+ 0,
31
+ 0,
32
+ 0,
33
+ 1,
34
+ 0,
35
+ 0,
36
+ 0,
37
+ 0,
38
+ 1,
39
+ 0,
40
+ 0,
41
+ 0,
42
+ 0,
43
+ 1
44
+ ], 4);
45
+ }
46
+ };
47
+ var AssetPath = class {
48
+ constructor(path) {
49
+ this.path = path;
50
+ }
51
+ path;
52
+ };
53
+
3
54
  // src/kinematics/transforms.ts
4
55
  var DEG2RAD = Math.PI / 180;
5
56
  var RAD2DEG = 180 / Math.PI;
@@ -254,6 +305,226 @@ function fromUsdMatrix(m) {
254
305
  function getTranslation(m) {
255
306
  return [m[12], m[13], m[14]];
256
307
  }
308
+ function toUsdMatrix(m) {
309
+ if (m.length !== 16) {
310
+ throw new Error(`expected 16 matrix elements but got ${m.length}`);
311
+ }
312
+ return new UsdMatrix([...m], 4);
313
+ }
314
+ function decomposeRigid(m) {
315
+ const position = [m[12], m[13], m[14]];
316
+ const b0 = [m[0], m[1], m[2]];
317
+ const b1 = [m[4], m[5], m[6]];
318
+ const b2 = [m[8], m[9], m[10]];
319
+ const EPS = 1e-6;
320
+ const rigid = Math.abs(norm(b0) - 1) < EPS && Math.abs(norm(b1) - 1) < EPS && Math.abs(norm(b2) - 1) < EPS && Math.abs(dot(b0, b1)) < EPS && Math.abs(dot(b1, b2)) < EPS && Math.abs(dot(b0, b2)) < EPS && dot(cross(b0, b1), b2) > 0;
321
+ const c0 = normalizeVec(b0);
322
+ const c1 = normalizeVec(subVec(b1, scaleVec(c0, dot(c0, b1))));
323
+ const c2 = cross(c0, c1);
324
+ const r11 = c0[0], r12 = c1[0], r13 = c2[0];
325
+ const r21 = c0[1], r22 = c1[1], r23 = c2[1];
326
+ const r31 = c0[2], r32 = c1[2], r33 = c2[2];
327
+ const trace = r11 + r22 + r33;
328
+ let w;
329
+ let x;
330
+ let y;
331
+ let z;
332
+ if (trace > 0) {
333
+ const s = 0.5 / Math.sqrt(trace + 1);
334
+ w = 0.25 / s;
335
+ x = (r32 - r23) * s;
336
+ y = (r13 - r31) * s;
337
+ z = (r21 - r12) * s;
338
+ } else if (r11 > r22 && r11 > r33) {
339
+ const s = 2 * Math.sqrt(1 + r11 - r22 - r33);
340
+ w = (r32 - r23) / s;
341
+ x = 0.25 * s;
342
+ y = (r12 + r21) / s;
343
+ z = (r13 + r31) / s;
344
+ } else if (r22 > r33) {
345
+ const s = 2 * Math.sqrt(1 + r22 - r11 - r33);
346
+ w = (r13 - r31) / s;
347
+ x = (r12 + r21) / s;
348
+ y = 0.25 * s;
349
+ z = (r23 + r32) / s;
350
+ } else {
351
+ const s = 2 * Math.sqrt(1 + r33 - r11 - r22);
352
+ w = (r21 - r12) / s;
353
+ x = (r13 + r31) / s;
354
+ y = (r23 + r32) / s;
355
+ z = 0.25 * s;
356
+ }
357
+ return { position, orientation: new Quat(w, [x, y, z]), rigid };
358
+ }
359
+ function dot(a, b) {
360
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
361
+ }
362
+ function norm(v) {
363
+ return Math.hypot(v[0], v[1], v[2]);
364
+ }
365
+ function subVec(a, b) {
366
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
367
+ }
368
+ function scaleVec(v, s) {
369
+ return [v[0] * s, v[1] * s, v[2] * s];
370
+ }
371
+ function normalizeVec(v) {
372
+ const n = norm(v);
373
+ return n > 0 ? scaleVec(v, 1 / n) : [1, 0, 0];
374
+ }
375
+ function cross(a, b) {
376
+ return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
377
+ }
378
+
379
+ // src/schemas/usdGeom.ts
380
+ function isXform(prim) {
381
+ return prim.GetTypeName() === "Xform";
382
+ }
383
+ function isScope(prim) {
384
+ return prim.GetTypeName() === "Scope";
385
+ }
386
+ function isMesh(prim) {
387
+ return prim.GetTypeName() === "Mesh";
388
+ }
389
+ function getPurpose(prim) {
390
+ const v = prim.GetAttribute("purpose").Get();
391
+ return typeof v === "string" ? v : "default";
392
+ }
393
+ function isNonVisualPurpose(prim) {
394
+ const p = getPurpose(prim);
395
+ return p === "guide" || p === "proxy";
396
+ }
397
+ function getMaterialSubsets(meshPrim) {
398
+ const out = [];
399
+ for (const child of meshPrim.GetChildren()) {
400
+ if (child.GetTypeName() !== "GeomSubset") continue;
401
+ const elementType = child.GetAttribute("elementType").Get();
402
+ if (elementType !== void 0 && elementType !== "face") continue;
403
+ const family = child.GetAttribute("familyName").Get();
404
+ if (family !== void 0 && family !== "materialBind") continue;
405
+ const indices = child.GetAttribute("indices").Get();
406
+ if (!Array.isArray(indices)) continue;
407
+ const faces = [];
408
+ for (const i of indices) {
409
+ if (typeof i === "number") faces.push(i);
410
+ }
411
+ if (faces.length > 0) out.push({ prim: child, faces });
412
+ }
413
+ return out;
414
+ }
415
+ function* iterDescendants(prim) {
416
+ for (const child of prim.GetChildren()) {
417
+ yield child;
418
+ yield* iterDescendants(child);
419
+ }
420
+ }
421
+ function gatherMeshDescendants(prim) {
422
+ const paths = [];
423
+ for (const d of iterDescendants(prim)) {
424
+ if (isMesh(d)) paths.push(d.GetPath());
425
+ }
426
+ return paths;
427
+ }
428
+
429
+ // src/schemas/usdPhysics.ts
430
+ var JOINT_TYPE_BY_SCHEMA = {
431
+ PhysicsFixedJoint: "fixed",
432
+ PhysicsRevoluteJoint: "revolute",
433
+ PhysicsPrismaticJoint: "prismatic"
434
+ };
435
+ var ARTICULATION_ROOT_API = "PhysicsArticulationRootAPI";
436
+ var RIGID_BODY_API = "PhysicsRigidBodyAPI";
437
+ var COLLISION_API = "PhysicsCollisionAPI";
438
+ var MASS_API = "PhysicsMassAPI";
439
+ var MESH_COLLISION_API = "PhysicsMeshCollisionAPI";
440
+ var PHYSICS_MATERIAL_API = "PhysicsMaterialAPI";
441
+ function getJointType(prim) {
442
+ return JOINT_TYPE_BY_SCHEMA[prim.GetTypeName()] ?? null;
443
+ }
444
+ function getJointBodies(prim) {
445
+ const body0 = prim.GetRelationship("physics:body0").GetTargets()[0];
446
+ const body1 = prim.GetRelationship("physics:body1").GetTargets()[0];
447
+ return {
448
+ ...body0 !== void 0 ? { body0 } : {},
449
+ ...body1 !== void 0 ? { body1 } : {}
450
+ };
451
+ }
452
+ function getJointAxis(prim) {
453
+ const v = prim.GetAttribute("physics:axis").Get();
454
+ return v === "Y" || v === "Z" ? v : "X";
455
+ }
456
+ function getJointLimits(prim) {
457
+ const lower = readNumber(prim, "physics:lowerLimit");
458
+ const upper = readNumber(prim, "physics:upperLimit");
459
+ return {
460
+ ...lower !== void 0 ? { lower } : {},
461
+ ...upper !== void 0 ? { upper } : {}
462
+ };
463
+ }
464
+ function getJointLocalFrame(prim, index) {
465
+ const pos = readVec3(prim, `physics:localPos${index}`, [0, 0, 0]);
466
+ const rot = readQuat(prim, `physics:localRot${index}`, Quat.identity());
467
+ return multiply(makeTranslation(pos), makeRotationFromQuat(rot));
468
+ }
469
+ function hasArticulationRootAPI(prim) {
470
+ return prim.HasAPI(ARTICULATION_ROOT_API);
471
+ }
472
+ function hasRigidBodyAPI(prim) {
473
+ return prim.HasAPI(RIGID_BODY_API);
474
+ }
475
+ function hasCollisionAPI(prim) {
476
+ return prim.HasAPI(COLLISION_API);
477
+ }
478
+ function driveKindFor(type) {
479
+ return type === "prismatic" ? "linear" : "angular";
480
+ }
481
+ function getJointDrive(prim, kind) {
482
+ const targetPosition = readNumber(prim, `drive:${kind}:physics:targetPosition`);
483
+ const stiffness = readNumber(prim, `drive:${kind}:physics:stiffness`);
484
+ const damping = readNumber(prim, `drive:${kind}:physics:damping`);
485
+ const maxForce = readNumber(prim, `drive:${kind}:physics:maxForce`);
486
+ return {
487
+ ...targetPosition !== void 0 ? { targetPosition } : {},
488
+ ...stiffness !== void 0 ? { stiffness } : {},
489
+ ...damping !== void 0 ? { damping } : {},
490
+ ...maxForce !== void 0 ? { maxForce } : {}
491
+ };
492
+ }
493
+ function getJointStatePosition(prim, kind) {
494
+ return readNumber(prim, `state:${kind}:physics:position`);
495
+ }
496
+ function getMassProperties(prim) {
497
+ const out = {};
498
+ const mass = readNumber(prim, "physics:mass");
499
+ if (mass !== void 0) out.mass = mass;
500
+ const density = readNumber(prim, "physics:density");
501
+ if (density !== void 0) out.density = density;
502
+ const centerOfMass = readVec3Opt(prim, "physics:centerOfMass");
503
+ if (centerOfMass) out.centerOfMass = centerOfMass;
504
+ const diagonalInertia = readVec3Opt(prim, "physics:diagonalInertia");
505
+ if (diagonalInertia) out.diagonalInertia = diagonalInertia;
506
+ const principalAxes = prim.GetAttribute("physics:principalAxes").Get();
507
+ if (principalAxes instanceof Quat) out.principalAxes = principalAxes;
508
+ return Object.keys(out).length > 0 ? out : void 0;
509
+ }
510
+ function readNumber(prim, name) {
511
+ const v = prim.GetAttribute(name).Get();
512
+ return typeof v === "number" ? v : void 0;
513
+ }
514
+ function readVec3(prim, name, def) {
515
+ return readVec3Opt(prim, name) ?? def;
516
+ }
517
+ function readVec3Opt(prim, name) {
518
+ const v = prim.GetAttribute(name).Get();
519
+ if (Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === "number")) {
520
+ return v;
521
+ }
522
+ return void 0;
523
+ }
524
+ function readQuat(prim, name, def) {
525
+ const v = prim.GetAttribute(name).Get();
526
+ return v instanceof Quat ? v : def;
527
+ }
257
528
 
258
529
  // src/usd/xformOps.ts
259
530
  var INVERT_PREFIX = "!invert!";
@@ -266,6 +537,15 @@ var ROTATE_ORDERS = /* @__PURE__ */ new Set([
266
537
  "rotateZXY",
267
538
  "rotateZYX"
268
539
  ]);
540
+ function computeWorldTransform(prim) {
541
+ const chain = [];
542
+ for (let p = prim; p && !p.IsPseudoRoot(); p = p.GetParent()) chain.push(p);
543
+ let m = identity4();
544
+ for (let i = chain.length - 1; i >= 0; i--) {
545
+ m = multiply(m, computeLocalTransform(chain[i]).matrix);
546
+ }
547
+ return m;
548
+ }
269
549
  function computeLocalTransform(prim) {
270
550
  const orderAttr = prim.GetAttribute("xformOpOrder");
271
551
  const order = orderAttr.Get();
@@ -287,9 +567,7 @@ function computeLocalTransform(prim) {
287
567
  opName = opName.slice(INVERT_PREFIX.length);
288
568
  }
289
569
  const attr = prim.GetAttribute(opName);
290
- if (!attr.IsValid()) {
291
- throw new Error(`${prim.GetPath()}: xformOpOrder references missing op "${opName}"`);
292
- }
570
+ if (!attr.IsValid()) continue;
293
571
  const opValue = attr.Get();
294
572
  if (opValue === void 0) continue;
295
573
  let opMatrix = opMatrixFor(parseOpType(opName), opValue, `${prim.GetPath()}.${opName}`);
@@ -345,56 +623,177 @@ function asMatrix(v, where) {
345
623
  throw new Error(`${where}: expected a matrix`);
346
624
  }
347
625
 
348
- // src/parser/ast.ts
349
- var Quat = class _Quat {
350
- constructor(real, imaginary) {
351
- this.real = real;
352
- this.imaginary = imaginary;
626
+ // src/three/MaterialBinding.ts
627
+ var DIFFUSE_INPUTS = [
628
+ "inputs:diffuseColor",
629
+ // UsdPreviewSurface
630
+ "inputs:diffuse_color_constant",
631
+ // OmniPBR
632
+ "inputs:diffuse_tint",
633
+ "inputs:base_color",
634
+ "inputs:baseColor"
635
+ ];
636
+ var OPACITY_INPUTS = ["inputs:opacity", "inputs:opacity_constant"];
637
+ var OPACITY_THRESHOLD_INPUTS = ["inputs:opacityThreshold", "inputs:opacity_threshold"];
638
+ var METALLIC_INPUTS = ["inputs:metallic", "inputs:metallic_constant"];
639
+ var ROUGHNESS_INPUTS = ["inputs:roughness", "inputs:reflection_roughness_constant"];
640
+ var EMISSIVE_INPUTS = ["inputs:emissiveColor", "inputs:emissive_color"];
641
+ var SURFACE_OUTPUTS = ["outputs:surface", "outputs:mdl:surface"];
642
+ var TEXTURE_LOOKUPS = {
643
+ color: {
644
+ surface: ["inputs:diffuseColor"],
645
+ direct: ["inputs:diffuse_texture", "inputs:diffuse_color_texture"]
646
+ },
647
+ opacity: {
648
+ surface: ["inputs:opacity"],
649
+ direct: ["inputs:opacity_texture", "inputs:opacity_color_texture"]
650
+ },
651
+ normal: {
652
+ surface: ["inputs:normal"],
653
+ direct: ["inputs:normalmap_texture", "inputs:normal_texture"]
654
+ },
655
+ roughness: {
656
+ surface: ["inputs:roughness"],
657
+ direct: ["inputs:reflectionroughness_texture", "inputs:roughness_texture"]
658
+ },
659
+ metalness: {
660
+ surface: ["inputs:metallic"],
661
+ direct: ["inputs:metallic_texture"]
662
+ },
663
+ occlusion: {
664
+ surface: ["inputs:occlusion"],
665
+ direct: ["inputs:ao_texture", "inputs:occlusion_texture"]
666
+ },
667
+ emissive: {
668
+ surface: ["inputs:emissiveColor"],
669
+ direct: ["inputs:emissive_color_texture", "inputs:emissive_mask_texture"]
353
670
  }
354
- real;
355
- imaginary;
356
- /** Components in Three.js order `[x, y, z, w]`. */
357
- toXYZW() {
358
- return [this.imaginary[0], this.imaginary[1], this.imaginary[2], this.real];
671
+ };
672
+ function resolveBoundMaterial(stage, prim) {
673
+ const materialPath = findBinding(prim);
674
+ if (!materialPath) return void 0;
675
+ const material = stage.GetPrimAtPath(materialPath);
676
+ if (!material) return void 0;
677
+ const shader = findSurfaceShader(material);
678
+ if (!shader) return void 0;
679
+ const result = { name: material.GetName() };
680
+ const color = firstColor(shader, DIFFUSE_INPUTS);
681
+ if (color) result.color = color;
682
+ const opacity = firstNumber(shader, OPACITY_INPUTS);
683
+ if (opacity !== void 0) result.opacity = opacity;
684
+ const opacityThreshold = firstNumber(shader, OPACITY_THRESHOLD_INPUTS);
685
+ if (opacityThreshold !== void 0) result.opacityThreshold = opacityThreshold;
686
+ const metalness = firstNumber(shader, METALLIC_INPUTS);
687
+ if (metalness !== void 0) result.metalness = metalness;
688
+ const roughness = firstNumber(shader, ROUGHNESS_INPUTS);
689
+ if (roughness !== void 0) result.roughness = roughness;
690
+ const emissive = firstColor(shader, EMISSIVE_INPUTS);
691
+ if (emissive && shader.GetAttribute("inputs:enable_emission").Get() !== false) {
692
+ result.emissiveColor = emissive;
693
+ }
694
+ const colorTex = findTexture(shader, TEXTURE_LOOKUPS.color);
695
+ if (colorTex !== void 0) result.colorTexture = colorTex;
696
+ const opacityTex = findTexture(shader, TEXTURE_LOOKUPS.opacity);
697
+ if (opacityTex !== void 0) result.opacityTexture = opacityTex;
698
+ const normal = findTexture(shader, TEXTURE_LOOKUPS.normal);
699
+ if (normal !== void 0) result.normalTexture = normal;
700
+ const roughTex = findTexture(shader, TEXTURE_LOOKUPS.roughness);
701
+ if (roughTex !== void 0) result.roughnessTexture = roughTex;
702
+ const metalTex = findTexture(shader, TEXTURE_LOOKUPS.metalness);
703
+ if (metalTex !== void 0) result.metalnessTexture = metalTex;
704
+ const aoTex = findTexture(shader, TEXTURE_LOOKUPS.occlusion);
705
+ if (aoTex !== void 0) result.occlusionTexture = aoTex;
706
+ const emissiveTex = findTexture(shader, TEXTURE_LOOKUPS.emissive);
707
+ if (emissiveTex !== void 0) result.emissiveTexture = emissiveTex;
708
+ return result;
709
+ }
710
+ function findTexture(shader, lookup) {
711
+ for (const name of lookup.direct) {
712
+ const v = shader.GetAttribute(name).Get();
713
+ if (v instanceof AssetPath && v.path) return { path: v.path };
714
+ }
715
+ for (const name of lookup.surface) {
716
+ const conn = shader.GetAttribute(name).GetConnections()[0];
717
+ if (!conn) continue;
718
+ const texPrim = shader.GetStage().GetPrimAtPath(conn.split(".")[0]);
719
+ if (!texPrim) continue;
720
+ const file = texPrim.GetAttribute("inputs:file").Get();
721
+ if (file instanceof AssetPath && file.path) return readUvTexture(texPrim, file.path);
722
+ }
723
+ return void 0;
724
+ }
725
+ var WRAP_VALUES = /* @__PURE__ */ new Set(["repeat", "clamp", "mirror", "black"]);
726
+ function readUvTexture(texPrim, path) {
727
+ const tex = { path };
728
+ const wrapS = texPrim.GetAttribute("inputs:wrapS").Get();
729
+ if (typeof wrapS === "string" && WRAP_VALUES.has(wrapS)) tex.wrapS = wrapS;
730
+ const wrapT = texPrim.GetAttribute("inputs:wrapT").Get();
731
+ if (typeof wrapT === "string" && WRAP_VALUES.has(wrapT)) tex.wrapT = wrapT;
732
+ const scale = numArray(texPrim, "inputs:scale", 4);
733
+ if (scale) tex.scale = scale;
734
+ const bias = numArray(texPrim, "inputs:bias", 4);
735
+ if (bias) tex.bias = bias;
736
+ const transform = readTransform2d(texPrim);
737
+ if (transform) tex.transform = transform;
738
+ return tex;
739
+ }
740
+ function readTransform2d(texPrim) {
741
+ const conn = texPrim.GetAttribute("inputs:st").GetConnections()[0];
742
+ if (!conn) return void 0;
743
+ const node = texPrim.GetStage().GetPrimAtPath(conn.split(".")[0]);
744
+ if (!node || node.GetAttribute("info:id").Get() !== "UsdTransform2d") return void 0;
745
+ const transform = {};
746
+ const translation = numArray(node, "inputs:translation", 2);
747
+ if (translation) transform.translation = translation;
748
+ const scale = numArray(node, "inputs:scale", 2);
749
+ if (scale) transform.scale = scale;
750
+ const rotation = node.GetAttribute("inputs:rotation").Get();
751
+ if (typeof rotation === "number") transform.rotation = rotation;
752
+ return Object.keys(transform).length > 0 ? transform : void 0;
753
+ }
754
+ function numArray(prim, name, length) {
755
+ const v = prim.GetAttribute(name).Get();
756
+ if (Array.isArray(v) && v.length >= length && v.every((n) => typeof n === "number")) {
757
+ return v.slice(0, length);
359
758
  }
360
- static identity() {
361
- return new _Quat(1, [0, 0, 0]);
759
+ return void 0;
760
+ }
761
+ function findBinding(prim) {
762
+ let p = prim;
763
+ while (p) {
764
+ const targets = p.GetRelationship("material:binding").GetTargets();
765
+ if (targets.length > 0) return targets[0];
766
+ p = p.GetParent();
362
767
  }
363
- };
364
- var UsdMatrix = class _UsdMatrix {
365
- constructor(values, dim) {
366
- this.values = values;
367
- this.dim = dim;
768
+ return void 0;
769
+ }
770
+ function findSurfaceShader(material) {
771
+ for (const out of SURFACE_OUTPUTS) {
772
+ const conn = material.GetAttribute(out).GetConnections()[0];
773
+ if (conn) {
774
+ const shaderPath = conn.split(".")[0];
775
+ const shader = material.GetStage().GetPrimAtPath(shaderPath);
776
+ if (shader) return shader;
777
+ }
368
778
  }
369
- values;
370
- dim;
371
- static identity4() {
372
- return new _UsdMatrix([
373
- 1,
374
- 0,
375
- 0,
376
- 0,
377
- 0,
378
- 1,
379
- 0,
380
- 0,
381
- 0,
382
- 0,
383
- 1,
384
- 0,
385
- 0,
386
- 0,
387
- 0,
388
- 1
389
- ], 4);
779
+ return material.GetChildren().find((c) => c.GetTypeName() === "Shader") ?? void 0;
780
+ }
781
+ function firstColor(shader, names) {
782
+ for (const name of names) {
783
+ const v = shader.GetAttribute(name).Get();
784
+ if (Array.isArray(v) && v.length >= 3 && v.every((n) => typeof n === "number")) {
785
+ return [v[0], v[1], v[2]];
786
+ }
390
787
  }
391
- };
392
- var AssetPath = class {
393
- constructor(path) {
394
- this.path = path;
788
+ return void 0;
789
+ }
790
+ function firstNumber(shader, names) {
791
+ for (const name of names) {
792
+ const v = shader.GetAttribute(name).Get();
793
+ if (typeof v === "number") return v;
395
794
  }
396
- path;
397
- };
795
+ return void 0;
796
+ }
398
797
 
399
798
  // src/kinematics/sampling.ts
400
799
  function interpolate(channel, t) {
@@ -423,118 +822,6 @@ function channelFromSamples(samples) {
423
822
  return { times, values };
424
823
  }
425
824
 
426
- // src/schemas/usdGeom.ts
427
- function isXform(prim) {
428
- return prim.GetTypeName() === "Xform";
429
- }
430
- function isScope(prim) {
431
- return prim.GetTypeName() === "Scope";
432
- }
433
- function isMesh(prim) {
434
- return prim.GetTypeName() === "Mesh";
435
- }
436
- function getPurpose(prim) {
437
- const v = prim.GetAttribute("purpose").Get();
438
- return typeof v === "string" ? v : "default";
439
- }
440
- function isNonVisualPurpose(prim) {
441
- const p = getPurpose(prim);
442
- return p === "guide" || p === "proxy";
443
- }
444
- function* iterDescendants(prim) {
445
- for (const child of prim.GetChildren()) {
446
- yield child;
447
- yield* iterDescendants(child);
448
- }
449
- }
450
- function gatherMeshDescendants(prim) {
451
- const paths = [];
452
- for (const d of iterDescendants(prim)) {
453
- if (isMesh(d)) paths.push(d.GetPath());
454
- }
455
- return paths;
456
- }
457
-
458
- // src/schemas/usdPhysics.ts
459
- var JOINT_TYPE_BY_SCHEMA = {
460
- PhysicsFixedJoint: "fixed",
461
- PhysicsRevoluteJoint: "revolute",
462
- PhysicsPrismaticJoint: "prismatic"
463
- };
464
- var ARTICULATION_ROOT_API = "PhysicsArticulationRootAPI";
465
- var RIGID_BODY_API = "PhysicsRigidBodyAPI";
466
- var COLLISION_API = "PhysicsCollisionAPI";
467
- function getJointType(prim) {
468
- return JOINT_TYPE_BY_SCHEMA[prim.GetTypeName()] ?? null;
469
- }
470
- function getJointBodies(prim) {
471
- const body0 = prim.GetRelationship("physics:body0").GetTargets()[0];
472
- const body1 = prim.GetRelationship("physics:body1").GetTargets()[0];
473
- return {
474
- ...body0 !== void 0 ? { body0 } : {},
475
- ...body1 !== void 0 ? { body1 } : {}
476
- };
477
- }
478
- function getJointAxis(prim) {
479
- const v = prim.GetAttribute("physics:axis").Get();
480
- return v === "Y" || v === "Z" ? v : "X";
481
- }
482
- function getJointLimits(prim) {
483
- const lower = readNumber(prim, "physics:lowerLimit");
484
- const upper = readNumber(prim, "physics:upperLimit");
485
- return {
486
- ...lower !== void 0 ? { lower } : {},
487
- ...upper !== void 0 ? { upper } : {}
488
- };
489
- }
490
- function getJointLocalFrame(prim, index) {
491
- const pos = readVec3(prim, `physics:localPos${index}`, [0, 0, 0]);
492
- const rot = readQuat(prim, `physics:localRot${index}`, Quat.identity());
493
- return multiply(makeTranslation(pos), makeRotationFromQuat(rot));
494
- }
495
- function hasArticulationRootAPI(prim) {
496
- return prim.HasAPI(ARTICULATION_ROOT_API);
497
- }
498
- function hasRigidBodyAPI(prim) {
499
- return prim.HasAPI(RIGID_BODY_API);
500
- }
501
- function hasCollisionAPI(prim) {
502
- return prim.HasAPI(COLLISION_API);
503
- }
504
- function driveKindFor(type) {
505
- return type === "prismatic" ? "linear" : "angular";
506
- }
507
- function getJointDrive(prim, kind) {
508
- const targetPosition = readNumber(prim, `drive:${kind}:physics:targetPosition`);
509
- const stiffness = readNumber(prim, `drive:${kind}:physics:stiffness`);
510
- const damping = readNumber(prim, `drive:${kind}:physics:damping`);
511
- const maxForce = readNumber(prim, `drive:${kind}:physics:maxForce`);
512
- return {
513
- ...targetPosition !== void 0 ? { targetPosition } : {},
514
- ...stiffness !== void 0 ? { stiffness } : {},
515
- ...damping !== void 0 ? { damping } : {},
516
- ...maxForce !== void 0 ? { maxForce } : {}
517
- };
518
- }
519
- function getJointStatePosition(prim, kind) {
520
- return readNumber(prim, `state:${kind}:physics:position`);
521
- }
522
- function readNumber(prim, name) {
523
- const v = prim.GetAttribute(name).Get();
524
- return typeof v === "number" ? v : void 0;
525
- }
526
- function readVec3(prim, name, def) {
527
- const v = prim.GetAttribute(name).Get();
528
- if (Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === "number")) {
529
- return v;
530
- }
531
- return def;
532
- }
533
- function readQuat(prim, name, def) {
534
- const v = prim.GetAttribute(name).Get();
535
- return v instanceof Quat ? v : def;
536
- }
537
-
538
825
  // src/robot/buildKinematicTree.ts
539
826
  var WORLD = "";
540
827
  function buildKinematicTree(robot, options = {}) {
@@ -654,6 +941,9 @@ function normalizeJointLimits(type, rawLower, rawUpper) {
654
941
  function jointValueToSI(angular, raw) {
655
942
  return angular ? raw * DEG2RAD : raw;
656
943
  }
944
+ function jointValueFromSI(angular, si) {
945
+ return angular ? si * RAD2DEG : si;
946
+ }
657
947
  function refineJointType(base, lower, upper) {
658
948
  if (base === "revolute" && lower === void 0 && upper === void 0) return "continuous";
659
949
  return base;
@@ -720,16 +1010,28 @@ function buildLink(path, prim) {
720
1010
  const collisionPrims = [];
721
1011
  for (const meshPath of gatherMeshDescendants(prim)) {
722
1012
  const mp = prim.GetStage().GetPrimAtPath(meshPath);
723
- if (mp && (hasCollisionAPI(mp) || isNonVisualPurpose(mp))) collisionPrims.push(meshPath);
724
- else visualPrims.push(meshPath);
1013
+ if (!mp) continue;
1014
+ const nonVisual = isNonVisualPurpose(mp);
1015
+ if (!nonVisual) visualPrims.push(meshPath);
1016
+ if (nonVisual || hasCollisionAPI(mp)) collisionPrims.push(meshPath);
725
1017
  }
1018
+ const inertial = getMassProperties(prim);
1019
+ const worldTransform = computeWorldTransform(prim);
726
1020
  return {
727
1021
  name,
728
1022
  primPath: path,
729
1023
  visualPrims,
730
- ...collisionPrims.length ? { collisionPrims } : {}
1024
+ ...collisionPrims.length ? { collisionPrims } : {},
1025
+ ...inertial ? { inertial } : {},
1026
+ ...isIdentityMat4(worldTransform) ? {} : { worldTransform }
731
1027
  };
732
1028
  }
1029
+ function isIdentityMat4(m) {
1030
+ for (let i = 0; i < 16; i++) {
1031
+ if (m[i] !== (i % 5 === 0 ? 1 : 0)) return false;
1032
+ }
1033
+ return true;
1034
+ }
733
1035
  function buildJoint(prim, linkKeyByPath, warn) {
734
1036
  const base = getJointType(prim);
735
1037
  if (!base) return null;
@@ -1347,11 +1649,11 @@ function parseVariantSet(r) {
1347
1649
  const variants = {};
1348
1650
  while (!r.is("rbrace") && !r.atEnd()) {
1349
1651
  const variantName = r.expect("string").value;
1350
- if (r.is("lparen")) parseMetadataBlock(r);
1652
+ const metadata = r.is("lparen") ? parseMetadataBlock(r) : {};
1351
1653
  r.expect("lbrace");
1352
1654
  const body = parseBody(r);
1353
1655
  r.expect("rbrace");
1354
- variants[variantName] = { properties: body.properties, children: body.children };
1656
+ variants[variantName] = { properties: body.properties, children: body.children, metadata };
1355
1657
  }
1356
1658
  r.expect("rbrace");
1357
1659
  return { setName, variants };
@@ -1523,12 +1825,243 @@ function parseDictionary(r) {
1523
1825
  return dict;
1524
1826
  }
1525
1827
 
1828
+ // src/writer/writeUsda.ts
1829
+ var INDENT = " ";
1830
+ var BARE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
1831
+ function serializeUsda(file) {
1832
+ const out = [`#usda ${file.version}`];
1833
+ const meta = Object.entries(file.metadata);
1834
+ if (meta.length > 0) {
1835
+ out.push("(");
1836
+ for (const [key, value] of meta) pushMetaEntry(out, key, value, 1);
1837
+ out.push(")");
1838
+ }
1839
+ for (const prim of file.prims) {
1840
+ out.push("");
1841
+ pushPrim(out, prim, 0);
1842
+ }
1843
+ return `${out.join("\n")}
1844
+ `;
1845
+ }
1846
+ function pushPrim(out, prim, level) {
1847
+ const pad = INDENT.repeat(level);
1848
+ const type = prim.typeName ? `${prim.typeName} ` : "";
1849
+ pushWithMetadata(
1850
+ out,
1851
+ `${pad}${prim.specifier} ${type}${quoteString(prim.name)}`,
1852
+ prim.metadata,
1853
+ level
1854
+ );
1855
+ out.push(`${pad}{`);
1856
+ pushPrimBody(out, prim.properties, prim.children, prim.variantSets, level + 1);
1857
+ out.push(`${pad}}`);
1858
+ }
1859
+ function pushPrimBody(out, properties, children, variantSets, level) {
1860
+ for (const prop of properties) pushProperty(out, prop, level);
1861
+ if (variantSets) {
1862
+ const pad = INDENT.repeat(level);
1863
+ for (const [setName, variants] of Object.entries(variantSets)) {
1864
+ out.push(`${pad}variantSet ${quoteString(setName)} = {`);
1865
+ for (const [variantName, content] of Object.entries(variants)) {
1866
+ const head = `${pad}${INDENT}${quoteString(variantName)}`;
1867
+ if (Object.keys(content.metadata).length > 0) {
1868
+ pushWithMetadata(out, head, content.metadata, level + 1);
1869
+ out.push(`${pad}${INDENT}{`);
1870
+ } else {
1871
+ out.push(`${head} {`);
1872
+ }
1873
+ pushPrimBody(out, content.properties, content.children, void 0, level + 2);
1874
+ out.push(`${pad}${INDENT}}`);
1875
+ }
1876
+ out.push(`${pad}}`);
1877
+ }
1878
+ }
1879
+ for (let i = 0; i < children.length; i++) {
1880
+ if (i > 0 || properties.length > 0 || variantSets) out.push("");
1881
+ pushPrim(out, children[i], level);
1882
+ }
1883
+ }
1884
+ function pushProperty(out, prop, level) {
1885
+ if (prop.kind === "relationship") pushRelationship(out, prop, level);
1886
+ else pushAttribute(out, prop, level);
1887
+ }
1888
+ function pushRelationship(out, rel, level) {
1889
+ const pad = INDENT.repeat(level);
1890
+ const custom = rel.custom ? "custom " : "";
1891
+ const listOp = rel.listOp !== "explicit" ? `${rel.listOp} ` : "";
1892
+ let head = `${pad}${custom}${listOp}rel ${rel.name}`;
1893
+ if (rel.targets.length > 0) head += ` = ${formatTargets(rel.targets)}`;
1894
+ pushWithMetadata(out, head, rel.metadata, level);
1895
+ }
1896
+ function pushAttribute(out, attr, level) {
1897
+ const pad = INDENT.repeat(level);
1898
+ const custom = attr.custom ? "custom " : "";
1899
+ const uniform = attr.variability === "uniform" ? "uniform " : "";
1900
+ const decl = `${pad}${custom}${uniform}${attr.typeName}${attr.isArray ? "[]" : ""} ${attr.name}`;
1901
+ let first = true;
1902
+ const attach = (head) => {
1903
+ if (first) pushWithMetadata(out, head, attr.metadata, level);
1904
+ else out.push(head);
1905
+ first = false;
1906
+ };
1907
+ if (attr.value !== void 0) {
1908
+ attach(`${decl} = ${formatAttrValue(attr.value, attr.isArray)}`);
1909
+ }
1910
+ if (attr.timeSamples) {
1911
+ out.push(`${decl}.timeSamples = {`);
1912
+ for (const [time, value] of attr.timeSamples) {
1913
+ out.push(`${pad}${INDENT}${formatNumber(time)}: ${formatAttrValue(value, attr.isArray)},`);
1914
+ }
1915
+ attach(`${pad}}`);
1916
+ }
1917
+ if (attr.connections) {
1918
+ attach(
1919
+ `${decl}.connect = ${attr.connections.length > 0 ? formatTargets(attr.connections) : "None"}`
1920
+ );
1921
+ }
1922
+ if (first) attach(decl);
1923
+ }
1924
+ function formatTargets(targets) {
1925
+ if (targets.length === 1) return `<${targets[0]}>`;
1926
+ return `[${targets.map((t) => `<${t}>`).join(", ")}]`;
1927
+ }
1928
+ function pushWithMetadata(out, head, meta, level) {
1929
+ const entries = Object.entries(meta);
1930
+ if (entries.length === 0) {
1931
+ out.push(head);
1932
+ return;
1933
+ }
1934
+ out.push(`${head} (`);
1935
+ for (const [key, value] of entries) pushMetaEntry(out, key, value, level + 1);
1936
+ out.push(`${INDENT.repeat(level)})`);
1937
+ }
1938
+ function pushMetaEntry(out, key, value, level) {
1939
+ const pad = INDENT.repeat(level);
1940
+ if (isDictionary(value)) {
1941
+ out.push(`${pad}${key} = {`);
1942
+ pushDictEntries(out, value, level + 1);
1943
+ out.push(`${pad}}`);
1944
+ return;
1945
+ }
1946
+ out.push(`${pad}${key} = ${formatMetaValue(value)}`);
1947
+ }
1948
+ function pushDictEntries(out, dict, level) {
1949
+ const pad = INDENT.repeat(level);
1950
+ for (const [key, value] of Object.entries(dict)) {
1951
+ const keyText = BARE_KEY_RE.test(key) ? key : quoteString(key);
1952
+ if (isDictionary(value)) {
1953
+ out.push(`${pad}dictionary ${keyText} = {`);
1954
+ pushDictEntries(out, value, level + 1);
1955
+ out.push(`${pad}}`);
1956
+ } else {
1957
+ out.push(`${pad}${dictEntryTypeName(value)} ${keyText} = ${formatMetaValue(value)}`);
1958
+ }
1959
+ }
1960
+ }
1961
+ function dictEntryTypeName(v) {
1962
+ if (typeof v === "string") return "string";
1963
+ if (typeof v === "boolean") return "bool";
1964
+ if (typeof v === "number") return isInt32(v) ? "int" : "double";
1965
+ if (typeof v === "bigint") return "int64";
1966
+ if (v instanceof AssetPath) return "asset";
1967
+ if (v instanceof Quat) return "quatd";
1968
+ if (v instanceof UsdMatrix) return v.dim === 4 ? "matrix4d" : "matrix3d";
1969
+ if (Array.isArray(v)) {
1970
+ const elems = v;
1971
+ if (elems.every((e) => typeof e === "boolean")) return "bool[]";
1972
+ let allInt = true;
1973
+ for (const e of elems) {
1974
+ if (typeof e !== "number") return "string[]";
1975
+ if (!isInt32(e)) allInt = false;
1976
+ }
1977
+ return allInt ? "int[]" : "double[]";
1978
+ }
1979
+ return "string";
1980
+ }
1981
+ function isInt32(v) {
1982
+ return Number.isInteger(v) && Math.abs(v) <= 2147483647;
1983
+ }
1984
+ function formatMetaValue(v) {
1985
+ if (v === null) return "None";
1986
+ if (typeof v === "number") return formatNumber(v);
1987
+ if (typeof v === "bigint") return String(v);
1988
+ if (typeof v === "boolean") return v ? "true" : "false";
1989
+ if (typeof v === "string") return quoteString(v);
1990
+ if (v instanceof AssetPath) return formatAsset(v.path);
1991
+ if (v instanceof Quat) return formatQuat(v);
1992
+ if (v instanceof UsdMatrix) return formatMatrix(v);
1993
+ if (Array.isArray(v)) return `[${v.map(formatMetaValue).join(", ")}]`;
1994
+ if (isCompositionArc(v)) return formatArc(v);
1995
+ throw new Error("cannot serialize a nested dictionary in list/value context");
1996
+ }
1997
+ function formatArc(arc) {
1998
+ const asset = arc.assetPath ? formatAsset(arc.assetPath.path) : "";
1999
+ const prim = arc.primPath !== void 0 ? `<${arc.primPath}>` : "";
2000
+ return `${asset}${prim}`;
2001
+ }
2002
+ function formatAttrValue(v, isArray) {
2003
+ if (v === null) return "None";
2004
+ if (isArray && Array.isArray(v)) {
2005
+ return `[${v.map((el) => formatAttrScalar(el)).join(", ")}]`;
2006
+ }
2007
+ return formatAttrScalar(v);
2008
+ }
2009
+ function formatAttrScalar(v) {
2010
+ if (v === null) return "None";
2011
+ if (typeof v === "number") return formatNumber(v);
2012
+ if (typeof v === "bigint") return String(v);
2013
+ if (typeof v === "boolean") return v ? "true" : "false";
2014
+ if (typeof v === "string") return quoteString(v);
2015
+ if (v instanceof Quat) return formatQuat(v);
2016
+ if (v instanceof UsdMatrix) return formatMatrix(v);
2017
+ if (v instanceof AssetPath) return formatAsset(v.path);
2018
+ if (Array.isArray(v)) return `(${v.map((el) => formatAttrScalar(el)).join(", ")})`;
2019
+ throw new Error("cannot serialize a dictionary as an attribute value");
2020
+ }
2021
+ function formatQuat(q) {
2022
+ const [i, j, k] = q.imaginary;
2023
+ return `(${formatNumber(q.real)}, ${formatNumber(i)}, ${formatNumber(j)}, ${formatNumber(k)})`;
2024
+ }
2025
+ function formatMatrix(m) {
2026
+ const rows = [];
2027
+ for (let r = 0; r < m.dim; r++) {
2028
+ const row = m.values.slice(r * m.dim, (r + 1) * m.dim).map(formatNumber);
2029
+ rows.push(`(${row.join(", ")})`);
2030
+ }
2031
+ return `( ${rows.join(", ")} )`;
2032
+ }
2033
+ function formatNumber(v) {
2034
+ if (Number.isNaN(v)) return "nan";
2035
+ if (v === Number.POSITIVE_INFINITY) return "inf";
2036
+ if (v === Number.NEGATIVE_INFINITY) return "-inf";
2037
+ if (Object.is(v, -0)) return "-0";
2038
+ return String(v);
2039
+ }
2040
+ function quoteString(s) {
2041
+ const escaped = s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
2042
+ return `"${escaped}"`;
2043
+ }
2044
+ function formatAsset(path) {
2045
+ return path.includes("@") ? `@@@${path}@@@` : `@${path}@`;
2046
+ }
2047
+ function isDictionary(v) {
2048
+ return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Quat) && !(v instanceof UsdMatrix) && !(v instanceof AssetPath) && !isCompositionArc(v);
2049
+ }
2050
+ function isCompositionArc(v) {
2051
+ const keys = Object.keys(v);
2052
+ return keys.length > 0 && keys.every((k) => k === "assetPath" || k === "primPath");
2053
+ }
2054
+
1526
2055
  // src/usd/Layer.ts
1527
2056
  var Layer = class {
1528
2057
  constructor(_file) {
1529
2058
  this._file = _file;
1530
2059
  }
1531
2060
  _file;
2061
+ /** Serialize this layer back to USDA text (`SdfLayer::ExportToString`-like). */
2062
+ ExportToString() {
2063
+ return serializeUsda(this._file);
2064
+ }
1532
2065
  GetVersion() {
1533
2066
  return this._file.version;
1534
2067
  }
@@ -1712,7 +2245,9 @@ var Prim = class {
1712
2245
  if (!this._attributes) {
1713
2246
  this._attributes = /* @__PURE__ */ new Map();
1714
2247
  for (const p of this._spec?.properties ?? []) {
1715
- if (p.kind === "attribute") this._attributes.set(p.name, p);
2248
+ if (p.kind !== "attribute") continue;
2249
+ const prev = this._attributes.get(p.name);
2250
+ this._attributes.set(p.name, prev ? mergeAttributeSpecs(prev, p) : p);
1716
2251
  }
1717
2252
  }
1718
2253
  return this._attributes;
@@ -1773,6 +2308,19 @@ var Prim = class {
1773
2308
  return this.GetAppliedSchemas().some((s) => s === schemaName || s.startsWith(`${schemaName}:`));
1774
2309
  }
1775
2310
  };
2311
+ function mergeAttributeSpecs(a, b) {
2312
+ const merged = { ...a, metadata: { ...a.metadata, ...b.metadata } };
2313
+ if (b.typeName) merged.typeName = b.typeName;
2314
+ if (b.isArray) merged.isArray = true;
2315
+ if (b.variability === "uniform") merged.variability = "uniform";
2316
+ if (b.custom) merged.custom = true;
2317
+ if (b.value !== void 0) merged.value = b.value;
2318
+ if (b.timeSamples) {
2319
+ merged.timeSamples = a.timeSamples ? new Map([...a.timeSamples, ...b.timeSamples]) : b.timeSamples;
2320
+ }
2321
+ if (b.connections) merged.connections = b.connections;
2322
+ return merged;
2323
+ }
1776
2324
 
1777
2325
  // src/usd/Stage.ts
1778
2326
  var DEFAULT_METERS_PER_UNIT = 0.01;
@@ -1804,6 +2352,14 @@ var Stage = class _Stage {
1804
2352
  GetRootLayer() {
1805
2353
  return this._layer;
1806
2354
  }
2355
+ /**
2356
+ * Serialize the stage's backing layer to USDA text. Loader-built stages wrap
2357
+ * the fully composed layer, so this is a flattened (`usdcat --flatten`-like)
2358
+ * export of everything that was read — including binary-crate sources.
2359
+ */
2360
+ ExportToString() {
2361
+ return this._layer.ExportToString();
2362
+ }
1807
2363
  GetPseudoRoot() {
1808
2364
  return this._pseudoRoot;
1809
2365
  }
@@ -1978,6 +2534,8 @@ var CrateType = {
1978
2534
  Specifier: 42,
1979
2535
  Permission: 43,
1980
2536
  Variability: 44,
2537
+ VariantSelectionMap: 45,
2538
+ StringVector: 50,
1981
2539
  PayloadListOp: 55};
1982
2540
  var ListOpBits = {
1983
2541
  HasExplicit: 1 << 1,
@@ -2333,6 +2891,10 @@ var CrateReader = class {
2333
2891
  return this.readIndexVector(off, "token").items;
2334
2892
  case CrateType.PathVector:
2335
2893
  return this.readIndexVector(off, "path").items;
2894
+ case CrateType.StringVector:
2895
+ return this.readIndexVector(off, "string").items;
2896
+ case CrateType.VariantSelectionMap:
2897
+ return this.readVariantSelectionMap(off);
2336
2898
  default:
2337
2899
  return void 0;
2338
2900
  }
@@ -2399,6 +2961,24 @@ var CrateReader = class {
2399
2961
  }
2400
2962
  return out;
2401
2963
  }
2964
+ /**
2965
+ * Read an `SdfVariantSelectionMap`: `[u64 count]` then `count` pairs of
2966
+ * string indexes (`variantSetName → variantName`). Surfaces as a dictionary
2967
+ * so it maps straight onto the USDA `variants = { ... }` metadatum.
2968
+ */
2969
+ readVariantSelectionMap(off) {
2970
+ const count = this.u64(off);
2971
+ const strings = this.getStrings();
2972
+ const out = {};
2973
+ let p = off + 8;
2974
+ for (let i = 0; i < count; i++) {
2975
+ const key = this.getToken(strings[this.view.getUint32(p, true)] ?? 0);
2976
+ const value = this.getToken(strings[this.view.getUint32(p + 4, true)] ?? 0);
2977
+ if (key) out[key] = value;
2978
+ p += 8;
2979
+ }
2980
+ return out;
2981
+ }
2402
2982
  /** Read a `[u64 count][count × uint32|int32 index]` vector → resolved strings. */
2403
2983
  readIndexVector(off, kind) {
2404
2984
  const count = this.u64(off);
@@ -2406,7 +2986,7 @@ var CrateReader = class {
2406
2986
  const items = new Array(count);
2407
2987
  for (let i = 0; i < count; i++) {
2408
2988
  const idx = this.view.getInt32(p, true);
2409
- items[i] = kind === "token" ? this.getToken(idx) : this.getPaths()[idx] ?? "";
2989
+ items[i] = kind === "token" ? this.getToken(idx) : kind === "string" ? this.getToken(this.getStrings()[idx] ?? 0) : this.getPaths()[idx] ?? "";
2410
2990
  p += 4;
2411
2991
  }
2412
2992
  return { items, next: p };
@@ -2488,6 +3068,7 @@ var SPEC_PRIM = 6;
2488
3068
  var SPEC_PSEUDO_ROOT = 7;
2489
3069
  var SPEC_ATTRIBUTE = 1;
2490
3070
  var SPEC_RELATIONSHIP = 8;
3071
+ var SPEC_VARIANT = 10;
2491
3072
  var SPECIFIERS2 = ["def", "over", "class"];
2492
3073
  function crateToUsdaFile(crate) {
2493
3074
  const paths = crate.getPaths();
@@ -2502,8 +3083,26 @@ function crateToUsdaFile(crate) {
2502
3083
  return map;
2503
3084
  };
2504
3085
  const primByPath = /* @__PURE__ */ new Map();
3086
+ const variantByPath = /* @__PURE__ */ new Map();
2505
3087
  const rootPrims = [];
2506
3088
  let layerMetadata = {};
3089
+ const containerAt = (path) => {
3090
+ const prim = primByPath.get(path);
3091
+ if (prim) return prim;
3092
+ const cached = variantByPath.get(path);
3093
+ if (cached) return cached;
3094
+ const selection = variantNode(path);
3095
+ if (!selection) return void 0;
3096
+ const owner = primByPath.get(selection.owner);
3097
+ if (!owner) return void 0;
3098
+ owner.variantSets ??= {};
3099
+ owner.variantSets[selection.setName] ??= {};
3100
+ const set = owner.variantSets[selection.setName];
3101
+ set[selection.variantName] ??= { properties: [], children: [], metadata: {} };
3102
+ const content = set[selection.variantName];
3103
+ variantByPath.set(path, content);
3104
+ return content;
3105
+ };
2507
3106
  for (const spec of specs) {
2508
3107
  const path = paths[spec.pathIndex] ?? "";
2509
3108
  if (spec.specType === SPEC_PSEUDO_ROOT) {
@@ -2522,31 +3121,50 @@ function crateToUsdaFile(crate) {
2522
3121
  line: 0
2523
3122
  });
2524
3123
  }
3124
+ for (const spec of specs) {
3125
+ if (spec.specType !== SPEC_VARIANT) continue;
3126
+ const content = containerAt(paths[spec.pathIndex] ?? "");
3127
+ if (content)
3128
+ Object.assign(content.metadata, buildPrimMetadata(crate, fieldsOf(spec.fieldSetIndex)));
3129
+ }
2525
3130
  for (const spec of specs) {
2526
3131
  if (spec.specType !== SPEC_ATTRIBUTE && spec.specType !== SPEC_RELATIONSHIP) continue;
2527
3132
  const split = splitProperty(paths[spec.pathIndex] ?? "");
2528
3133
  if (!split) continue;
2529
- const prim = primByPath.get(split.primPath);
2530
- if (!prim) continue;
3134
+ const owner = containerAt(split.primPath);
3135
+ if (!owner) continue;
2531
3136
  const fm = fieldsOf(spec.fieldSetIndex);
2532
- prim.properties.push(
3137
+ owner.properties.push(
2533
3138
  spec.specType === SPEC_ATTRIBUTE ? buildAttribute(crate, split.propName, fm) : buildRelationship(crate, split.propName, fm)
2534
3139
  );
2535
3140
  }
2536
3141
  for (const [path, prim] of primByPath) {
2537
3142
  const parentPath = parentOf(path);
2538
3143
  if (parentPath === "/") rootPrims.push(prim);
2539
- else primByPath.get(parentPath)?.children.push(prim);
3144
+ else containerAt(parentPath)?.children.push(prim);
2540
3145
  }
2541
3146
  return { version: crate.version.join("."), metadata: layerMetadata, prims: rootPrims };
2542
3147
  }
3148
+ function variantNode(path) {
3149
+ if (!path.endsWith("}")) return null;
3150
+ const open = path.lastIndexOf("{");
3151
+ if (open < 0) return null;
3152
+ const selection = path.slice(open + 1, -1);
3153
+ const eq = selection.indexOf("=");
3154
+ if (eq < 0) return null;
3155
+ const variantName = selection.slice(eq + 1);
3156
+ if (variantName === "") return null;
3157
+ return { owner: path.slice(0, open), setName: selection.slice(0, eq), variantName };
3158
+ }
2543
3159
  function buildAttribute(crate, name, fm) {
2544
3160
  const defaultRep = fm.get("default");
3161
+ const rawType = asString(crate, fm.get("typeName")) ?? "";
3162
+ const isArrayType = rawType.endsWith("[]");
2545
3163
  const attr = {
2546
3164
  kind: "attribute",
2547
3165
  name,
2548
- typeName: asString(crate, fm.get("typeName")) ?? "",
2549
- isArray: defaultRep !== void 0 ? decodeRepBits(defaultRep).isArray : false,
3166
+ typeName: isArrayType ? rawType.slice(0, -2) : rawType,
3167
+ isArray: isArrayType || (defaultRep !== void 0 ? decodeRepBits(defaultRep).isArray : false),
2550
3168
  variability: asNumber2(crate, fm.get("variability")) === 1 ? "uniform" : "varying",
2551
3169
  custom: false,
2552
3170
  metadata: {},
@@ -2578,6 +3196,10 @@ function buildPrimMetadata(crate, fm) {
2578
3196
  if (Array.isArray(apiSchemas)) meta.apiSchemas = apiSchemas;
2579
3197
  const kind = asString(crate, fm.get("kind"));
2580
3198
  if (kind !== void 0) meta.kind = kind;
3199
+ const variants = fm.has("variantSelection") ? crate.getValue(fm.get("variantSelection")) : void 0;
3200
+ if (variants && typeof variants === "object" && !Array.isArray(variants)) {
3201
+ meta.variants = variants;
3202
+ }
2581
3203
  for (const key of ARC_FIELDS) {
2582
3204
  if (!fm.has(key)) continue;
2583
3205
  const arcs = crate.getValue(fm.get(key));
@@ -2587,6 +3209,11 @@ function buildPrimMetadata(crate, fm) {
2587
3209
  }
2588
3210
  function buildLayerMetadata(crate, fm) {
2589
3211
  const meta = {};
3212
+ const subLayers = fm.has("subLayers") ? crate.getValue(fm.get("subLayers")) : void 0;
3213
+ if (Array.isArray(subLayers)) {
3214
+ const paths = subLayers.filter((s) => typeof s === "string" && s.length > 0);
3215
+ if (paths.length > 0) meta.subLayers = paths.map((p) => new AssetPath(p));
3216
+ }
2590
3217
  const upAxis = asString(crate, fm.get("upAxis"));
2591
3218
  if (upAxis !== void 0) meta.upAxis = upAxis;
2592
3219
  const defaultPrim2 = asString(crate, fm.get("defaultPrim"));
@@ -2607,10 +3234,10 @@ function asNumber2(crate, rep) {
2607
3234
  }
2608
3235
  function splitProperty(path) {
2609
3236
  const slash = path.lastIndexOf("/");
2610
- const dot = path.indexOf(".", slash < 0 ? 0 : slash);
2611
- if (dot === -1) return null;
3237
+ const dot2 = path.indexOf(".", slash < 0 ? 0 : slash);
3238
+ if (dot2 === -1) return null;
2612
3239
  if (path.includes("[")) return null;
2613
- return { primPath: path.slice(0, dot), propName: path.slice(dot + 1) };
3240
+ return { primPath: path.slice(0, dot2), propName: path.slice(dot2 + 1) };
2614
3241
  }
2615
3242
  function parentOf(path) {
2616
3243
  const i = path.lastIndexOf("/");
@@ -2623,16 +3250,24 @@ function leaf(path) {
2623
3250
  // src/usd/composition.ts
2624
3251
  var ARC_KEYS = ["references", "payload", "payloads", "inherits", "specializes"];
2625
3252
  var STRIP_KEYS = [...ARC_KEYS, "variants", "variantSets"];
2626
- async function composeLayer(text, baseUrl, resolver, options = {}, stack = /* @__PURE__ */ new Set()) {
2627
- return composeFile(parseUsda(text), baseUrl, resolver, options, stack);
3253
+ async function composeLayer(text, baseUrl, resolver, options = {}, stack = /* @__PURE__ */ new Set(), cache = /* @__PURE__ */ new Map()) {
3254
+ return composeFile(parseUsda(text), baseUrl, resolver, options, stack, cache);
2628
3255
  }
2629
- async function composeFile(file, baseUrl, resolver, options = {}, stack = /* @__PURE__ */ new Set()) {
3256
+ async function composeFile(file, baseUrl, resolver, options = {}, stack = /* @__PURE__ */ new Set(), cache = /* @__PURE__ */ new Map()) {
2630
3257
  const warn = options.onWarn ?? (() => {
2631
3258
  });
2632
3259
  let weak = [];
2633
3260
  const subLayers = toArcs(file.metadata.subLayers);
2634
3261
  for (let i = subLayers.length - 1; i >= 0; i--) {
2635
- const sub = await loadExternalFile(subLayers[i], baseUrl, resolver, options, stack, warn);
3262
+ const sub = await loadExternalFile(
3263
+ subLayers[i],
3264
+ baseUrl,
3265
+ resolver,
3266
+ options,
3267
+ stack,
3268
+ warn,
3269
+ cache
3270
+ );
2636
3271
  if (sub) weak = mergePrimLists(weak, sub.prims);
2637
3272
  }
2638
3273
  const ctx = {
@@ -2642,7 +3277,8 @@ async function composeFile(file, baseUrl, resolver, options = {}, stack = /* @__
2642
3277
  warn,
2643
3278
  stack,
2644
3279
  index: buildPathIndex(file.prims),
2645
- resolving: /* @__PURE__ */ new Set()
3280
+ resolving: /* @__PURE__ */ new Set(),
3281
+ cache
2646
3282
  };
2647
3283
  const resolved = [];
2648
3284
  for (const prim of file.prims) resolved.push(await resolvePrim(prim, "", ctx));
@@ -2653,13 +3289,15 @@ async function resolvePrim(spec, parentPath, ctx) {
2653
3289
  const path = `${parentPath}/${spec.name}`;
2654
3290
  let properties = spec.properties;
2655
3291
  let children = spec.children;
3292
+ const variantArcs = [];
2656
3293
  const selection = spec.metadata.variants;
2657
- if (spec.variantSets && isDictionary(selection)) {
3294
+ if (spec.variantSets && isDictionary2(selection)) {
2658
3295
  for (const [setName, variantName] of Object.entries(selection)) {
2659
3296
  const variant = spec.variantSets[setName]?.[String(variantName)];
2660
3297
  if (!variant) continue;
2661
3298
  properties = mergeProperties(variant.properties, properties);
2662
3299
  children = mergePrimLists(variant.children, children);
3300
+ for (const key of ARC_KEYS) variantArcs.push(...toArcs(variant.metadata[key]));
2663
3301
  }
2664
3302
  }
2665
3303
  const resolvedChildren = [];
@@ -2673,7 +3311,7 @@ async function resolvePrim(spec, parentPath, ctx) {
2673
3311
  children: resolvedChildren,
2674
3312
  line: spec.line
2675
3313
  };
2676
- const arcs = ARC_KEYS.flatMap((k) => toArcs(spec.metadata[k]));
3314
+ const arcs = [...variantArcs, ...ARC_KEYS.flatMap((k) => toArcs(spec.metadata[k]))];
2677
3315
  let base = null;
2678
3316
  for (const arc of arcs) {
2679
3317
  const target = await loadReferencedPrim(arc, ctx, path);
@@ -2682,20 +3320,21 @@ async function resolvePrim(spec, parentPath, ctx) {
2682
3320
  return base ? mergePrim(base, local) : local;
2683
3321
  }
2684
3322
  async function loadReferencedPrim(arc, ctx, destPath) {
2685
- if (arc.assetPath) {
3323
+ if (arc.assetPath?.path) {
2686
3324
  const composed = await loadExternalFile(
2687
3325
  arc,
2688
3326
  ctx.baseUrl,
2689
3327
  ctx.resolver,
2690
3328
  ctx.options,
2691
3329
  ctx.stack,
2692
- ctx.warn
3330
+ ctx.warn,
3331
+ ctx.cache
2693
3332
  );
2694
3333
  if (!composed) return null;
2695
3334
  const target = arc.primPath ? findPrimByPath(composed, arc.primPath) : defaultPrim(composed, ctx.warn);
2696
3335
  if (!target) {
2697
3336
  ctx.warn(
2698
- `reference target ${arc.primPath ?? "(defaultPrim)"} not found in ${arc.assetPath.path}`
3337
+ `reference target ${arc.primPath ?? "(defaultPrim)"} not found in ${arc.assetPath?.path}`
2699
3338
  );
2700
3339
  return null;
2701
3340
  }
@@ -2721,9 +3360,10 @@ async function loadReferencedPrim(arc, ctx, destPath) {
2721
3360
  }
2722
3361
  return null;
2723
3362
  }
2724
- async function loadExternalFile(arc, baseUrl, resolver, options, stack, warn) {
2725
- if (!arc.assetPath) return null;
2726
- const url = resolver.resolve(arc.assetPath.path, baseUrl);
3363
+ async function loadExternalFile(arc, baseUrl, resolver, options, stack, warn, cache) {
3364
+ if (!arc.assetPath?.path) return null;
3365
+ const assetPath = arc.assetPath.path;
3366
+ const url = resolver.resolve(assetPath, baseUrl);
2727
3367
  if (stack.has(url)) {
2728
3368
  warn(`composition cycle detected at ${url}; skipping`);
2729
3369
  return null;
@@ -2732,18 +3372,31 @@ async function loadExternalFile(arc, baseUrl, resolver, options, stack, warn) {
2732
3372
  warn(`composition exceeded max depth at ${url}; skipping`);
2733
3373
  return null;
2734
3374
  }
2735
- let bytes;
2736
- try {
2737
- bytes = await fetchLayerBytes(resolver, url);
2738
- } catch (err) {
2739
- warn(`cannot resolve "${arc.assetPath.path}" -> ${url}: ${err.message}`);
2740
- return null;
2741
- }
2742
- const childStack = /* @__PURE__ */ new Set([...stack, url]);
2743
- if (CrateReader.isCrate(bytes)) {
2744
- return composeFile(crateToUsdaFile(new CrateReader(bytes)), url, resolver, options, childStack);
2745
- }
2746
- return composeLayer(new TextDecoder().decode(bytes), url, resolver, options, childStack);
3375
+ const cached = cache.get(url);
3376
+ if (cached) return cached;
3377
+ const pending = (async () => {
3378
+ let bytes;
3379
+ try {
3380
+ bytes = await fetchLayerBytes(resolver, url);
3381
+ } catch (err) {
3382
+ warn(`cannot resolve "${assetPath}" -> ${url}: ${err.message}`);
3383
+ return null;
3384
+ }
3385
+ const childStack = /* @__PURE__ */ new Set([...stack, url]);
3386
+ if (CrateReader.isCrate(bytes)) {
3387
+ return composeFile(
3388
+ crateToUsdaFile(new CrateReader(bytes)),
3389
+ url,
3390
+ resolver,
3391
+ options,
3392
+ childStack,
3393
+ cache
3394
+ );
3395
+ }
3396
+ return composeLayer(new TextDecoder().decode(bytes), url, resolver, options, childStack, cache);
3397
+ })();
3398
+ cache.set(url, pending);
3399
+ return pending;
2747
3400
  }
2748
3401
  async function fetchLayerBytes(resolver, url) {
2749
3402
  if (resolver.fetchBytes) return resolver.fetchBytes(url);
@@ -2881,7 +3534,7 @@ function toArcs(value) {
2881
3534
  }
2882
3535
  return arcs;
2883
3536
  }
2884
- function isDictionary(v) {
3537
+ function isDictionary2(v) {
2885
3538
  return typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Quat) && !(v instanceof UsdMatrix) && !(v instanceof AssetPath);
2886
3539
  }
2887
3540
  function stripKeys(meta, keys) {
@@ -2921,6 +3574,6 @@ function openUsdz(bytes) {
2921
3574
  return { rootEntry, resolver };
2922
3575
  }
2923
3576
 
2924
- export { ARTICULATION_ROOT_API, AssetPath, Attribute, COLLISION_API, CrateReader, DEFAULT_METERS_PER_UNIT, DEG2RAD, DefaultAssetResolver, Layer, ParseError, Prim, Quat, RAD2DEG, RIGID_BODY_API, Relationship, Stage, TokenizeError, UsdMatrix, buildKinematicTree, channelFromSamples, composeFile, composeLayer, computeLocalTransform, crateToUsdaFile, createMemoryResolver, driveKindFor, extractRobotDescription, fromUsdMatrix, gatherMeshDescendants, getJointAxis, getJointBodies, getJointDrive, getJointLimits, getJointLocalFrame, getJointStatePosition, getJointType, getTranslation, hasArticulationRootAPI, hasCollisionAPI, hasRigidBodyAPI, identity4, interpolate, invert, isMesh, isScope, isXform, iterDescendants, joinPosix, jointValueToSI, makeEuler, makeRotationFromQuat, makeRotationX, makeRotationY, makeRotationZ, makeScale, makeTranslation, multiply, multiplyAll, normalizeJointLimits, openUsdz, parseOpType, parseUsda, refineJointType, tokenize };
2925
- //# sourceMappingURL=chunk-36T6YOEX.js.map
2926
- //# sourceMappingURL=chunk-36T6YOEX.js.map
3577
+ export { ARTICULATION_ROOT_API, AssetPath, Attribute, COLLISION_API, CrateReader, DEFAULT_METERS_PER_UNIT, DEG2RAD, DefaultAssetResolver, Layer, MASS_API, MESH_COLLISION_API, PHYSICS_MATERIAL_API, ParseError, Prim, Quat, RAD2DEG, RIGID_BODY_API, Relationship, Stage, TokenizeError, UsdMatrix, buildKinematicTree, channelFromSamples, composeFile, composeLayer, computeLocalTransform, computeWorldTransform, crateToUsdaFile, createMemoryResolver, decomposeRigid, driveKindFor, extractRobotDescription, fromUsdMatrix, gatherMeshDescendants, getJointAxis, getJointBodies, getJointDrive, getJointLimits, getJointLocalFrame, getJointStatePosition, getJointType, getMassProperties, getMaterialSubsets, getTranslation, hasArticulationRootAPI, hasCollisionAPI, hasRigidBodyAPI, identity4, interpolate, invert, isMesh, isNonVisualPurpose, isScope, isXform, iterDescendants, joinPosix, jointValueFromSI, jointValueToSI, makeEuler, makeRotationFromQuat, makeRotationX, makeRotationY, makeRotationZ, makeScale, makeTranslation, multiply, multiplyAll, normalizeJointLimits, openUsdz, parseOpType, parseUsda, refineJointType, resolveBoundMaterial, serializeUsda, toUsdMatrix, tokenize };
3578
+ //# sourceMappingURL=chunk-4GPCBXUS.js.map
3579
+ //# sourceMappingURL=chunk-4GPCBXUS.js.map