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
@@ -89,7 +89,7 @@ A script must return one of three shapes:
89
89
  ];
90
90
  ```
91
91
 
92
- 3. **A metadata object** — a plain object whose renderable values are rendered and whose non-renderable values (numbers, hole tables, builder functions) are silently skipped at render but flow to importers via `require()`.
92
+ 3. **A metadata object** — a plain object whose renderable values are rendered and whose non-renderable values (numbers, hole tables, builder functions) are silently skipped at render but flow to importers via `require()`. Each key becomes a named group, so don't pile independent parts into one array key (`{ parts: [a, b, c] }`) — the integrity gate reads that as a single fragmented part. Give each part its own key (`{ collar12, collar16, plug }`) or use named descriptors (form 2).
93
93
 
94
94
  Return an unsolved `Assembly` directly — ForgeCAD solves it at default joint values for display. Use `assembly.solve(state)` for a specific pose. Never call `.toGroup()` just to make an assembly render; use it only when you need `ShapeGroup` composition or named-child lookup.
95
95
 
@@ -143,10 +143,10 @@ Point2D, Points, polygon, polygonVertices, port, Product, ProductPanelBuilder, P
143
143
  ProductSkin, ProductSkinBuilder, ProductStationBuilder, ProductSurfaceBuilder, ProductSurfaceRef, projectToPlane, queueMicrotask, rect
144
144
  Rectangle2D, robotExport, roundedRect, Route3D, scene, Sculpt, sdf, SdfShape
145
145
  selectEdge, selectEdges, self, setActiveBackend, setImmediate, setInterval, setTimeout, Shape
146
- ShapeGroup, sheetMetal, SheetMetalPart, sheetStock, Sketch, sketchToDxf, sketchToSvg, slot
147
- SolvedAssembly, spec, sphere, spline2d, stroke, Surface, SurfaceBody, SurfaceMembers
148
- sweep, text2d, textWidth, torus, toShape, Transform, union, union2d
149
- variableSweep, verify, Viewport, window, Wood
146
+ ShapeGroup, sheetMetal, SheetMetalPart, sheetStock, Sim, Sketch, sketchToDxf, sketchToSvg
147
+ slot, SolvedAssembly, spec, sphere, spline2d, stroke, Surface, SurfaceBody
148
+ SurfaceMembers, sweep, text2d, textWidth, torus, toShape, Transform, union
149
+ union2d, variableSweep, verify, Viewport, window, Wood
150
150
  ```
151
151
 
152
152
  `showLabels` is also a runtime global, but it is not part of the top-level collision check. Avoid reusing it unless you intentionally want a local value with that name.
@@ -263,6 +263,8 @@ The `edges` parameter is flexible:
263
263
 
264
264
  Throws if no edges match the selection, or if `radius` is not a positive finite number.
265
265
 
266
+ 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.
267
+
266
268
  ```ts
267
269
  // Fillet all edges
268
270
  fillet(myShape, 2)
@@ -285,6 +287,8 @@ fillet(base, 5, base.edge('vert-br'))
285
287
 
286
288
  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.
287
289
 
290
+ 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.
291
+
288
292
  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).
289
293
 
290
294
  ```ts
@@ -1558,18 +1562,21 @@ Namespaced file-format import helpers — the single vocabulary for bringing ext
1558
1562
 
1559
1563
  - `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.
1560
1564
  - `svgSketch(fileName: string, options?: SvgImportOptions): Sketch` — Parse an SVG file and return it as a Sketch with options for region filtering, scaling, and simplification.
1561
- - `mesh(fileName: string, options?: { scale?: number; center?: boolean; object?: string; separateObjects?: boolean; }): Shape | ShapeGroup` — Import an external mesh file (STL, OBJ, 3MF).
1565
+ - `mesh(fileName: string, options?: MeshImportOptions): Shape | ShapeGroup` — Import an external mesh file (STL, OBJ, 3MF).
1562
1566
 
1563
1567
  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`.
1564
1568
 
1565
1569
  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.
1566
1570
 
1571
+ 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"`.
1572
+
1567
1573
  ```js
1568
1574
  const all = Import.mesh("./assembly.3mf", { separateObjects: true });
1569
1575
  const pin = all.child("Pin #001");
1570
1576
  const plate = Import.mesh("./assembly.3mf", { object: "3mf:build:001:object:7" });
1577
+ const yUpPart = Import.mesh("./part.obj", { sourceFrame: { up: "+Y" } });
1571
1578
  ```
1572
- - `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.
1579
+ - `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.
1573
1580
 
1574
1581
  ---
1575
1582
 
@@ -3868,7 +3875,7 @@ const arm = bottle.path()
3868
3875
 
3869
3876
  # Assembly API
3870
3877
 
3871
- Assembly-owned links, constraints, connectors, solved poses, and robot export.
3878
+ Assembly-owned links, constraints, connectors, solved poses, and source-level simulation metadata.
3872
3879
 
3873
3880
  ## Contents
3874
3881
 
@@ -3881,6 +3888,59 @@ Assembly-owned links, constraints, connectors, solved poses, and robot export.
3881
3888
 
3882
3889
  ### Assembly & Joints
3883
3890
 
3891
+ #### `Sim.material(name: string, options?: SimMaterialOptions): SimMaterialDef` — Create a named physical material with density and contact coefficients for simulation export and checks.
3892
+
3893
+ `SimMaterialOptions`: `{ densityKgM3?: number, staticFriction?: number, dynamicFriction?: number, restitution?: number }`
3894
+
3895
+ `SimMaterialDef`: `{ kind: "material", name: string }`
3896
+
3897
+ #### `Sim.body(options: SimBodyOptions): SimBodyDef` — Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces.
3898
+
3899
+ **`SimBodyOptions`**: `massKg?: number`, `densityKgM3?: number`, `material?: SimMaterialDef`, `collider?: SimColliderDef`, `contacts?: Record<string, SimContactDef>`
3900
+
3901
+ `SimColliderDef`: `{ kind: "collider", mode: SimColliderMode, reason?: string }`
3902
+
3903
+ `SimContactDef`: `{ kind: "wheelSurface" | "gripperSurface", connectorName: string }`
3904
+
3905
+ `SimBodyDef`: `{ kind: "body" }`
3906
+
3907
+ #### `Sim.collider` — Collision-geometry intent constructors for physical parts.
3908
+
3909
+ - `Sim.collider.convexHull(): SimColliderDef` — Use a generated collision mesh for the part. This is the default fast rigid-body collider for irregular parts.
3910
+ - `Sim.collider.boundingBox(): SimColliderDef` — Use the part bounding box as the collision geometry. This is fastest and works well for chassis and simple blocks.
3911
+ - `Sim.collider.visualMesh(): SimColliderDef` — Use the visual mesh as collision geometry. This is exact but usually slower in physics engines.
3912
+ - `Sim.collider.none(reason: string): SimColliderDef` — Disable collision for a part with an explicit reason, such as a sensor-only or decorative object.
3913
+
3914
+ #### `Sim.drive` — Joint-drive intent constructors for passive or powered assembly joints.
3915
+
3916
+ - `Sim.drive.passive(options?: SimPassiveDriveOptions): SimDriveDef` — Mark a joint as passive while preserving damping and friction metadata for simulation export.
3917
+ - `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.
3918
+
3919
+ `SimPassiveDriveOptions`: `{ damping?: number, friction?: number }`
3920
+
3921
+ `SimVelocityDriveOptions`: `{ maxTorqueNm: number, maxSpeedRpm: number }`
3922
+
3923
+ #### `Sim.contact` — Contact-surface metadata over existing part connectors.
3924
+
3925
+ - `Sim.contact.wheelSurface(connectorName: string): SimContactDef` — Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata.
3926
+ - `Sim.contact.gripperSurface(connectorName: string): SimContactDef` — Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata.
3927
+
3928
+ #### `Sim.profile` — Named validation/export profile constructors.
3929
+
3930
+ - `Sim.profile.robotBodyRunnable(): SimProfileDef` — SimReady-style profile for a robot body that should be runnable in a physics simulator.
3931
+ - `Sim.profile.robotBodyIsaac(): SimProfileDef` — SimReady-style profile for robot bodies targeting Isaac Sim readiness.
3932
+ - `Sim.profile.roboticsAssetPhysx(): SimProfileDef` — SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders.
3933
+
3934
+ `SimProfileDef`: `{ kind: "profile", name: SimProfileName }`
3935
+
3936
+ #### `Sim.controller` — Standard controller metadata constructors for simulator package generation.
3937
+
3938
+ - `Sim.controller.diffDrive(options: SimDiffDriveControllerOptions): SimDiffDriveControllerDef` — Describe a differential-drive controller from left/right wheel joints and wheel dimensions.
3939
+
3940
+ **`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`
3941
+
3942
+ `SimDiffDriveControllerDef`: `{ kind: "diffDrive" }`
3943
+
3884
3944
  #### `assembly(name?: string): Assembly` — Create an assembly container with named parts, connectors, and kinematic links.
3885
3945
 
3886
3946
  **Use this from iteration 1 for any model with moving parts.** Do not build one static pose and retrofit motion later.
@@ -4014,7 +4074,7 @@ const housing = group(
4014
4074
  assembly.addPart("Base Assembly", housing);
4015
4075
  ```
4016
4076
 
4017
- **`PartOptions`**: `transform?: TransformInput`, `metadata?: PartMetadata`, `mate?: AssemblyPartMateInput | AssemblyPartMateInput[]`, `bindToFrame?: string`
4077
+ **`PartOptions`**: `transform?: TransformInput`, `metadata?: PartMetadata`, `sim?: SimBodyDef`, `mate?: AssemblyPartMateInput | AssemblyPartMateInput[]`, `bindToFrame?: string`
4018
4078
 
4019
4079
  **`PartMetadata`**
4020
4080
 
@@ -4075,7 +4135,9 @@ Frame semantics: `origin` is the pivot/contact point, `axis` the hinge or slide
4075
4135
 
4076
4136
  **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()`).
4077
4137
 
4078
- **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.
4138
+ **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.
4139
+
4140
+ **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.
4079
4141
 
4080
4142
  Joint type defaults to the connector's `kind`. For `start`/`end` connectors, `align` / `parentAlign` / `childAlign` (`'start' | 'middle' | 'end'`) choose which point meets.
4081
4143
 
@@ -4102,7 +4164,7 @@ assembly("Door").addPart("Frame", frame).addPart("Door", door)
4102
4164
  | `align?` | `PortAlign` | Shorthand: set both parentAlign and childAlign at once. |
4103
4165
  | `follows?` | `JointFollowOptions` | Slave this joint to another joint: `value = ratio × source + offset` (e.g. a mirrored jaw with `ratio: -1`). |
4104
4166
 
4105
- Also: `as?: string`, `type?: JointType`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`.
4167
+ Also: `as?: string`, `type?: JointType`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `drive?: SimDriveDef`.
4106
4168
 
4107
4169
  **`JointFollowOptions`**
4108
4170
  - `joint: string` — Name of the source joint that drives this one.
@@ -4162,6 +4224,12 @@ return mech.solve({ theta: 45 });
4162
4224
 
4163
4225
  **Other**
4164
4226
 
4227
+ #### `withSimulation(options: SimAssemblySimulationOptions): Assembly` — Attach the root simulation contract for this assembly.
4228
+
4229
+ 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.
4230
+
4231
+ `SimAssemblySimulationOptions`: `{ profile: SimProfileDef, rootPart?: string, controllers?: SimControllerDef[] }`
4232
+
4165
4233
  #### `edgeBetweenFrames(a: string, b: string, options?: AssemblyFrameEdgeOptions): Assembly` — Add a visual skeleton edge between two rig frame origins.
4166
4234
 
4167
4235
  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.
@@ -4297,7 +4365,7 @@ The sub-assembly must have exactly one root part before it can be merged (collap
4297
4365
  | `connectorRefs?` | `JointConnectorRefs` | Connector refs that define this joint contract. Usually set by `connect()` / `match()`. |
4298
4366
  | `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. |
4299
4367
 
4300
- Also: `frame?: TransformInput`, `origin?: Vec3`, `axis?: Vec3`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`.
4368
+ 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`.
4301
4369
 
4302
4370
  `JointConnectorRefs`: `{ parent: string, child: string, parentAlign?: PortAlign, childAlign?: PortAlign }`
4303
4371
 
@@ -4430,12 +4498,14 @@ bom(tubeLen, "rectangular steel tube", {
4430
4498
  | `notes?` | `string` | Free-form notes |
4431
4499
  | `grain?` | `string` | Wood grain direction, e.g. "long", "cross" |
4432
4500
 
4433
- #### `robotExport(options: RobotExportOptions): CollectedRobotExport` — Declare that this script should export the assembly as a SDF/URDF robot package.
4501
+ #### `robotExport(options: RobotExportOptions): CollectedRobotExport` — Compatibility shim for SDF/URDF robot package metadata.
4434
4502
 
4435
- 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:
4503
+ 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.
4504
+
4505
+ `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:
4436
4506
 
4437
4507
  - Mesh-based inertia tensors (full 6-component, not bounding-box approximations)
4438
- - Separate collision meshes (convex hull by default — ~50–80% smaller)
4508
+ - Separate collision meshes
4439
4509
  - Joint limits, effort/velocity/damping/friction metadata from assembly joints
4440
4510
 
4441
4511
  **Collision mesh modes** (set per-link via `links["PartName"].collision`):
@@ -4454,6 +4524,8 @@ Call `robotExport()` alongside your assembly definition. `forgecad export sdf` /
4454
4524
  - `massKg` is preferred; `densityKgM3` is used when mass is unknown.
4455
4525
  - 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.
4456
4526
 
4527
+ **Legacy example**
4528
+
4457
4529
  ```ts
4458
4530
  robotExport({
4459
4531
  assembly: rover, // assembly() with parts + revolute wheel joints
@@ -4469,6 +4541,13 @@ robotExport({
4469
4541
  });
4470
4542
  ```
4471
4543
 
4544
+ **Preferred CLI usage**
4545
+
4546
+ ```bash
4547
+ forgecad export sdf model.forge.js # SDF package (Gazebo/Ignition)
4548
+ forgecad export urdf model.forge.js # URDF package (ROS/PyBullet/MuJoCo)
4549
+ ```
4550
+
4472
4551
  **`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`
4473
4552
 
4474
4553
  `RobotLinkExportOptions`: `{ massKg?: number, densityKgM3?: number, collision?: "visual" | "convex" | "box" | "none" }`
@@ -4483,46 +4562,6 @@ robotExport({
4483
4562
 
4484
4563
  `RobotWorldKeyboardTeleopOptions`: `{ enabled?: boolean, linearStep?: number, angularStep?: number }`
4485
4564
 
4486
- **`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`
4487
-
4488
- **`AssemblyDefinition`**: `name: string`, `parts: AssemblyPartDef[]`, `joints: AssemblyJointDef[]`, `jointCouplings: AssemblyJointCouplingDef[]`, `kinematics: AssemblyKinematicGraphDef`, `frames: AssemblyFrameDef[]`, `frameJoints: AssemblyFrameJointDef[]`, `frameEdges: AssemblyFrameEdgeDef[]`
4489
-
4490
- **`AssemblyPartDef`**: `name: string`, `part: AssemblyPart`, `base: Transform`, `metadata?: PartMetadata`, `mates: AssemblyPartMateInput[]`, `bindToFrame?: string`
4491
-
4492
- `PartMetadata` — defined in [assembly](/docs/assembly).
4493
-
4494
- `AssemblyPartMateInput` — defined in [assembly](/docs/assembly).
4495
-
4496
- **`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`
4497
-
4498
- `JointConnectorRefs` — defined in [assembly](/docs/assembly).
4499
-
4500
- `AssemblyJointCouplingDef`: `{ joint: string, terms: JointCouplingTermRecord[], offset: number }`
4501
-
4502
- `JointCouplingTermRecord`: `{ joint: string, ratio: number }`
4503
-
4504
- **`AssemblyKinematicGraphDef`**: `links: AssemblyLinkDef[]`, `edges: AssemblyEdgeBetweenLinksDef[]`, `angles: AssemblyAngleBetweenLinksDef[]`, `derivedLinks: AssemblyDerivedLinkDef[]`
4505
-
4506
- `AssemblyLinkDef`: `{ name: string, at: Vec3, fixed: boolean, metadata?: Record<string, unknown> }`
4507
-
4508
- **`AssemblyEdgeBetweenLinksDef`**: `name: string`, `a: string`, `b: string`, `length: number | null`, `min?: number`, `max?: number`, `visualOnly: boolean`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
4509
-
4510
- `AssemblyKinematicControlOptions` — defined in [assembly](/docs/assembly).
4511
-
4512
- **`AssemblyAngleBetweenLinksDef`**: `name: string`, `a?: string`, `b: string`, `c: string`, `reference?: AssemblyAngleReferenceDef`, `target?: number`, `min?: number`, `max?: number`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
4513
-
4514
- `AssemblyAngleReferenceDef`: `{ kind: "worldDirection", direction: Vec3 }`
4515
-
4516
- **`AssemblyDerivedLinkDef`**
4517
- - `distance: number` — Signed: positive moves from `fromLink` toward `towardLink`, negative moves away.
4518
- - Also: `name: string`, `fromLink: string`, `towardLink: string`.
4519
-
4520
- `AssemblyFrameDef`: `{ name: string, origin: Vec3, axis: Vec3, up: Vec3, fixed: boolean, metadata?: Record<string, unknown> }`
4521
-
4522
- **`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>`
4523
-
4524
- `AssemblyFrameEdgeDef`: `{ name: string, a: string, b: string, metadata?: Record<string, unknown> }`
4525
-
4526
4565
  #### `dim()` — Add a dimension annotation between two points, or along an entity.
4527
4566
 
4528
4567
  Overloads:
@@ -23,7 +23,7 @@ A script must return one of three shapes:
23
23
  ];
24
24
  ```
25
25
 
26
- 3. **A metadata object** — a plain object whose renderable values are rendered and whose non-renderable values (numbers, hole tables, builder functions) are silently skipped at render but flow to importers via `require()`.
26
+ 3. **A metadata object** — a plain object whose renderable values are rendered and whose non-renderable values (numbers, hole tables, builder functions) are silently skipped at render but flow to importers via `require()`. Each key becomes a named group, so don't pile independent parts into one array key (`{ parts: [a, b, c] }`) — the integrity gate reads that as a single fragmented part. Give each part its own key (`{ collar12, collar16, plug }`) or use named descriptors (form 2).
27
27
 
28
28
  Return an unsolved `Assembly` directly — ForgeCAD solves it at default joint values for display. Use `assembly.solve(state)` for a specific pose. Never call `.toGroup()` just to make an assembly render; use it only when you need `ShapeGroup` composition or named-child lookup.
29
29
 
@@ -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,59 @@ 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
+ `SimMaterialDef`: `{ kind: "material", name: string }`
26
+
27
+ #### `Sim.body(options: SimBodyOptions): SimBodyDef` — Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces.
28
+
29
+ **`SimBodyOptions`**: `massKg?: number`, `densityKgM3?: number`, `material?: SimMaterialDef`, `collider?: SimColliderDef`, `contacts?: Record<string, SimContactDef>`
30
+
31
+ `SimColliderDef`: `{ kind: "collider", mode: SimColliderMode, reason?: string }`
32
+
33
+ `SimContactDef`: `{ kind: "wheelSurface" | "gripperSurface", connectorName: string }`
34
+
35
+ `SimBodyDef`: `{ kind: "body" }`
36
+
37
+ #### `Sim.collider` — Collision-geometry intent constructors for physical parts.
38
+
39
+ - `Sim.collider.convexHull(): SimColliderDef` — Use a generated collision mesh for the part. This is the default fast rigid-body collider for irregular parts.
40
+ - `Sim.collider.boundingBox(): SimColliderDef` — Use the part bounding box as the collision geometry. This is fastest and works well for chassis and simple blocks.
41
+ - `Sim.collider.visualMesh(): SimColliderDef` — Use the visual mesh as collision geometry. This is exact but usually slower in physics engines.
42
+ - `Sim.collider.none(reason: string): SimColliderDef` — Disable collision for a part with an explicit reason, such as a sensor-only or decorative object.
43
+
44
+ #### `Sim.drive` — Joint-drive intent constructors for passive or powered assembly joints.
45
+
46
+ - `Sim.drive.passive(options?: SimPassiveDriveOptions): SimDriveDef` — Mark a joint as passive while preserving damping and friction metadata for simulation export.
47
+ - `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.
48
+
49
+ `SimPassiveDriveOptions`: `{ damping?: number, friction?: number }`
50
+
51
+ `SimVelocityDriveOptions`: `{ maxTorqueNm: number, maxSpeedRpm: number }`
52
+
53
+ #### `Sim.contact` — Contact-surface metadata over existing part connectors.
54
+
55
+ - `Sim.contact.wheelSurface(connectorName: string): SimContactDef` — Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata.
56
+ - `Sim.contact.gripperSurface(connectorName: string): SimContactDef` — Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata.
57
+
58
+ #### `Sim.profile` — Named validation/export profile constructors.
59
+
60
+ - `Sim.profile.robotBodyRunnable(): SimProfileDef` — SimReady-style profile for a robot body that should be runnable in a physics simulator.
61
+ - `Sim.profile.robotBodyIsaac(): SimProfileDef` — SimReady-style profile for robot bodies targeting Isaac Sim readiness.
62
+ - `Sim.profile.roboticsAssetPhysx(): SimProfileDef` — SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders.
63
+
64
+ `SimProfileDef`: `{ kind: "profile", name: SimProfileName }`
65
+
66
+ #### `Sim.controller` — Standard controller metadata constructors for simulator package generation.
67
+
68
+ - `Sim.controller.diffDrive(options: SimDiffDriveControllerOptions): SimDiffDriveControllerDef` — Describe a differential-drive controller from left/right wheel joints and wheel dimensions.
69
+
70
+ **`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`
71
+
72
+ `SimDiffDriveControllerDef`: `{ kind: "diffDrive" }`
73
+
21
74
  #### `assembly(name?: string): Assembly` — Create an assembly container with named parts, connectors, and kinematic links.
22
75
 
23
76
  **Use this from iteration 1 for any model with moving parts.** Do not build one static pose and retrofit motion later.
@@ -151,7 +204,7 @@ const housing = group(
151
204
  assembly.addPart("Base Assembly", housing);
152
205
  ```
153
206
 
154
- **`PartOptions`**: `transform?: TransformInput`, `metadata?: PartMetadata`, `mate?: AssemblyPartMateInput | AssemblyPartMateInput[]`, `bindToFrame?: string`
207
+ **`PartOptions`**: `transform?: TransformInput`, `metadata?: PartMetadata`, `sim?: SimBodyDef`, `mate?: AssemblyPartMateInput | AssemblyPartMateInput[]`, `bindToFrame?: string`
155
208
 
156
209
  **`PartMetadata`**
157
210
 
@@ -212,7 +265,9 @@ Frame semantics: `origin` is the pivot/contact point, `axis` the hinge or slide
212
265
 
213
266
  **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
267
 
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.
268
+ **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.
269
+
270
+ **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
271
 
217
272
  Joint type defaults to the connector's `kind`. For `start`/`end` connectors, `align` / `parentAlign` / `childAlign` (`'start' | 'middle' | 'end'`) choose which point meets.
218
273
 
@@ -239,7 +294,7 @@ assembly("Door").addPart("Frame", frame).addPart("Door", door)
239
294
  | `align?` | `PortAlign` | Shorthand: set both parentAlign and childAlign at once. |
240
295
  | `follows?` | `JointFollowOptions` | Slave this joint to another joint: `value = ratio × source + offset` (e.g. a mirrored jaw with `ratio: -1`). |
241
296
 
242
- Also: `as?: string`, `type?: JointType`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`.
297
+ Also: `as?: string`, `type?: JointType`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `drive?: SimDriveDef`.
243
298
 
244
299
  **`JointFollowOptions`**
245
300
  - `joint: string` — Name of the source joint that drives this one.
@@ -299,6 +354,12 @@ return mech.solve({ theta: 45 });
299
354
 
300
355
  **Other**
301
356
 
357
+ #### `withSimulation(options: SimAssemblySimulationOptions): Assembly` — Attach the root simulation contract for this assembly.
358
+
359
+ 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.
360
+
361
+ `SimAssemblySimulationOptions`: `{ profile: SimProfileDef, rootPart?: string, controllers?: SimControllerDef[] }`
362
+
302
363
  #### `edgeBetweenFrames(a: string, b: string, options?: AssemblyFrameEdgeOptions): Assembly` — Add a visual skeleton edge between two rig frame origins.
303
364
 
304
365
  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.
@@ -434,7 +495,7 @@ The sub-assembly must have exactly one root part before it can be merged (collap
434
495
  | `connectorRefs?` | `JointConnectorRefs` | Connector refs that define this joint contract. Usually set by `connect()` / `match()`. |
435
496
  | `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. |
436
497
 
437
- Also: `frame?: TransformInput`, `origin?: Vec3`, `axis?: Vec3`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`.
498
+ 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`.
438
499
 
439
500
  `JointConnectorRefs`: `{ parent: string, child: string, parentAlign?: PortAlign, childAlign?: PortAlign }`
440
501
 
@@ -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: