three-usd-robot 0.7.0 → 0.8.1

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,387 @@
1
+ import { multiply, invert, interpolate } from './chunk-IYKPVUZ2.js';
2
+ import * as THREE4 from 'three';
3
+
4
+ function axisVector(axis) {
5
+ switch (axis) {
6
+ case "X":
7
+ return new THREE4.Vector3(1, 0, 0);
8
+ case "Y":
9
+ return new THREE4.Vector3(0, 1, 0);
10
+ case "Z":
11
+ return new THREE4.Vector3(0, 0, 1);
12
+ }
13
+ }
14
+ var JointObject = class extends THREE4.Object3D {
15
+ isJointObject = true;
16
+ jointName;
17
+ /** Full USD prim path of the joint — the collision-proof address. */
18
+ primPath;
19
+ jointType;
20
+ axisToken;
21
+ axis;
22
+ lower;
23
+ upper;
24
+ _value = 0;
25
+ constructor(joint) {
26
+ super();
27
+ this.name = joint.name;
28
+ this.jointName = joint.name;
29
+ this.primPath = joint.primPath;
30
+ this.jointType = joint.type;
31
+ this.axisToken = joint.axis;
32
+ this.axis = axisVector(joint.axis);
33
+ this.lower = joint.lower;
34
+ this.upper = joint.upper;
35
+ }
36
+ get value() {
37
+ return this._value;
38
+ }
39
+ get articulated() {
40
+ return this.jointType !== "fixed";
41
+ }
42
+ /**
43
+ * Set the joint value (radians for revolute/continuous, length for prismatic).
44
+ * Optionally clamps to authored limits. Returns the value actually applied.
45
+ */
46
+ setValue(value, clampToLimits = true) {
47
+ if (!this.articulated) return this._value;
48
+ let v = value;
49
+ if (clampToLimits) {
50
+ if (this.lower !== void 0 && v < this.lower) v = this.lower;
51
+ if (this.upper !== void 0 && v > this.upper) v = this.upper;
52
+ }
53
+ this._value = v;
54
+ if (this.jointType === "prismatic") {
55
+ this.position.copy(this.axis).multiplyScalar(v);
56
+ this.quaternion.identity();
57
+ } else {
58
+ this.quaternion.setFromAxisAngle(this.axis, v);
59
+ this.position.set(0, 0, 0);
60
+ }
61
+ return v;
62
+ }
63
+ };
64
+ var LinkObject = class extends THREE4.Object3D {
65
+ isLinkObject = true;
66
+ linkName;
67
+ primPath;
68
+ constructor(link) {
69
+ super();
70
+ this.name = link.name;
71
+ this.linkName = link.name;
72
+ this.primPath = link.primPath;
73
+ this.matrixAutoUpdate = false;
74
+ }
75
+ };
76
+ var ThreeUsdRobot = class extends THREE4.Object3D {
77
+ isThreeUsdRobot = true;
78
+ robot;
79
+ tree;
80
+ clampJointLimits;
81
+ linkObjects = /* @__PURE__ */ new Map();
82
+ jointObjects = /* @__PURE__ */ new Map();
83
+ linkKeyByPath = /* @__PURE__ */ new Map();
84
+ jointKeyByPath = /* @__PURE__ */ new Map();
85
+ dirty = true;
86
+ helperSize;
87
+ _showVisual = true;
88
+ _showCollision = false;
89
+ _showJointAxes = false;
90
+ _showLinkFrames = false;
91
+ jointAxesHelpers = [];
92
+ linkFrameHelpers = [];
93
+ constructor(robot, tree, options = {}) {
94
+ super();
95
+ this.name = robot.name;
96
+ this.robot = robot;
97
+ this.tree = tree;
98
+ this.clampJointLimits = options.clampJointLimits ?? true;
99
+ this.helperSize = options.helperSize ?? 0.15;
100
+ for (const [key, link] of Object.entries(robot.links)) {
101
+ this.linkObjects.set(key, new LinkObject(link));
102
+ this.linkKeyByPath.set(link.primPath, key);
103
+ }
104
+ for (const [key, joint] of Object.entries(robot.joints)) {
105
+ this.jointKeyByPath.set(joint.primPath, key);
106
+ }
107
+ this.attachRoot();
108
+ this.attachTreeEdges();
109
+ this.attachIsolatedLinks();
110
+ this.applyStageNormalization(robot, options);
111
+ if (options.applyInitialPose ?? true) this.applyInitialPose(robot);
112
+ }
113
+ /** Orient (authored upAxis → target world up) and scale (metersPerUnit × unitScale) the root. */
114
+ applyStageNormalization(robot, options) {
115
+ const scale = (robot.metersPerUnit || 1) * (options.unitScale ?? 1);
116
+ if (scale !== 1) this.scale.setScalar(scale);
117
+ const rotateX = (angle) => this.quaternion.setFromAxisAngle(new THREE4.Vector3(1, 0, 0), angle);
118
+ if (options.worldUp) {
119
+ if (options.worldUp === "Y" && robot.upAxis === "Z") rotateX(-Math.PI / 2);
120
+ else if (options.worldUp === "Z" && robot.upAxis === "Y") rotateX(Math.PI / 2);
121
+ return;
122
+ }
123
+ const conv = options.upAxisConversion ?? "none";
124
+ if (conv === "Z" || conv === "auto" && robot.upAxis === "Z") rotateX(-Math.PI / 2);
125
+ }
126
+ /** Apply each joint's authored initial value, if any. */
127
+ applyInitialPose(robot) {
128
+ for (const [key, joint] of Object.entries(robot.joints)) {
129
+ if (joint.initialValue === void 0) continue;
130
+ this.jointObjects.get(key)?.setValue(joint.initialValue, this.clampJointLimits);
131
+ }
132
+ this.dirty = true;
133
+ }
134
+ attachRoot() {
135
+ const rootObj = this.linkObjects.get(this.tree.root);
136
+ if (!rootObj) return;
137
+ const rootJointKey = this.tree.rootJoint;
138
+ const rootLink = this.robot.links[this.tree.root];
139
+ if (rootJointKey) {
140
+ const j = this.robot.joints[rootJointKey];
141
+ if (j) setMatrix(rootObj, multiply(j.jointFrame0, invert(j.jointFrame1)));
142
+ } else if (rootLink?.worldTransform) {
143
+ setMatrix(rootObj, rootLink.worldTransform);
144
+ }
145
+ this.add(rootObj);
146
+ }
147
+ attachTreeEdges() {
148
+ for (const linkKey of this.tree.order) {
149
+ const node = this.tree.nodes[linkKey];
150
+ if (!node || node.parent === null || node.jointToParent === null) continue;
151
+ const parentObj = this.linkObjects.get(node.parent);
152
+ const childObj = this.linkObjects.get(linkKey);
153
+ const joint = this.robot.joints[node.jointToParent];
154
+ if (!parentObj || !childObj || !joint) continue;
155
+ this.attachJointChain(parentObj, childObj, node.jointToParent, joint);
156
+ }
157
+ }
158
+ /** Build `parent → frame0 → motion → frame1⁻¹ → child` for one joint. */
159
+ attachJointChain(parent, child, jointKey, joint) {
160
+ const frame0 = new THREE4.Group();
161
+ frame0.name = `${joint.name}:frame0`;
162
+ setMatrix(frame0, joint.jointFrame0);
163
+ const motion = new JointObject(joint);
164
+ const frame1Inv = new THREE4.Group();
165
+ frame1Inv.name = `${joint.name}:frame1Inv`;
166
+ setMatrix(frame1Inv, invert(joint.jointFrame1));
167
+ parent.add(frame0);
168
+ frame0.add(motion);
169
+ motion.add(frame1Inv);
170
+ frame1Inv.add(child);
171
+ this.jointObjects.set(jointKey, motion);
172
+ }
173
+ attachIsolatedLinks() {
174
+ for (const key of this.tree.isolatedLinks) {
175
+ const obj = this.linkObjects.get(key);
176
+ if (!obj || obj.parent) continue;
177
+ const worldTransform = this.robot.links[key]?.worldTransform;
178
+ if (worldTransform) setMatrix(obj, worldTransform);
179
+ this.add(obj);
180
+ }
181
+ }
182
+ // -- Naming --------------------------------------------------------------
183
+ /** Resolve a link reference — key or full prim path — to the extractor key. */
184
+ linkKey(ref) {
185
+ if (this.linkObjects.has(ref)) return ref;
186
+ return this.linkKeyByPath.get(ref) ?? ref;
187
+ }
188
+ /** Resolve a joint reference — key or full prim path — to the extractor key. */
189
+ jointKey(ref) {
190
+ if (this.jointObjects.has(ref)) return ref;
191
+ return this.jointKeyByPath.get(ref) ?? ref;
192
+ }
193
+ // -- Joint control -------------------------------------------------------
194
+ /**
195
+ * Set one joint value, addressed by key or full prim path. Unknown joints
196
+ * are ignored. Returns whether it applied.
197
+ */
198
+ setJointValue(name, value) {
199
+ const joint = this.jointObjects.get(this.jointKey(name));
200
+ if (!joint) return false;
201
+ joint.setValue(value, this.clampJointLimits);
202
+ this.dirty = true;
203
+ return true;
204
+ }
205
+ /** Set several joint values at once (matrix update is coalesced). */
206
+ setJointValues(values) {
207
+ for (const [name, value] of Object.entries(values)) {
208
+ const joint = this.jointObjects.get(this.jointKey(name));
209
+ if (joint) {
210
+ joint.setValue(value, this.clampJointLimits);
211
+ this.dirty = true;
212
+ }
213
+ }
214
+ }
215
+ getJointValue(name) {
216
+ return this.jointObjects.get(this.jointKey(name))?.value;
217
+ }
218
+ /** Recompute world matrices. Called lazily by the getters; safe to call directly. */
219
+ updateKinematics() {
220
+ this.updateMatrixWorld(true);
221
+ this.dirty = false;
222
+ }
223
+ ensureUpdated() {
224
+ if (this.dirty) this.updateKinematics();
225
+ }
226
+ // -- Queries -------------------------------------------------------------
227
+ /** World matrix of a link, addressed by key or full prim path. */
228
+ getLinkWorldMatrix(name) {
229
+ const obj = this.linkObjects.get(this.linkKey(name));
230
+ if (!obj) throw new Error(`unknown link "${name}"`);
231
+ this.ensureUpdated();
232
+ return obj.matrixWorld.clone();
233
+ }
234
+ getLinkWorldPosition(name) {
235
+ return new THREE4.Vector3().setFromMatrixPosition(this.getLinkWorldMatrix(name));
236
+ }
237
+ /** Link object by key or full prim path. */
238
+ getLinkObject(name) {
239
+ return this.linkObjects.get(this.linkKey(name));
240
+ }
241
+ /** Joint object by key or full prim path. */
242
+ getJointObject(name) {
243
+ return this.jointObjects.get(this.jointKey(name));
244
+ }
245
+ /**
246
+ * Table of link prim path → {@link LinkObject}. Prim paths are the
247
+ * collision-proof way to pin a link (e.g. to attach tools or gizmos).
248
+ */
249
+ getLinkObjectsByPath() {
250
+ const out = /* @__PURE__ */ new Map();
251
+ for (const [path, key] of this.linkKeyByPath) {
252
+ const obj = this.linkObjects.get(key);
253
+ if (obj) out.set(path, obj);
254
+ }
255
+ return out;
256
+ }
257
+ /**
258
+ * Table of joint prim path → {@link JointObject}, covering the joints
259
+ * realized in the kinematic tree (loop joints have no motion node).
260
+ */
261
+ getJointObjectsByPath() {
262
+ const out = /* @__PURE__ */ new Map();
263
+ for (const [path, key] of this.jointKeyByPath) {
264
+ const obj = this.jointObjects.get(key);
265
+ if (obj) out.set(path, obj);
266
+ }
267
+ return out;
268
+ }
269
+ getJoints() {
270
+ return Object.values(this.robot.joints);
271
+ }
272
+ getLinks() {
273
+ return Object.values(this.robot.links);
274
+ }
275
+ /** Names of the articulated (controllable) joints. */
276
+ getJointNames() {
277
+ return [...this.jointObjects.keys()];
278
+ }
279
+ getLinkNames() {
280
+ return [...this.linkObjects.keys()];
281
+ }
282
+ getKinematicTree() {
283
+ return this.tree;
284
+ }
285
+ /** Authored stage up-axis (`"Y"` or `"Z"`) — unaffected by `worldUp` normalization. */
286
+ get upAxis() {
287
+ return this.robot.upAxis;
288
+ }
289
+ /** Authored stage scale in meters per unit (already applied to the root). */
290
+ get metersPerUnit() {
291
+ return this.robot.metersPerUnit;
292
+ }
293
+ // -- Animation playback --------------------------------------------------
294
+ /** Playback rate in time codes per second (from the stage; default 24). */
295
+ getTimeCodesPerSecond() {
296
+ return this.robot.timeCodesPerSecond ?? 24;
297
+ }
298
+ /** Whether any joint has a time-sampled trajectory. */
299
+ hasAnimation() {
300
+ return Object.values(this.robot.joints).some((j) => j.valueSamples !== void 0);
301
+ }
302
+ /**
303
+ * Animation range in time codes: the union of authored joint sample ranges,
304
+ * falling back to the stage `startTimeCode`/`endTimeCode`. `null` if neither.
305
+ */
306
+ getTimeRange() {
307
+ let start = Number.POSITIVE_INFINITY;
308
+ let end = Number.NEGATIVE_INFINITY;
309
+ for (const joint of Object.values(this.robot.joints)) {
310
+ const times = joint.valueSamples?.times;
311
+ if (!times || times.length === 0) continue;
312
+ start = Math.min(start, times[0]);
313
+ end = Math.max(end, times[times.length - 1]);
314
+ }
315
+ if (start <= end) return { start, end };
316
+ const { startTimeCode, endTimeCode } = this.robot;
317
+ if (startTimeCode !== void 0 && endTimeCode !== void 0) {
318
+ return { start: startTimeCode, end: endTimeCode };
319
+ }
320
+ return null;
321
+ }
322
+ /** Sample every animated joint at time code `t` and apply the values. */
323
+ setTime(t) {
324
+ for (const [key, joint] of Object.entries(this.robot.joints)) {
325
+ if (joint.valueSamples) this.setJointValue(key, interpolate(joint.valueSamples, t));
326
+ }
327
+ }
328
+ // -- Display toggles -----------------------------------------------------
329
+ get showVisual() {
330
+ return this._showVisual;
331
+ }
332
+ set showVisual(v) {
333
+ this._showVisual = v;
334
+ this.setKindVisibility("visual", v);
335
+ }
336
+ get showCollision() {
337
+ return this._showCollision;
338
+ }
339
+ set showCollision(v) {
340
+ this._showCollision = v;
341
+ this.setKindVisibility("collision", v);
342
+ }
343
+ get showJointAxes() {
344
+ return this._showJointAxes;
345
+ }
346
+ set showJointAxes(v) {
347
+ this._showJointAxes = v;
348
+ if (v && this.jointAxesHelpers.length === 0) {
349
+ for (const joint of this.jointObjects.values()) {
350
+ const h = new THREE4.AxesHelper(this.helperSize);
351
+ h.name = `${joint.jointName}:axes`;
352
+ joint.add(h);
353
+ this.jointAxesHelpers.push(h);
354
+ }
355
+ }
356
+ for (const h of this.jointAxesHelpers) h.visible = v;
357
+ }
358
+ get showLinkFrames() {
359
+ return this._showLinkFrames;
360
+ }
361
+ set showLinkFrames(v) {
362
+ this._showLinkFrames = v;
363
+ if (v && this.linkFrameHelpers.length === 0) {
364
+ for (const link of this.linkObjects.values()) {
365
+ const h = new THREE4.AxesHelper(this.helperSize);
366
+ h.name = `${link.linkName}:frame`;
367
+ link.add(h);
368
+ this.linkFrameHelpers.push(h);
369
+ }
370
+ }
371
+ for (const h of this.linkFrameHelpers) h.visible = v;
372
+ }
373
+ setKindVisibility(kind, visible) {
374
+ this.traverse((o) => {
375
+ if (o.userData.kind === kind) o.visible = visible;
376
+ });
377
+ }
378
+ };
379
+ function setMatrix(obj, m) {
380
+ obj.matrixAutoUpdate = false;
381
+ obj.matrix.fromArray(m);
382
+ obj.matrixWorldNeedsUpdate = true;
383
+ }
384
+
385
+ export { JointObject, LinkObject, ThreeUsdRobot, axisVector };
386
+ //# sourceMappingURL=chunk-FUPFJOXW.js.map
387
+ //# sourceMappingURL=chunk-FUPFJOXW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/three/axis.ts","../src/three/JointObject.ts","../src/three/LinkObject.ts","../src/three/ThreeUsdRobot.ts"],"names":["THREE","THREE2","THREE3"],"mappings":"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","file":"chunk-FUPFJOXW.js","sourcesContent":["import * as THREE from \"three\";\nimport type { Axis } from \"../robot/RobotDescription.js\";\n\n/** Unit vector for a USD joint axis token. Returns a fresh vector each call. */\nexport function axisVector(axis: Axis): THREE.Vector3 {\n switch (axis) {\n case \"X\":\n return new THREE.Vector3(1, 0, 0);\n case \"Y\":\n return new THREE.Vector3(0, 1, 0);\n case \"Z\":\n return new THREE.Vector3(0, 0, 1);\n }\n}\n","import * as THREE from \"three\";\nimport type { Axis, JointDescription, JointType } from \"../robot/RobotDescription.js\";\nimport { axisVector } from \"./axis.js\";\n\n/**\n * The articulated \"motion\" node of a joint, inserted between the joint's two\n * fixed frames (`jointFrame0` and `inverse(jointFrame1)`). {@link setValue}\n * rotates it about the axis (revolute/continuous) or slides it along the axis\n * (prismatic); fixed joints are inert.\n */\nexport class JointObject extends THREE.Object3D {\n readonly isJointObject = true;\n readonly jointName: string;\n /** Full USD prim path of the joint — the collision-proof address. */\n readonly primPath: string;\n readonly jointType: JointType;\n readonly axisToken: Axis;\n readonly axis: THREE.Vector3;\n readonly lower: number | undefined;\n readonly upper: number | undefined;\n\n private _value = 0;\n\n constructor(joint: JointDescription) {\n super();\n this.name = joint.name;\n this.jointName = joint.name;\n this.primPath = joint.primPath;\n this.jointType = joint.type;\n this.axisToken = joint.axis;\n this.axis = axisVector(joint.axis);\n this.lower = joint.lower;\n this.upper = joint.upper;\n }\n\n get value(): number {\n return this._value;\n }\n\n get articulated(): boolean {\n return this.jointType !== \"fixed\";\n }\n\n /**\n * Set the joint value (radians for revolute/continuous, length for prismatic).\n * Optionally clamps to authored limits. Returns the value actually applied.\n */\n setValue(value: number, clampToLimits = true): number {\n if (!this.articulated) return this._value; // fixed joint: inert\n\n let v = value;\n if (clampToLimits) {\n if (this.lower !== undefined && v < this.lower) v = this.lower;\n if (this.upper !== undefined && v > this.upper) v = this.upper;\n }\n this._value = v;\n\n if (this.jointType === \"prismatic\") {\n this.position.copy(this.axis).multiplyScalar(v);\n this.quaternion.identity();\n } else {\n this.quaternion.setFromAxisAngle(this.axis, v);\n this.position.set(0, 0, 0);\n }\n return v;\n }\n}\n","import * as THREE from \"three\";\nimport type { LinkDescription } from \"../robot/RobotDescription.js\";\n\n/**\n * Three.js node for a robot link. Its frame is the USD link prim's frame;\n * visual/collision meshes (M6) attach as children relative to it. Placement\n * relative to the parent is supplied by the joint chain, so a link's own local\n * matrix is identity (except the root, which carries the world-fixed placement).\n */\nexport class LinkObject extends THREE.Object3D {\n readonly isLinkObject = true;\n readonly linkName: string;\n readonly primPath: string;\n\n constructor(link: LinkDescription) {\n super();\n this.name = link.name;\n this.linkName = link.name;\n this.primPath = link.primPath;\n // Placement comes from the parent joint chain; keep the local matrix fixed.\n this.matrixAutoUpdate = false;\n }\n}\n","import * as THREE from \"three\";\nimport { interpolate } from \"../kinematics/sampling.js\";\nimport { type Mat4, invert, multiply } from \"../kinematics/transforms.js\";\nimport type {\n JointDescription,\n LinkDescription,\n RobotDescription,\n} from \"../robot/RobotDescription.js\";\nimport type { KinematicTree } from \"../robot/buildKinematicTree.js\";\nimport { JointObject } from \"./JointObject.js\";\nimport { LinkObject } from \"./LinkObject.js\";\n\n/** Target world up-axis: normalize to Y-up / Z-up, or keep the authored orientation. */\nexport type WorldUpAxis = \"Y\" | \"Z\" | \"keep\";\n\nexport type ThreeUsdRobotOptions = {\n /** Clamp `setJointValue` to authored limits (default `true`). */\n clampJointLimits?: boolean;\n /** Size of the built-in joint-axis / link-frame helpers (stage units, default `0.15`). */\n helperSize?: number;\n /**\n * Target world up-axis. The root is rotated so the stage's authored `upAxis`\n * lands in that convention: `\"Y\"` for a standard three.js scene, `\"Z\"` for a\n * robotics-style Z-up world, `\"keep\"` for no correction. Takes precedence\n * over the deprecated {@link ThreeUsdRobotOptions.upAxisConversion}.\n */\n worldUp?: WorldUpAxis;\n /**\n * Legacy up-axis correction: `\"auto\"` ≡ `worldUp: \"Y\"`, `\"Y\"` / `\"none\"` ≡\n * `worldUp: \"keep\"`, and `\"Z\"` forces the Z-up→Y-up rotation regardless of\n * stage metadata. Default `\"none\"` (the loader defaults to `\"auto\"`).\n * @deprecated Use {@link ThreeUsdRobotOptions.worldUp}.\n */\n upAxisConversion?: \"auto\" | \"Y\" | \"Z\" | \"none\";\n /** Extra uniform scale multiplied with the stage `metersPerUnit` (default `1`). */\n unitScale?: number;\n /** Seed joints from their authored initial value (drive target / joint state). Default `true`. */\n applyInitialPose?: boolean;\n};\n\n/**\n * A Three.js `Object3D` that realizes a {@link RobotDescription} as a kinematic\n * hierarchy and drives forward kinematics via {@link setJointValue}.\n *\n * Per joint the hierarchy is\n * `parentLink → jointFrame0 → jointMotion → jointFrame1⁻¹ → childLink`,\n * where only `jointMotion` (a {@link JointObject}) changes with the joint value;\n * world poses then fall out of Three.js's `updateMatrixWorld`.\n *\n * **Naming contract** — a link/joint's key is its prim's leaf name when that\n * is unique across the robot, else its full prim path (deterministic; see the\n * extractor). Every accessor taking a name equally accepts the full prim path,\n * which is stable regardless of collisions; {@link getLinkObjectsByPath} /\n * {@link getJointObjectsByPath} enumerate the path-keyed tables.\n */\nexport class ThreeUsdRobot extends THREE.Object3D {\n readonly isThreeUsdRobot = true;\n readonly robot: RobotDescription;\n readonly tree: KinematicTree;\n readonly clampJointLimits: boolean;\n\n private readonly linkObjects = new Map<string, LinkObject>();\n private readonly jointObjects = new Map<string, JointObject>();\n private readonly linkKeyByPath = new Map<string, string>();\n private readonly jointKeyByPath = new Map<string, string>();\n private dirty = true;\n\n private readonly helperSize: number;\n private _showVisual = true;\n private _showCollision = false;\n private _showJointAxes = false;\n private _showLinkFrames = false;\n private jointAxesHelpers: THREE.AxesHelper[] = [];\n private linkFrameHelpers: THREE.AxesHelper[] = [];\n\n constructor(robot: RobotDescription, tree: KinematicTree, options: ThreeUsdRobotOptions = {}) {\n super();\n this.name = robot.name;\n this.robot = robot;\n this.tree = tree;\n this.clampJointLimits = options.clampJointLimits ?? true;\n this.helperSize = options.helperSize ?? 0.15;\n\n // Create a node for every link up front; index links and joints by path.\n for (const [key, link] of Object.entries(robot.links)) {\n this.linkObjects.set(key, new LinkObject(link));\n this.linkKeyByPath.set(link.primPath, key);\n }\n for (const [key, joint] of Object.entries(robot.joints)) {\n this.jointKeyByPath.set(joint.primPath, key);\n }\n\n this.attachRoot();\n this.attachTreeEdges();\n this.attachIsolatedLinks();\n this.applyStageNormalization(robot, options);\n if (options.applyInitialPose ?? true) this.applyInitialPose(robot);\n }\n\n /** Orient (authored upAxis → target world up) and scale (metersPerUnit × unitScale) the root. */\n private applyStageNormalization(robot: RobotDescription, options: ThreeUsdRobotOptions): void {\n const scale = (robot.metersPerUnit || 1) * (options.unitScale ?? 1);\n if (scale !== 1) this.scale.setScalar(scale);\n\n const rotateX = (angle: number) =>\n this.quaternion.setFromAxisAngle(new THREE.Vector3(1, 0, 0), angle);\n\n if (options.worldUp) {\n if (options.worldUp === \"Y\" && robot.upAxis === \"Z\") rotateX(-Math.PI / 2);\n else if (options.worldUp === \"Z\" && robot.upAxis === \"Y\") rotateX(Math.PI / 2);\n return; // matching axis or \"keep\": leave as authored\n }\n const conv = options.upAxisConversion ?? \"none\";\n if (conv === \"Z\" || (conv === \"auto\" && robot.upAxis === \"Z\")) rotateX(-Math.PI / 2);\n }\n\n /** Apply each joint's authored initial value, if any. */\n private applyInitialPose(robot: RobotDescription): void {\n for (const [key, joint] of Object.entries(robot.joints)) {\n if (joint.initialValue === undefined) continue;\n this.jointObjects.get(key)?.setValue(joint.initialValue, this.clampJointLimits);\n }\n this.dirty = true;\n }\n\n private attachRoot(): void {\n const rootObj = this.linkObjects.get(this.tree.root);\n if (!rootObj) return; // empty robot\n const rootJointKey = this.tree.rootJoint;\n const rootLink = this.robot.links[this.tree.root];\n if (rootJointKey) {\n const j = this.robot.joints[rootJointKey];\n // World-fixed placement: jointFrame0 (in world) · inverse(jointFrame1).\n if (j) setMatrix(rootObj, multiply(j.jointFrame0, invert(j.jointFrame1)));\n } else if (rootLink?.worldTransform) {\n // Floating base: keep the authored stage placement.\n setMatrix(rootObj, rootLink.worldTransform);\n }\n this.add(rootObj);\n }\n\n private attachTreeEdges(): void {\n for (const linkKey of this.tree.order) {\n const node = this.tree.nodes[linkKey];\n if (!node || node.parent === null || node.jointToParent === null) continue;\n\n const parentObj = this.linkObjects.get(node.parent);\n const childObj = this.linkObjects.get(linkKey);\n const joint = this.robot.joints[node.jointToParent];\n if (!parentObj || !childObj || !joint) continue;\n\n this.attachJointChain(parentObj, childObj, node.jointToParent, joint);\n }\n }\n\n /** Build `parent → frame0 → motion → frame1⁻¹ → child` for one joint. */\n private attachJointChain(\n parent: LinkObject,\n child: LinkObject,\n jointKey: string,\n joint: JointDescription,\n ): void {\n const frame0 = new THREE.Group();\n frame0.name = `${joint.name}:frame0`;\n setMatrix(frame0, joint.jointFrame0);\n\n const motion = new JointObject(joint);\n\n const frame1Inv = new THREE.Group();\n frame1Inv.name = `${joint.name}:frame1Inv`;\n setMatrix(frame1Inv, invert(joint.jointFrame1));\n\n parent.add(frame0);\n frame0.add(motion);\n motion.add(frame1Inv);\n frame1Inv.add(child);\n\n this.jointObjects.set(jointKey, motion);\n }\n\n private attachIsolatedLinks(): void {\n for (const key of this.tree.isolatedLinks) {\n const obj = this.linkObjects.get(key);\n if (!obj || obj.parent) continue;\n // Not placeable by any joint chain — keep the authored stage placement\n // (other machines / free bodies on a multi-articulation stage).\n const worldTransform = this.robot.links[key]?.worldTransform;\n if (worldTransform) setMatrix(obj, worldTransform);\n this.add(obj);\n }\n }\n\n // -- Naming --------------------------------------------------------------\n\n /** Resolve a link reference — key or full prim path — to the extractor key. */\n private linkKey(ref: string): string {\n if (this.linkObjects.has(ref)) return ref;\n return this.linkKeyByPath.get(ref) ?? ref;\n }\n\n /** Resolve a joint reference — key or full prim path — to the extractor key. */\n private jointKey(ref: string): string {\n if (this.jointObjects.has(ref)) return ref;\n return this.jointKeyByPath.get(ref) ?? ref;\n }\n\n // -- Joint control -------------------------------------------------------\n\n /**\n * Set one joint value, addressed by key or full prim path. Unknown joints\n * are ignored. Returns whether it applied.\n */\n setJointValue(name: string, value: number): boolean {\n const joint = this.jointObjects.get(this.jointKey(name));\n if (!joint) return false;\n joint.setValue(value, this.clampJointLimits);\n this.dirty = true;\n return true;\n }\n\n /** Set several joint values at once (matrix update is coalesced). */\n setJointValues(values: Record<string, number>): void {\n for (const [name, value] of Object.entries(values)) {\n const joint = this.jointObjects.get(this.jointKey(name));\n if (joint) {\n joint.setValue(value, this.clampJointLimits);\n this.dirty = true;\n }\n }\n }\n\n getJointValue(name: string): number | undefined {\n return this.jointObjects.get(this.jointKey(name))?.value;\n }\n\n /** Recompute world matrices. Called lazily by the getters; safe to call directly. */\n updateKinematics(): void {\n this.updateMatrixWorld(true);\n this.dirty = false;\n }\n\n private ensureUpdated(): void {\n if (this.dirty) this.updateKinematics();\n }\n\n // -- Queries -------------------------------------------------------------\n\n /** World matrix of a link, addressed by key or full prim path. */\n getLinkWorldMatrix(name: string): THREE.Matrix4 {\n const obj = this.linkObjects.get(this.linkKey(name));\n if (!obj) throw new Error(`unknown link \"${name}\"`);\n this.ensureUpdated();\n return obj.matrixWorld.clone();\n }\n\n getLinkWorldPosition(name: string): THREE.Vector3 {\n return new THREE.Vector3().setFromMatrixPosition(this.getLinkWorldMatrix(name));\n }\n\n /** Link object by key or full prim path. */\n getLinkObject(name: string): LinkObject | undefined {\n return this.linkObjects.get(this.linkKey(name));\n }\n\n /** Joint object by key or full prim path. */\n getJointObject(name: string): JointObject | undefined {\n return this.jointObjects.get(this.jointKey(name));\n }\n\n /**\n * Table of link prim path → {@link LinkObject}. Prim paths are the\n * collision-proof way to pin a link (e.g. to attach tools or gizmos).\n */\n getLinkObjectsByPath(): Map<string, LinkObject> {\n const out = new Map<string, LinkObject>();\n for (const [path, key] of this.linkKeyByPath) {\n const obj = this.linkObjects.get(key);\n if (obj) out.set(path, obj);\n }\n return out;\n }\n\n /**\n * Table of joint prim path → {@link JointObject}, covering the joints\n * realized in the kinematic tree (loop joints have no motion node).\n */\n getJointObjectsByPath(): Map<string, JointObject> {\n const out = new Map<string, JointObject>();\n for (const [path, key] of this.jointKeyByPath) {\n const obj = this.jointObjects.get(key);\n if (obj) out.set(path, obj);\n }\n return out;\n }\n\n getJoints(): JointDescription[] {\n return Object.values(this.robot.joints);\n }\n\n getLinks(): LinkDescription[] {\n return Object.values(this.robot.links);\n }\n\n /** Names of the articulated (controllable) joints. */\n getJointNames(): string[] {\n return [...this.jointObjects.keys()];\n }\n\n getLinkNames(): string[] {\n return [...this.linkObjects.keys()];\n }\n\n getKinematicTree(): KinematicTree {\n return this.tree;\n }\n\n /** Authored stage up-axis (`\"Y\"` or `\"Z\"`) — unaffected by `worldUp` normalization. */\n get upAxis(): \"Y\" | \"Z\" {\n return this.robot.upAxis;\n }\n\n /** Authored stage scale in meters per unit (already applied to the root). */\n get metersPerUnit(): number {\n return this.robot.metersPerUnit;\n }\n\n // -- Animation playback --------------------------------------------------\n\n /** Playback rate in time codes per second (from the stage; default 24). */\n getTimeCodesPerSecond(): number {\n return this.robot.timeCodesPerSecond ?? 24;\n }\n\n /** Whether any joint has a time-sampled trajectory. */\n hasAnimation(): boolean {\n return Object.values(this.robot.joints).some((j) => j.valueSamples !== undefined);\n }\n\n /**\n * Animation range in time codes: the union of authored joint sample ranges,\n * falling back to the stage `startTimeCode`/`endTimeCode`. `null` if neither.\n */\n getTimeRange(): { start: number; end: number } | null {\n let start = Number.POSITIVE_INFINITY;\n let end = Number.NEGATIVE_INFINITY;\n for (const joint of Object.values(this.robot.joints)) {\n const times = joint.valueSamples?.times;\n if (!times || times.length === 0) continue;\n start = Math.min(start, times[0]!);\n end = Math.max(end, times[times.length - 1]!);\n }\n if (start <= end) return { start, end };\n\n const { startTimeCode, endTimeCode } = this.robot;\n if (startTimeCode !== undefined && endTimeCode !== undefined) {\n return { start: startTimeCode, end: endTimeCode };\n }\n return null;\n }\n\n /** Sample every animated joint at time code `t` and apply the values. */\n setTime(t: number): void {\n for (const [key, joint] of Object.entries(this.robot.joints)) {\n if (joint.valueSamples) this.setJointValue(key, interpolate(joint.valueSamples, t));\n }\n }\n\n // -- Display toggles -----------------------------------------------------\n\n get showVisual(): boolean {\n return this._showVisual;\n }\n set showVisual(v: boolean) {\n this._showVisual = v;\n this.setKindVisibility(\"visual\", v);\n }\n\n get showCollision(): boolean {\n return this._showCollision;\n }\n set showCollision(v: boolean) {\n this._showCollision = v;\n this.setKindVisibility(\"collision\", v);\n }\n\n get showJointAxes(): boolean {\n return this._showJointAxes;\n }\n set showJointAxes(v: boolean) {\n this._showJointAxes = v;\n if (v && this.jointAxesHelpers.length === 0) {\n for (const joint of this.jointObjects.values()) {\n const h = new THREE.AxesHelper(this.helperSize);\n h.name = `${joint.jointName}:axes`;\n joint.add(h);\n this.jointAxesHelpers.push(h);\n }\n }\n for (const h of this.jointAxesHelpers) h.visible = v;\n }\n\n get showLinkFrames(): boolean {\n return this._showLinkFrames;\n }\n set showLinkFrames(v: boolean) {\n this._showLinkFrames = v;\n if (v && this.linkFrameHelpers.length === 0) {\n for (const link of this.linkObjects.values()) {\n const h = new THREE.AxesHelper(this.helperSize);\n h.name = `${link.linkName}:frame`;\n link.add(h);\n this.linkFrameHelpers.push(h);\n }\n }\n for (const h of this.linkFrameHelpers) h.visible = v;\n }\n\n private setKindVisibility(kind: string, visible: boolean): void {\n this.traverse((o) => {\n if ((o.userData as { kind?: string }).kind === kind) o.visible = visible;\n });\n }\n}\n\n/** Assign a fixed local matrix to an object (disables Three.js auto-update). */\nfunction setMatrix(obj: THREE.Object3D, m: Mat4): void {\n obj.matrixAutoUpdate = false;\n obj.matrix.fromArray(m);\n obj.matrixWorldNeedsUpdate = true;\n}\n"]}