three-usd-robot 0.7.0 → 0.8.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/README.md +68 -7
- package/dist/{ThreeUsdRobot-Ljh4PDVD.d.ts → ThreeUsdRobot-CsjraHqP.d.ts} +32 -2
- package/dist/{ThreeUsdRobotLoader-BSIVFA48.d.ts → ThreeUsdRobotLoader-CxOGRLXH.d.ts} +31 -16
- package/dist/{buildKinematicTree-Ccd2DIKH.d.ts → buildKinematicTree-D4R0hEtN.d.ts} +2 -2
- package/dist/{AssetResolver-CpIJNgWZ.d.ts → bytes-MOJ2oN-u.d.ts} +14 -1
- package/dist/chunk-3CANZKP3.js +374 -0
- package/dist/chunk-3CANZKP3.js.map +1 -0
- package/dist/chunk-IYKPVUZ2.js +406 -0
- package/dist/chunk-IYKPVUZ2.js.map +1 -0
- package/dist/{chunk-O7XP5FQ4.js → chunk-P6CHTMGW.js} +4 -3
- package/dist/chunk-P6CHTMGW.js.map +1 -0
- package/dist/{chunk-4GPCBXUS.js → chunk-PHHMWMCA.js} +45 -410
- package/dist/chunk-PHHMWMCA.js.map +1 -0
- package/dist/{chunk-FL554IS5.js → chunk-YX5GIEMT.js} +114 -366
- package/dist/chunk-YX5GIEMT.js.map +1 -0
- package/dist/core.d.ts +17 -5
- package/dist/core.js +3 -2
- package/dist/extras.d.ts +2 -2
- package/dist/helpers.d.ts +73 -3
- package/dist/helpers.js +125 -13
- package/dist/helpers.js.map +1 -1
- package/dist/index.d.ts +28 -18
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +4 -4
- package/dist/react.js +4 -2
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4GPCBXUS.js.map +0 -1
- package/dist/chunk-FL554IS5.js.map +0 -1
- package/dist/chunk-O7XP5FQ4.js.map +0 -1
package/README.md
CHANGED
|
@@ -23,8 +23,10 @@ exported back to `.usda` / `.usdz` in the browser.
|
|
|
23
23
|
instanceable prims are composed for you.
|
|
24
24
|
- **Robots** — links, joints (fixed / revolute / continuous / prismatic), limits,
|
|
25
25
|
drives and the initial pose become a `setJointValue`-able hierarchy.
|
|
26
|
-
- **Rendering** — meshes
|
|
27
|
-
|
|
26
|
+
- **Rendering** — meshes and solid gprims (`Cube` / `Sphere` / `Cylinder` /
|
|
27
|
+
`Capsule` / `Cone`) with `UsdShade` materials (UsdPreviewSurface / OmniPBR)
|
|
28
|
+
and textures; up-axis and units normalized automatically. Articulation-free
|
|
29
|
+
stages load as static scenes.
|
|
28
30
|
- **Animation** — plays back time-sampled joint trajectories.
|
|
29
31
|
- **Export** — write robots and whole cells back to `.usda` / `.usdz`,
|
|
30
32
|
simulation-ready for Isaac Sim.
|
|
@@ -48,8 +50,8 @@ The CDN is public and CORS-enabled, so this works in the browser too. Try
|
|
|
48
50
|
FK check, and a re-export to one self-contained file), or open the Vite example
|
|
49
51
|
and pick a robot from the preset list.
|
|
50
52
|
|
|
51
|
-
Not yet supported: time samples stored inside binary crate files,
|
|
52
|
-
gprims (`
|
|
53
|
+
Not yet supported: time samples stored inside binary crate files, point/curve
|
|
54
|
+
gprims (`Points`, `BasisCurves`, …), and full material/shader fidelity.
|
|
53
55
|
|
|
54
56
|
## Install
|
|
55
57
|
|
|
@@ -79,8 +81,38 @@ robot.getJointNames(); // controllable joints
|
|
|
79
81
|
robot.getKinematicTree(); // root, ordering, loop joints, ...
|
|
80
82
|
```
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
`
|
|
84
|
+
No URL? `parse` also takes in-memory content: USDA source text, or an
|
|
85
|
+
`ArrayBuffer` / typed array / `Blob` holding any supported format — the zip /
|
|
86
|
+
crate magic is sniffed, so a dropped `File` or a response body works as-is:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const loader = new ThreeUsdRobotLoader();
|
|
90
|
+
await loader.parse(usdaText); // USDA source string
|
|
91
|
+
await loader.parse(file); // File / Blob from drag & drop or <input type="file">
|
|
92
|
+
await loader.parse(await res.arrayBuffer()); // a fetch you did yourself
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`parseUsdz(data)` / `parseCrate(data, baseUrl)` stay as explicit-format entries,
|
|
96
|
+
and `parseRobotDescription(data)` returns the Three.js-independent IR. Pass a
|
|
97
|
+
`baseUrl` as the second `parse` argument if the layer has relative references
|
|
98
|
+
or texture paths to resolve.
|
|
99
|
+
|
|
100
|
+
### Stable addressing (naming contract)
|
|
101
|
+
|
|
102
|
+
Joints and links are keyed by their prim's **leaf name** while it is unique
|
|
103
|
+
across the robot; on a collision (say, two arms each with a `seg` link) the
|
|
104
|
+
colliding entries are keyed by their **full prim path** instead —
|
|
105
|
+
deterministically. Every accessor also takes the full prim path directly, so
|
|
106
|
+
tooling can pin exact prims no matter how the asset is named:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
robot.setJointValue("/World/armL/j1", 0.4); // same joint as its key
|
|
110
|
+
robot.getLinkWorldMatrix("/World/armL/seg");
|
|
111
|
+
robot.getLinkObjectsByPath(); // Map<primPath, LinkObject>
|
|
112
|
+
robot.getJointObjectsByPath(); // Map<primPath, JointObject>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`LinkObject.primPath` / `JointObject.primPath` carry the reverse direction.
|
|
84
116
|
|
|
85
117
|
### Viewer toggles & helpers
|
|
86
118
|
|
|
@@ -94,9 +126,38 @@ import { addJointLimitHelpers } from "three-usd-robot/helpers";
|
|
|
94
126
|
addJointLimitHelpers(robot); // arc (revolute) / segment (prismatic) per joint
|
|
95
127
|
```
|
|
96
128
|
|
|
129
|
+
### Link highlighting & ghosts
|
|
130
|
+
|
|
131
|
+
Per-link appearance helpers cover the common viewer chores — flagging
|
|
132
|
+
colliding links, material swaps, and translucent "ghost" pose previews:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import {
|
|
136
|
+
createGhostRobot,
|
|
137
|
+
highlightLink,
|
|
138
|
+
restoreLinkMaterials,
|
|
139
|
+
setLinkMaterial,
|
|
140
|
+
} from "three-usd-robot/helpers";
|
|
141
|
+
|
|
142
|
+
highlightLink(robot, "link1"); // emissive red tint; maps/colors kept
|
|
143
|
+
highlightLink(robot, "/World/armL/seg", { color: 0xffaa00, opacity: 0.6 });
|
|
144
|
+
setLinkMaterial(robot, "link2", new THREE.MeshBasicMaterial({ wireframe: true }));
|
|
145
|
+
restoreLinkMaterials(robot, "link1"); // exact original materials back
|
|
146
|
+
|
|
147
|
+
const ghost = createGhostRobot(robot, { jointValues: { joint1: 1.2 } });
|
|
148
|
+
scene.add(ghost); // translucent copy previewing the target pose
|
|
149
|
+
ghost.setJointValues(ikSolution); // a full ThreeUsdRobot, driveable like the source
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Highlights never stack (each call re-tints from the originals), and ghosts
|
|
153
|
+
share the source's geometry — cloning is cheap enough for onion-skinning.
|
|
154
|
+
|
|
97
155
|
Loading a whole cell rather than a bare robot? Pass
|
|
98
156
|
`{ loadSceneGeometry: true }` to the loader to draw the static environment
|
|
99
|
-
(floor, guarding, racking, …) around the machines.
|
|
157
|
+
(floor, guarding, racking, …) around the machines. A stage with **no
|
|
158
|
+
articulation at all** — a plain static USD scene — is detected and rendered as
|
|
159
|
+
scene geometry automatically, with the same unit / up-axis normalization; pass
|
|
160
|
+
`loadSceneGeometry: false` to opt out.
|
|
100
161
|
|
|
101
162
|
### Joint slider panel (lil-gui)
|
|
102
163
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
-
import { J as JointType, A as Axis, a as JointDescription, L as LinkDescription, R as RobotDescription, K as KinematicTree } from './buildKinematicTree-
|
|
2
|
+
import { J as JointType, A as Axis, a as JointDescription, L as LinkDescription, R as RobotDescription, K as KinematicTree } from './buildKinematicTree-D4R0hEtN.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The articulated "motion" node of a joint, inserted between the joint's two
|
|
@@ -10,6 +10,8 @@ import { J as JointType, A as Axis, a as JointDescription, L as LinkDescription,
|
|
|
10
10
|
declare class JointObject extends THREE.Object3D {
|
|
11
11
|
readonly isJointObject = true;
|
|
12
12
|
readonly jointName: string;
|
|
13
|
+
/** Full USD prim path of the joint — the collision-proof address. */
|
|
14
|
+
readonly primPath: string;
|
|
13
15
|
readonly jointType: JointType;
|
|
14
16
|
readonly axisToken: Axis;
|
|
15
17
|
readonly axis: THREE.Vector3;
|
|
@@ -63,6 +65,12 @@ type ThreeUsdRobotOptions = {
|
|
|
63
65
|
* `parentLink → jointFrame0 → jointMotion → jointFrame1⁻¹ → childLink`,
|
|
64
66
|
* where only `jointMotion` (a {@link JointObject}) changes with the joint value;
|
|
65
67
|
* world poses then fall out of Three.js's `updateMatrixWorld`.
|
|
68
|
+
*
|
|
69
|
+
* **Naming contract** — a link/joint's key is its prim's leaf name when that
|
|
70
|
+
* is unique across the robot, else its full prim path (deterministic; see the
|
|
71
|
+
* extractor). Every accessor taking a name equally accepts the full prim path,
|
|
72
|
+
* which is stable regardless of collisions; {@link getLinkObjectsByPath} /
|
|
73
|
+
* {@link getJointObjectsByPath} enumerate the path-keyed tables.
|
|
66
74
|
*/
|
|
67
75
|
declare class ThreeUsdRobot extends THREE.Object3D {
|
|
68
76
|
readonly isThreeUsdRobot = true;
|
|
@@ -71,6 +79,8 @@ declare class ThreeUsdRobot extends THREE.Object3D {
|
|
|
71
79
|
readonly clampJointLimits: boolean;
|
|
72
80
|
private readonly linkObjects;
|
|
73
81
|
private readonly jointObjects;
|
|
82
|
+
private readonly linkKeyByPath;
|
|
83
|
+
private readonly jointKeyByPath;
|
|
74
84
|
private dirty;
|
|
75
85
|
private readonly helperSize;
|
|
76
86
|
private _showVisual;
|
|
@@ -89,7 +99,14 @@ declare class ThreeUsdRobot extends THREE.Object3D {
|
|
|
89
99
|
/** Build `parent → frame0 → motion → frame1⁻¹ → child` for one joint. */
|
|
90
100
|
private attachJointChain;
|
|
91
101
|
private attachIsolatedLinks;
|
|
92
|
-
/**
|
|
102
|
+
/** Resolve a link reference — key or full prim path — to the extractor key. */
|
|
103
|
+
private linkKey;
|
|
104
|
+
/** Resolve a joint reference — key or full prim path — to the extractor key. */
|
|
105
|
+
private jointKey;
|
|
106
|
+
/**
|
|
107
|
+
* Set one joint value, addressed by key or full prim path. Unknown joints
|
|
108
|
+
* are ignored. Returns whether it applied.
|
|
109
|
+
*/
|
|
93
110
|
setJointValue(name: string, value: number): boolean;
|
|
94
111
|
/** Set several joint values at once (matrix update is coalesced). */
|
|
95
112
|
setJointValues(values: Record<string, number>): void;
|
|
@@ -97,10 +114,23 @@ declare class ThreeUsdRobot extends THREE.Object3D {
|
|
|
97
114
|
/** Recompute world matrices. Called lazily by the getters; safe to call directly. */
|
|
98
115
|
updateKinematics(): void;
|
|
99
116
|
private ensureUpdated;
|
|
117
|
+
/** World matrix of a link, addressed by key or full prim path. */
|
|
100
118
|
getLinkWorldMatrix(name: string): THREE.Matrix4;
|
|
101
119
|
getLinkWorldPosition(name: string): THREE.Vector3;
|
|
120
|
+
/** Link object by key or full prim path. */
|
|
102
121
|
getLinkObject(name: string): LinkObject | undefined;
|
|
122
|
+
/** Joint object by key or full prim path. */
|
|
103
123
|
getJointObject(name: string): JointObject | undefined;
|
|
124
|
+
/**
|
|
125
|
+
* Table of link prim path → {@link LinkObject}. Prim paths are the
|
|
126
|
+
* collision-proof way to pin a link (e.g. to attach tools or gizmos).
|
|
127
|
+
*/
|
|
128
|
+
getLinkObjectsByPath(): Map<string, LinkObject>;
|
|
129
|
+
/**
|
|
130
|
+
* Table of joint prim path → {@link JointObject}, covering the joints
|
|
131
|
+
* realized in the kinematic tree (loop joints have no motion node).
|
|
132
|
+
*/
|
|
133
|
+
getJointObjectsByPath(): Map<string, JointObject>;
|
|
104
134
|
getJoints(): JointDescription[];
|
|
105
135
|
getLinks(): LinkDescription[];
|
|
106
136
|
/** Names of the articulated (controllable) joints. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { R as RobotDescription } from './buildKinematicTree-
|
|
2
|
-
import { A as AssetResolver } from './
|
|
3
|
-
import { T as ThreeUsdRobot } from './ThreeUsdRobot-
|
|
1
|
+
import { R as RobotDescription } from './buildKinematicTree-D4R0hEtN.js';
|
|
2
|
+
import { A as AssetResolver, U as UsdSource, B as BinarySource } from './bytes-MOJ2oN-u.js';
|
|
3
|
+
import { T as ThreeUsdRobot } from './ThreeUsdRobot-CsjraHqP.js';
|
|
4
4
|
|
|
5
5
|
type ThreeUsdRobotLoaderOptions = {
|
|
6
6
|
/** Resolver for references / payloads / sublayers (default {@link DefaultAssetResolver}). */
|
|
@@ -10,9 +10,10 @@ type ThreeUsdRobotLoaderOptions = {
|
|
|
10
10
|
/** Render collision meshes (M6). */
|
|
11
11
|
loadCollisions?: boolean;
|
|
12
12
|
/**
|
|
13
|
-
* Also render
|
|
14
|
-
*
|
|
15
|
-
* authored stage transform. Default `false
|
|
13
|
+
* Also render gprims that belong to no link — the static scenery of a cell
|
|
14
|
+
* that contains robots (floor, guarding, racking, …). Placed by their
|
|
15
|
+
* authored stage transform. Default `false`; a stage with no articulation at
|
|
16
|
+
* all (a pure static scene) defaults to `true` so it renders out of the box.
|
|
16
17
|
*/
|
|
17
18
|
loadSceneGeometry?: boolean;
|
|
18
19
|
/** Load diffuse textures referenced by materials (default `true`). */
|
|
@@ -33,6 +34,8 @@ type ThreeUsdRobotLoaderOptions = {
|
|
|
33
34
|
/**
|
|
34
35
|
* Loads Isaac Sim / OpenUSD robot assets into a controllable {@link ThreeUsdRobot}.
|
|
35
36
|
*
|
|
37
|
+
* Assets load by URL ({@link loadAsync}) or from in-memory content
|
|
38
|
+
* ({@link parse} — USDA text, `ArrayBuffer`, typed array, or `Blob`/`File`).
|
|
36
39
|
* Composition (references / payloads / sublayers, M8) is resolved through an
|
|
37
40
|
* {@link AssetResolver}. Mesh rendering is M6; unit / up-axis / initial-pose
|
|
38
41
|
* handling is M9; USDC and variants are M10.
|
|
@@ -43,19 +46,31 @@ declare class ThreeUsdRobotLoader {
|
|
|
43
46
|
private get resolver();
|
|
44
47
|
/**
|
|
45
48
|
* Fetch an asset by URL and build the robot. `.usdz` packages are unzipped and
|
|
46
|
-
* composed from their entries; everything else is sniffed for the crate
|
|
47
|
-
* and parsed as binary USDC or USDA text.
|
|
49
|
+
* composed from their entries; everything else is sniffed for the zip / crate
|
|
50
|
+
* magic and parsed as USDZ, binary USDC, or USDA text.
|
|
48
51
|
*/
|
|
49
52
|
loadAsync(url: string): Promise<ThreeUsdRobot>;
|
|
50
53
|
private fetchRootBytes;
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Build a robot from in-memory content — no fetch involved. Accepts USDA
|
|
56
|
+
* source text, or an `ArrayBuffer` / typed array / `Blob` (e.g. a dropped
|
|
57
|
+
* `File`) holding USDA text, a binary crate, or a `.usdz` package; binary
|
|
58
|
+
* input is sniffed for the zip / crate magic. `baseUrl` anchors relative
|
|
59
|
+
* references, payloads, and texture paths of non-package input.
|
|
60
|
+
*/
|
|
61
|
+
parse(data: UsdSource, baseUrl?: string): Promise<ThreeUsdRobot>;
|
|
62
|
+
/** Build a robot from a `.usdz` package (bytes, `ArrayBuffer`, or `Blob`). */
|
|
63
|
+
parseUsdz(data: BinarySource): Promise<ThreeUsdRobot>;
|
|
64
|
+
/** Build a robot from a binary crate (`.usdc` / binary `.usd`). */
|
|
65
|
+
parseCrate(data: BinarySource, baseUrl?: string): Promise<ThreeUsdRobot>;
|
|
66
|
+
/**
|
|
67
|
+
* Parse + compose in-memory content (same formats as {@link parse}) into the
|
|
68
|
+
* Three.js-independent robot IR.
|
|
69
|
+
*/
|
|
70
|
+
parseRobotDescription(data: UsdSource, baseUrl?: string): Promise<RobotDescription>;
|
|
71
|
+
/** Sniff in-memory content (text / zip / crate) and compose it into a stage. */
|
|
72
|
+
private openSource;
|
|
73
|
+
private openUsdzStage;
|
|
59
74
|
private buildFromStage;
|
|
60
75
|
private composeStage;
|
|
61
76
|
/** Compose a layer from raw bytes, sniffing binary crate vs USDA text. */
|
|
@@ -256,9 +256,9 @@ type LinkDescription = {
|
|
|
256
256
|
/** Display name (leaf prim name). */
|
|
257
257
|
name: string;
|
|
258
258
|
primPath: string;
|
|
259
|
-
/**
|
|
259
|
+
/** Renderable gprim paths (Mesh / Cube / Sphere / Cylinder / Capsule / Cone). */
|
|
260
260
|
visualPrims: string[];
|
|
261
|
-
/**
|
|
261
|
+
/** Gprim paths flagged with a collision API (or a non-visual purpose). */
|
|
262
262
|
collisionPrims?: string[];
|
|
263
263
|
/** Mass properties (`UsdPhysicsMassAPI`), if authored (M16). */
|
|
264
264
|
inertial?: LinkInertialDescription;
|
|
@@ -26,4 +26,17 @@ declare function createMemoryResolver(files: Record<string, string | Uint8Array>
|
|
|
26
26
|
/** Resolve `rel` against `baseUrl` using posix semantics, normalizing `.`/`..`. */
|
|
27
27
|
declare function joinPosix(baseUrl: string, rel: string): string;
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* In-memory input normalization for the loader entry points. Callers hold USD
|
|
31
|
+
* content in many shapes — a `fetch` response's `ArrayBuffer`, a `Uint8Array`
|
|
32
|
+
* (or any typed-array view), a dropped `File` / `Blob` — and every byte-eating
|
|
33
|
+
* API here funnels through {@link toBytes} so all of them are accepted.
|
|
34
|
+
*/
|
|
35
|
+
/** Binary USD content: an `ArrayBuffer`, any typed-array view, or a `Blob`/`File`. */
|
|
36
|
+
type BinarySource = ArrayBuffer | ArrayBufferView | Blob;
|
|
37
|
+
/** In-memory USD content: USDA source text, or {@link BinarySource} bytes. */
|
|
38
|
+
type UsdSource = string | BinarySource;
|
|
39
|
+
/** Normalize a {@link BinarySource} to bytes, honoring a view's offset/length. */
|
|
40
|
+
declare function toBytes(data: BinarySource): Promise<Uint8Array>;
|
|
41
|
+
|
|
42
|
+
export { type AssetResolver as A, type BinarySource as B, DefaultAssetResolver as D, type UsdSource as U, createMemoryResolver as c, joinPosix as j, toBytes as t };
|
|
@@ -0,0 +1,374 @@
|
|
|
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 (Z-up → Y-up) and scale (metersPerUnit × unitScale) the robot root. */
|
|
114
|
+
applyStageNormalization(robot, options) {
|
|
115
|
+
const scale = (robot.metersPerUnit || 1) * (options.unitScale ?? 1);
|
|
116
|
+
if (scale !== 1) this.scale.setScalar(scale);
|
|
117
|
+
const conv = options.upAxisConversion ?? "none";
|
|
118
|
+
const toY = conv === "Z" || conv === "auto" && robot.upAxis === "Z";
|
|
119
|
+
if (toY) this.quaternion.setFromAxisAngle(new THREE4.Vector3(1, 0, 0), -Math.PI / 2);
|
|
120
|
+
}
|
|
121
|
+
/** Apply each joint's authored initial value, if any. */
|
|
122
|
+
applyInitialPose(robot) {
|
|
123
|
+
for (const [key, joint] of Object.entries(robot.joints)) {
|
|
124
|
+
if (joint.initialValue === void 0) continue;
|
|
125
|
+
this.jointObjects.get(key)?.setValue(joint.initialValue, this.clampJointLimits);
|
|
126
|
+
}
|
|
127
|
+
this.dirty = true;
|
|
128
|
+
}
|
|
129
|
+
attachRoot() {
|
|
130
|
+
const rootObj = this.linkObjects.get(this.tree.root);
|
|
131
|
+
if (!rootObj) return;
|
|
132
|
+
const rootJointKey = this.tree.rootJoint;
|
|
133
|
+
const rootLink = this.robot.links[this.tree.root];
|
|
134
|
+
if (rootJointKey) {
|
|
135
|
+
const j = this.robot.joints[rootJointKey];
|
|
136
|
+
if (j) setMatrix(rootObj, multiply(j.jointFrame0, invert(j.jointFrame1)));
|
|
137
|
+
} else if (rootLink?.worldTransform) {
|
|
138
|
+
setMatrix(rootObj, rootLink.worldTransform);
|
|
139
|
+
}
|
|
140
|
+
this.add(rootObj);
|
|
141
|
+
}
|
|
142
|
+
attachTreeEdges() {
|
|
143
|
+
for (const linkKey of this.tree.order) {
|
|
144
|
+
const node = this.tree.nodes[linkKey];
|
|
145
|
+
if (!node || node.parent === null || node.jointToParent === null) continue;
|
|
146
|
+
const parentObj = this.linkObjects.get(node.parent);
|
|
147
|
+
const childObj = this.linkObjects.get(linkKey);
|
|
148
|
+
const joint = this.robot.joints[node.jointToParent];
|
|
149
|
+
if (!parentObj || !childObj || !joint) continue;
|
|
150
|
+
this.attachJointChain(parentObj, childObj, node.jointToParent, joint);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Build `parent → frame0 → motion → frame1⁻¹ → child` for one joint. */
|
|
154
|
+
attachJointChain(parent, child, jointKey, joint) {
|
|
155
|
+
const frame0 = new THREE4.Group();
|
|
156
|
+
frame0.name = `${joint.name}:frame0`;
|
|
157
|
+
setMatrix(frame0, joint.jointFrame0);
|
|
158
|
+
const motion = new JointObject(joint);
|
|
159
|
+
const frame1Inv = new THREE4.Group();
|
|
160
|
+
frame1Inv.name = `${joint.name}:frame1Inv`;
|
|
161
|
+
setMatrix(frame1Inv, invert(joint.jointFrame1));
|
|
162
|
+
parent.add(frame0);
|
|
163
|
+
frame0.add(motion);
|
|
164
|
+
motion.add(frame1Inv);
|
|
165
|
+
frame1Inv.add(child);
|
|
166
|
+
this.jointObjects.set(jointKey, motion);
|
|
167
|
+
}
|
|
168
|
+
attachIsolatedLinks() {
|
|
169
|
+
for (const key of this.tree.isolatedLinks) {
|
|
170
|
+
const obj = this.linkObjects.get(key);
|
|
171
|
+
if (!obj || obj.parent) continue;
|
|
172
|
+
const worldTransform = this.robot.links[key]?.worldTransform;
|
|
173
|
+
if (worldTransform) setMatrix(obj, worldTransform);
|
|
174
|
+
this.add(obj);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// -- Naming --------------------------------------------------------------
|
|
178
|
+
/** Resolve a link reference — key or full prim path — to the extractor key. */
|
|
179
|
+
linkKey(ref) {
|
|
180
|
+
if (this.linkObjects.has(ref)) return ref;
|
|
181
|
+
return this.linkKeyByPath.get(ref) ?? ref;
|
|
182
|
+
}
|
|
183
|
+
/** Resolve a joint reference — key or full prim path — to the extractor key. */
|
|
184
|
+
jointKey(ref) {
|
|
185
|
+
if (this.jointObjects.has(ref)) return ref;
|
|
186
|
+
return this.jointKeyByPath.get(ref) ?? ref;
|
|
187
|
+
}
|
|
188
|
+
// -- Joint control -------------------------------------------------------
|
|
189
|
+
/**
|
|
190
|
+
* Set one joint value, addressed by key or full prim path. Unknown joints
|
|
191
|
+
* are ignored. Returns whether it applied.
|
|
192
|
+
*/
|
|
193
|
+
setJointValue(name, value) {
|
|
194
|
+
const joint = this.jointObjects.get(this.jointKey(name));
|
|
195
|
+
if (!joint) return false;
|
|
196
|
+
joint.setValue(value, this.clampJointLimits);
|
|
197
|
+
this.dirty = true;
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
/** Set several joint values at once (matrix update is coalesced). */
|
|
201
|
+
setJointValues(values) {
|
|
202
|
+
for (const [name, value] of Object.entries(values)) {
|
|
203
|
+
const joint = this.jointObjects.get(this.jointKey(name));
|
|
204
|
+
if (joint) {
|
|
205
|
+
joint.setValue(value, this.clampJointLimits);
|
|
206
|
+
this.dirty = true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
getJointValue(name) {
|
|
211
|
+
return this.jointObjects.get(this.jointKey(name))?.value;
|
|
212
|
+
}
|
|
213
|
+
/** Recompute world matrices. Called lazily by the getters; safe to call directly. */
|
|
214
|
+
updateKinematics() {
|
|
215
|
+
this.updateMatrixWorld(true);
|
|
216
|
+
this.dirty = false;
|
|
217
|
+
}
|
|
218
|
+
ensureUpdated() {
|
|
219
|
+
if (this.dirty) this.updateKinematics();
|
|
220
|
+
}
|
|
221
|
+
// -- Queries -------------------------------------------------------------
|
|
222
|
+
/** World matrix of a link, addressed by key or full prim path. */
|
|
223
|
+
getLinkWorldMatrix(name) {
|
|
224
|
+
const obj = this.linkObjects.get(this.linkKey(name));
|
|
225
|
+
if (!obj) throw new Error(`unknown link "${name}"`);
|
|
226
|
+
this.ensureUpdated();
|
|
227
|
+
return obj.matrixWorld.clone();
|
|
228
|
+
}
|
|
229
|
+
getLinkWorldPosition(name) {
|
|
230
|
+
return new THREE4.Vector3().setFromMatrixPosition(this.getLinkWorldMatrix(name));
|
|
231
|
+
}
|
|
232
|
+
/** Link object by key or full prim path. */
|
|
233
|
+
getLinkObject(name) {
|
|
234
|
+
return this.linkObjects.get(this.linkKey(name));
|
|
235
|
+
}
|
|
236
|
+
/** Joint object by key or full prim path. */
|
|
237
|
+
getJointObject(name) {
|
|
238
|
+
return this.jointObjects.get(this.jointKey(name));
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Table of link prim path → {@link LinkObject}. Prim paths are the
|
|
242
|
+
* collision-proof way to pin a link (e.g. to attach tools or gizmos).
|
|
243
|
+
*/
|
|
244
|
+
getLinkObjectsByPath() {
|
|
245
|
+
const out = /* @__PURE__ */ new Map();
|
|
246
|
+
for (const [path, key] of this.linkKeyByPath) {
|
|
247
|
+
const obj = this.linkObjects.get(key);
|
|
248
|
+
if (obj) out.set(path, obj);
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Table of joint prim path → {@link JointObject}, covering the joints
|
|
254
|
+
* realized in the kinematic tree (loop joints have no motion node).
|
|
255
|
+
*/
|
|
256
|
+
getJointObjectsByPath() {
|
|
257
|
+
const out = /* @__PURE__ */ new Map();
|
|
258
|
+
for (const [path, key] of this.jointKeyByPath) {
|
|
259
|
+
const obj = this.jointObjects.get(key);
|
|
260
|
+
if (obj) out.set(path, obj);
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
getJoints() {
|
|
265
|
+
return Object.values(this.robot.joints);
|
|
266
|
+
}
|
|
267
|
+
getLinks() {
|
|
268
|
+
return Object.values(this.robot.links);
|
|
269
|
+
}
|
|
270
|
+
/** Names of the articulated (controllable) joints. */
|
|
271
|
+
getJointNames() {
|
|
272
|
+
return [...this.jointObjects.keys()];
|
|
273
|
+
}
|
|
274
|
+
getLinkNames() {
|
|
275
|
+
return [...this.linkObjects.keys()];
|
|
276
|
+
}
|
|
277
|
+
getKinematicTree() {
|
|
278
|
+
return this.tree;
|
|
279
|
+
}
|
|
280
|
+
// -- Animation playback --------------------------------------------------
|
|
281
|
+
/** Playback rate in time codes per second (from the stage; default 24). */
|
|
282
|
+
getTimeCodesPerSecond() {
|
|
283
|
+
return this.robot.timeCodesPerSecond ?? 24;
|
|
284
|
+
}
|
|
285
|
+
/** Whether any joint has a time-sampled trajectory. */
|
|
286
|
+
hasAnimation() {
|
|
287
|
+
return Object.values(this.robot.joints).some((j) => j.valueSamples !== void 0);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Animation range in time codes: the union of authored joint sample ranges,
|
|
291
|
+
* falling back to the stage `startTimeCode`/`endTimeCode`. `null` if neither.
|
|
292
|
+
*/
|
|
293
|
+
getTimeRange() {
|
|
294
|
+
let start = Number.POSITIVE_INFINITY;
|
|
295
|
+
let end = Number.NEGATIVE_INFINITY;
|
|
296
|
+
for (const joint of Object.values(this.robot.joints)) {
|
|
297
|
+
const times = joint.valueSamples?.times;
|
|
298
|
+
if (!times || times.length === 0) continue;
|
|
299
|
+
start = Math.min(start, times[0]);
|
|
300
|
+
end = Math.max(end, times[times.length - 1]);
|
|
301
|
+
}
|
|
302
|
+
if (start <= end) return { start, end };
|
|
303
|
+
const { startTimeCode, endTimeCode } = this.robot;
|
|
304
|
+
if (startTimeCode !== void 0 && endTimeCode !== void 0) {
|
|
305
|
+
return { start: startTimeCode, end: endTimeCode };
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
/** Sample every animated joint at time code `t` and apply the values. */
|
|
310
|
+
setTime(t) {
|
|
311
|
+
for (const [key, joint] of Object.entries(this.robot.joints)) {
|
|
312
|
+
if (joint.valueSamples) this.setJointValue(key, interpolate(joint.valueSamples, t));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// -- Display toggles -----------------------------------------------------
|
|
316
|
+
get showVisual() {
|
|
317
|
+
return this._showVisual;
|
|
318
|
+
}
|
|
319
|
+
set showVisual(v) {
|
|
320
|
+
this._showVisual = v;
|
|
321
|
+
this.setKindVisibility("visual", v);
|
|
322
|
+
}
|
|
323
|
+
get showCollision() {
|
|
324
|
+
return this._showCollision;
|
|
325
|
+
}
|
|
326
|
+
set showCollision(v) {
|
|
327
|
+
this._showCollision = v;
|
|
328
|
+
this.setKindVisibility("collision", v);
|
|
329
|
+
}
|
|
330
|
+
get showJointAxes() {
|
|
331
|
+
return this._showJointAxes;
|
|
332
|
+
}
|
|
333
|
+
set showJointAxes(v) {
|
|
334
|
+
this._showJointAxes = v;
|
|
335
|
+
if (v && this.jointAxesHelpers.length === 0) {
|
|
336
|
+
for (const joint of this.jointObjects.values()) {
|
|
337
|
+
const h = new THREE4.AxesHelper(this.helperSize);
|
|
338
|
+
h.name = `${joint.jointName}:axes`;
|
|
339
|
+
joint.add(h);
|
|
340
|
+
this.jointAxesHelpers.push(h);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
for (const h of this.jointAxesHelpers) h.visible = v;
|
|
344
|
+
}
|
|
345
|
+
get showLinkFrames() {
|
|
346
|
+
return this._showLinkFrames;
|
|
347
|
+
}
|
|
348
|
+
set showLinkFrames(v) {
|
|
349
|
+
this._showLinkFrames = v;
|
|
350
|
+
if (v && this.linkFrameHelpers.length === 0) {
|
|
351
|
+
for (const link of this.linkObjects.values()) {
|
|
352
|
+
const h = new THREE4.AxesHelper(this.helperSize);
|
|
353
|
+
h.name = `${link.linkName}:frame`;
|
|
354
|
+
link.add(h);
|
|
355
|
+
this.linkFrameHelpers.push(h);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
for (const h of this.linkFrameHelpers) h.visible = v;
|
|
359
|
+
}
|
|
360
|
+
setKindVisibility(kind, visible) {
|
|
361
|
+
this.traverse((o) => {
|
|
362
|
+
if (o.userData.kind === kind) o.visible = visible;
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
function setMatrix(obj, m) {
|
|
367
|
+
obj.matrixAutoUpdate = false;
|
|
368
|
+
obj.matrix.fromArray(m);
|
|
369
|
+
obj.matrixWorldNeedsUpdate = true;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export { JointObject, LinkObject, ThreeUsdRobot, axisVector };
|
|
373
|
+
//# sourceMappingURL=chunk-3CANZKP3.js.map
|
|
374
|
+
//# sourceMappingURL=chunk-3CANZKP3.js.map
|