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.
package/dist/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- import { Stage, Prim } from './core.js';
2
- export { ARTICULATION_ROOT_API, Attribute, COLLISION_API, ComposeOptions, CrateReader, DEFAULT_METERS_PER_UNIT, ExtractOptions, Layer, PACKAGE_NAME, ParseError, RIGID_BODY_API, Relationship, ResolvedXform, TokenizeError, UpAxis, UsdzPackage, VERSION, composeFile, composeLayer, computeLocalTransform, crateToUsdaFile, driveKindFor, extractRobotDescription, gatherMeshDescendants, getJointAxis, getJointBodies, getJointDrive, getJointLimits, getJointLocalFrame, getJointStatePosition, getJointType, hasArticulationRootAPI, hasCollisionAPI, hasRigidBodyAPI, isMesh, isScope, isXform, iterDescendants, jointValueToSI, normalizeJointLimits, openUsdz, parseOpType, parseUsda, refineJointType, tokenize } from './core.js';
1
+ import { Stage, Prim, CollisionApproximation, ExportPhysicsMaterial, RobotGeometryProvider, ExportRobotOptions, ExportMaterial, ExportMesh } from './core.js';
2
+ export { ARTICULATION_ROOT_API, Attribute, COLLISION_API, ComposeOptions, CrateReader, DEFAULT_METERS_PER_UNIT, ExtractOptions, Layer, MASS_API, MESH_COLLISION_API, PACKAGE_NAME, PHYSICS_MATERIAL_API, ParseError, RIGID_BODY_API, Relationship, ResolvedXform, TextureChannel, TokenizeError, UpAxis, UsdzPackage, VERSION, ValidateRobotOptions, ValidationIssue, ValidationSeverity, composeFile, composeLayer, computeLocalTransform, computeWorldTransform, crateToUsdaFile, driveKindFor, exportRobotUsda, extractRobotDescription, gatherMeshDescendants, getJointAxis, getJointBodies, getJointDrive, getJointLimits, getJointLocalFrame, getJointStatePosition, getJointType, getMassProperties, hasArticulationRootAPI, hasCollisionAPI, hasRigidBodyAPI, isMesh, isScope, isXform, iterDescendants, jointValueFromSI, jointValueToSI, normalizeJointLimits, openUsdz, parseOpType, parseUsda, refineJointType, serializeUsda, stageGeometryProvider, tokenize, validateRobotDescription, writeUsdz } from './core.js';
3
3
  import * as THREE from 'three';
4
- import { A as Axis, V as Vec2, b as Vec3, R as RobotDescription } from './buildKinematicTree-3oKG00Sc.js';
5
- export { c as AssetPath, d as AttributeSpec, B as BuildTreeOptions, C as CompositionArc, D as DEG2RAD, a as JointDescription, e as JointDriveDescription, J as JointType, f as KinematicNode, K as KinematicTree, L as LinkDescription, g as ListOp, M as Mat4, h as MetadataMap, P as PrimSpec, i as PropertySpec, Q as Quat, j as RAD2DEG, k as RelationshipSpec, S as SampleChannel, l as SdfPath, m as Specifier, T as TreeEdge, U as UsdDictionary, n as UsdMatrix, o as UsdValue, p as UsdaFile, q as Variability, r as Vec4, s as buildKinematicTree, t as channelFromSamples, u as fromUsdMatrix, v as getTranslation, w as identity4, x as interpolate, y as invert, z as makeEuler, E as makeRotationFromQuat, F as makeRotationX, G as makeRotationY, H as makeRotationZ, I as makeScale, N as makeTranslation, O as multiply, W as multiplyAll } from './buildKinematicTree-3oKG00Sc.js';
6
- import { T as ThreeUsdRobot } from './ThreeUsdRobot-D9AOUaVh.js';
7
- export { J as JointObject, L as LinkObject, a as ThreeUsdRobotOptions } from './ThreeUsdRobot-D9AOUaVh.js';
4
+ import { A as Axis, V as Vec2, b as Vec3, R as RobotDescription, c as JointDriveDescription, d as LinkInertialDescription, K as KinematicTree, U as UsdaFile, M as Mat4 } from './buildKinematicTree-Ccd2DIKH.js';
5
+ export { e as AssetPath, f as AttributeSpec, B as BuildTreeOptions, C as CompositionArc, D as DEG2RAD, a as JointDescription, J as JointType, g as KinematicNode, L as LinkDescription, h as ListOp, i as MetadataMap, P as PrimSpec, j as PropertySpec, Q as Quat, k as RAD2DEG, l as RelationshipSpec, S as SampleChannel, m as SdfPath, n as Specifier, T as TreeEdge, o as UsdDictionary, p as UsdMatrix, q as UsdValue, r as Variability, s as Vec4, t as buildKinematicTree, u as channelFromSamples, v as decomposeRigid, w as fromUsdMatrix, x as getTranslation, y as identity4, z as interpolate, E as invert, F as makeEuler, G as makeRotationFromQuat, H as makeRotationX, I as makeRotationY, N as makeRotationZ, O as makeScale, W as makeTranslation, X as multiply, Y as multiplyAll, Z as toUsdMatrix } from './buildKinematicTree-Ccd2DIKH.js';
6
+ import { T as ThreeUsdRobot } from './ThreeUsdRobot-Ljh4PDVD.js';
7
+ export { J as JointObject, L as LinkObject, a as ThreeUsdRobotOptions } from './ThreeUsdRobot-Ljh4PDVD.js';
8
8
  import { A as AssetResolver } from './AssetResolver-CpIJNgWZ.js';
9
9
  export { D as DefaultAssetResolver, c as createMemoryResolver, j as joinPosix } from './AssetResolver-CpIJNgWZ.js';
10
- export { a as ThreeUsdRobotLoader, T as ThreeUsdRobotLoaderOptions } from './ThreeUsdRobotLoader-BgsnuJWd.js';
10
+ export { a as ThreeUsdRobotLoader, T as ThreeUsdRobotLoaderOptions } from './ThreeUsdRobotLoader-BSIVFA48.js';
11
11
 
12
12
  /** Unit vector for a USD joint axis token. Returns a fresh vector each call. */
13
13
  declare function axisVector(axis: Axis): THREE.Vector3;
@@ -49,6 +49,8 @@ type ResolvedTexture = {
49
49
  bias?: [number, number, number, number];
50
50
  };
51
51
  type ResolvedMaterial = {
52
+ /** Name of the bound `Material` prim, for round-tripping and debugging. */
53
+ name?: string;
52
54
  color?: Vec3;
53
55
  opacity?: number;
54
56
  metalness?: number;
@@ -129,7 +131,14 @@ type BindMeshesOptions = {
129
131
  /** Resolves diffuse texture asset paths to `THREE.Texture` (M-tex). */
130
132
  textureProvider?: TextureProvider;
131
133
  };
132
- /** Build a `BufferGeometry` from a Mesh prim, or `null` if it has no points. */
134
+ /**
135
+ * Build a `BufferGeometry` from a Mesh prim, or `null` if it has no points.
136
+ *
137
+ * When the mesh carries `materialBind` face subsets, the triangles are ordered
138
+ * subset by subset and a geometry group is added for each — so the mesh can be
139
+ * drawn with one material per subset (see {@link buildMeshMaterials}). Group
140
+ * order matches {@link getMaterialSubsets}, with any unassigned faces last.
141
+ */
133
142
  declare function buildMeshGeometry(meshPrim: Prim): THREE.BufferGeometry | null;
134
143
  /**
135
144
  * Build a material for a Mesh prim. Color priority: bound `UsdShade` material
@@ -139,12 +148,156 @@ declare function buildMeshGeometry(meshPrim: Prim): THREE.BufferGeometry | null;
139
148
  * (linear data) and emissive `emissiveMap`. Metalness / roughness / opacity /
140
149
  * emissive constants come from the bound material when present.
141
150
  */
142
- declare function buildMeshMaterial(meshPrim: Prim, stage?: Stage, textures?: TextureProvider): THREE.Material;
151
+ declare function buildMeshMaterial(meshPrim: Prim, stage?: Stage, textures?: TextureProvider,
152
+ /** Resolve the material binding from here instead (a `GeomSubset`). */
153
+ bindingPrim?: Prim): THREE.Material;
143
154
  /**
144
155
  * Attach visual (and optionally collision) meshes to every link of a built
145
156
  * {@link ThreeUsdRobot}. Each mesh is positioned by its transform relative to
146
157
  * the owning link prim.
147
158
  */
148
159
  declare function bindRobotMeshes(stage: Stage, robot3d: ThreeUsdRobot, desc: RobotDescription, options?: BindMeshesOptions): void;
160
+ /**
161
+ * Attach the Mesh prims that belong to no link — the static scenery of a cell
162
+ * that also contains robots. They are placed by their authored stage transform
163
+ * under the robot root, so the loader's up-axis and unit normalization applies
164
+ * to them too. Collision-only and guide/proxy prims are skipped.
165
+ */
166
+ declare function bindSceneMeshes(stage: Stage, robot3d: ThreeUsdRobot, desc: RobotDescription, options?: {
167
+ textureProvider?: TextureProvider;
168
+ }): number;
169
+
170
+ /**
171
+ * Authoring API (M14): assemble a robot from Three.js objects and export it.
172
+ *
173
+ * Links and joints are declared by name. Every joint takes **one** world-space
174
+ * frame; both body-local joint frames derive from it —
175
+ * `frame0 = linkWorld(parent)⁻¹ · frame`, `frame1 = linkWorld(child)⁻¹ · frame`
176
+ * — so the arrangement at build time is the robot's zero pose. Values are SI
177
+ * (radians / stage linear units); the exporter converts to authored units on
178
+ * write. World matrices are captured at `addLink`/`add*Joint` time.
179
+ */
180
+
181
+ /** A world-space frame: a matrix, or an object whose `matrixWorld` is used. */
182
+ type WorldFrame = THREE.Matrix4 | THREE.Object3D;
183
+ type RobotBuilderOptions = {
184
+ name: string;
185
+ /** Stage up axis; defaults to `"Z"` (Isaac Sim convention). */
186
+ upAxis?: "Y" | "Z";
187
+ /** Stage linear unit; defaults to `1` (meters). */
188
+ metersPerUnit?: number;
189
+ /** Link to flag with `PhysicsArticulationRootAPI` (default: the container prim). */
190
+ articulationRoot?: string;
191
+ onWarn?: (message: string) => void;
192
+ };
193
+ type AddLinkOptions = {
194
+ name: string;
195
+ /** Link frame in world space at build pose (default identity). */
196
+ frame?: WorldFrame;
197
+ /** Objects whose `THREE.Mesh` descendants become the link's visual meshes. */
198
+ visuals?: THREE.Object3D[];
199
+ /** Objects whose `THREE.Mesh` descendants become collision meshes. */
200
+ collisions?: THREE.Object3D[];
201
+ /** Mass properties, exported as `PhysicsMassAPI` (M16). */
202
+ inertial?: LinkInertialDescription;
203
+ /** `physics:approximation` applied to this link's collision meshes (M16). */
204
+ collisionApproximation?: CollisionApproximation;
205
+ /** Physics material bound to this link's collision meshes (M16). */
206
+ physicsMaterial?: ExportPhysicsMaterial;
207
+ };
208
+ type AddJointOptions = {
209
+ name: string;
210
+ /** Parent link name; omit to fix the child to the world. */
211
+ parent?: string;
212
+ child: string;
213
+ /** Joint frame in world space (default: the child link's frame). */
214
+ frame?: WorldFrame;
215
+ /** Rotation / sliding axis token in the joint frame (default `"X"`). */
216
+ axis?: Axis;
217
+ /** Limits in SI: radians (revolute) or stage linear units (prismatic). */
218
+ lower?: number;
219
+ upper?: number;
220
+ /** Initial joint value in SI, exported as `PhysicsJointStateAPI`. */
221
+ initialValue?: number;
222
+ drive?: JointDriveDescription;
223
+ };
224
+ declare class RobotBuilder {
225
+ private readonly robotName;
226
+ private readonly upAxis;
227
+ private readonly metersPerUnit;
228
+ private readonly articulationRoot?;
229
+ private readonly warn;
230
+ private readonly links;
231
+ private readonly joints;
232
+ private readonly materialCache;
233
+ private materialAutoId;
234
+ constructor(options: RobotBuilderOptions);
235
+ addLink(options: AddLinkOptions): this;
236
+ addFixedJoint(options: Omit<AddJointOptions, "axis" | "lower" | "upper">): this;
237
+ addRevoluteJoint(options: AddJointOptions): this;
238
+ addPrismaticJoint(options: AddJointOptions): this;
239
+ private addJoint;
240
+ /** Assemble the IR + geometry provider (validates the kinematic graph). */
241
+ build(): {
242
+ desc: RobotDescription;
243
+ geometry: RobotGeometryProvider;
244
+ tree: KinematicTree;
245
+ };
246
+ /** Build and export in one step (pass the result to `serializeUsda`). */
247
+ toUsda(options?: Omit<ExportRobotOptions, "geometry">): UsdaFile;
248
+ /** Collect `THREE.Mesh` descendants of `roots` as link-relative export meshes. */
249
+ private gatherMeshes;
250
+ private convertMaterial;
251
+ }
252
+
253
+ /**
254
+ * Three.js-side export (M14).
255
+ *
256
+ * Converts `BufferGeometry` / `MeshStandardMaterial` into the plain
257
+ * {@link ExportMesh} data the exporter consumes, harvests the meshes attached
258
+ * under a loaded robot's links, and re-exports a {@link ThreeUsdRobot} as a
259
+ * USDA AST. Texture maps are not carried yet (M15) — constants only.
260
+ */
261
+
262
+ type GeometryToExportMeshOptions = {
263
+ name: string;
264
+ kind?: "visual" | "collision";
265
+ /** Transform relative to the link frame (column-major); identity when omitted. */
266
+ transform?: Mat4;
267
+ material?: ExportMaterial;
268
+ doubleSided?: boolean;
269
+ /**
270
+ * Export only this slice of the index buffer — a `BufferGeometry` group, as
271
+ * used by multi-material meshes. Vertices are re-indexed onto the slice.
272
+ */
273
+ group?: {
274
+ start: number;
275
+ count: number;
276
+ };
277
+ };
278
+ /**
279
+ * Convert a triangle `BufferGeometry` into exportable mesh data. Non-indexed
280
+ * geometry becomes sequential triangles; a trailing partial triangle (or the
281
+ * absence of a position attribute) returns `null`.
282
+ */
283
+ declare function geometryToExportMesh(geometry: THREE.BufferGeometry, options: GeometryToExportMeshOptions): ExportMesh | null;
284
+ /**
285
+ * Extract constant PBR parameters from a Three.js material. Only
286
+ * `MeshStandardMaterial` maps onto `UsdPreviewSurface`; anything else returns
287
+ * `undefined` (with a warning). Texture maps are reported but not carried.
288
+ */
289
+ declare function materialToExportMaterial(material: THREE.Material, name: string, onWarn?: (message: string) => void): ExportMaterial | undefined;
290
+ /**
291
+ * Provider over the meshes `bindRobotMeshes` attached under each link of a
292
+ * loaded robot (direct children tagged `userData.kind`), with their link-local
293
+ * transforms and materials.
294
+ */
295
+ declare function threeGeometryProvider(robot: ThreeUsdRobot, onWarn?: (message: string) => void): RobotGeometryProvider;
296
+ type ExportThreeUsdRobotOptions = Omit<ExportRobotOptions, "geometry"> & {
297
+ /** Re-read mesh data from the source stage instead of the Three.js scene. */
298
+ stage?: Stage;
299
+ };
300
+ /** Export a loaded robot back to a USDA AST (pass the result to `serializeUsda`). */
301
+ declare function exportThreeUsdRobot(robot: ThreeUsdRobot, options?: ExportThreeUsdRobotOptions): UsdaFile;
149
302
 
150
- export { AssetResolver, Axis, type BindMeshesOptions, type MeshKind, Prim, type ResolvedMaterial, type ResolvedTexture, RobotDescription, Stage, type TextureColorSpace, type TextureOptions, type TextureProvider, type TextureTransform, type TextureWrap, ThreeUsdRobot, Vec2, Vec3, axisVector, bindRobotMeshes, buildMeshGeometry, buildMeshMaterial, createTextureProvider, resolveBoundMaterial };
303
+ export { type AddJointOptions, type AddLinkOptions, AssetResolver, Axis, type BindMeshesOptions, CollisionApproximation, ExportMaterial, ExportMesh, ExportPhysicsMaterial, ExportRobotOptions, type ExportThreeUsdRobotOptions, type GeometryToExportMeshOptions, JointDriveDescription, KinematicTree, LinkInertialDescription, Mat4, type MeshKind, Prim, type ResolvedMaterial, type ResolvedTexture, RobotBuilder, type RobotBuilderOptions, RobotDescription, RobotGeometryProvider, Stage, type TextureColorSpace, type TextureOptions, type TextureProvider, type TextureTransform, type TextureWrap, ThreeUsdRobot, UsdaFile, Vec2, Vec3, type WorldFrame, axisVector, bindRobotMeshes, bindSceneMeshes, buildMeshGeometry, buildMeshMaterial, createTextureProvider, exportThreeUsdRobot, geometryToExportMesh, materialToExportMaterial, resolveBoundMaterial, threeGeometryProvider };
package/dist/index.js CHANGED
@@ -1,5 +1,337 @@
1
- export { PACKAGE_NAME, VERSION } from './chunk-OFUMT72H.js';
2
- export { JointObject, LinkObject, ThreeUsdRobot, ThreeUsdRobotLoader, axisVector, bindRobotMeshes, buildMeshGeometry, buildMeshMaterial, createTextureProvider, resolveBoundMaterial } from './chunk-Y3NXBSX3.js';
3
- 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 } from './chunk-36T6YOEX.js';
1
+ import { stageGeometryProvider, exportRobotUsda } from './chunk-O7XP5FQ4.js';
2
+ export { PACKAGE_NAME, VERSION, exportRobotUsda, stageGeometryProvider, validateRobotDescription, writeUsdz } from './chunk-O7XP5FQ4.js';
3
+ export { JointObject, LinkObject, ThreeUsdRobot, ThreeUsdRobotLoader, axisVector, bindRobotMeshes, bindSceneMeshes, buildMeshGeometry, buildMeshMaterial, createTextureProvider } from './chunk-FL554IS5.js';
4
+ import { identity4, refineJointType, multiply, invert, buildKinematicTree } from './chunk-4GPCBXUS.js';
5
+ 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, getTranslation, hasArticulationRootAPI, hasCollisionAPI, hasRigidBodyAPI, identity4, interpolate, invert, isMesh, isScope, isXform, iterDescendants, joinPosix, jointValueFromSI, jointValueToSI, makeEuler, makeRotationFromQuat, makeRotationX, makeRotationY, makeRotationZ, makeScale, makeTranslation, multiply, multiplyAll, normalizeJointLimits, openUsdz, parseOpType, parseUsda, refineJointType, resolveBoundMaterial, serializeUsda, toUsdMatrix, tokenize } from './chunk-4GPCBXUS.js';
6
+ import * as THREE from 'three';
7
+
8
+ function geometryToExportMesh(geometry, options) {
9
+ const position = geometry.getAttribute("position");
10
+ if (!position || position.count === 0) return null;
11
+ const index = geometry.getIndex();
12
+ const all = index ? Array.from(index.array) : Array.from({ length: position.count }, (_, i) => i);
13
+ const slice = options.group ? all.slice(options.group.start, options.group.start + options.group.count) : all;
14
+ const triangles = Math.floor(slice.length / 3);
15
+ if (triangles === 0) return null;
16
+ const normal = geometry.getAttribute("normal");
17
+ const uv = geometry.getAttribute("uv");
18
+ const hasNormals = !!normal && normal.count === position.count;
19
+ const hasUv = !!uv && uv.count === position.count;
20
+ const points = [];
21
+ const normals = [];
22
+ const st = [];
23
+ let faceVertexIndices;
24
+ if (options.group) {
25
+ const remap = /* @__PURE__ */ new Map();
26
+ faceVertexIndices = [];
27
+ for (const vertex of slice.slice(0, triangles * 3)) {
28
+ let mapped = remap.get(vertex);
29
+ if (mapped === void 0) {
30
+ mapped = points.length;
31
+ remap.set(vertex, mapped);
32
+ points.push([position.getX(vertex), position.getY(vertex), position.getZ(vertex)]);
33
+ if (hasNormals)
34
+ normals.push([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)]);
35
+ if (hasUv) st.push([uv.getX(vertex), uv.getY(vertex)]);
36
+ }
37
+ faceVertexIndices.push(mapped);
38
+ }
39
+ } else {
40
+ for (let i = 0; i < position.count; i++) {
41
+ points.push([position.getX(i), position.getY(i), position.getZ(i)]);
42
+ if (hasNormals) normals.push([normal.getX(i), normal.getY(i), normal.getZ(i)]);
43
+ if (hasUv) st.push([uv.getX(i), uv.getY(i)]);
44
+ }
45
+ faceVertexIndices = slice.slice(0, triangles * 3);
46
+ }
47
+ const mesh = {
48
+ name: options.name,
49
+ kind: options.kind ?? "visual",
50
+ points,
51
+ faceVertexCounts: new Array(triangles).fill(3),
52
+ faceVertexIndices
53
+ };
54
+ if (hasNormals) mesh.normals = normals;
55
+ if (hasUv) mesh.st = st;
56
+ if (options.transform && !isIdentityMat4(options.transform)) mesh.transform = options.transform;
57
+ if (options.material) mesh.material = options.material;
58
+ if (options.doubleSided) mesh.doubleSided = true;
59
+ return mesh;
60
+ }
61
+ function materialToExportMaterial(material, name, onWarn) {
62
+ const std = material;
63
+ if (!std.isMeshStandardMaterial) {
64
+ onWarn?.(`material "${name}": only MeshStandardMaterial exports to UsdPreviewSurface; skipped`);
65
+ return void 0;
66
+ }
67
+ if (std.map || std.normalMap || std.roughnessMap || std.metalnessMap || std.aoMap || std.emissiveMap || std.alphaMap) {
68
+ onWarn?.(
69
+ `material "${name}": texture maps are not carried from Three.js materials (image extraction is app-side); constants only`
70
+ );
71
+ }
72
+ const out = {
73
+ name,
74
+ diffuseColor: [std.color.r, std.color.g, std.color.b],
75
+ metallic: std.metalness,
76
+ roughness: std.roughness
77
+ };
78
+ if (std.transparent && std.opacity < 1) out.opacity = std.opacity;
79
+ if (std.emissive.r > 0 || std.emissive.g > 0 || std.emissive.b > 0) {
80
+ out.emissiveColor = [std.emissive.r, std.emissive.g, std.emissive.b];
81
+ }
82
+ return out;
83
+ }
84
+ function threeGeometryProvider(robot, onWarn) {
85
+ const materialNames = /* @__PURE__ */ new Map();
86
+ let autoId = 0;
87
+ const convertMaterial = (material) => {
88
+ if (!materialNames.has(material)) {
89
+ const name = material.name || `Material_${autoId++}`;
90
+ materialNames.set(material, materialToExportMaterial(material, name, onWarn));
91
+ }
92
+ return materialNames.get(material);
93
+ };
94
+ return (linkKey) => {
95
+ const linkObj = robot.getLinkObject(linkKey);
96
+ if (!linkObj) return [];
97
+ const out = [];
98
+ for (const childObj of linkObj.children) {
99
+ const meshObj = childObj;
100
+ if (!meshObj.isMesh) continue;
101
+ const kind = meshObj.userData.kind === "collision" ? "collision" : "visual";
102
+ const transform = [...meshObj.matrix.elements];
103
+ const baseName = meshObj.name || "mesh";
104
+ const materials = Array.isArray(meshObj.material) ? meshObj.material : [meshObj.material];
105
+ const groups = meshObj.geometry.groups;
106
+ if (materials.length > 1 && groups.length > 0) {
107
+ for (const [i, group] of groups.entries()) {
108
+ const material2 = materials[group.materialIndex ?? 0];
109
+ const exportMaterial2 = material2 ? convertMaterial(material2) : void 0;
110
+ const mesh2 = geometryToExportMesh(meshObj.geometry, {
111
+ name: `${baseName}_${material2?.name || i}`,
112
+ kind,
113
+ transform,
114
+ group: { start: group.start, count: group.count },
115
+ ...exportMaterial2 ? { material: exportMaterial2 } : {},
116
+ doubleSided: material2?.side === THREE.DoubleSide
117
+ });
118
+ if (mesh2) out.push(mesh2);
119
+ }
120
+ continue;
121
+ }
122
+ const material = materials[0];
123
+ const exportMaterial = material ? convertMaterial(material) : void 0;
124
+ const mesh = geometryToExportMesh(meshObj.geometry, {
125
+ name: baseName,
126
+ kind,
127
+ transform,
128
+ ...exportMaterial ? { material: exportMaterial } : {},
129
+ doubleSided: material?.side === THREE.DoubleSide
130
+ });
131
+ if (mesh) out.push(mesh);
132
+ }
133
+ return out;
134
+ };
135
+ }
136
+ function exportThreeUsdRobot(robot, options = {}) {
137
+ const { stage, ...rest } = options;
138
+ const geometry = stage ? stageGeometryProvider(stage) : threeGeometryProvider(robot, options.onWarn);
139
+ return exportRobotUsda(robot.robot, { ...rest, geometry });
140
+ }
141
+ function isIdentityMat4(m) {
142
+ for (let i = 0; i < 16; i++) {
143
+ if (m[i] !== (i % 5 === 0 ? 1 : 0)) return false;
144
+ }
145
+ return true;
146
+ }
147
+
148
+ // src/three/RobotBuilder.ts
149
+ var RobotBuilder = class {
150
+ robotName;
151
+ upAxis;
152
+ metersPerUnit;
153
+ articulationRoot;
154
+ warn;
155
+ links = /* @__PURE__ */ new Map();
156
+ joints = /* @__PURE__ */ new Map();
157
+ materialCache = /* @__PURE__ */ new Map();
158
+ materialAutoId = 0;
159
+ constructor(options) {
160
+ this.robotName = options.name;
161
+ this.upAxis = options.upAxis ?? "Z";
162
+ this.metersPerUnit = options.metersPerUnit ?? 1;
163
+ if (options.articulationRoot !== void 0) this.articulationRoot = options.articulationRoot;
164
+ this.warn = options.onWarn ?? (() => {
165
+ });
166
+ }
167
+ addLink(options) {
168
+ if (this.links.has(options.name)) {
169
+ throw new Error(`RobotBuilder: link "${options.name}" is already defined`);
170
+ }
171
+ const world = resolveFrame(options.frame) ?? identity4();
172
+ const collisionExtras = {
173
+ ...options.collisionApproximation !== void 0 ? { approximation: options.collisionApproximation } : {},
174
+ ...options.physicsMaterial ? { material: options.physicsMaterial } : {}
175
+ };
176
+ const meshes = [
177
+ ...this.gatherMeshes(options.visuals ?? [], world, "visual"),
178
+ ...this.gatherMeshes(options.collisions ?? [], world, "collision", collisionExtras)
179
+ ];
180
+ this.links.set(options.name, {
181
+ world,
182
+ meshes,
183
+ ...options.inertial ? { inertial: options.inertial } : {}
184
+ });
185
+ return this;
186
+ }
187
+ addFixedJoint(options) {
188
+ return this.addJoint("fixed", options);
189
+ }
190
+ addRevoluteJoint(options) {
191
+ return this.addJoint("revolute", options);
192
+ }
193
+ addPrismaticJoint(options) {
194
+ return this.addJoint("prismatic", options);
195
+ }
196
+ addJoint(type, options) {
197
+ if (this.joints.has(options.name)) {
198
+ throw new Error(`RobotBuilder: joint "${options.name}" is already defined`);
199
+ }
200
+ const world = resolveFrame(options.frame);
201
+ this.joints.set(options.name, { type, options, ...world ? { world } : {} });
202
+ return this;
203
+ }
204
+ /** Assemble the IR + geometry provider (validates the kinematic graph). */
205
+ build() {
206
+ const links = {};
207
+ for (const [name, link] of this.links) {
208
+ links[name] = {
209
+ name,
210
+ primPath: `/${this.robotName}/${name}`,
211
+ visualPrims: [],
212
+ ...link.inertial ? { inertial: link.inertial } : {},
213
+ // Authored placement, so links no joint reaches keep their build pose.
214
+ ...isIdentityMat42(link.world) ? {} : { worldTransform: link.world }
215
+ };
216
+ }
217
+ const joints = {};
218
+ for (const [name, pending] of this.joints) {
219
+ const { options } = pending;
220
+ const parent = options.parent ?? "";
221
+ if (parent !== "" && !this.links.has(parent)) {
222
+ throw new Error(`RobotBuilder: joint "${name}" references unknown parent "${parent}"`);
223
+ }
224
+ const childLink = this.links.get(options.child);
225
+ if (!childLink) {
226
+ throw new Error(
227
+ `RobotBuilder: joint "${name}" references unknown child "${options.child}"`
228
+ );
229
+ }
230
+ const jointWorld = pending.world ?? childLink.world;
231
+ const parentWorld = parent === "" ? identity4() : this.links.get(parent).world;
232
+ const type = refineJointType(pending.type, options.lower, options.upper);
233
+ joints[name] = {
234
+ name,
235
+ primPath: `/${this.robotName}/${name}`,
236
+ type,
237
+ parent,
238
+ child: options.child,
239
+ axis: options.axis ?? "X",
240
+ jointFrame0: multiply(invert(parentWorld), jointWorld),
241
+ jointFrame1: multiply(invert(childLink.world), jointWorld),
242
+ ...options.lower !== void 0 ? { lower: options.lower } : {},
243
+ ...options.upper !== void 0 ? { upper: options.upper } : {},
244
+ ...options.initialValue !== void 0 ? { initialValue: options.initialValue } : {},
245
+ ...options.drive ? { drive: options.drive } : {}
246
+ };
247
+ }
248
+ if (this.articulationRoot !== void 0 && !this.links.has(this.articulationRoot)) {
249
+ throw new Error(`RobotBuilder: articulationRoot "${this.articulationRoot}" is not a link`);
250
+ }
251
+ const desc = {
252
+ name: this.robotName,
253
+ rootLink: "",
254
+ links,
255
+ joints,
256
+ upAxis: this.upAxis,
257
+ metersPerUnit: this.metersPerUnit,
258
+ ...this.articulationRoot !== void 0 ? { articulationRoots: [this.articulationRoot] } : {}
259
+ };
260
+ const tree = buildKinematicTree(desc, { onWarn: this.warn });
261
+ desc.rootLink = tree.root;
262
+ if (tree.loopJoints.length > 0) desc.loopJoints = tree.loopJoints;
263
+ if (tree.warnings.length > 0) desc.warnings = tree.warnings;
264
+ const byLink = this.links;
265
+ const geometry = (linkKey) => byLink.get(linkKey)?.meshes ?? [];
266
+ return { desc, geometry, tree };
267
+ }
268
+ /** Build and export in one step (pass the result to `serializeUsda`). */
269
+ toUsda(options = {}) {
270
+ const { desc, geometry } = this.build();
271
+ return exportRobotUsda(desc, { onWarn: this.warn, ...options, geometry });
272
+ }
273
+ /** Collect `THREE.Mesh` descendants of `roots` as link-relative export meshes. */
274
+ gatherMeshes(roots, linkWorld, kind, collisionExtras = {}) {
275
+ const linkInverse = invert(linkWorld);
276
+ const out = [];
277
+ for (const root of roots) {
278
+ root.updateWorldMatrix(true, true);
279
+ root.traverse((obj) => {
280
+ const meshObj = obj;
281
+ if (!meshObj.isMesh) return;
282
+ const material = Array.isArray(meshObj.material) ? meshObj.material[0] : meshObj.material;
283
+ if (Array.isArray(meshObj.material) && meshObj.material.length > 1) {
284
+ this.warn(
285
+ `mesh "${meshObj.name}": multi-material meshes export their first material only`
286
+ );
287
+ }
288
+ const exportMaterial = material ? this.convertMaterial(material) : void 0;
289
+ const mesh = geometryToExportMesh(meshObj.geometry, {
290
+ name: meshObj.name || "mesh",
291
+ kind,
292
+ transform: multiply(linkInverse, [...meshObj.matrixWorld.elements]),
293
+ ...exportMaterial ? { material: exportMaterial } : {},
294
+ doubleSided: material?.side === THREE.DoubleSide
295
+ });
296
+ if (mesh) {
297
+ if (kind === "collision") {
298
+ if (collisionExtras.approximation !== void 0) {
299
+ mesh.collisionApproximation = collisionExtras.approximation;
300
+ }
301
+ if (collisionExtras.material) mesh.physicsMaterial = collisionExtras.material;
302
+ }
303
+ out.push(mesh);
304
+ } else {
305
+ this.warn(`mesh "${meshObj.name}": no triangle geometry; skipped`);
306
+ }
307
+ });
308
+ }
309
+ return out;
310
+ }
311
+ convertMaterial(material) {
312
+ if (!this.materialCache.has(material)) {
313
+ const name = material.name || `Material_${this.materialAutoId++}`;
314
+ this.materialCache.set(material, materialToExportMaterial(material, name, this.warn));
315
+ }
316
+ return this.materialCache.get(material);
317
+ }
318
+ };
319
+ function isIdentityMat42(m) {
320
+ for (let i = 0; i < 16; i++) {
321
+ if (m[i] !== (i % 5 === 0 ? 1 : 0)) return false;
322
+ }
323
+ return true;
324
+ }
325
+ function resolveFrame(frame) {
326
+ if (!frame) return void 0;
327
+ if (frame.isObject3D) {
328
+ const obj = frame;
329
+ obj.updateWorldMatrix(true, false);
330
+ return [...obj.matrixWorld.elements];
331
+ }
332
+ return [...frame.elements];
333
+ }
334
+
335
+ export { RobotBuilder, exportThreeUsdRobot, geometryToExportMesh, materialToExportMaterial, threeGeometryProvider };
4
336
  //# sourceMappingURL=index.js.map
5
337
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
1
+ {"version":3,"sources":["../src/three/exportThreeUsdRobot.ts","../src/three/RobotBuilder.ts"],"names":["material","exportMaterial","mesh","isIdentityMat4","THREE2"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Three.js-side export (M14).\n *\n * Converts `BufferGeometry` / `MeshStandardMaterial` into the plain\n * {@link ExportMesh} data the exporter consumes, harvests the meshes attached\n * under a loaded robot's links, and re-exports a {@link ThreeUsdRobot} as a\n * USDA AST. Texture maps are not carried yet (M15) — constants only.\n */\n\nimport * as THREE from \"three\";\nimport type {\n ExportMaterial,\n ExportMesh,\n RobotGeometryProvider,\n} from \"../export/GeometryProvider.js\";\nimport { stageGeometryProvider } from \"../export/GeometryProvider.js\";\nimport { type ExportRobotOptions, exportRobotUsda } from \"../export/exportRobot.js\";\nimport type { Mat4 } from \"../kinematics/transforms.js\";\nimport type { UsdaFile, Vec2, Vec3 } from \"../parser/ast.js\";\nimport type { Stage } from \"../usd/Stage.js\";\nimport type { ThreeUsdRobot } from \"./ThreeUsdRobot.js\";\n\nexport type GeometryToExportMeshOptions = {\n name: string;\n kind?: \"visual\" | \"collision\";\n /** Transform relative to the link frame (column-major); identity when omitted. */\n transform?: Mat4;\n material?: ExportMaterial;\n doubleSided?: boolean;\n /**\n * Export only this slice of the index buffer — a `BufferGeometry` group, as\n * used by multi-material meshes. Vertices are re-indexed onto the slice.\n */\n group?: { start: number; count: number };\n};\n\n/**\n * Convert a triangle `BufferGeometry` into exportable mesh data. Non-indexed\n * geometry becomes sequential triangles; a trailing partial triangle (or the\n * absence of a position attribute) returns `null`.\n */\nexport function geometryToExportMesh(\n geometry: THREE.BufferGeometry,\n options: GeometryToExportMeshOptions,\n): ExportMesh | null {\n const position = geometry.getAttribute(\"position\");\n if (!position || position.count === 0) return null;\n\n const index = geometry.getIndex();\n const all = index\n ? Array.from(index.array as ArrayLike<number>)\n : Array.from({ length: position.count }, (_, i) => i);\n const slice = options.group\n ? all.slice(options.group.start, options.group.start + options.group.count)\n : all;\n const triangles = Math.floor(slice.length / 3);\n if (triangles === 0) return null;\n\n const normal = geometry.getAttribute(\"normal\");\n const uv = geometry.getAttribute(\"uv\");\n const hasNormals = !!normal && normal.count === position.count;\n const hasUv = !!uv && uv.count === position.count;\n\n const points: Vec3[] = [];\n const normals: Vec3[] = [];\n const st: Vec2[] = [];\n let faceVertexIndices: number[];\n\n if (options.group) {\n // Re-index onto just the vertices this group touches.\n const remap = new Map<number, number>();\n faceVertexIndices = [];\n for (const vertex of slice.slice(0, triangles * 3)) {\n let mapped = remap.get(vertex);\n if (mapped === undefined) {\n mapped = points.length;\n remap.set(vertex, mapped);\n points.push([position.getX(vertex), position.getY(vertex), position.getZ(vertex)]);\n if (hasNormals)\n normals.push([normal.getX(vertex), normal.getY(vertex), normal.getZ(vertex)]);\n if (hasUv) st.push([uv.getX(vertex), uv.getY(vertex)]);\n }\n faceVertexIndices.push(mapped);\n }\n } else {\n for (let i = 0; i < position.count; i++) {\n points.push([position.getX(i), position.getY(i), position.getZ(i)]);\n if (hasNormals) normals.push([normal.getX(i), normal.getY(i), normal.getZ(i)]);\n if (hasUv) st.push([uv.getX(i), uv.getY(i)]);\n }\n faceVertexIndices = slice.slice(0, triangles * 3);\n }\n\n const mesh: ExportMesh = {\n name: options.name,\n kind: options.kind ?? \"visual\",\n points,\n faceVertexCounts: new Array(triangles).fill(3),\n faceVertexIndices,\n };\n if (hasNormals) mesh.normals = normals;\n if (hasUv) mesh.st = st;\n\n if (options.transform && !isIdentityMat4(options.transform)) mesh.transform = options.transform;\n if (options.material) mesh.material = options.material;\n if (options.doubleSided) mesh.doubleSided = true;\n return mesh;\n}\n\n/**\n * Extract constant PBR parameters from a Three.js material. Only\n * `MeshStandardMaterial` maps onto `UsdPreviewSurface`; anything else returns\n * `undefined` (with a warning). Texture maps are reported but not carried.\n */\nexport function materialToExportMaterial(\n material: THREE.Material,\n name: string,\n onWarn?: (message: string) => void,\n): ExportMaterial | undefined {\n const std = material as THREE.MeshStandardMaterial;\n if (!std.isMeshStandardMaterial) {\n onWarn?.(`material \"${name}\": only MeshStandardMaterial exports to UsdPreviewSurface; skipped`);\n return undefined;\n }\n if (\n std.map ||\n std.normalMap ||\n std.roughnessMap ||\n std.metalnessMap ||\n std.aoMap ||\n std.emissiveMap ||\n std.alphaMap\n ) {\n onWarn?.(\n `material \"${name}\": texture maps are not carried from Three.js materials (image extraction is app-side); constants only`,\n );\n }\n\n const out: ExportMaterial = {\n name,\n diffuseColor: [std.color.r, std.color.g, std.color.b],\n metallic: std.metalness,\n roughness: std.roughness,\n };\n if (std.transparent && std.opacity < 1) out.opacity = std.opacity;\n if (std.emissive.r > 0 || std.emissive.g > 0 || std.emissive.b > 0) {\n out.emissiveColor = [std.emissive.r, std.emissive.g, std.emissive.b];\n }\n return out;\n}\n\n/**\n * Provider over the meshes `bindRobotMeshes` attached under each link of a\n * loaded robot (direct children tagged `userData.kind`), with their link-local\n * transforms and materials.\n */\nexport function threeGeometryProvider(\n robot: ThreeUsdRobot,\n onWarn?: (message: string) => void,\n): RobotGeometryProvider {\n const materialNames = new Map<THREE.Material, ExportMaterial | undefined>();\n let autoId = 0;\n const convertMaterial = (material: THREE.Material): ExportMaterial | undefined => {\n if (!materialNames.has(material)) {\n const name = material.name || `Material_${autoId++}`;\n materialNames.set(material, materialToExportMaterial(material, name, onWarn));\n }\n return materialNames.get(material);\n };\n\n return (linkKey) => {\n const linkObj = robot.getLinkObject(linkKey);\n if (!linkObj) return [];\n\n const out: ExportMesh[] = [];\n for (const childObj of linkObj.children) {\n const meshObj = childObj as THREE.Mesh;\n if (!meshObj.isMesh) continue;\n const kind = meshObj.userData.kind === \"collision\" ? \"collision\" : \"visual\";\n const transform = [...meshObj.matrix.elements];\n const baseName = meshObj.name || \"mesh\";\n const materials = Array.isArray(meshObj.material) ? meshObj.material : [meshObj.material];\n const groups = meshObj.geometry.groups;\n\n // A multi-material mesh exports one USD Mesh per geometry group, so each\n // group keeps its own material instead of collapsing to the first.\n if (materials.length > 1 && groups.length > 0) {\n for (const [i, group] of groups.entries()) {\n const material = materials[group.materialIndex ?? 0];\n const exportMaterial = material ? convertMaterial(material) : undefined;\n const mesh = geometryToExportMesh(meshObj.geometry, {\n name: `${baseName}_${material?.name || i}`,\n kind,\n transform,\n group: { start: group.start, count: group.count },\n ...(exportMaterial ? { material: exportMaterial } : {}),\n doubleSided: material?.side === THREE.DoubleSide,\n });\n if (mesh) out.push(mesh);\n }\n continue;\n }\n\n const material = materials[0];\n const exportMaterial = material ? convertMaterial(material) : undefined;\n const mesh = geometryToExportMesh(meshObj.geometry, {\n name: baseName,\n kind,\n transform,\n ...(exportMaterial ? { material: exportMaterial } : {}),\n doubleSided: material?.side === THREE.DoubleSide,\n });\n if (mesh) out.push(mesh);\n }\n return out;\n };\n}\n\nexport type ExportThreeUsdRobotOptions = Omit<ExportRobotOptions, \"geometry\"> & {\n /** Re-read mesh data from the source stage instead of the Three.js scene. */\n stage?: Stage;\n};\n\n/** Export a loaded robot back to a USDA AST (pass the result to `serializeUsda`). */\nexport function exportThreeUsdRobot(\n robot: ThreeUsdRobot,\n options: ExportThreeUsdRobotOptions = {},\n): UsdaFile {\n const { stage, ...rest } = options;\n const geometry = stage\n ? stageGeometryProvider(stage)\n : threeGeometryProvider(robot, options.onWarn);\n return exportRobotUsda(robot.robot, { ...rest, geometry });\n}\n\nfunction isIdentityMat4(m: Mat4): boolean {\n for (let i = 0; i < 16; i++) {\n if (m[i] !== (i % 5 === 0 ? 1 : 0)) return false;\n }\n return true;\n}\n","/**\n * Authoring API (M14): assemble a robot from Three.js objects and export it.\n *\n * Links and joints are declared by name. Every joint takes **one** world-space\n * frame; both body-local joint frames derive from it —\n * `frame0 = linkWorld(parent)⁻¹ · frame`, `frame1 = linkWorld(child)⁻¹ · frame`\n * — so the arrangement at build time is the robot's zero pose. Values are SI\n * (radians / stage linear units); the exporter converts to authored units on\n * write. World matrices are captured at `addLink`/`add*Joint` time.\n */\n\nimport * as THREE from \"three\";\nimport type {\n CollisionApproximation,\n ExportMaterial,\n ExportMesh,\n ExportPhysicsMaterial,\n RobotGeometryProvider,\n} from \"../export/GeometryProvider.js\";\nimport { type ExportRobotOptions, exportRobotUsda } from \"../export/exportRobot.js\";\nimport { type Mat4, identity4, invert, multiply } from \"../kinematics/transforms.js\";\nimport type { UsdaFile } from \"../parser/ast.js\";\nimport type {\n Axis,\n JointDescription,\n JointDriveDescription,\n JointType,\n LinkDescription,\n LinkInertialDescription,\n RobotDescription,\n} from \"../robot/RobotDescription.js\";\nimport { type KinematicTree, buildKinematicTree } from \"../robot/buildKinematicTree.js\";\nimport { refineJointType } from \"../robot/normalize.js\";\nimport { geometryToExportMesh, materialToExportMaterial } from \"./exportThreeUsdRobot.js\";\n\n/** A world-space frame: a matrix, or an object whose `matrixWorld` is used. */\nexport type WorldFrame = THREE.Matrix4 | THREE.Object3D;\n\nexport type RobotBuilderOptions = {\n name: string;\n /** Stage up axis; defaults to `\"Z\"` (Isaac Sim convention). */\n upAxis?: \"Y\" | \"Z\";\n /** Stage linear unit; defaults to `1` (meters). */\n metersPerUnit?: number;\n /** Link to flag with `PhysicsArticulationRootAPI` (default: the container prim). */\n articulationRoot?: string;\n onWarn?: (message: string) => void;\n};\n\nexport type AddLinkOptions = {\n name: string;\n /** Link frame in world space at build pose (default identity). */\n frame?: WorldFrame;\n /** Objects whose `THREE.Mesh` descendants become the link's visual meshes. */\n visuals?: THREE.Object3D[];\n /** Objects whose `THREE.Mesh` descendants become collision meshes. */\n collisions?: THREE.Object3D[];\n /** Mass properties, exported as `PhysicsMassAPI` (M16). */\n inertial?: LinkInertialDescription;\n /** `physics:approximation` applied to this link's collision meshes (M16). */\n collisionApproximation?: CollisionApproximation;\n /** Physics material bound to this link's collision meshes (M16). */\n physicsMaterial?: ExportPhysicsMaterial;\n};\n\nexport type AddJointOptions = {\n name: string;\n /** Parent link name; omit to fix the child to the world. */\n parent?: string;\n child: string;\n /** Joint frame in world space (default: the child link's frame). */\n frame?: WorldFrame;\n /** Rotation / sliding axis token in the joint frame (default `\"X\"`). */\n axis?: Axis;\n /** Limits in SI: radians (revolute) or stage linear units (prismatic). */\n lower?: number;\n upper?: number;\n /** Initial joint value in SI, exported as `PhysicsJointStateAPI`. */\n initialValue?: number;\n drive?: JointDriveDescription;\n};\n\ntype PendingJoint = {\n type: JointType;\n options: AddJointOptions;\n /** Joint frame world matrix captured at add time (undefined → child frame). */\n world?: Mat4;\n};\n\nexport class RobotBuilder {\n private readonly robotName: string;\n private readonly upAxis: \"Y\" | \"Z\";\n private readonly metersPerUnit: number;\n private readonly articulationRoot?: string;\n private readonly warn: (message: string) => void;\n\n private readonly links = new Map<\n string,\n { world: Mat4; meshes: ExportMesh[]; inertial?: LinkInertialDescription }\n >();\n private readonly joints = new Map<string, PendingJoint>();\n private readonly materialCache = new Map<THREE.Material, ExportMaterial | undefined>();\n private materialAutoId = 0;\n\n constructor(options: RobotBuilderOptions) {\n this.robotName = options.name;\n this.upAxis = options.upAxis ?? \"Z\";\n this.metersPerUnit = options.metersPerUnit ?? 1;\n if (options.articulationRoot !== undefined) this.articulationRoot = options.articulationRoot;\n this.warn = options.onWarn ?? (() => {});\n }\n\n addLink(options: AddLinkOptions): this {\n if (this.links.has(options.name)) {\n throw new Error(`RobotBuilder: link \"${options.name}\" is already defined`);\n }\n const world = resolveFrame(options.frame) ?? identity4();\n const collisionExtras = {\n ...(options.collisionApproximation !== undefined\n ? { approximation: options.collisionApproximation }\n : {}),\n ...(options.physicsMaterial ? { material: options.physicsMaterial } : {}),\n };\n const meshes = [\n ...this.gatherMeshes(options.visuals ?? [], world, \"visual\"),\n ...this.gatherMeshes(options.collisions ?? [], world, \"collision\", collisionExtras),\n ];\n this.links.set(options.name, {\n world,\n meshes,\n ...(options.inertial ? { inertial: options.inertial } : {}),\n });\n return this;\n }\n\n addFixedJoint(options: Omit<AddJointOptions, \"axis\" | \"lower\" | \"upper\">): this {\n return this.addJoint(\"fixed\", options);\n }\n\n addRevoluteJoint(options: AddJointOptions): this {\n return this.addJoint(\"revolute\", options);\n }\n\n addPrismaticJoint(options: AddJointOptions): this {\n return this.addJoint(\"prismatic\", options);\n }\n\n private addJoint(type: JointType, options: AddJointOptions): this {\n if (this.joints.has(options.name)) {\n throw new Error(`RobotBuilder: joint \"${options.name}\" is already defined`);\n }\n const world = resolveFrame(options.frame);\n this.joints.set(options.name, { type, options, ...(world ? { world } : {}) });\n return this;\n }\n\n /** Assemble the IR + geometry provider (validates the kinematic graph). */\n build(): { desc: RobotDescription; geometry: RobotGeometryProvider; tree: KinematicTree } {\n const links: Record<string, LinkDescription> = {};\n for (const [name, link] of this.links) {\n links[name] = {\n name,\n primPath: `/${this.robotName}/${name}`,\n visualPrims: [],\n ...(link.inertial ? { inertial: link.inertial } : {}),\n // Authored placement, so links no joint reaches keep their build pose.\n ...(isIdentityMat4(link.world) ? {} : { worldTransform: link.world }),\n };\n }\n\n const joints: Record<string, JointDescription> = {};\n for (const [name, pending] of this.joints) {\n const { options } = pending;\n const parent = options.parent ?? \"\";\n if (parent !== \"\" && !this.links.has(parent)) {\n throw new Error(`RobotBuilder: joint \"${name}\" references unknown parent \"${parent}\"`);\n }\n const childLink = this.links.get(options.child);\n if (!childLink) {\n throw new Error(\n `RobotBuilder: joint \"${name}\" references unknown child \"${options.child}\"`,\n );\n }\n\n const jointWorld = pending.world ?? childLink.world;\n const parentWorld = parent === \"\" ? identity4() : this.links.get(parent)!.world;\n const type = refineJointType(pending.type, options.lower, options.upper);\n\n joints[name] = {\n name,\n primPath: `/${this.robotName}/${name}`,\n type,\n parent,\n child: options.child,\n axis: options.axis ?? \"X\",\n jointFrame0: multiply(invert(parentWorld), jointWorld),\n jointFrame1: multiply(invert(childLink.world), jointWorld),\n ...(options.lower !== undefined ? { lower: options.lower } : {}),\n ...(options.upper !== undefined ? { upper: options.upper } : {}),\n ...(options.initialValue !== undefined ? { initialValue: options.initialValue } : {}),\n ...(options.drive ? { drive: options.drive } : {}),\n };\n }\n\n if (this.articulationRoot !== undefined && !this.links.has(this.articulationRoot)) {\n throw new Error(`RobotBuilder: articulationRoot \"${this.articulationRoot}\" is not a link`);\n }\n\n const desc: RobotDescription = {\n name: this.robotName,\n rootLink: \"\",\n links,\n joints,\n upAxis: this.upAxis,\n metersPerUnit: this.metersPerUnit,\n ...(this.articulationRoot !== undefined\n ? { articulationRoots: [this.articulationRoot] }\n : {}),\n };\n\n const tree = buildKinematicTree(desc, { onWarn: this.warn });\n desc.rootLink = tree.root;\n if (tree.loopJoints.length > 0) desc.loopJoints = tree.loopJoints;\n if (tree.warnings.length > 0) desc.warnings = tree.warnings;\n\n const byLink = this.links;\n const geometry: RobotGeometryProvider = (linkKey) => byLink.get(linkKey)?.meshes ?? [];\n return { desc, geometry, tree };\n }\n\n /** Build and export in one step (pass the result to `serializeUsda`). */\n toUsda(options: Omit<ExportRobotOptions, \"geometry\"> = {}): UsdaFile {\n const { desc, geometry } = this.build();\n return exportRobotUsda(desc, { onWarn: this.warn, ...options, geometry });\n }\n\n /** Collect `THREE.Mesh` descendants of `roots` as link-relative export meshes. */\n private gatherMeshes(\n roots: THREE.Object3D[],\n linkWorld: Mat4,\n kind: \"visual\" | \"collision\",\n collisionExtras: {\n approximation?: CollisionApproximation;\n material?: ExportPhysicsMaterial;\n } = {},\n ): ExportMesh[] {\n const linkInverse = invert(linkWorld);\n const out: ExportMesh[] = [];\n for (const root of roots) {\n root.updateWorldMatrix(true, true);\n root.traverse((obj) => {\n const meshObj = obj as THREE.Mesh;\n if (!meshObj.isMesh) return;\n const material = Array.isArray(meshObj.material) ? meshObj.material[0] : meshObj.material;\n if (Array.isArray(meshObj.material) && meshObj.material.length > 1) {\n this.warn(\n `mesh \"${meshObj.name}\": multi-material meshes export their first material only`,\n );\n }\n const exportMaterial = material ? this.convertMaterial(material) : undefined;\n const mesh = geometryToExportMesh(meshObj.geometry, {\n name: meshObj.name || \"mesh\",\n kind,\n transform: multiply(linkInverse, [...meshObj.matrixWorld.elements]),\n ...(exportMaterial ? { material: exportMaterial } : {}),\n doubleSided: material?.side === THREE.DoubleSide,\n });\n if (mesh) {\n if (kind === \"collision\") {\n if (collisionExtras.approximation !== undefined) {\n mesh.collisionApproximation = collisionExtras.approximation;\n }\n if (collisionExtras.material) mesh.physicsMaterial = collisionExtras.material;\n }\n out.push(mesh);\n } else {\n this.warn(`mesh \"${meshObj.name}\": no triangle geometry; skipped`);\n }\n });\n }\n return out;\n }\n\n private convertMaterial(material: THREE.Material): ExportMaterial | undefined {\n if (!this.materialCache.has(material)) {\n const name = material.name || `Material_${this.materialAutoId++}`;\n this.materialCache.set(material, materialToExportMaterial(material, name, this.warn));\n }\n return this.materialCache.get(material);\n }\n}\n\nfunction isIdentityMat4(m: Mat4): boolean {\n for (let i = 0; i < 16; i++) {\n if (m[i] !== (i % 5 === 0 ? 1 : 0)) return false;\n }\n return true;\n}\n\n/** World matrix of a frame argument (`matrixWorld` refreshed for objects). */\nfunction resolveFrame(frame: WorldFrame | undefined): Mat4 | undefined {\n if (!frame) return undefined;\n if ((frame as THREE.Object3D).isObject3D) {\n const obj = frame as THREE.Object3D;\n obj.updateWorldMatrix(true, false);\n return [...obj.matrixWorld.elements];\n }\n return [...(frame as THREE.Matrix4).elements];\n}\n"]}