forgecad 0.10.0 → 0.10.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.
Files changed (58) hide show
  1. package/dist/assets/{AdminPage-DwYHz72L.js → AdminPage-DcCnj0qo.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-a9_f-1US.js → BenchmarkPage-BVEpJSVk.js} +1 -1
  3. package/dist/assets/{BlogPage-DodHpvmf.js → BlogPage-DHaGP50_.js} +1 -1
  4. package/dist/assets/{DocsPage-B5LePEuj.js → DocsPage-CDoxHkz8.js} +33 -2
  5. package/dist/assets/{EditorApp-QXsAISLR.js → EditorApp-BJ0Dloyh.js} +174 -35
  6. package/dist/assets/{EmbedViewer-DdEHGUMU.js → EmbedViewer-CRKZbY0y.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-yhhOodbf.js → LandingPageProofDriven-BxHkYRE7.js} +1 -1
  8. package/dist/assets/{LegalPage-5RbKRGYK.js → LegalPage-B-u6FrVv.js} +1 -1
  9. package/dist/assets/{PricingPage-E3Rma7aV.js → PricingPage-CzpZ6-Ce.js} +1 -1
  10. package/dist/assets/{SettingsPage-BJZcM97j.js → SettingsPage-CIZSSAd0.js} +1 -1
  11. package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
  12. package/dist/assets/{app-DSYrDg0V.js → app-DaTMg3nH.js} +612 -120
  13. package/dist/assets/cli/{render-ZMHR9HkV.js → render-DPf4AYJK.js} +38 -16
  14. package/dist/assets/{evalWorker-DbNs7Dkp.js → evalWorker-CjZZWRWW.js} +1428 -1038
  15. package/dist/assets/{jointPose-DO6mnXn_.js → jointPose-DzQOViQH.js} +1 -1
  16. package/dist/assets/{manifold-BGlQBBH9.js → manifold-BYlzU521.js} +1 -1
  17. package/dist/assets/{manifold-fy2MV7K1.js → manifold-DgXo0T5P.js} +2 -2
  18. package/dist/assets/{manifold-BU-tJwQh.js → manifold-K1SkarlQ.js} +1 -1
  19. package/dist/assets/{reportWorker-DO6hcQbh.js → reportWorker-B9nWwSrB.js} +1402 -1012
  20. package/dist/assets/{scalar-sampling-budget-o90NSNmF.js → scalar-sampling-budget-prBw_s8t.js} +2139 -1749
  21. package/dist/cli/render.html +1 -1
  22. package/dist/docs/index.html +2 -2
  23. package/dist/docs-raw/CLI.md +18 -3
  24. package/dist/docs-raw/generated/assembly.md +70 -5
  25. package/dist/docs-raw/generated/concepts.md +16 -2
  26. package/dist/docs-raw/generated/core.md +9 -2
  27. package/dist/docs-raw/generated/lib.md +1 -1
  28. package/dist/docs-raw/generated/output.md +14 -43
  29. package/dist/docs-raw/generated/runtime-names.md +4 -4
  30. package/dist/docs-raw/guides/simready-quickstart.md +171 -0
  31. package/dist/docs-raw/simulation-workflow.md +273 -0
  32. package/dist/index.html +2 -2
  33. package/dist/sitemap.xml +25 -13
  34. package/dist-cli/{check-compiler-JTVBITCR.js → check-compiler-II7NLPAB.js} +1 -1
  35. package/dist-cli/{check-query-propagation-3FFLSMVN.js → check-query-propagation-7462TR3R.js} +1 -1
  36. package/dist-cli/{chunk-OAN5T4XD.js → chunk-UWTJCGXF.js} +1455 -722
  37. package/dist-cli/forgecad.js +2994 -529
  38. package/dist-skill/CONTEXT.md +94 -55
  39. package/dist-skill/docs/API/core/concepts.md +1 -1
  40. package/dist-skill/docs/CLI.md +18 -3
  41. package/dist-skill/docs/generated/assembly.md +66 -5
  42. package/dist-skill/docs/generated/core.md +9 -2
  43. package/dist-skill/docs/generated/lib.md +1 -1
  44. package/dist-skill/docs/generated/output.md +14 -43
  45. package/dist-skill/docs/generated/runtime-names.md +4 -4
  46. package/examples/robotics/README.md +46 -0
  47. package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
  48. package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
  49. package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
  50. package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
  51. package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
  52. package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
  53. package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
  54. package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
  55. package/examples/robotics/simready-asset-crate.forge.js +79 -0
  56. package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
  57. package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
  58. package/package.json +1 -1
@@ -46,7 +46,9 @@ ForgeCAD includes a local editor. Open it around a dedicated project folder, edi
46
46
  | `dev <project-path> [project-path ...]` | Start the Vite dev server for ForgeCAD source development. |
47
47
  | `web` | Start a local dev server in web/playground mode (no filesystem, localStorage only). |
48
48
 
49
- `forgecad studio <project-path>` is the normal installed-CLI command; `forgecad dev` starts the Vite dev server and is mainly for ForgeCAD source development. Keep one long-running `forgecad studio <project-path> [project-path ...]` process open with every active project folder as an argument — the user opens the single printed localhost port once, and AI agents only create or edit files under those folders so the browser updates live without starting more servers.
49
+ `forgecad studio <project-path>` is the normal installed-CLI command for users. `forgecad dev <project-path>` starts the Vite dev server and is mainly for ForgeCAD source development.
50
+
51
+ Keep one long-running `forgecad studio <project-path> [project-path ...]` process open with every active project folder listed in its arguments; the user opens the single printed localhost port once, and AI agents should only create or edit files under those folders so the browser updates live without starting more servers.
50
52
 
51
53
  <details>
52
54
  <summary>Common flags for studio / dev</summary>
@@ -291,6 +293,7 @@ Export to every format you need.
291
293
  | `export stl` | STL | 3D printing |
292
294
  | `export gcode` **\[Production\]** | G-code | Toolpath (scripted, not sliced) |
293
295
  | `export sdf` **\[Production\]** | SDF package | Gazebo robot simulation |
296
+ | `export mjcf` **\[Production\]** | MJCF package | MuJoCo / MJX robot simulation |
294
297
  | `export urdf` **\[Production\]** | URDF package | ROS / PyBullet / MuJoCo |
295
298
  | `export report` **\[Production\]** | PDF report | Multi-view report with BOM and dimensions |
296
299
  | `export cutting-layout` **\[Production\]** | PDF/DXF | Sheet cutting layout with cut sequence |
@@ -314,6 +317,7 @@ forgecad export report bracket.forge.js out/report.pdf
314
317
 
315
318
  # Robot simulation
316
319
  forgecad export sdf rover.forge.js --output out/forge_scout
320
+ forgecad export mjcf rover.forge.js --output out/forge_scout_mjcf
317
321
  ```
318
322
 
319
323
  <details>
@@ -451,7 +455,7 @@ forgecad skill flattened-files ~/Desktop/forgecad-skills
451
455
 
452
456
  ## Validation
453
457
 
454
- Check printability and run focused model integrity reviews.
458
+ Check printability, simulation readiness, and focused model integrity.
455
459
 
456
460
  ### `forgecad compare 3d`
457
461
 
@@ -477,6 +481,17 @@ forgecad check print model.forge.js
477
481
  forgecad check print model.forge.js --json
478
482
  ```
479
483
 
484
+ ### `forgecad check simready`
485
+
486
+ Validate source-authored robot and physics metadata before simulation export.
487
+
488
+ Runs a Forge script and checks the returned `assembly(...).withSimulation(...)` contract without Isaac Sim, OpenUSD, or NVIDIA validators. The gate validates Sim.body metadata, explicit colliders, contact connectors, controller joints, numeric physics values, and the robot joint graph.
489
+
490
+ ```bash
491
+ forgecad check simready model.forge.js
492
+ forgecad check simready robot.forge.js --json
493
+ ```
494
+
480
495
  ### `forgecad inspect mechanical-integrity`
481
496
 
482
497
  Inspect generated ForgeCAD models for mechanical integrity failures.
@@ -514,7 +529,7 @@ The CLI is free for personal non-commercial use. Pro covers human-operated comme
514
529
 
515
530
  | Free | Production outputs | Pro | Enterprise |
516
531
  |------|--------------------|-----|------------|
517
- | `run`, `dev`, `studio`, `render 3d`, `export stl`, `export 3mf`, `export svg`, `compare 3d`, `check print`, `inspect fit interference`, `inspect mechanical-integrity` for personal non-commercial use | `cut-list`, `export sketch-pdf`, `export step`, `export brep`, `export gcode`, `export sdf`, `export urdf`, `export report`, `export cutting-layout` are free to run for personal non-commercial use; Pro covers human-operated commercial CAD work | `render hq`, `capture gif`, `capture mp4` plus commercial coverage for client/customer work | Backend, hosted, embedded, or application workflows that call ForgeCAD automatically |
532
+ | `run`, `dev`, `studio`, `render 3d`, `export stl`, `export 3mf`, `export svg`, `compare 3d`, `check print`, `inspect fit interference`, `inspect mechanical-integrity` for personal non-commercial use | `cut-list`, `export sketch-pdf`, `export step`, `export brep`, `export gcode`, `export sdf`, `export mjcf`, `export urdf`, `export report`, `export cutting-layout` are free to run for personal non-commercial use; Pro covers human-operated commercial CAD work | `render hq`, `capture gif`, `capture mp4` plus commercial coverage for client/customer work | Backend, hosted, embedded, or application workflows that call ForgeCAD automatically |
518
533
 
519
534
  ```bash
520
535
  forgecad license # Check local license status
@@ -5,7 +5,7 @@ skill-order: 100
5
5
 
6
6
  # Assembly API
7
7
 
8
- Assembly-owned links, constraints, connectors, solved poses, and robot export.
8
+ Assembly-owned links, constraints, connectors, solved poses, and source-level simulation metadata.
9
9
 
10
10
  ## Contents
11
11
 
@@ -18,6 +18,63 @@ Assembly-owned links, constraints, connectors, solved poses, and robot export.
18
18
 
19
19
  ### Assembly & Joints
20
20
 
21
+ #### `Sim.material(name: string, options?: SimMaterialOptions): SimMaterialDef` — Create a named physical material with density and contact coefficients for simulation export and checks.
22
+
23
+ `SimMaterialOptions`: `{ densityKgM3?: number, staticFriction?: number, dynamicFriction?: number, restitution?: number }`
24
+
25
+
26
+ `SimMaterialDef`: `{ kind: "material", name: string }`
27
+
28
+ #### `Sim.body(options: SimBodyOptions): SimBodyDef` — Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces.
29
+
30
+ **`SimBodyOptions`**: `massKg?: number`, `densityKgM3?: number`, `material?: SimMaterialDef`, `collider?: SimColliderDef`, `contacts?: Record<string, SimContactDef>`
31
+
32
+ `SimColliderDef`: `{ kind: "collider", mode: SimColliderMode, reason?: string }`
33
+
34
+ `SimContactDef`: `{ kind: "wheelSurface" | "gripperSurface", connectorName: string }`
35
+
36
+
37
+ `SimBodyDef`: `{ kind: "body" }`
38
+
39
+ #### `Sim.collider` — Collision-geometry intent constructors for physical parts.
40
+
41
+ - `Sim.collider.convexHull(): SimColliderDef` — Use a generated collision mesh for the part. This is the default fast rigid-body collider for irregular parts.
42
+ - `Sim.collider.boundingBox(): SimColliderDef` — Use the part bounding box as the collision geometry. This is fastest and works well for chassis and simple blocks.
43
+ - `Sim.collider.visualMesh(): SimColliderDef` — Use the visual mesh as collision geometry. This is exact but usually slower in physics engines.
44
+ - `Sim.collider.none(reason: string): SimColliderDef` — Disable collision for a part with an explicit reason, such as a sensor-only or decorative object.
45
+
46
+ #### `Sim.drive` — Joint-drive intent constructors for passive or powered assembly joints.
47
+
48
+ - `Sim.drive.passive(options?: SimPassiveDriveOptions): SimDriveDef` — Mark a joint as passive while preserving damping and friction metadata for simulation export.
49
+ - `Sim.drive.velocity(options: SimVelocityDriveOptions): SimDriveDef` — Mark a revolute joint as velocity-driven with torque and speed limits. Speed is authored in rpm and exported as deg/s or rad/s as needed.
50
+
51
+ `SimPassiveDriveOptions`: `{ damping?: number, friction?: number }`
52
+
53
+
54
+ `SimVelocityDriveOptions`: `{ maxTorqueNm: number, maxSpeedRpm: number }`
55
+
56
+ #### `Sim.contact` — Contact-surface metadata over existing part connectors.
57
+
58
+ - `Sim.contact.wheelSurface(connectorName: string): SimContactDef` — Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata.
59
+ - `Sim.contact.gripperSurface(connectorName: string): SimContactDef` — Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata.
60
+
61
+ #### `Sim.profile` — Named validation/export profile constructors.
62
+
63
+ - `Sim.profile.robotBodyRunnable(): SimProfileDef` — SimReady-style profile for a robot body that should be runnable in a physics simulator.
64
+ - `Sim.profile.robotBodyIsaac(): SimProfileDef` — SimReady-style profile for robot bodies targeting Isaac Sim readiness.
65
+ - `Sim.profile.roboticsAssetPhysx(): SimProfileDef` — SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders.
66
+
67
+ `SimProfileDef`: `{ kind: "profile", name: SimProfileName }`
68
+
69
+ #### `Sim.controller` — Standard controller metadata constructors for simulator package generation.
70
+
71
+ - `Sim.controller.diffDrive(options: SimDiffDriveControllerOptions): SimDiffDriveControllerDef` — Describe a differential-drive controller from left/right wheel joints and wheel dimensions.
72
+
73
+ **`SimDiffDriveControllerOptions`**: `leftJoints: string[]`, `rightJoints: string[]`, `wheelSeparationMm: number`, `wheelRadiusMm: number`, `topic?: string`, `odomTopic?: string`, `tfTopic?: string`, `frameId?: string`, `odomFrameId?: string`, `maxLinearVelocity?: number`, `maxAngularVelocity?: number`, `linearAcceleration?: number`, `angularAcceleration?: number`
74
+
75
+
76
+ `SimDiffDriveControllerDef`: `{ kind: "diffDrive" }`
77
+
21
78
  #### `assembly(name?: string): Assembly` — Create an assembly container with named parts, connectors, and kinematic links.
22
79
 
23
80
  **Use this from iteration 1 for any model with moving parts.** Do not build one static pose and retrofit motion later.
@@ -151,7 +208,7 @@ const housing = group(
151
208
  assembly.addPart("Base Assembly", housing);
152
209
  ```
153
210
 
154
- **`PartOptions`**: `transform?: TransformInput`, `metadata?: PartMetadata`, `mate?: AssemblyPartMateInput | AssemblyPartMateInput[]`, `bindToFrame?: string`
211
+ **`PartOptions`**: `transform?: TransformInput`, `metadata?: PartMetadata`, `sim?: SimBodyDef`, `mate?: AssemblyPartMateInput | AssemblyPartMateInput[]`, `bindToFrame?: string`
155
212
 
156
213
  **`PartMetadata`**
157
214
 
@@ -212,7 +269,9 @@ Frame semantics: `origin` is the pivot/contact point, `axis` the hinge or slide
212
269
 
213
270
  **Face-to-face:** each connector's axis points outward from its part; mating makes the axes anti-parallel, like a plug meeting a socket (same convention as `matchTo()`).
214
271
 
215
- **Mirrored revolute axes:** revolute values follow the right-hand rule, so a mirrored hinge axis (`[1, 0, 0]` vs `[-1, 0, 0]`) rotates oppositely for the same `+theta`: negate the mirrored side's value and mirror limits as `[min, max] -> [-max, -min]`. Prismatic joints have no handedness flip. Use an explicit per-side sign mapping (or side-neutral link controls) for bilateral mechanisms.
272
+ **Revolute sign:** a positive joint value follows the right-hand rule about the **child** connector's placed axis. Because face-to-face mating makes the axes anti-parallel, that is the *left*-hand rule about the parent connector's outward axis if `+30` swings the opposite way you expected, you predicted from the parent's axis. `forgecad debug assembly` prints each joint's resolved world axis.
273
+
274
+ **Mirrored revolute axes:** because of the right-hand rule, a mirrored hinge axis (`[1, 0, 0]` vs `[-1, 0, 0]`) rotates oppositely for the same `+theta`: negate the mirrored side's value and mirror limits as `[min, max] -> [-max, -min]`. Prismatic joints have no handedness flip. Use an explicit per-side sign mapping (or side-neutral link controls) for bilateral mechanisms.
216
275
 
217
276
  Joint type defaults to the connector's `kind`. For `start`/`end` connectors, `align` / `parentAlign` / `childAlign` (`'start' | 'middle' | 'end'`) choose which point meets.
218
277
 
@@ -239,7 +298,7 @@ assembly("Door").addPart("Frame", frame).addPart("Door", door)
239
298
  | `align?` | `PortAlign` | Shorthand: set both parentAlign and childAlign at once. |
240
299
  | `follows?` | `JointFollowOptions` | Slave this joint to another joint: `value = ratio × source + offset` (e.g. a mirrored jaw with `ratio: -1`). |
241
300
 
242
- Also: `as?: string`, `type?: JointType`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`.
301
+ Also: `as?: string`, `type?: JointType`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `drive?: SimDriveDef`.
243
302
 
244
303
  **`JointFollowOptions`**
245
304
  - `joint: string` — Name of the source joint that drives this one.
@@ -312,6 +371,12 @@ return mech.solve({ theta: 45 });
312
371
 
313
372
  **Other**
314
373
 
374
+ #### `withSimulation(options: SimAssemblySimulationOptions): Assembly` — Attach the root simulation contract for this assembly.
375
+
376
+ Use this after adding physical parts and joints. Robot-body profiles require `rootPart`; asset profiles can describe one-part or multi-part physical assets. URDF/SDF exporters and `forgecad check simready` read this contract directly, so model files no longer need a separate `robotExport(...)` side effect.
377
+
378
+ `SimAssemblySimulationOptions`: `{ profile: SimProfileDef, rootPart?: string, controllers?: SimControllerDef[] }`
379
+
315
380
  #### `edgeBetweenFrames(a: string, b: string, options?: AssemblyFrameEdgeOptions): Assembly` — Add a visual skeleton edge between two rig frame origins.
316
381
 
317
382
  Frame edges follow the solved frame poses produced by `fixedJoint()`, `revoluteJoint()`, and `prismaticJoint()`. They do not add constraints, degrees of freedom, parts, or geometry; use them to make a frame-only rig readable in the Motion/rig inspection overlay.
@@ -447,7 +512,7 @@ The sub-assembly must have exactly one root part before it can be merged (collap
447
512
  | `connectorRefs?` | `JointConnectorRefs` | Connector refs that define this joint contract. Usually set by `connect()` / `match()`. |
448
513
  | `follows?` | `JointFollowOptions` | Slave this joint to another joint: `value = ratio × source + offset`. Use for mechanisms with one physical DOF expressed through several joints — a mirrored gripper jaw (`ratio: -1`), a gear pair, a drive crank turning with its servo. A followed joint stops being an independent control: the Motion view drives it from its source, `solve()` derives its value (a direct state override is ignored with a warning), and limits still clamp the derived value. |
449
514
 
450
- Also: `frame?: TransformInput`, `origin?: Vec3`, `axis?: Vec3`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`.
515
+ Also: `frame?: TransformInput`, `origin?: Vec3`, `axis?: Vec3`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `drive?: SimDriveDef`.
451
516
 
452
517
  `JointConnectorRefs`: `{ parent: string, child: string, parentAlign?: PortAlign, childAlign?: PortAlign }`
453
518
 
@@ -13,7 +13,7 @@ Every public API function belongs to one of 16 fundamental concepts. This is an
13
13
  - **[C7: Pattern Replication](#c7-pattern-replication)** — Duplicate geometry in regular arrangements (linear, circular, mirror). *(6 functions)*
14
14
  - **[C8: Constraint Solving](#c8-constraint-solving)** — Define geometry by relationships and let a solver find positions. *(1 functions)*
15
15
  - **[C9: Spatial Placement](#c9-spatial-placement)** — Position geometry relative to other geometry using semantic anchors. *(6 functions)*
16
- - **[C10: Assembly & Kinematics](#c10-assembly-kinematics)** — Compose parts with joints for kinematic simulation. *(1 functions)*
16
+ - **[C10: Assembly & Kinematics](#c10-assembly-kinematics)** — Compose parts with joints for kinematic simulation. *(15 functions)*
17
17
  - **[C11: Parameterization & UI](#c11-parameterization-ui)** — Declare user-facing controls that drive model geometry. *(6 functions)*
18
18
  - **[C12: Dimensional Demotion](#c12-dimensional-demotion)** — Extract 2D geometry from a 3D solid (section, projection). *(3 functions)*
19
19
  - **[C13: Export & Output](#c13-export-output)** — Convert geometry to external formats (STL, 3MF, SVG, DXF, G-code, PDF). *(15 functions)*
@@ -230,6 +230,20 @@ Position geometry relative to other geometry using semantic anchors.
230
230
 
231
231
  Compose parts with joints for kinematic simulation.
232
232
 
233
+ - `Sim.material(name, options?)` — Create a named physical material with density and contact coefficients for simulation export and checks. → [assembly](/docs/assembly#assembly-joints)
234
+ - `Sim.body(options)` — Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces. → [assembly](/docs/assembly#assembly-joints)
235
+ - `Sim.collider.convexHull()` — Use a generated collision mesh for the part. → [assembly](/docs/assembly#assembly-joints)
236
+ - `Sim.collider.boundingBox()` — Use the part bounding box as the collision geometry. → [assembly](/docs/assembly#assembly-joints)
237
+ - `Sim.collider.visualMesh()` — Use the visual mesh as collision geometry. → [assembly](/docs/assembly#assembly-joints)
238
+ - `Sim.collider.none(reason)` — Disable collision for a part with an explicit reason, such as a sensor-only or decorative object. → [assembly](/docs/assembly#assembly-joints)
239
+ - `Sim.drive.passive(options?)` — Mark a joint as passive while preserving damping and friction metadata for simulation export. → [assembly](/docs/assembly#assembly-joints)
240
+ - `Sim.drive.velocity(options)` — Mark a revolute joint as velocity-driven with torque and speed limits. → [assembly](/docs/assembly#assembly-joints)
241
+ - `Sim.contact.wheelSurface(connectorName)` — Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata. → [assembly](/docs/assembly#assembly-joints)
242
+ - `Sim.contact.gripperSurface(connectorName)` — Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata. → [assembly](/docs/assembly#assembly-joints)
243
+ - `Sim.profile.robotBodyRunnable()` — SimReady-style profile for a robot body that should be runnable in a physics simulator. → [assembly](/docs/assembly#assembly-joints)
244
+ - `Sim.profile.robotBodyIsaac()` — SimReady-style profile for robot bodies targeting Isaac Sim readiness. → [assembly](/docs/assembly#assembly-joints)
245
+ - `Sim.profile.roboticsAssetPhysx()` — SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders. → [assembly](/docs/assembly#assembly-joints)
246
+ - `Sim.controller.diffDrive(options)` — Describe a differential-drive controller from left/right wheel joints and wheel dimensions. → [assembly](/docs/assembly#assembly-joints)
233
247
  - `assembly(name?)` — Create an assembly container with named parts, connectors, and kinematic links. → [assembly](/docs/assembly#assembly)
234
248
 
235
249
  ---
@@ -272,7 +286,7 @@ Convert geometry to external formats (STL, 3MF, SVG, DXF, G-code, PDF).
272
286
  - `Laser.lookupKerf(material, thickness, laserType?)` — Look up kerf for a material + thickness + laser combo in `Laser.COMMON_KERFS`. → [sheet-metal](/docs/sheet-metal#laser)
273
287
  - `Laser.COMMON_KERFS` — Common full-kerf values by material, thickness, and laser type. → [sheet-metal](/docs/sheet-metal#laser)
274
288
  - `bom(quantity, description, opts?)` — Register a Bill of Materials entry for report export. → [output](/docs/output#bom)
275
- - `robotExport(options)` — Declare that this script should export the assembly as a SDF/URDF robot package. → [output](/docs/output#robotexport)
289
+ - `robotExport(options)` — Compatibility shim for SDF/URDF robot package metadata. → [output](/docs/output#robotexport)
276
290
  - `sheetMetal(options)` — Create a parametric sheet metal part with flanges, bend allowances, and flat-pattern unfolding. → [sheet-metal](/docs/sheet-metal#sheetmetal)
277
291
  - `sketchToDxf(sketch, options?)` — Export a 2D sketch as a DXF string (R12/AC1009 — maximally compatible). → [output](/docs/output#sketchtodxf)
278
292
  - `sketchToSvg(sketch, options?)` — Export a 2D sketch as an SVG string. → [output](/docs/output#sketchtosvg)
@@ -111,6 +111,8 @@ The `edges` parameter is flexible:
111
111
 
112
112
  Throws if no edges match the selection, or if `radius` is not a positive finite number.
113
113
 
114
+ Selectorless (all-edges) calls draw from a per-run broad edge-feature budget. Exceeding it throws — except in live preview, which skips the finish with a warning for responsiveness. Explicit edge selectors are never budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1` raise or lift the budget.
115
+
114
116
  ```ts
115
117
  // Fillet all edges
116
118
  fillet(myShape, 2)
@@ -133,6 +135,8 @@ fillet(base, 5, base.edge('vert-br'))
133
135
 
134
136
  Produces a 45° bevel at the specified `size` (distance from edge). Edge selections compile into backend operations; unsupported selections fail as explicit kernel gaps instead of using TypeScript geometry fallbacks.
135
137
 
138
+ Selectorless (all-edges) calls draw from a per-run broad edge-feature budget. Exceeding it throws — except in live preview, which skips the finish with a warning for responsiveness. Explicit edge selectors are never budgeted; `FORGECAD_BROAD_EDGE_FEATURE_BUDGET` / `FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1` raise or lift the budget.
139
+
136
140
  The `edges` parameter accepts the same options as `fillet()`: inline `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, a tracked `EdgeRef` from `shape.edge('vert-br')` (exact compiler-owned path), or `undefined` (all sharp edges).
137
141
 
138
142
  ```ts
@@ -1406,15 +1410,18 @@ Namespaced file-format import helpers — the single vocabulary for bringing ext
1406
1410
 
1407
1411
  - `dxfSketch(fileName: string, options?: DxfImportOptions): Sketch` — Parse a DXF file and return closed 2D profile geometry as a Sketch. The result can be extruded directly.
1408
1412
  - `svgSketch(fileName: string, options?: SvgImportOptions): Sketch` — Parse an SVG file and return it as a Sketch with options for region filtering, scaling, and simplification.
1409
- - `mesh(fileName: string, options?: { scale?: number; center?: boolean; object?: string; separateObjects?: boolean; }): Shape | ShapeGroup` — Import an external mesh file (STL, OBJ, 3MF).
1413
+ - `mesh(fileName: string, options?: MeshImportOptions): Shape | ShapeGroup` — Import an external mesh file (STL, OBJ, 3MF).
1410
1414
 
1411
1415
  By default, 3MF build items are flattened into one Shape for compatibility. Use `separateObjects: true` to import 3MF build items/resource objects as a named ShapeGroup whose children are targetable by `forgecad ls`. Use `object` to import one item by the stable ref/name reported by `forgecad run`.
1412
1416
 
1413
1417
  For 3MF sources, `forgecad run` prints a source-structure table with one line per build item: `[3mf:build:NNN:object:N] name type=... verts=... tris=... bbox=[min] → [max]`. Build items are numbered from `001`; files with no build items list resource objects as `3mf:object:N` instead. Per-item bboxes reveal multi-part structure — account for every substantial item before flattening. Pass any listed stable ref or name as `object` to import that item alone.
1414
1418
 
1419
+ Use `sourceFrame: { up: "+Y" }` when the file was authored in a non-Z-up coordinate system. ForgeCAD remains Z-up; the import is rotated so the named source axis becomes ForgeCAD +Z. Supported values: `"+X"`, `"-X"`, `"+Y"`, `"-Y"`, `"+Z"`, `"-Z"`.
1420
+
1415
1421
  ```js
1416
1422
  const all = Import.mesh("./assembly.3mf", { separateObjects: true });
1417
1423
  const pin = all.child("Pin #001");
1418
1424
  const plate = Import.mesh("./assembly.3mf", { object: "3mf:build:001:object:7" });
1425
+ const yUpPart = Import.mesh("./part.obj", { sourceFrame: { up: "+Y" } });
1419
1426
  ```
1420
- - `step(fileName: string): Shape` — Import a STEP file (.step, .stp) as an exact OCCT-backed Shape. Preserves NURBS curves, B-spline surfaces, and exact topology. Requires running with the OCCT backend.
1427
+ - `step(fileName: string, options?: StepImportOptions): Shape` — Import a STEP file (.step, .stp) as an exact OCCT-backed Shape. Preserves NURBS curves, B-spline surfaces, and exact topology. Requires running with the OCCT backend. Use `sourceFrame: { up: "+Y" }` to rotate Y-up source files into ForgeCAD's Z-up world.
@@ -63,7 +63,7 @@ Pre-built fasteners, gears, pipes, structural profiles, and utility shapes. Acce
63
63
 
64
64
  Pre-built parametric parts available in user scripts as `lib.*`.
65
65
 
66
- Every key in this object becomes a method or namespace on the `lib` object exposed to `.forge.js` scripts — see the member list below for the catalog. Sizes outside the supported ranges throw at runtime with a descriptive error.
66
+ exposed to `.forge.js` scripts — see the member list below for the catalog. Sizes outside the supported ranges throw at runtime with a descriptive error.
67
67
 
68
68
  - `fastenerHole(opts: FastenerHoleOptions): Shape` — ISO metric fastener hole cutter with optional counterbore or countersink.
69
69
 
@@ -58,12 +58,14 @@ bom(tubeLen, "rectangular steel tube", {
58
58
  | `notes?` | `string` | Free-form notes |
59
59
  | `grain?` | `string` | Wood grain direction, e.g. "long", "cross" |
60
60
 
61
- #### `robotExport(options: RobotExportOptions): CollectedRobotExport` — Declare that this script should export the assembly as a SDF/URDF robot package.
61
+ #### `robotExport(options: RobotExportOptions): CollectedRobotExport` — Compatibility shim for SDF/URDF robot package metadata.
62
62
 
63
- Call `robotExport()` alongside your assembly definition. `forgecad export sdf` / `forgecad export urdf` pick up the declaration (see the CLI docs for flags) and produce a robot package with:
63
+ Prefer returning `assembly(...).withSimulation(...)` with `Sim.body(...)`, `Sim.drive.*(...)`, and `Sim.controller.*(...)` metadata. The CLI commands `forgecad export sdf` and `forgecad export urdf` now read that assembly simulation contract directly.
64
+
65
+ `robotExport()` remains available for one compatibility window. It converts the legacy descriptor into the same internal simulation model used by source-authored `Sim` metadata and produces a robot package with:
64
66
 
65
67
  - Mesh-based inertia tensors (full 6-component, not bounding-box approximations)
66
- - Separate collision meshes (convex hull by default — ~50–80% smaller)
68
+ - Separate collision meshes
67
69
  - Joint limits, effort/velocity/damping/friction metadata from assembly joints
68
70
 
69
71
  **Collision mesh modes** (set per-link via `links["PartName"].collision`):
@@ -82,6 +84,8 @@ Call `robotExport()` alongside your assembly definition. `forgecad export sdf` /
82
84
  - `massKg` is preferred; `densityKgM3` is used when mass is unknown.
83
85
  - Compatibility coupling metadata, when present, maps only the primary term (largest ratio) to `<mimic>` — SDF/URDF support single-leader mimic only. Dropped terms emit a warning.
84
86
 
87
+ **Legacy example**
88
+
85
89
  ```ts
86
90
  robotExport({
87
91
  assembly: rover, // assembly() with parts + revolute wheel joints
@@ -97,6 +101,13 @@ robotExport({
97
101
  });
98
102
  ```
99
103
 
104
+ **Preferred CLI usage**
105
+
106
+ ```bash
107
+ forgecad export sdf model.forge.js # SDF package (Gazebo/Ignition)
108
+ forgecad export urdf model.forge.js # URDF package (ROS/PyBullet/MuJoCo)
109
+ ```
110
+
100
111
  **`RobotExportOptions`**: `assembly: Assembly`, `modelName?: string`, `state?: JointState`, `static?: boolean`, `selfCollide?: boolean`, `allowAutoDisable?: boolean`, `links?: Record<string, RobotLinkExportOptions>`, `joints?: Record<string, RobotJointExportOptions>`, `plugins?: { diffDrive?: RobotDiffDrivePluginOptions; jointStatePublisher?: RobotJointStatePublisherOptions; }`, `world?: RobotWorldOptions`
101
112
 
102
113
  `RobotLinkExportOptions`: `{ massKg?: number, densityKgM3?: number, collision?: "visual" | "convex" | "box" | "none" }`
@@ -111,46 +122,6 @@ robotExport({
111
122
 
112
123
  `RobotWorldKeyboardTeleopOptions`: `{ enabled?: boolean, linearStep?: number, angularStep?: number }`
113
124
 
114
- **`CollectedRobotExport`**: `modelName: string`, `assembly: AssemblyDefinition`, `state: JointState`, `static: boolean`, `selfCollide: boolean`, `allowAutoDisable: boolean`, `links: Record<string, RobotLinkExportOptions>`, `joints: Record<string, RobotJointExportOptions>`, `plugins: { diffDrive?: RobotDiffDrivePluginOptions; jointStatePublisher?: RobotJointStatePublisherOptions; }`, `world: RobotWorldOptions | null`
115
-
116
- **`AssemblyDefinition`**: `name: string`, `parts: AssemblyPartDef[]`, `joints: AssemblyJointDef[]`, `jointCouplings: AssemblyJointCouplingDef[]`, `kinematics: AssemblyKinematicGraphDef`, `frames: AssemblyFrameDef[]`, `frameJoints: AssemblyFrameJointDef[]`, `frameEdges: AssemblyFrameEdgeDef[]`
117
-
118
- **`AssemblyPartDef`**: `name: string`, `part: AssemblyPart`, `base: Transform`, `metadata?: PartMetadata`, `mates: AssemblyPartMateInput[]`, `bindToFrame?: string`
119
-
120
- `PartMetadata` — defined in [assembly](/docs/assembly).
121
-
122
- `AssemblyPartMateInput` — defined in [assembly](/docs/assembly).
123
-
124
- **`AssemblyJointDef`**: `name: string`, `type: JointType`, `parent: string`, `child: string`, `frame: Transform`, `axis: Vec3`, `min?: number`, `max?: number`, `defaultValue: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `connectorRefs?: JointConnectorRefs`
125
-
126
- `JointConnectorRefs` — defined in [assembly](/docs/assembly).
127
-
128
- `AssemblyJointCouplingDef`: `{ joint: string, terms: JointCouplingTermRecord[], offset: number }`
129
-
130
- `JointCouplingTermRecord`: `{ joint: string, ratio: number }`
131
-
132
- **`AssemblyKinematicGraphDef`**: `links: AssemblyLinkDef[]`, `edges: AssemblyEdgeBetweenLinksDef[]`, `angles: AssemblyAngleBetweenLinksDef[]`, `derivedLinks: AssemblyDerivedLinkDef[]`
133
-
134
- `AssemblyLinkDef`: `{ name: string, at: Vec3, fixed: boolean, metadata?: Record<string, unknown> }`
135
-
136
- **`AssemblyEdgeBetweenLinksDef`**: `name: string`, `a: string`, `b: string`, `length: number | null`, `min?: number`, `max?: number`, `visualOnly: boolean`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
137
-
138
- `AssemblyKinematicControlOptions` — defined in [assembly](/docs/assembly).
139
-
140
- **`AssemblyAngleBetweenLinksDef`**: `name: string`, `a?: string`, `b: string`, `c: string`, `reference?: AssemblyAngleReferenceDef`, `target?: number`, `min?: number`, `max?: number`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
141
-
142
- `AssemblyAngleReferenceDef`: `{ kind: "worldDirection", direction: Vec3 }`
143
-
144
- **`AssemblyDerivedLinkDef`**
145
- - `distance: number` — Signed: positive moves from `fromLink` toward `towardLink`, negative moves away.
146
- - Also: `name: string`, `fromLink: string`, `towardLink: string`.
147
-
148
- `AssemblyFrameDef`: `{ name: string, origin: Vec3, axis: Vec3, up: Vec3, fixed: boolean, metadata?: Record<string, unknown> }`
149
-
150
- **`AssemblyFrameJointDef`**: `name: string`, `type: AssemblyFrameJointType`, `parent: string`, `child: string`, `rest: Transform`, `min?: number`, `max?: number`, `defaultValue: number`, `unit?: string`, `control: boolean`, `metadata?: Record<string, unknown>`
151
-
152
- `AssemblyFrameEdgeDef`: `{ name: string, a: string, b: string, metadata?: Record<string, unknown> }`
153
-
154
125
  #### `dim()` — Add a dimension annotation between two points, or along an entity.
155
126
 
156
127
  Overloads:
@@ -26,10 +26,10 @@ Point2D, Points, polygon, polygonVertices, port, Product, ProductPanelBuilder, P
26
26
  ProductSkin, ProductSkinBuilder, ProductStationBuilder, ProductSurfaceBuilder, ProductSurfaceRef, projectToPlane, queueMicrotask, rect
27
27
  Rectangle2D, robotExport, roundedRect, Route3D, scene, Sculpt, sdf, SdfShape
28
28
  selectEdge, selectEdges, self, setActiveBackend, setImmediate, setInterval, setTimeout, Shape
29
- ShapeGroup, sheetMetal, SheetMetalPart, sheetStock, Sketch, sketchToDxf, sketchToSvg, slot
30
- SolvedAssembly, spec, sphere, spline2d, stroke, Surface, SurfaceBody, SurfaceMembers
31
- sweep, text2d, textWidth, torus, toShape, Transform, union, union2d
32
- variableSweep, verify, Viewport, window, Wood
29
+ ShapeGroup, sheetMetal, SheetMetalPart, sheetStock, Sim, Sketch, sketchToDxf, sketchToSvg
30
+ slot, SolvedAssembly, spec, sphere, spline2d, stroke, Surface, SurfaceBody
31
+ SurfaceMembers, sweep, text2d, textWidth, torus, toShape, Transform, union
32
+ union2d, variableSweep, verify, Viewport, window, Wood
33
33
  ```
34
34
 
35
35
  <!-- forgecad-skill:exclude-start symbol="compatibility runtime names" reason="Compatibility-only renamed runtime globals." -->
@@ -0,0 +1,171 @@
1
+ # SimReady Quickstart
2
+
3
+ ForgeCAD simulation readiness starts in the model file. The `.forge.js` script should return the same assembly you want to inspect, export, and simulate; simulation metadata lives on that assembly instead of in a separate export command.
4
+
5
+ Use this pattern when you want a robot, fixture, gripper, crate, or imported asset to become a physics package later:
6
+
7
+ 1. Build real parts with connectors.
8
+ 2. Add each part to an `assembly(...)` with `sim: Sim.body(...)`.
9
+ 3. Connect moving parts with `connect(...)` and put drive intent on the joint.
10
+ 4. Finish the root assembly with `withSimulation(...)`.
11
+ 5. Run `forgecad check simready` before exporting.
12
+
13
+ ## Robot Pattern
14
+
15
+ ```js
16
+ const aluminum = Sim.material("6061 aluminum", {
17
+ densityKgM3: 2700,
18
+ staticFriction: 0.45,
19
+ dynamicFriction: 0.35,
20
+ restitution: 0.05,
21
+ });
22
+
23
+ const rubber = Sim.material("rubber tire", {
24
+ densityKgM3: 1100,
25
+ staticFriction: 0.9,
26
+ dynamicFriction: 0.75,
27
+ restitution: 0.12,
28
+ });
29
+
30
+ const chassis = box(220, 120, 28).withConnectors({
31
+ left_axle: connector({ origin: [0, 82, 0], axis: [0, 1, 0], up: [0, 0, 1], kind: "revolute" }),
32
+ right_axle: connector({ origin: [0, -82, 0], axis: [0, -1, 0], up: [0, 0, 1], kind: "revolute" }),
33
+ });
34
+
35
+ const wheel = cylinder(34, 28, undefined, 48)
36
+ .rotateX(90)
37
+ .withConnectors({
38
+ bore: connector({ origin: [0, 0, 0], axis: [0, 1, 0], up: [0, 0, 1], kind: "revolute" }),
39
+ tread: connector({ origin: [0, 0, -34], axis: [0, 0, -1], up: [1, 0, 0] }),
40
+ });
41
+
42
+ return assembly("Rover")
43
+ .addPart("Chassis", chassis, {
44
+ sim: Sim.body({
45
+ massKg: 4.2,
46
+ material: aluminum,
47
+ collider: Sim.collider.boundingBox(),
48
+ }),
49
+ })
50
+ .addPart("Left Wheel", wheel, {
51
+ sim: Sim.body({
52
+ massKg: 0.32,
53
+ material: rubber,
54
+ collider: Sim.collider.convexHull(),
55
+ contacts: { tread: Sim.contact.wheelSurface("tread") },
56
+ }),
57
+ })
58
+ .addPart("Right Wheel", wheel, {
59
+ sim: Sim.body({
60
+ massKg: 0.32,
61
+ material: rubber,
62
+ collider: Sim.collider.convexHull(),
63
+ contacts: { tread: Sim.contact.wheelSurface("tread") },
64
+ }),
65
+ })
66
+ .connect("Chassis.left_axle", "Left Wheel.bore", {
67
+ as: "leftWheel",
68
+ type: "revolute",
69
+ drive: Sim.drive.velocity({ maxTorqueNm: 1.8, maxSpeedRpm: 220, damping: 0.02, friction: 0.01 }),
70
+ })
71
+ .connect("Chassis.right_axle", "Right Wheel.bore", {
72
+ as: "rightWheel",
73
+ type: "revolute",
74
+ drive: Sim.drive.velocity({ maxTorqueNm: 1.8, maxSpeedRpm: 220, damping: 0.02, friction: 0.01 }),
75
+ })
76
+ .withSimulation({
77
+ rootPart: "Chassis",
78
+ profile: Sim.profile.robotBodyRunnable(),
79
+ controllers: [
80
+ Sim.controller.diffDrive({
81
+ leftJoints: ["leftWheel"],
82
+ rightJoints: ["rightWheel"],
83
+ wheelRadiusMm: 34,
84
+ wheelSeparationMm: 164,
85
+ }),
86
+ ],
87
+ });
88
+ ```
89
+
90
+ The important names are authored once:
91
+
92
+ - `rootPart` names the body that downstream simulators treat as the robot root.
93
+ - `leftWheel` and `rightWheel` are joint names, so controllers and exporters do not need to guess motor wiring.
94
+ - `tread` is an existing connector on the wheel, so contact metadata is checked against geometry-owned names.
95
+
96
+ ## Asset Pattern
97
+
98
+ Assets use the same assembly contract. In V1, a crate, prop, imported STEP, or static environment object is usually a one-part assembly:
99
+
100
+ ```js
101
+ const pine = Sim.material("pine crate", {
102
+ densityKgM3: 520,
103
+ staticFriction: 0.55,
104
+ dynamicFriction: 0.42,
105
+ restitution: 0.08,
106
+ });
107
+
108
+ const crate = box(140, 110, 80).withConnectors({
109
+ grip: connector({ origin: [0, -56, 8], axis: [0, -1, 0], up: [0, 0, 1] }),
110
+ });
111
+
112
+ return assembly("Crate Asset")
113
+ .addPart("Body", crate, {
114
+ sim: Sim.body({
115
+ massKg: 2.4,
116
+ material: pine,
117
+ collider: Sim.collider.convexHull(),
118
+ contacts: { grip: Sim.contact.gripperSurface("grip") },
119
+ }),
120
+ })
121
+ .withSimulation({
122
+ profile: Sim.profile.roboticsAssetPhysx(),
123
+ });
124
+ ```
125
+
126
+ Asset profiles require physical body metadata and explicit collider intent. They do not require `rootPart`, joints, or controllers.
127
+
128
+ ## Check Then Export
129
+
130
+ Run the offline gate first:
131
+
132
+ ```bash
133
+ node dist-cli/forgecad.js check simready examples/robotics/simready-diff-drive-rover.forge.js
134
+ ```
135
+
136
+ Then export the package that matches your simulator:
137
+
138
+ ```bash
139
+ node dist-cli/forgecad.js export mjcf examples/robotics/simready-diff-drive-rover.forge.js --output /tmp/rover-mjcf
140
+ node dist-cli/forgecad.js export sdf examples/robotics/simready-diff-drive-rover.forge.js --output /tmp/rover-sdf
141
+ node dist-cli/forgecad.js export urdf examples/robotics/simready-diff-drive-rover.forge.js --output /tmp/rover-urdf
142
+ ```
143
+
144
+ Use MJCF/MuJoCo for the fastest local physics loop. Use SDF for Gazebo Sim. Use URDF when the next tool expects a ROS/PyBullet/Isaac import path.
145
+
146
+ ForgeCAD does not currently have a native USD/USDZ export command. For Isaac workflows today, export URDF as the import artifact and carry the richer ForgeCAD simulation intent through `simready-manifest.json`. A future USD exporter should consume the same source-level simulation contract instead of re-inferring physics metadata from mesh geometry.
147
+
148
+ Every package includes `simready-manifest.json`. That manifest is the neutral handoff: model name, units, profile, bodies, joints, drives, controllers, contacts, and validation results.
149
+
150
+ ## What The Check Catches
151
+
152
+ `forgecad check simready` is source-level validation. It does not need Isaac Sim, OpenUSD, `pxr`, MuJoCo, or NVIDIA validators installed.
153
+
154
+ It fails when:
155
+
156
+ - the returned model has no `withSimulation(...)` contract
157
+ - a physical part is missing `Sim.body(...)`
158
+ - mass, density, friction, restitution, torque, speed, damping, or world pose values are non-finite or invalid
159
+ - a collider is missing for profiles that require explicit collision intent
160
+ - `Sim.collider.none(...)` has no reason
161
+ - a contact surface names a missing connector
162
+ - a controller names an unknown or wrong-type joint
163
+ - a robot profile has no `rootPart`, an unknown `rootPart`, or a disconnected joint graph
164
+
165
+ It intentionally treats joint-connected bodies as connected. A robot with separated wheels, arms, or gripper fingers should pass when the articulated graph is valid.
166
+
167
+ ## Where Control Code Lives
168
+
169
+ ForgeCAD owns geometry, physical metadata, joints, drives, controllers, manifests, and starter simulator packages. It should not own a single blessed balance controller, reward function, or RL library.
170
+
171
+ For a runnable training loop, export MJCF and use the generated package scripts. The Scout Cam Rover example includes `gym_env.py`, `train_balance.py`, `train_sb3.py`, `play_balance.py`, and `live_policy_viewer.py`; see [Simulation Workflow](../simulation-workflow.md) for the full MuJoCo and Isaac Lab path.