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.
@@ -0,0 +1,935 @@
1
+ import { buildKinematicTree, multiply, invert, identity4, ARTICULATION_ROOT_API, Quat, COLLISION_API, MESH_COLLISION_API, PHYSICS_MATERIAL_API, AssetPath, toUsdMatrix, driveKindFor, jointValueFromSI, decomposeRigid, getMaterialSubsets, resolveBoundMaterial, computeLocalTransform, RIGID_BODY_API, MASS_API } from './chunk-4GPCBXUS.js';
2
+ import { strToU8, zipSync } from 'fflate';
3
+
4
+ // src/version.ts
5
+ var PACKAGE_NAME = "three-usd-robot";
6
+ var VERSION = "0.0.0";
7
+
8
+ // src/export/exportRobot.ts
9
+ var PHYSX_ARTICULATION_API = "PhysxArticulationAPI";
10
+ var JOINT_SCHEMA_BY_TYPE = {
11
+ fixed: "PhysicsFixedJoint",
12
+ revolute: "PhysicsRevoluteJoint",
13
+ continuous: "PhysicsRevoluteJoint",
14
+ prismatic: "PhysicsPrismaticJoint"
15
+ };
16
+ function exportRobotUsda(desc, options = {}) {
17
+ const warn = (m) => options.onWarn?.(m);
18
+ const tree = buildKinematicTree(desc, { onWarn: warn });
19
+ const robotName = sanitizeName(options.robotName ?? desc.name ?? "robot");
20
+ const alloc = nameAllocator(warn);
21
+ const linkNames = /* @__PURE__ */ new Map();
22
+ for (const key of Object.keys(desc.links)) linkNames.set(key, alloc(key, `link "${key}"`));
23
+ const jointNames = /* @__PURE__ */ new Map();
24
+ for (const key of Object.keys(desc.joints)) jointNames.set(key, alloc(key, `joint "${key}"`));
25
+ const linkPath = (key) => `/${robotName}/${linkNames.get(key)}`;
26
+ const authoredWorld = (key) => desc.links[key]?.worldTransform ?? identity4();
27
+ const worldByLink = /* @__PURE__ */ new Map();
28
+ for (const key of tree.order) {
29
+ const node = tree.nodes[key];
30
+ if (node.parent === null || node.jointToParent === null) {
31
+ const rootJoint = tree.rootJoint ? desc.joints[tree.rootJoint] : void 0;
32
+ worldByLink.set(
33
+ key,
34
+ rootJoint ? multiply(rootJoint.jointFrame0, invert(rootJoint.jointFrame1)) : authoredWorld(key)
35
+ );
36
+ continue;
37
+ }
38
+ const joint = desc.joints[node.jointToParent];
39
+ const parentWorld = worldByLink.get(node.parent) ?? identity4();
40
+ worldByLink.set(
41
+ key,
42
+ multiply(multiply(parentWorld, joint.jointFrame0), invert(joint.jointFrame1))
43
+ );
44
+ }
45
+ for (const key of tree.isolatedLinks) worldByLink.set(key, authoredWorld(key));
46
+ const articulationLinks = new Set(desc.articulationRoots ?? []);
47
+ const meshesByLink = /* @__PURE__ */ new Map();
48
+ const materials = /* @__PURE__ */ new Map();
49
+ const physicsMaterials = /* @__PURE__ */ new Map();
50
+ for (const [key, link] of Object.entries(desc.links)) {
51
+ const meshes = options.geometry?.(key, link) ?? [];
52
+ meshesByLink.set(key, meshes);
53
+ for (const mesh of meshes) {
54
+ if (mesh.material && !materials.has(mesh.material.name)) {
55
+ materials.set(mesh.material.name, mesh.material);
56
+ }
57
+ if (mesh.physicsMaterial && !physicsMaterials.has(mesh.physicsMaterial.name)) {
58
+ physicsMaterials.set(mesh.physicsMaterial.name, mesh.physicsMaterial);
59
+ }
60
+ }
61
+ }
62
+ const looksName = materials.size > 0 ? alloc("Looks") : void 0;
63
+ const materialAlloc = nameAllocator();
64
+ const materialNames = /* @__PURE__ */ new Map();
65
+ for (const name of materials.keys()) materialNames.set(name, materialAlloc(name));
66
+ const materialPath = (name) => looksName ? `/${robotName}/${looksName}/${materialNames.get(name)}` : void 0;
67
+ const physicsScopeName = physicsMaterials.size > 0 ? alloc("PhysicsMaterials") : void 0;
68
+ const physicsMaterialAlloc = nameAllocator();
69
+ const physicsMaterialNames = /* @__PURE__ */ new Map();
70
+ for (const name of physicsMaterials.keys()) {
71
+ physicsMaterialNames.set(name, physicsMaterialAlloc(name));
72
+ }
73
+ const physicsMaterialPath = (name) => physicsScopeName ? `/${robotName}/${physicsScopeName}/${physicsMaterialNames.get(name)}` : void 0;
74
+ const isaac = options.isaacRobotSchema === true;
75
+ const children = [];
76
+ for (const [key, link] of Object.entries(desc.links)) {
77
+ const apiSchemas = [RIGID_BODY_API];
78
+ if (articulationLinks.has(key)) apiSchemas.push(ARTICULATION_ROOT_API);
79
+ if (link.inertial) apiSchemas.push(MASS_API);
80
+ if (isaac) apiSchemas.push("IsaacLinkAPI");
81
+ const selfCollision = articulationLinks.has(key) ? options.enabledSelfCollisions : void 0;
82
+ if (selfCollision !== void 0) apiSchemas.push(PHYSX_ARTICULATION_API);
83
+ children.push(
84
+ buildLinkPrim({
85
+ name: linkNames.get(key),
86
+ world: worldByLink.get(key) ?? identity4(),
87
+ apiSchemas,
88
+ meshes: meshesByLink.get(key) ?? [],
89
+ materialPath,
90
+ physicsMaterialPath,
91
+ warn,
92
+ ...link.inertial ? { inertial: link.inertial } : {},
93
+ ...selfCollision !== void 0 ? { selfCollision } : {}
94
+ })
95
+ );
96
+ }
97
+ const emittedJoints = /* @__PURE__ */ new Set();
98
+ for (const [key, joint] of Object.entries(desc.joints)) {
99
+ if (!desc.links[joint.child] || joint.parent !== "" && !desc.links[joint.parent]) {
100
+ warn(`joint "${key}" references an unknown link; skipped`);
101
+ continue;
102
+ }
103
+ emittedJoints.add(key);
104
+ children.push(buildJointPrim(jointNames.get(key), key, joint, linkPath, isaac, warn));
105
+ }
106
+ if (looksName) {
107
+ children.push(buildLooksPrim(looksName, materials, materialNames, materialPath));
108
+ }
109
+ if (physicsScopeName) {
110
+ children.push(
111
+ buildPhysicsMaterialsPrim(physicsScopeName, physicsMaterials, physicsMaterialNames)
112
+ );
113
+ }
114
+ const rootApiSchemas = [];
115
+ const rootProperties = [];
116
+ if (articulationLinks.size === 0) {
117
+ rootApiSchemas.push(ARTICULATION_ROOT_API);
118
+ if (options.enabledSelfCollisions !== void 0) {
119
+ rootApiSchemas.push(PHYSX_ARTICULATION_API);
120
+ rootProperties.push(
121
+ attr("physxArticulation:enabledSelfCollisions", "bool", options.enabledSelfCollisions)
122
+ );
123
+ }
124
+ }
125
+ if (isaac) {
126
+ rootApiSchemas.push("IsaacRobotAPI");
127
+ const orderedLinks = [...tree.order, ...tree.isolatedLinks];
128
+ const orderedJoints = [];
129
+ const pushJoint = (key) => {
130
+ if (key && emittedJoints.has(key) && !orderedJoints.includes(key)) orderedJoints.push(key);
131
+ };
132
+ pushJoint(tree.rootJoint);
133
+ for (const linkKey of tree.order) pushJoint(tree.nodes[linkKey]?.jointToParent);
134
+ for (const jointKey of tree.loopJoints) pushJoint(jointKey);
135
+ for (const jointKey of Object.keys(desc.joints)) pushJoint(jointKey);
136
+ rootProperties.push(rel("isaac:physics:robotLinks", orderedLinks.map(linkPath)));
137
+ rootProperties.push(
138
+ rel(
139
+ "isaac:physics:robotJoints",
140
+ orderedJoints.map((key) => `/${robotName}/${jointNames.get(key)}`)
141
+ )
142
+ );
143
+ }
144
+ const root = {
145
+ specifier: "def",
146
+ typeName: "Xform",
147
+ name: robotName,
148
+ metadata: rootApiSchemas.length > 0 ? { apiSchemas: rootApiSchemas } : {},
149
+ properties: rootProperties,
150
+ children,
151
+ line: 0
152
+ };
153
+ const metadata = {
154
+ defaultPrim: robotName,
155
+ metersPerUnit: desc.metersPerUnit,
156
+ upAxis: desc.upAxis,
157
+ ...desc.timeCodesPerSecond !== void 0 ? { timeCodesPerSecond: desc.timeCodesPerSecond } : {},
158
+ ...desc.startTimeCode !== void 0 ? { startTimeCode: desc.startTimeCode } : {},
159
+ ...desc.endTimeCode !== void 0 ? { endTimeCode: desc.endTimeCode } : {}
160
+ };
161
+ return { version: "1.0", metadata, prims: [root] };
162
+ }
163
+ function buildLinkPrim(args) {
164
+ const meshAlloc = nameAllocator();
165
+ const properties = transformProps(args.world);
166
+ if (args.inertial) properties.push(...massProps(args.inertial, args.name, args.warn));
167
+ if (args.selfCollision !== void 0) {
168
+ properties.push(attr("physxArticulation:enabledSelfCollisions", "bool", args.selfCollision));
169
+ }
170
+ return {
171
+ specifier: "def",
172
+ typeName: "Xform",
173
+ name: args.name,
174
+ metadata: { apiSchemas: args.apiSchemas },
175
+ properties,
176
+ children: args.meshes.map(
177
+ (mesh) => buildMeshPrim(mesh, meshAlloc(mesh.name), args.materialPath, args.physicsMaterialPath)
178
+ ),
179
+ line: 0
180
+ };
181
+ }
182
+ function massProps(inertial, linkName, warn) {
183
+ const out = [];
184
+ if (inertial.mass !== void 0) out.push(attr("physics:mass", "float", inertial.mass));
185
+ if (inertial.density !== void 0) out.push(attr("physics:density", "float", inertial.density));
186
+ if (inertial.centerOfMass) {
187
+ out.push(attr("physics:centerOfMass", "point3f", inertial.centerOfMass));
188
+ }
189
+ if (inertial.diagonalInertia) {
190
+ out.push(attr("physics:diagonalInertia", "float3", inertial.diagonalInertia));
191
+ out.push(attr("physics:principalAxes", "quatf", inertial.principalAxes ?? Quat.identity()));
192
+ } else if (inertial.principalAxes) {
193
+ warn(`link "${linkName}": principalAxes without diagonalInertia is meaningless; dropped`);
194
+ }
195
+ return out;
196
+ }
197
+ function buildMeshPrim(mesh, name, materialPath, physicsMaterialPath) {
198
+ const properties = [];
199
+ if (mesh.transform) properties.push(...transformProps(mesh.transform));
200
+ properties.push(
201
+ arrayAttr("faceVertexCounts", "int", mesh.faceVertexCounts),
202
+ arrayAttr("faceVertexIndices", "int", mesh.faceVertexIndices),
203
+ arrayAttr("points", "point3f", mesh.points)
204
+ );
205
+ if (mesh.normals) properties.push(arrayAttr("normals", "normal3f", mesh.normals));
206
+ if (mesh.st) {
207
+ properties.push(arrayAttr("primvars:st", "texCoord2f", mesh.st, { interpolation: "vertex" }));
208
+ }
209
+ if (mesh.displayColor) {
210
+ properties.push(arrayAttr("primvars:displayColor", "color3f", [mesh.displayColor]));
211
+ }
212
+ if (mesh.doubleSided) properties.push(attr("doubleSided", "bool", true));
213
+ properties.push(uniformToken("subdivisionScheme", "none"));
214
+ const apiSchemas = [];
215
+ if (mesh.kind === "collision") {
216
+ apiSchemas.push(COLLISION_API);
217
+ properties.push(uniformToken("purpose", "guide"));
218
+ if (mesh.collisionApproximation) {
219
+ apiSchemas.push(MESH_COLLISION_API);
220
+ properties.push(uniformToken("physics:approximation", mesh.collisionApproximation));
221
+ }
222
+ }
223
+ const binding = mesh.material ? materialPath(mesh.material.name) : void 0;
224
+ if (binding) properties.push(rel("material:binding", [binding]));
225
+ const physicsBinding = mesh.kind === "collision" && mesh.physicsMaterial ? physicsMaterialPath(mesh.physicsMaterial.name) : void 0;
226
+ if (physicsBinding) properties.push(rel("material:binding:physics", [physicsBinding]));
227
+ if (binding || physicsBinding) apiSchemas.push("MaterialBindingAPI");
228
+ return {
229
+ specifier: "def",
230
+ typeName: "Mesh",
231
+ name,
232
+ metadata: apiSchemas.length > 0 ? { apiSchemas } : {},
233
+ properties,
234
+ children: [],
235
+ line: 0
236
+ };
237
+ }
238
+ function buildPhysicsMaterialsPrim(scopeName, materials, names) {
239
+ const children = [];
240
+ for (const [key, material] of materials) {
241
+ children.push(buildPhysicsMaterialPrim(names.get(key), material));
242
+ }
243
+ return {
244
+ specifier: "def",
245
+ typeName: "Scope",
246
+ name: scopeName,
247
+ metadata: {},
248
+ properties: [],
249
+ children,
250
+ line: 0
251
+ };
252
+ }
253
+ function buildPhysicsMaterialPrim(name, material) {
254
+ const properties = [];
255
+ if (material.staticFriction !== void 0) {
256
+ properties.push(attr("physics:staticFriction", "float", material.staticFriction));
257
+ }
258
+ if (material.dynamicFriction !== void 0) {
259
+ properties.push(attr("physics:dynamicFriction", "float", material.dynamicFriction));
260
+ }
261
+ if (material.restitution !== void 0) {
262
+ properties.push(attr("physics:restitution", "float", material.restitution));
263
+ }
264
+ if (material.density !== void 0) {
265
+ properties.push(attr("physics:density", "float", material.density));
266
+ }
267
+ return {
268
+ specifier: "def",
269
+ typeName: "Material",
270
+ name,
271
+ metadata: { apiSchemas: [PHYSICS_MATERIAL_API] },
272
+ properties,
273
+ children: [],
274
+ line: 0
275
+ };
276
+ }
277
+ function buildLooksPrim(looksName, materials, materialNames, materialPath) {
278
+ const children = [];
279
+ for (const [key, material] of materials) {
280
+ children.push(buildMaterialPrim(materialNames.get(key), material, materialPath(key)));
281
+ }
282
+ return {
283
+ specifier: "def",
284
+ typeName: "Scope",
285
+ name: looksName,
286
+ metadata: {},
287
+ properties: [],
288
+ children,
289
+ line: 0
290
+ };
291
+ }
292
+ var TEXTURE_WIRING = {
293
+ color: {
294
+ input: "inputs:diffuseColor",
295
+ inputType: "color3f",
296
+ output: "outputs:rgb",
297
+ outputType: "float3",
298
+ colorSpace: "sRGB"
299
+ },
300
+ emissive: {
301
+ input: "inputs:emissiveColor",
302
+ inputType: "color3f",
303
+ output: "outputs:rgb",
304
+ outputType: "float3",
305
+ colorSpace: "sRGB"
306
+ },
307
+ normal: {
308
+ input: "inputs:normal",
309
+ inputType: "normal3f",
310
+ output: "outputs:rgb",
311
+ outputType: "float3",
312
+ colorSpace: "raw"
313
+ },
314
+ opacity: {
315
+ input: "inputs:opacity",
316
+ inputType: "float",
317
+ output: "outputs:a",
318
+ outputType: "float",
319
+ colorSpace: "raw"
320
+ },
321
+ roughness: {
322
+ input: "inputs:roughness",
323
+ inputType: "float",
324
+ output: "outputs:r",
325
+ outputType: "float",
326
+ colorSpace: "raw"
327
+ },
328
+ metalness: {
329
+ input: "inputs:metallic",
330
+ inputType: "float",
331
+ output: "outputs:r",
332
+ outputType: "float",
333
+ colorSpace: "raw"
334
+ },
335
+ occlusion: {
336
+ input: "inputs:occlusion",
337
+ inputType: "float",
338
+ output: "outputs:r",
339
+ outputType: "float",
340
+ colorSpace: "raw"
341
+ }
342
+ };
343
+ function buildMaterialPrim(name, material, selfPath) {
344
+ const textures = material.textures ?? {};
345
+ const shaderProps = [uniformToken("info:id", "UsdPreviewSurface")];
346
+ const input = (channel, value) => {
347
+ const wiring = TEXTURE_WIRING[channel];
348
+ const texturePath = textures[channel];
349
+ if (value === void 0 && texturePath === void 0) return;
350
+ const spec = attr(wiring.input, wiring.inputType, value);
351
+ if (texturePath !== void 0) {
352
+ spec.connections = [`${selfPath}/${channel}Texture.${wiring.output}`];
353
+ }
354
+ shaderProps.push(spec);
355
+ };
356
+ input("color", material.diffuseColor);
357
+ input("metalness", material.metallic);
358
+ input("roughness", material.roughness);
359
+ input("opacity", material.opacity);
360
+ input("emissive", material.emissiveColor);
361
+ input("normal");
362
+ input("occlusion");
363
+ shaderProps.push(attr("outputs:surface", "token", void 0));
364
+ const children = [shaderPrim("PreviewSurface", shaderProps)];
365
+ const textureEntries = Object.entries(textures);
366
+ if (textureEntries.length > 0) {
367
+ children.push(
368
+ shaderPrim("stReader", [
369
+ uniformToken("info:id", "UsdPrimvarReader_float2"),
370
+ attr("inputs:varname", "string", "st"),
371
+ attr("outputs:result", "float2", void 0)
372
+ ])
373
+ );
374
+ for (const [channel, path] of textureEntries) {
375
+ children.push(buildUvTexturePrim(channel, path, selfPath));
376
+ }
377
+ }
378
+ const surfaceConnect = attr("outputs:surface", "token", void 0);
379
+ surfaceConnect.connections = [`${selfPath}/PreviewSurface.outputs:surface`];
380
+ return {
381
+ specifier: "def",
382
+ typeName: "Material",
383
+ name,
384
+ metadata: {},
385
+ properties: [surfaceConnect],
386
+ children,
387
+ line: 0
388
+ };
389
+ }
390
+ function buildUvTexturePrim(channel, path, selfPath) {
391
+ const wiring = TEXTURE_WIRING[channel];
392
+ const st = attr("inputs:st", "float2", void 0);
393
+ st.connections = [`${selfPath}/stReader.outputs:result`];
394
+ return shaderPrim(`${channel}Texture`, [
395
+ uniformToken("info:id", "UsdUVTexture"),
396
+ attr("inputs:file", "asset", new AssetPath(path)),
397
+ uniformToken("inputs:sourceColorSpace", wiring.colorSpace),
398
+ st,
399
+ attr(wiring.output, wiring.outputType, void 0)
400
+ ]);
401
+ }
402
+ function shaderPrim(name, properties) {
403
+ return {
404
+ specifier: "def",
405
+ typeName: "Shader",
406
+ name,
407
+ metadata: {},
408
+ properties,
409
+ children: [],
410
+ line: 0
411
+ };
412
+ }
413
+ function transformProps(m) {
414
+ return [
415
+ attr("xformOp:transform", "matrix4d", toUsdMatrix(m)),
416
+ arrayAttr("xformOpOrder", "token", ["xformOp:transform"], void 0, "uniform")
417
+ ];
418
+ }
419
+ function buildJointPrim(name, key, joint, linkPath, isaac, warn) {
420
+ const kind = driveKindFor(joint.type);
421
+ const angular = kind === "angular";
422
+ const properties = [];
423
+ const apiSchemas = [];
424
+ if (joint.parent !== "") properties.push(rel("physics:body0", [linkPath(joint.parent)]));
425
+ properties.push(rel("physics:body1", [linkPath(joint.child)]));
426
+ if (joint.type !== "fixed") properties.push(uniformToken("physics:axis", joint.axis));
427
+ if (joint.lower !== void 0) {
428
+ properties.push(attr("physics:lowerLimit", "float", jointValueFromSI(angular, joint.lower)));
429
+ }
430
+ if (joint.upper !== void 0) {
431
+ properties.push(attr("physics:upperLimit", "float", jointValueFromSI(angular, joint.upper)));
432
+ }
433
+ for (const index of [0, 1]) {
434
+ const frame = index === 0 ? joint.jointFrame0 : joint.jointFrame1;
435
+ const { position, orientation, rigid } = decomposeRigid(frame);
436
+ if (!rigid) {
437
+ warn(`joint "${key}": jointFrame${index} is not rigid; scale/shear was discarded`);
438
+ }
439
+ properties.push(attr(`physics:localPos${index}`, "point3f", position));
440
+ properties.push(attr(`physics:localRot${index}`, "quatf", orientation));
441
+ }
442
+ if (joint.initialValue !== void 0 || joint.valueSamples) {
443
+ const state = attr(
444
+ `state:${kind}:physics:position`,
445
+ "float",
446
+ joint.initialValue !== void 0 ? jointValueFromSI(angular, joint.initialValue) : void 0
447
+ );
448
+ if (joint.valueSamples) {
449
+ state.timeSamples = new Map(
450
+ joint.valueSamples.times.map((t, i) => [
451
+ t,
452
+ jointValueFromSI(angular, joint.valueSamples.values[i] ?? 0)
453
+ ])
454
+ );
455
+ }
456
+ properties.push(state);
457
+ apiSchemas.push(`PhysicsJointStateAPI:${kind}`);
458
+ }
459
+ if (joint.drive) {
460
+ const d = joint.drive;
461
+ if (d.targetPosition !== void 0) {
462
+ properties.push(
463
+ attr(
464
+ `drive:${kind}:physics:targetPosition`,
465
+ "float",
466
+ jointValueFromSI(angular, d.targetPosition)
467
+ )
468
+ );
469
+ }
470
+ if (d.stiffness !== void 0) {
471
+ properties.push(attr(`drive:${kind}:physics:stiffness`, "float", d.stiffness));
472
+ }
473
+ if (d.damping !== void 0) {
474
+ properties.push(attr(`drive:${kind}:physics:damping`, "float", d.damping));
475
+ }
476
+ if (d.maxForce !== void 0) {
477
+ properties.push(attr(`drive:${kind}:physics:maxForce`, "float", d.maxForce));
478
+ }
479
+ apiSchemas.push(`PhysicsDriveAPI:${kind}`);
480
+ }
481
+ if (isaac) {
482
+ apiSchemas.push("IsaacJointAPI");
483
+ if (joint.type !== "fixed") {
484
+ const prefix = joint.type === "prismatic" ? "Trans" : "Rot";
485
+ properties.push(
486
+ arrayAttr("isaac:physics:DofOffsetOpOrder", "token", [`${prefix}${joint.axis}`])
487
+ );
488
+ }
489
+ }
490
+ return {
491
+ specifier: "def",
492
+ typeName: JOINT_SCHEMA_BY_TYPE[joint.type],
493
+ name,
494
+ metadata: apiSchemas.length > 0 ? { apiSchemas } : {},
495
+ properties,
496
+ children: [],
497
+ line: 0
498
+ };
499
+ }
500
+ function sanitizeName(raw) {
501
+ const cleaned = raw.replace(/[^A-Za-z0-9_]/g, "_");
502
+ const named = cleaned.length > 0 ? cleaned : "_";
503
+ return /^[0-9]/.test(named) ? `_${named}` : named;
504
+ }
505
+ function nameAllocator(warn) {
506
+ const used = /* @__PURE__ */ new Set();
507
+ return (raw, label) => {
508
+ let name = sanitizeName(raw);
509
+ for (let i = 2; used.has(name); i++) name = `${sanitizeName(raw)}_${i}`;
510
+ used.add(name);
511
+ if (name !== raw && label && warn) warn(`${label} exported as prim "${name}"`);
512
+ return name;
513
+ };
514
+ }
515
+ function attr(name, typeName, value, metadata = {}) {
516
+ return {
517
+ kind: "attribute",
518
+ name,
519
+ typeName,
520
+ isArray: false,
521
+ variability: "varying",
522
+ custom: false,
523
+ ...value !== void 0 ? { value } : {},
524
+ metadata,
525
+ line: 0
526
+ };
527
+ }
528
+ function arrayAttr(name, typeName, value, metadata = {}, variability = "varying") {
529
+ return {
530
+ kind: "attribute",
531
+ name,
532
+ typeName,
533
+ isArray: true,
534
+ variability,
535
+ custom: false,
536
+ value,
537
+ metadata,
538
+ line: 0
539
+ };
540
+ }
541
+ function uniformToken(name, value) {
542
+ return {
543
+ kind: "attribute",
544
+ name,
545
+ typeName: "token",
546
+ isArray: false,
547
+ variability: "uniform",
548
+ custom: false,
549
+ value,
550
+ metadata: {},
551
+ line: 0
552
+ };
553
+ }
554
+ function rel(name, targets) {
555
+ return {
556
+ kind: "relationship",
557
+ name,
558
+ custom: false,
559
+ listOp: "explicit",
560
+ targets,
561
+ metadata: {},
562
+ line: 0
563
+ };
564
+ }
565
+
566
+ // src/export/GeometryProvider.ts
567
+ function stageGeometryProvider(stage) {
568
+ return (_linkKey, link) => {
569
+ const linkPrim = stage.GetPrimAtPath(link.primPath);
570
+ if (!linkPrim) return [];
571
+ const visualSet = new Set(link.visualPrims);
572
+ const out = [];
573
+ for (const path of link.visualPrims) {
574
+ out.push(...readMeshes(stage, linkPrim, path, "visual"));
575
+ }
576
+ for (const path of link.collisionPrims ?? []) {
577
+ if (visualSet.has(path)) continue;
578
+ out.push(...readMeshes(stage, linkPrim, path, "collision"));
579
+ }
580
+ return out;
581
+ };
582
+ }
583
+ function readMeshes(stage, linkPrim, meshPath, kind) {
584
+ const prim = stage.GetPrimAtPath(meshPath);
585
+ if (!prim) return [];
586
+ const points = prim.GetAttribute("points").Get();
587
+ if (!isVec3Array(points) || points.length === 0) return [];
588
+ const countsValue = prim.GetAttribute("faceVertexCounts").Get();
589
+ const indicesValue = prim.GetAttribute("faceVertexIndices").Get();
590
+ const counts = isNumberArray(countsValue) ? countsValue : [];
591
+ const indices = isNumberArray(indicesValue) ? indicesValue : [];
592
+ const normalsValue = prim.GetAttribute("normals").Get();
593
+ const stValue = prim.GetAttribute("primvars:st").Get();
594
+ const displayColor = prim.GetAttribute("primvars:displayColor").Get();
595
+ const normals = isVec3Array(normalsValue) && normalsValue.length === points.length ? normalsValue : void 0;
596
+ const st = isVec2Array(stValue) && stValue.length === points.length ? stValue : void 0;
597
+ const transform = relativeTransform(linkPrim, prim);
598
+ const common = {
599
+ kind,
600
+ ...isVec3Array(displayColor) && displayColor[0] ? { displayColor: displayColor[0] } : {},
601
+ ...prim.GetAttribute("doubleSided").Get() === true ? { doubleSided: true } : {},
602
+ ...isIdentity(transform) ? {} : { transform }
603
+ };
604
+ const withPhysics = (mesh) => {
605
+ if (kind !== "collision") return mesh;
606
+ const approximation = prim.GetAttribute("physics:approximation").Get();
607
+ if (typeof approximation === "string" && APPROXIMATIONS.has(approximation)) {
608
+ mesh.collisionApproximation = approximation;
609
+ }
610
+ const physicsMaterial = readBoundPhysicsMaterial(stage, prim);
611
+ if (physicsMaterial) mesh.physicsMaterial = physicsMaterial;
612
+ return mesh;
613
+ };
614
+ const subsets = getMaterialSubsets(prim);
615
+ if (subsets.length === 0) {
616
+ const material = readBoundMaterial(stage, prim);
617
+ return [
618
+ withPhysics({
619
+ ...common,
620
+ name: prim.GetName(),
621
+ points: points.slice(),
622
+ faceVertexCounts: counts.slice(),
623
+ faceVertexIndices: indices.slice(),
624
+ ...normals ? { normals: normals.slice() } : {},
625
+ ...st ? { st: st.slice() } : {},
626
+ ...material ? { material } : {}
627
+ })
628
+ ];
629
+ }
630
+ const faceStart = [];
631
+ let offset = 0;
632
+ for (const count of counts) {
633
+ faceStart.push(offset);
634
+ offset += count;
635
+ }
636
+ const piece = (faces, name, material) => {
637
+ const remap = /* @__PURE__ */ new Map();
638
+ const outPoints = [];
639
+ const outNormals = [];
640
+ const outSt = [];
641
+ const outCounts = [];
642
+ const outIndices = [];
643
+ for (const face of faces) {
644
+ const start = faceStart[face];
645
+ const count = counts[face];
646
+ if (start === void 0 || count === void 0) continue;
647
+ outCounts.push(count);
648
+ for (let k = 0; k < count; k++) {
649
+ const vertex = indices[start + k];
650
+ if (vertex === void 0 || !points[vertex]) continue;
651
+ let mapped = remap.get(vertex);
652
+ if (mapped === void 0) {
653
+ mapped = outPoints.length;
654
+ remap.set(vertex, mapped);
655
+ outPoints.push(points[vertex]);
656
+ if (normals) outNormals.push(normals[vertex]);
657
+ if (st) outSt.push(st[vertex]);
658
+ }
659
+ outIndices.push(mapped);
660
+ }
661
+ }
662
+ if (outPoints.length === 0) return null;
663
+ return withPhysics({
664
+ ...common,
665
+ name,
666
+ points: outPoints,
667
+ faceVertexCounts: outCounts,
668
+ faceVertexIndices: outIndices,
669
+ ...normals ? { normals: outNormals } : {},
670
+ ...st ? { st: outSt } : {},
671
+ ...material ? { material } : {}
672
+ });
673
+ };
674
+ const out = [];
675
+ const claimed = /* @__PURE__ */ new Set();
676
+ subsets.forEach((subset, index) => {
677
+ const faces = subset.faces.filter((f) => f >= 0 && f < counts.length && !claimed.has(f));
678
+ for (const f of faces) claimed.add(f);
679
+ const material = readBoundMaterial(stage, subset.prim);
680
+ const mesh = piece(faces, `${prim.GetName()}_${material?.name ?? index}`, material);
681
+ if (mesh) out.push(mesh);
682
+ });
683
+ const leftover = counts.map((_, f) => f).filter((f) => !claimed.has(f));
684
+ if (leftover.length > 0) {
685
+ const material = readBoundMaterial(stage, prim);
686
+ const mesh = piece(leftover, prim.GetName(), material);
687
+ if (mesh) out.push(mesh);
688
+ }
689
+ return out;
690
+ }
691
+ var APPROXIMATIONS = /* @__PURE__ */ new Set([
692
+ "none",
693
+ "convexHull",
694
+ "convexDecomposition",
695
+ "boundingSphere",
696
+ "boundingCube",
697
+ "meshSimplification"
698
+ ]);
699
+ function readBoundPhysicsMaterial(stage, prim) {
700
+ const target = firstBindingTarget(prim, "material:binding:physics");
701
+ if (!target) return void 0;
702
+ const materialPrim = stage.GetPrimAtPath(target);
703
+ if (!materialPrim) return void 0;
704
+ const out = {
705
+ name: target.split("/").filter(Boolean).pop() ?? "PhysicsMaterial"
706
+ };
707
+ const read = (attr2) => {
708
+ const v = materialPrim.GetAttribute(attr2).Get();
709
+ return typeof v === "number" ? v : void 0;
710
+ };
711
+ const staticFriction = read("physics:staticFriction");
712
+ if (staticFriction !== void 0) out.staticFriction = staticFriction;
713
+ const dynamicFriction = read("physics:dynamicFriction");
714
+ if (dynamicFriction !== void 0) out.dynamicFriction = dynamicFriction;
715
+ const restitution = read("physics:restitution");
716
+ if (restitution !== void 0) out.restitution = restitution;
717
+ const density = read("physics:density");
718
+ if (density !== void 0) out.density = density;
719
+ return Object.keys(out).length > 1 ? out : void 0;
720
+ }
721
+ function firstBindingTarget(prim, relName) {
722
+ let p = prim;
723
+ while (p) {
724
+ const targets = p.GetRelationship(relName).GetTargets();
725
+ if (targets.length > 0) return targets[0];
726
+ p = p.GetParent();
727
+ }
728
+ return void 0;
729
+ }
730
+ function readBoundMaterial(stage, prim) {
731
+ const bound = resolveBoundMaterial(stage, prim);
732
+ if (!bound) return void 0;
733
+ const material = { name: boundMaterialName(prim) ?? "Material" };
734
+ if (bound.color) material.diffuseColor = bound.color;
735
+ if (bound.metalness !== void 0) material.metallic = bound.metalness;
736
+ if (bound.roughness !== void 0) material.roughness = bound.roughness;
737
+ if (bound.opacity !== void 0) material.opacity = bound.opacity;
738
+ if (bound.emissiveColor) material.emissiveColor = bound.emissiveColor;
739
+ const textures = {};
740
+ if (bound.colorTexture) textures.color = bound.colorTexture.path;
741
+ if (bound.opacityTexture) textures.opacity = bound.opacityTexture.path;
742
+ if (bound.normalTexture) textures.normal = bound.normalTexture.path;
743
+ if (bound.roughnessTexture) textures.roughness = bound.roughnessTexture.path;
744
+ if (bound.metalnessTexture) textures.metalness = bound.metalnessTexture.path;
745
+ if (bound.occlusionTexture) textures.occlusion = bound.occlusionTexture.path;
746
+ if (bound.emissiveTexture) textures.emissive = bound.emissiveTexture.path;
747
+ if (Object.keys(textures).length > 0) material.textures = textures;
748
+ return Object.keys(material).length > 1 ? material : void 0;
749
+ }
750
+ function boundMaterialName(prim) {
751
+ let p = prim;
752
+ while (p) {
753
+ const targets = p.GetRelationship("material:binding").GetTargets();
754
+ if (targets.length > 0) return targets[0].split("/").filter(Boolean).pop();
755
+ p = p.GetParent();
756
+ }
757
+ return void 0;
758
+ }
759
+ function relativeTransform(linkPrim, meshPrim) {
760
+ const chain = [];
761
+ let p = meshPrim;
762
+ const stop = linkPrim.GetPath();
763
+ while (p && p.GetPath() !== stop) {
764
+ chain.push(p);
765
+ p = p.GetParent();
766
+ }
767
+ chain.reverse();
768
+ let m = identity4();
769
+ for (const prim of chain) {
770
+ m = multiply(m, computeLocalTransform(prim).matrix);
771
+ }
772
+ return m;
773
+ }
774
+ function isIdentity(m) {
775
+ for (let i = 0; i < 16; i++) {
776
+ if (m[i] !== (i % 5 === 0 ? 1 : 0)) return false;
777
+ }
778
+ return true;
779
+ }
780
+ function isNumberArray(v) {
781
+ return Array.isArray(v) && v.every((n) => typeof n === "number");
782
+ }
783
+ function isVec3Array(v) {
784
+ return Array.isArray(v) && v.every((e) => Array.isArray(e) && e.length === 3);
785
+ }
786
+ function isVec2Array(v) {
787
+ return Array.isArray(v) && v.every((e) => Array.isArray(e) && e.length === 2);
788
+ }
789
+ var USD_ENTRY = /\.(usda|usdc|usd)$/i;
790
+ var PAD_EXTRA_ID = 12345;
791
+ var LOCAL_HEADER_SIZE = 30;
792
+ var EXTRA_HEADER_SIZE = 4;
793
+ function writeUsdz(entries) {
794
+ const names = Object.keys(entries);
795
+ const root = names.find((n) => USD_ENTRY.test(n));
796
+ if (!root) {
797
+ throw new Error("writeUsdz: entries must include a root USD layer (.usda/.usdc/.usd)");
798
+ }
799
+ const ordered = [root, ...names.filter((n) => n !== root)];
800
+ const files = {};
801
+ let offset = 0;
802
+ for (const name of ordered) {
803
+ const raw = entries[name];
804
+ const data = typeof raw === "string" ? strToU8(raw) : raw;
805
+ const nameLength = strToU8(name).length;
806
+ const dataStart = offset + LOCAL_HEADER_SIZE + nameLength;
807
+ if ((dataStart & 63) === 0) {
808
+ files[name] = data;
809
+ offset = dataStart + data.length;
810
+ } else {
811
+ const padLength = 64 - (dataStart + EXTRA_HEADER_SIZE & 63) & 63;
812
+ files[name] = [data, { extra: { [PAD_EXTRA_ID]: new Uint8Array(padLength) } }];
813
+ offset = dataStart + EXTRA_HEADER_SIZE + padLength + data.length;
814
+ }
815
+ }
816
+ return zipSync(files, { level: 0 });
817
+ }
818
+
819
+ // src/robot/RobotValidator.ts
820
+ function validateRobotDescription(desc, options = {}) {
821
+ const issues = [];
822
+ const add = (severity, code, message, subject) => issues.push({ severity, code, message, ...subject !== void 0 ? { subject } : {} });
823
+ if (Object.keys(desc.links).length === 0) {
824
+ add("error", "no-links", "robot has no links");
825
+ }
826
+ for (const [key, joint] of Object.entries(desc.joints)) {
827
+ if (joint.parent !== "" && !desc.links[joint.parent]) {
828
+ add(
829
+ "error",
830
+ "unknown-link",
831
+ `joint "${key}" references unknown parent "${joint.parent}"`,
832
+ key
833
+ );
834
+ }
835
+ if (!desc.links[joint.child]) {
836
+ add("error", "unknown-link", `joint "${key}" references unknown child "${joint.child}"`, key);
837
+ }
838
+ if (joint.lower !== void 0 && joint.upper !== void 0 && joint.lower > joint.upper) {
839
+ add(
840
+ "error",
841
+ "invalid-limits",
842
+ `joint "${key}" lower limit ${joint.lower} exceeds upper ${joint.upper}`,
843
+ key
844
+ );
845
+ }
846
+ if (joint.initialValue !== void 0 && (joint.lower !== void 0 && joint.initialValue < joint.lower || joint.upper !== void 0 && joint.initialValue > joint.upper)) {
847
+ add(
848
+ "warning",
849
+ "initial-out-of-limits",
850
+ `joint "${key}" initial value ${joint.initialValue} is outside its limits`,
851
+ key
852
+ );
853
+ }
854
+ const frames = [
855
+ ["jointFrame0", joint.jointFrame0],
856
+ ["jointFrame1", joint.jointFrame1]
857
+ ];
858
+ for (const [label, frame] of frames) {
859
+ if (!decomposeRigid(frame).rigid) {
860
+ add(
861
+ "warning",
862
+ "non-rigid-frame",
863
+ `joint "${key}" ${label} carries scale/shear/reflection (discarded on export)`,
864
+ key
865
+ );
866
+ }
867
+ }
868
+ }
869
+ const tree = buildKinematicTree(desc);
870
+ for (const jointKey of tree.loopJoints) {
871
+ add(
872
+ "warning",
873
+ "closed-loop",
874
+ `joint "${jointKey}" closes a kinematic loop (dropped from the spanning tree)`,
875
+ jointKey
876
+ );
877
+ }
878
+ for (const linkKey of tree.isolatedLinks) {
879
+ add(
880
+ "warning",
881
+ "isolated-link",
882
+ `link "${linkKey}" is unreachable from root "${tree.root}"`,
883
+ linkKey
884
+ );
885
+ }
886
+ if ((desc.articulationRoots ?? []).length === 0 && !tree.rootJoint) {
887
+ add(
888
+ "warning",
889
+ "no-articulation-root",
890
+ "no ArticulationRootAPI link and no world-fixed joint \u2014 the base will float"
891
+ );
892
+ }
893
+ for (const [key, link] of Object.entries(desc.links)) {
894
+ const inertial = link.inertial;
895
+ if (!inertial || inertial.mass === void 0 && inertial.density === void 0) {
896
+ add("warning", "no-inertial", `link "${key}" has no mass or density (PhysicsMassAPI)`, key);
897
+ }
898
+ if (inertial?.mass !== void 0 && inertial.mass <= 0) {
899
+ add("error", "bad-mass", `link "${key}" has non-positive mass ${inertial.mass}`, key);
900
+ }
901
+ if (inertial?.diagonalInertia?.some((v) => v < 0)) {
902
+ add("error", "bad-inertia", `link "${key}" has a negative diagonal-inertia component`, key);
903
+ }
904
+ if (inertial?.principalAxes && !inertial.diagonalInertia) {
905
+ add(
906
+ "warning",
907
+ "inertia-pairing",
908
+ `link "${key}" authors principalAxes without diagonalInertia (dropped on export)`,
909
+ key
910
+ );
911
+ }
912
+ if (options.geometry) {
913
+ const meshes = options.geometry(key, link);
914
+ const collisions = meshes.filter((mesh) => mesh.kind === "collision");
915
+ if (collisions.length === 0) {
916
+ add("warning", "no-collision", `link "${key}" has no collision geometry`, key);
917
+ }
918
+ for (const mesh of collisions) {
919
+ if (!mesh.collisionApproximation) {
920
+ add(
921
+ "warning",
922
+ "collision-approximation",
923
+ `link "${key}" collision mesh "${mesh.name}" has no physics:approximation (PhysX dynamic bodies need a convex or primitive approximation)`,
924
+ key
925
+ );
926
+ }
927
+ }
928
+ }
929
+ }
930
+ return issues.sort((a, b) => a.severity === b.severity ? 0 : a.severity === "error" ? -1 : 1);
931
+ }
932
+
933
+ export { PACKAGE_NAME, VERSION, exportRobotUsda, stageGeometryProvider, validateRobotDescription, writeUsdz };
934
+ //# sourceMappingURL=chunk-O7XP5FQ4.js.map
935
+ //# sourceMappingURL=chunk-O7XP5FQ4.js.map