three-usd-robot 0.5.0 → 0.6.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 CHANGED
@@ -1,32 +1,32 @@
1
1
  # three-usd-robot
2
2
 
3
- > **Kinematic OpenUSD robot loader for Three.js.**
4
- > Load Isaac Sim / OpenUSD robot assets, extract joints and links, and control
5
- > articulations directly in the browser no physics engine required.
3
+ > **Kinematic OpenUSD robot loader — and exporter — for Three.js.**
4
+ > Load Isaac Sim / OpenUSD robot assets, control their joints in the browser,
5
+ > and write scenes back out as USD. No physics engine, no OpenUSD/WASM
6
+ > dependency.
6
7
 
7
8
  Think of it as a **USD version of [`urdf-loader`](https://www.npmjs.com/package/urdf-loader)**:
8
- it reads the link / joint / xform / mesh structure out of `UsdPhysics` robot assets
9
+ it reads the link / joint / xform / mesh structure out of `UsdPhysics` assets
9
10
  and drives forward kinematics on a Three.js `Object3D` hierarchy.
10
11
 
11
- > 🚧 **Status: v0.3.** Loads **ASCII `.usda`**, **binary `.usdc` / `.usd`
12
- > (crate)**, and **`.usdz`** robots — including **multi-file assets** via
13
- > references / payloads / sublayers (resolved across `.usda` *and* binary layers,
14
- > with relationship-path remapping), **variant selections**, and **instanceable**
15
- > prims — drives forward kinematics with meshes, applies flat **`UsdShade`
16
- > material colors** (UsdPreviewSurface / OmniPBR constants) and **diffuse
17
- > textures** (`UsdUVTexture`), normalizes up-axis & units, seeds the initial
18
- > pose, and **plays back time-sampled joint trajectories**. The crate reader is a
19
- > from-scratch TypeScript implementation (no OpenUSD/WASM dependency). Not yet:
20
- > time samples and variant *selection* stored inside binary crate.
12
+ ![A robot cell loaded in the browser](assets/threejs.png)
21
13
 
22
- ```ts
23
- // .usda / .usdc / binary .usd / .usdz are all auto-detected:
24
- const robot = await new ThreeUsdRobotLoader().loadAsync("/assets/robot.usd");
25
- // or from bytes you already have:
26
- const robot = await new ThreeUsdRobotLoader().parseCrate(usdcBytes);
27
- ```
14
+ ## Features
15
+
16
+ - **Formats** ASCII `.usda`, binary `.usdc` / `.usd`, and `.usdz`, auto-detected.
17
+ Multi-file assets (references / payloads / sublayers), variant selections and
18
+ instanceable prims are composed for you.
19
+ - **Robots** — links, joints (fixed / revolute / continuous / prismatic), limits,
20
+ drives and the initial pose become a `setJointValue`-able hierarchy.
21
+ - **Rendering** — meshes with `UsdShade` materials (UsdPreviewSurface / OmniPBR)
22
+ and textures; up-axis and units normalized automatically.
23
+ - **Animation** — plays back time-sampled joint trajectories.
24
+ - **Export** — write robots and whole cells back to `.usda` / `.usdz`,
25
+ simulation-ready for Isaac Sim.
26
+ - **React** — declarative `<UsdRobot>` for React Three Fiber.
28
27
 
29
- ![demo](assets/anim_demo.gif)
28
+ Not yet supported: time samples and variant *selections* stored inside binary
29
+ crate files, and full material/shader fidelity.
30
30
 
31
31
  ## Install
32
32
 
@@ -36,11 +36,12 @@ npm install three-usd-robot three
36
36
 
37
37
  `three` is a **peer dependency** (`>=0.160.0`).
38
38
 
39
- ## Usage
39
+ ## Quick start
40
40
 
41
41
  ```ts
42
42
  import { ThreeUsdRobotLoader } from "three-usd-robot";
43
43
 
44
+ // .usda / .usdc / binary .usd / .usdz are all auto-detected.
44
45
  const robot = await new ThreeUsdRobotLoader().loadAsync("/assets/arm.usda");
45
46
  scene.add(robot);
46
47
 
@@ -52,9 +53,12 @@ const handMatrix = robot.getLinkWorldMatrix("tool0"); // THREE.Matrix4
52
53
  const handPos = robot.getLinkWorldPosition("tool0"); // THREE.Vector3
53
54
 
54
55
  robot.getJointNames(); // controllable joints
55
- robot.getKinematicTree(); // root, ordering, loopJoints, ...
56
+ robot.getKinematicTree(); // root, ordering, loop joints, ...
56
57
  ```
57
58
 
59
+ Already have the bytes? Use `parse(usdaText)`, `parseCrate(usdcBytes)` or
60
+ `parseUsdz(bytes)` instead of `loadAsync`.
61
+
58
62
  ### Viewer toggles & helpers
59
63
 
60
64
  ```ts
@@ -67,6 +71,10 @@ import { addJointLimitHelpers } from "three-usd-robot/helpers";
67
71
  addJointLimitHelpers(robot); // arc (revolute) / segment (prismatic) per joint
68
72
  ```
69
73
 
74
+ Loading a whole cell rather than a bare robot? Pass
75
+ `{ loadSceneGeometry: true }` to the loader to draw the static environment
76
+ (floor, guarding, racking, …) around the machines.
77
+
70
78
  ### Joint slider panel (lil-gui)
71
79
 
72
80
  ```ts
@@ -76,26 +84,24 @@ import { createJointSliderPanel } from "three-usd-robot/extras";
76
84
  createJointSliderPanel(robot, new GUI()); // one slider per articulated joint
77
85
  ```
78
86
 
79
- The `extras` panel takes the GUI instance from you, so the library never bundles
80
- `lil-gui`. See [`examples/`](./examples) for runnable Vite demos.
87
+ The panel takes the GUI instance from you, so the library never bundles
88
+ `lil-gui`.
81
89
 
82
90
  ### Animation playback
83
91
 
84
- If the asset has time-sampled joint trajectories (joint-state or drive-target
85
- time samples), the robot plays them back:
92
+ If the asset has time-sampled joint trajectories, the robot plays them back:
86
93
 
87
94
  ```ts
88
- if (robot.hasAnimation()) {
89
- const { start, end } = robot.getTimeRange()!;
90
- const fps = robot.getTimeCodesPerSecond();
91
- // in your render loop, advance a time code and sample:
95
+ const range = robot.getTimeRange();
96
+ if (range) {
97
+ // in your render loop, advance a time code between range.start and range.end:
92
98
  robot.setTime(t); // interpolates every animated joint and updates FK
93
99
  }
94
100
  ```
95
101
 
96
102
  ### React Three Fiber
97
103
 
98
- `three-usd-robot/react` provides a declarative `<UsdRobot>` for R3F (`react` and
104
+ `three-usd-robot/react` provides a declarative `<UsdRobot>` (`react` and
99
105
  `@react-three/fiber` are **optional** peer deps):
100
106
 
101
107
  ```tsx
@@ -117,11 +123,68 @@ import { UsdRobot } from "three-usd-robot/react";
117
123
  </Canvas>;
118
124
  ```
119
125
 
120
- Also exported: `useUsdRobot(url)` (Suspense loader), `useRobotAnimation(robot)`
121
- (per-frame playback), `preloadUsdRobot`, `clearUsdRobotCache`. Pass a `ref` to
122
- `<UsdRobot>` for the imperative `ThreeUsdRobot` API.
126
+ Also exported: `useUsdRobot(url)` (Suspense loader), `useRobotAnimation(robot)`,
127
+ `preloadUsdRobot`, `clearUsdRobotCache`. Pass a `ref` to `<UsdRobot>` for the
128
+ imperative API.
129
+
130
+ ## Export USD
131
+
132
+ The loader's inverse: re-export something you loaded, or author a robot from
133
+ Three.js objects and open the result in Isaac Sim.
134
+
135
+ ![The exported cell opened in Isaac Sim](assets/isaacsim.png)
136
+
137
+ ```ts
138
+ import {
139
+ exportThreeUsdRobot,
140
+ RobotBuilder,
141
+ serializeUsda,
142
+ writeUsdz,
143
+ } from "three-usd-robot";
144
+
145
+ // Re-export a loaded robot (meshes harvested from the Three.js scene):
146
+ const usda = serializeUsda(exportThreeUsdRobot(robot));
147
+
148
+ // …or build one from Three.js meshes. Z-up and metres by default; each joint
149
+ // takes ONE world-space frame, and the build-time arrangement is the zero pose.
150
+ const builder = new RobotBuilder({ name: "my_robot" });
151
+ builder.addLink({ name: "base", visuals: [baseMesh] });
152
+ builder.addLink({ name: "arm", frame: armFrame, visuals: [armMesh] });
153
+ builder.addFixedJoint({ name: "root_joint", child: "base" });
154
+ builder.addRevoluteJoint({
155
+ name: "j1", parent: "base", child: "arm",
156
+ frame: jointFrame, axis: "Z", lower: -Math.PI, upper: Math.PI,
157
+ });
158
+
159
+ const file = builder.toUsda();
160
+ writeFileSync("robot.usda", serializeUsda(file));
161
+ writeFileSync("robot.usdz", writeUsdz({ "robot.usda": serializeUsda(file) }));
162
+ ```
163
+
164
+ Joints export as `UsdPhysics` prims with their limits, drives and initial pose.
165
+ For a simulation-ready asset, links also take mass properties, and collision
166
+ meshes take physics materials and a collision approximation:
167
+
168
+ ```ts
169
+ builder.addLink({
170
+ name: "arm",
171
+ visuals: [armMesh],
172
+ collisions: [armCollisionMesh],
173
+ inertial: { mass: 2.5, centerOfMass: [0, 0, 0.2], diagonalInertia: [0.02, 0.02, 0.004] },
174
+ collisionApproximation: "convexHull",
175
+ physicsMaterial: { name: "steel", staticFriction: 0.6, dynamicFriction: 0.5 },
176
+ });
177
+ ```
178
+
179
+ Both screenshots above come from `npx tsx scripts/demo-factory.ts`, which
180
+ authors a complete robot cell — a 7-DOF arm with a gripper, a conveyor, a
181
+ turntable and the surrounding scenery — and exports it to
182
+ `out/factory.usda` / `.usdz`.
183
+
184
+ ## Use it without Three.js
123
185
 
124
- ### Inspect without Three.js
186
+ `three-usd-robot/core` is a standalone USD parser, writer and robot IR — handy
187
+ for server-side validation or asset tooling:
125
188
 
126
189
  ```ts
127
190
  import { parseUsda, Stage, extractRobotDescription } from "three-usd-robot/core";
@@ -130,15 +193,24 @@ const desc = extractRobotDescription(Stage.OpenFromString(usdaText));
130
193
  console.log(desc.rootLink, Object.keys(desc.joints));
131
194
  ```
132
195
 
196
+ Command line: `npx tsx scripts/usdc-to-usda.ts robot.usd` converts binary crate
197
+ and `.usdz` packages to ASCII USDA.
198
+
199
+ ## Examples
200
+
201
+ [`examples/`](./examples) holds runnable Vite demos, including a React Three
202
+ Fiber viewer that loads any asset via `?asset=<url>` and exports it back to
203
+ `.usda` / `.usdz`.
204
+
133
205
  ## Package entry points
134
206
 
135
207
  | Import | Contents |
136
208
  | --- | --- |
137
- | `three-usd-robot` | Three.js runtime — `ThreeUsdRobotLoader`, `ThreeUsdRobot` |
138
- | `three-usd-robot/core` | Three.js-independent USDA parser, robot IR, forward-kinematics math |
209
+ | `three-usd-robot` | Three.js runtime — `ThreeUsdRobotLoader`, `ThreeUsdRobot`, `RobotBuilder`, export helpers |
210
+ | `three-usd-robot/core` | Three.js-independent USD parser & writer, robot IR, forward-kinematics math |
139
211
  | `three-usd-robot/helpers` | Viewer helpers (joint axes, link frames, joint limits) |
140
- | `three-usd-robot/extras` | Heavier convenience utilities (e.g. joint slider panel) |
141
- | `three-usd-robot/react` | React Three Fiber `<UsdRobot>` component + hooks (optional `react` / `@react-three/fiber` peers) |
212
+ | `three-usd-robot/extras` | Joint slider panel (bring your own `lil-gui`) |
213
+ | `three-usd-robot/react` | React Three Fiber `<UsdRobot>` component + hooks |
142
214
 
143
215
  ## Development
144
216
 
@@ -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-3oKG00Sc.js';
2
+ import { J as JointType, A as Axis, a as JointDescription, L as LinkDescription, R as RobotDescription, K as KinematicTree } from './buildKinematicTree-wtZPzy0B.js';
3
3
 
4
4
  /**
5
5
  * The articulated "motion" node of a joint, inserted between the joint's two
@@ -1,6 +1,6 @@
1
- import { R as RobotDescription } from './buildKinematicTree-3oKG00Sc.js';
1
+ import { R as RobotDescription } from './buildKinematicTree-wtZPzy0B.js';
2
2
  import { A as AssetResolver } from './AssetResolver-CpIJNgWZ.js';
3
- import { T as ThreeUsdRobot } from './ThreeUsdRobot-D9AOUaVh.js';
3
+ import { T as ThreeUsdRobot } from './ThreeUsdRobot-CajXJqYU.js';
4
4
 
5
5
  type ThreeUsdRobotLoaderOptions = {
6
6
  /** Resolver for references / payloads / sublayers (default {@link DefaultAssetResolver}). */
@@ -9,6 +9,12 @@ type ThreeUsdRobotLoaderOptions = {
9
9
  loadVisuals?: boolean;
10
10
  /** Render collision meshes (M6). */
11
11
  loadCollisions?: boolean;
12
+ /**
13
+ * Also render Mesh prims that belong to no link — the static scenery of a
14
+ * cell that contains robots (floor, guarding, racking, …). Placed by their
15
+ * authored stage transform. Default `false`.
16
+ */
17
+ loadSceneGeometry?: boolean;
12
18
  /** Load diffuse textures referenced by materials (default `true`). */
13
19
  loadTextures?: boolean;
14
20
  /** Up-axis correction strategy (M9). */
@@ -125,6 +125,25 @@ type RelationshipSpec = {
125
125
  line: number;
126
126
  };
127
127
 
128
+ /**
129
+ * Time-sample interpolation for animated values (e.g. joint trajectories).
130
+ *
131
+ * USD time samples are keyed by time code. We linearly interpolate between the
132
+ * two bracketing samples and hold the endpoints outside the authored range
133
+ * (matching USD's held extrapolation).
134
+ */
135
+ /** A sorted time-sampled scalar channel. */
136
+ type SampleChannel = {
137
+ /** Sample times, ascending. */
138
+ times: number[];
139
+ /** Sample values, parallel to {@link times} (SI units). */
140
+ values: number[];
141
+ };
142
+ /** Linearly sample `channel` at time `t` (held outside the range). */
143
+ declare function interpolate(channel: SampleChannel, t: number): number;
144
+ /** Build a sorted {@link SampleChannel} from `(time → value)` pairs via `map`. */
145
+ declare function channelFromSamples(samples: Map<number, number>): SampleChannel;
146
+
128
147
  /**
129
148
  * Minimal 4×4 matrix math, Three.js-independent.
130
149
  *
@@ -176,25 +195,21 @@ declare function makeEuler(angles: Vec3, order: string): Mat4;
176
195
  declare function fromUsdMatrix(m: UsdMatrix): Mat4;
177
196
  /** Extract the translation component `[x, y, z]` from a {@link Mat4}. */
178
197
  declare function getTranslation(m: Mat4): Vec3;
179
-
198
+ /** Copy a {@link Mat4} into a USD `matrix4d` — same flat layout (see module docs). */
199
+ declare function toUsdMatrix(m: Mat4): UsdMatrix;
180
200
  /**
181
- * Time-sample interpolation for animated values (e.g. joint trajectories).
201
+ * Decompose a rigid transform into translation + orientation (M13 export).
182
202
  *
183
- * USD time samples are keyed by time code. We linearly interpolate between the
184
- * two bracketing samples and hold the endpoints outside the authored range
185
- * (matching USD's held extrapolation).
203
+ * The rotation basis is orthonormalized (Gram-Schmidt, right-handed) before
204
+ * quaternion extraction; `rigid` reports whether the input already was rigid
205
+ * within tolerance, so callers can warn that scale/shear/reflection was
206
+ * discarded.
186
207
  */
187
- /** A sorted time-sampled scalar channel. */
188
- type SampleChannel = {
189
- /** Sample times, ascending. */
190
- times: number[];
191
- /** Sample values, parallel to {@link times} (SI units). */
192
- values: number[];
208
+ declare function decomposeRigid(m: Mat4): {
209
+ position: Vec3;
210
+ orientation: Quat;
211
+ rigid: boolean;
193
212
  };
194
- /** Linearly sample `channel` at time `t` (held outside the range). */
195
- declare function interpolate(channel: SampleChannel, t: number): number;
196
- /** Build a sorted {@link SampleChannel} from `(time → value)` pairs via `map`. */
197
- declare function channelFromSamples(samples: Map<number, number>): SampleChannel;
198
213
 
199
214
  /**
200
215
  * Robot intermediate representation (IR) — Three.js-independent.
@@ -240,6 +255,28 @@ type LinkDescription = {
240
255
  visualPrims: string[];
241
256
  /** Mesh prim paths flagged with a collision API. */
242
257
  collisionPrims?: string[];
258
+ /** Mass properties (`UsdPhysicsMassAPI`), if authored (M16). */
259
+ inertial?: LinkInertialDescription;
260
+ /**
261
+ * Authored stage (world) transform of the link prim, when not identity.
262
+ * Joint chains place tree links; this places floating roots and links that
263
+ * are isolated from the chosen tree (e.g. other machines / free bodies on a
264
+ * multi-articulation stage).
265
+ */
266
+ worldTransform?: Mat4;
267
+ };
268
+ /** Mass properties as authored by `UsdPhysicsMassAPI` (stage units). */
269
+ type LinkInertialDescription = {
270
+ /** Mass in kilograms. */
271
+ mass?: number;
272
+ /** Center of mass in the link frame (stage linear units). */
273
+ centerOfMass?: Vec3;
274
+ /** Principal moments of inertia. */
275
+ diagonalInertia?: Vec3;
276
+ /** Orientation of the principal inertia axes in the link frame. */
277
+ principalAxes?: Quat;
278
+ /** Density — the physics engine derives mass from it when `mass` is absent. */
279
+ density?: number;
243
280
  };
244
281
  type JointDescription = {
245
282
  /** Display name (leaf prim name). */
@@ -319,4 +356,4 @@ type BuildTreeOptions = {
319
356
  };
320
357
  declare function buildKinematicTree(robot: RobotDescription, options?: BuildTreeOptions): KinematicTree;
321
358
 
322
- export { type Axis as A, type BuildTreeOptions as B, type CompositionArc as C, DEG2RAD as D, makeRotationFromQuat as E, makeRotationX as F, makeRotationY as G, makeRotationZ as H, makeScale as I, type JointType as J, type KinematicTree as K, type LinkDescription as L, type Mat4 as M, makeTranslation as N, multiply as O, type PrimSpec as P, Quat as Q, type RobotDescription as R, type SampleChannel as S, type TreeEdge as T, type UsdDictionary as U, type Vec2 as V, multiplyAll as W, type JointDescription as a, type Vec3 as b, AssetPath as c, type AttributeSpec as d, type JointDriveDescription as e, type KinematicNode as f, type ListOp as g, type MetadataMap as h, type PropertySpec as i, RAD2DEG as j, type RelationshipSpec as k, type SdfPath as l, type Specifier as m, UsdMatrix as n, type UsdValue as o, type UsdaFile as p, type Variability as q, type Vec4 as r, buildKinematicTree as s, channelFromSamples as t, fromUsdMatrix as u, getTranslation as v, identity4 as w, interpolate as x, invert as y, makeEuler as z };
359
+ export { type Axis as A, type BuildTreeOptions as B, type CompositionArc as C, DEG2RAD as D, invert as E, makeEuler as F, makeRotationFromQuat as G, makeRotationX as H, makeRotationY as I, type JointType as J, type KinematicTree as K, type LinkDescription as L, type Mat4 as M, makeRotationZ as N, makeScale as O, type PrimSpec as P, Quat as Q, type RobotDescription as R, type SampleChannel as S, type TreeEdge as T, type UsdaFile as U, type Vec2 as V, makeTranslation as W, multiply as X, multiplyAll as Y, toUsdMatrix as Z, type JointDescription as a, type Vec3 as b, type JointDriveDescription as c, type LinkInertialDescription as d, AssetPath as e, type AttributeSpec as f, type KinematicNode as g, type ListOp as h, type MetadataMap as i, type PropertySpec as j, RAD2DEG as k, type RelationshipSpec as l, type SdfPath as m, type Specifier as n, type UsdDictionary as o, UsdMatrix as p, type UsdValue as q, type Variability as r, type Vec4 as s, buildKinematicTree as t, channelFromSamples as u, decomposeRigid as v, fromUsdMatrix as w, getTranslation as x, identity4 as y, interpolate as z };