forgecad 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AdminPage-DwYHz72L.js → AdminPage-CHY6ZN-p.js} +1 -1
- package/dist/assets/{BenchmarkPage-a9_f-1US.js → BenchmarkPage-BcRT5iGN.js} +1 -1
- package/dist/assets/{BlogPage-DodHpvmf.js → BlogPage-BssBbnb-.js} +1 -1
- package/dist/assets/{DocsPage-B5LePEuj.js → DocsPage-DsvdiRNK.js} +33 -2
- package/dist/assets/{EditorApp-QXsAISLR.js → EditorApp-Bfd3jbtC.js} +185 -44
- package/dist/assets/{EmbedViewer-DdEHGUMU.js → EmbedViewer-D5t8WamV.js} +3 -3
- package/dist/assets/{LandingPageProofDriven-yhhOodbf.js → LandingPageProofDriven-DbN7o-Be.js} +1 -1
- package/dist/assets/{LegalPage-5RbKRGYK.js → LegalPage-DNGrrY0p.js} +1 -1
- package/dist/assets/{PricingPage-E3Rma7aV.js → PricingPage-Nczr3pRz.js} +1 -1
- package/dist/assets/{SettingsPage-BJZcM97j.js → SettingsPage-DZlyu4d4.js} +1 -1
- package/dist/assets/{app-DSYrDg0V.js → app-C9ct2hRD.js} +1752 -474
- package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
- package/dist/assets/{scalar-sampling-budget-o90NSNmF.js → backendInit-ymjonyQp.js} +85756 -78750
- package/dist/assets/cli/{render-ZMHR9HkV.js → render-B_0lQwKU.js} +71 -193
- package/dist/assets/{constructionHistoryWorker-AwMMWSxg.js → constructionHistoryWorker-CZ42Dksy.js} +8058 -1225
- package/dist/assets/{evalWorker-DbNs7Dkp.js → evalWorker-C2pm8LHP.js} +23037 -15821
- package/dist/assets/{forgecad_geometry-Dgceylq9.js → forgecad_geometry-BlMtqluF.js} +120 -1
- package/dist/assets/{forgecad_geometry_bg-dD4RNQF1.wasm → forgecad_geometry_bg-BllP_WiL.wasm} +0 -0
- package/dist/assets/{inspectWorker-CZsCFtQT.js → inspectWorker-D5T5VbfK.js} +31375 -32603
- package/dist/assets/{jointPose-DO6mnXn_.js → jointPose-4r8ed8_5.js} +1 -1
- package/dist/assets/{manifold-BU-tJwQh.js → manifold-5PP1eGLN.js} +1 -1
- package/dist/assets/{manifold-fy2MV7K1.js → manifold-C4r6B-XY.js} +2 -2
- package/dist/assets/{manifold-BGlQBBH9.js → manifold-DjBkyIc8.js} +1 -1
- package/dist/assets/{reportWorker-DO6hcQbh.js → reportWorker-CwenM7wB.js} +46620 -44936
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/CLI.md +43 -16
- package/dist/docs-raw/generated/assembly.md +71 -6
- package/dist/docs-raw/generated/concepts.md +17 -3
- package/dist/docs-raw/generated/core.md +10 -3
- package/dist/docs-raw/generated/output.md +14 -43
- package/dist/docs-raw/generated/runtime-names.md +4 -4
- package/dist/docs-raw/generated/sdf.md +2 -2
- package/dist/docs-raw/guides/simready-quickstart.md +173 -0
- package/dist/docs-raw/simulation-workflow.md +273 -0
- package/dist/index.html +2 -2
- package/dist/sitemap.xml +25 -13
- package/dist-cli/{check-compiler-JTVBITCR.js → check-compiler-SP7FAL7R.js} +1 -1
- package/dist-cli/{check-query-propagation-3FFLSMVN.js → check-query-propagation-BRLSHP22.js} +1 -1
- package/dist-cli/{chunk-OAN5T4XD.js → chunk-RQQ42YCP.js} +51209 -43456
- package/dist-cli/forgecad.js +5783 -1691
- package/dist-cli/{forgecad_geometry-QOQIIP53.js → forgecad_geometry-7TVSNVUB.js} +119 -0
- package/dist-cli/forgecad_geometry_bg.wasm +0 -0
- package/dist-skill/CONTEXT.md +107 -68
- package/dist-skill/docs/API/core/concepts.md +2 -2
- package/dist-skill/docs/CLI.md +43 -16
- package/dist-skill/docs/generated/assembly.md +67 -6
- package/dist-skill/docs/generated/core.md +10 -3
- package/dist-skill/docs/generated/output.md +14 -43
- package/dist-skill/docs/generated/runtime-names.md +4 -4
- package/dist-skill/docs/generated/sdf.md +2 -2
- package/examples/api/gyroid-voronoi-blend.forge.js +1 -1
- package/examples/api/organic-noise-sculpture.forge.js +1 -1
- package/examples/api/sdf-circular-array-knurling.forge.js +1 -1
- package/examples/api/{sdf-custom-raymarch.forge.js → sdf-custom-field-mesh-preview.forge.js} +3 -4
- package/examples/api/sdf-materialize-tree.forge.js +2 -2
- package/examples/api/sdf-plain-return.forge.js +3 -2
- package/examples/api/sdf-shapes.forge.js +2 -2
- package/examples/api/sdf-surface-basket-weave.forge.js +2 -2
- package/examples/generative/twisted-lattice-tower.forge.js +1 -1
- package/examples/generative/voronoi-lampshade.forge.js +1 -1
- package/examples/robotics/README.md +46 -0
- package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
- package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
- package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
- package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
- package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
- package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
- package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
- package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
- package/examples/robotics/simready-asset-crate.forge.js +79 -0
- package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
- package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
- package/package.json +2 -2
- package/dist/assets/manifold-CzYf_iub.js +0 -3023
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
node dist-cli/forgecad.js export usd examples/robotics/simready-diff-drive-rover.forge.js --output /tmp/rover-usd
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
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.
|
|
146
|
+
Use USD for native Isaac Sim/OpenUSD handoff: the package contains a text `.usda` stage with USD Physics bodies, colliders, mass, joints, velocity drives, a `PhysicsScene`, and package-local validation script.
|
|
147
|
+
|
|
148
|
+
For Isaac workflows, prefer `export usd` when you want to avoid importer heuristics and preserve authored physics metadata directly in USD. Use URDF only when a downstream tool specifically expects URDF or when you need an importer-managed conversion step.
|
|
149
|
+
|
|
150
|
+
Every package includes `simready-manifest.json`. That manifest is the neutral handoff: model name, units, profile, bodies, joints, drives, controllers, contacts, and validation results.
|
|
151
|
+
|
|
152
|
+
## What The Check Catches
|
|
153
|
+
|
|
154
|
+
`forgecad check simready` is source-level validation. It does not need Isaac Sim, OpenUSD, `pxr`, MuJoCo, or NVIDIA validators installed.
|
|
155
|
+
|
|
156
|
+
It fails when:
|
|
157
|
+
|
|
158
|
+
- the returned model has no `withSimulation(...)` contract
|
|
159
|
+
- a physical part is missing `Sim.body(...)`
|
|
160
|
+
- mass, density, friction, restitution, torque, speed, damping, or world pose values are non-finite or invalid
|
|
161
|
+
- a collider is missing for profiles that require explicit collision intent
|
|
162
|
+
- `Sim.collider.none(...)` has no reason
|
|
163
|
+
- a contact surface names a missing connector
|
|
164
|
+
- a controller names an unknown or wrong-type joint
|
|
165
|
+
- a robot profile has no `rootPart`, an unknown `rootPart`, or a disconnected joint graph
|
|
166
|
+
|
|
167
|
+
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.
|
|
168
|
+
|
|
169
|
+
## Where Control Code Lives
|
|
170
|
+
|
|
171
|
+
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.
|
|
172
|
+
|
|
173
|
+
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.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Simulation Workflow
|
|
2
|
+
|
|
3
|
+
ForgeCAD should make a robot or simulated asset runnable without making the CAD API own the control algorithm.
|
|
4
|
+
|
|
5
|
+
The source `.forge.js` model owns geometry, assembly structure, physical metadata, joints, drive intent, contact hints, and the neutral simulation contract. Exported packages then generate an editable simulator lab with all names and wiring connected. Rewards, policies, perturbation tests, curriculum, and RL libraries stay in simulator-side code.
|
|
6
|
+
|
|
7
|
+
## Current Path: ForgeCAD To MuJoCo
|
|
8
|
+
|
|
9
|
+
1. Source-author the model as an assembly with `withSimulation(...)`.
|
|
10
|
+
2. Put physical metadata on every part with `Sim.body(...)`.
|
|
11
|
+
3. Put motor intent on joints with `Sim.drive.velocity(...)` or passive dynamics with `Sim.drive.passive(...)`.
|
|
12
|
+
4. Add high-level controller metadata such as `Sim.controller.diffDrive(...)` when the assembly has a standard command interface.
|
|
13
|
+
5. Run the offline gate:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node dist-cli/forgecad.js check simready examples/robotics/scout-cam-rover-simready/main.forge.js
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
6. Export the MuJoCo package:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
node dist-cli/forgecad.js export mjcf examples/robotics/scout-cam-rover-simready/main.forge.js --output /tmp/scout-cam-rover-mjcf
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The generated package contains:
|
|
26
|
+
|
|
27
|
+
- `scene.xml`: a runnable MuJoCo scene with floor, light, and render settings.
|
|
28
|
+
- `manifest.json`: ForgeCAD-to-MJCF names for links, joints, actuators, generated scripts, and suggested commands.
|
|
29
|
+
- `simready-manifest.json`: neutral simulation contract and validation results.
|
|
30
|
+
- `scripts/gym_env.py`: Gymnasium-compatible MuJoCo env helpers.
|
|
31
|
+
- `scripts/policy_runtime.py`: shared policy loader for ForgeCAD JSON and Stable-Baselines3 zip policies.
|
|
32
|
+
- `scripts/smoke_control.py`: basic actuator smoke test and GIF renderer.
|
|
33
|
+
- `scripts/train_balance.py`: small editable balance trainer.
|
|
34
|
+
- `scripts/play_balance.py`: policy playback with optional external force and torque pulses.
|
|
35
|
+
- `scripts/train_sb3.py`: Stable-Baselines3 PPO/SAC training starter.
|
|
36
|
+
- `scripts/live_policy_viewer.py`: live MuJoCo passive-viewer policy runner.
|
|
37
|
+
|
|
38
|
+
## What The Generated Gym Env Does
|
|
39
|
+
|
|
40
|
+
`scripts/gym_env.py` is the bridge between the exported CAD package and control code.
|
|
41
|
+
|
|
42
|
+
It loads the package manifests, compiles `scene.xml`, discovers actuator IDs, identifies the simulation root body, and calibrates diff-drive signs from MuJoCo joint axes. This matters because mirrored wheel axes can make a naive same-sign left/right command spin or drift instead of moving forward.
|
|
43
|
+
|
|
44
|
+
The balance environment exposes a compact observation:
|
|
45
|
+
|
|
46
|
+
- tilt around the wheel axle
|
|
47
|
+
- side tilt
|
|
48
|
+
- angular velocity
|
|
49
|
+
- uprightness
|
|
50
|
+
- root height
|
|
51
|
+
- longitudinal velocity
|
|
52
|
+
- wheel velocities
|
|
53
|
+
- previous forward command
|
|
54
|
+
|
|
55
|
+
The semantic action is:
|
|
56
|
+
|
|
57
|
+
- `semanticForward`
|
|
58
|
+
- `semanticTurn`
|
|
59
|
+
|
|
60
|
+
The env converts those semantic actions into calibrated actuator commands.
|
|
61
|
+
|
|
62
|
+
## Training The Starter Balance Policy
|
|
63
|
+
|
|
64
|
+
The generated trainer intentionally avoids heavy RL machinery.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
cd /tmp/scout-cam-rover-mjcf
|
|
68
|
+
uv run --python 3.11 --with mujoco --with numpy --with gymnasium --with pillow python scripts/train_balance.py --render-gif balance.gif --fps 10
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
It trains a linear policy:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
observation -> weighted sum -> tanh -> motor command
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The optimizer is cross-entropy method:
|
|
78
|
+
|
|
79
|
+
1. Try many random weight sets.
|
|
80
|
+
2. Simulate each candidate.
|
|
81
|
+
3. Score survival, uprightness, angular rate, and command cost.
|
|
82
|
+
4. Keep the best candidates.
|
|
83
|
+
5. Sample the next generation around those.
|
|
84
|
+
|
|
85
|
+
This is not deep RL. It is a fast, inspectable starter controller. The right next steps for serious RL are PPO/SAC/PufferLib/MJX/Isaac Lab integrations that reuse the generated env or manifests.
|
|
86
|
+
|
|
87
|
+
## Serious RL Workflow
|
|
88
|
+
|
|
89
|
+
For a real reinforcement-learning run, treat the generated MJCF package as the simulator adapter and replace `train_balance.py` with the training stack you actually want. ForgeCAD's job is to produce a valid robot model, named actuators, controller metadata, and a Gymnasium-style environment scaffold; the RL project owns the policy class, reward, curriculum, vectorization, checkpointing, and evaluation harness.
|
|
90
|
+
|
|
91
|
+
The usual loop is:
|
|
92
|
+
|
|
93
|
+
1. Load `scene.xml` with MuJoCo.
|
|
94
|
+
2. Load `manifest.json` and `simready-manifest.json` to map ForgeCAD names to MuJoCo bodies, joints, and actuators.
|
|
95
|
+
3. Build observations from `mjData`, such as pose, tilt, angular velocity, wheel velocity, sensors, previous command, and task-specific state.
|
|
96
|
+
4. Let the policy choose an action.
|
|
97
|
+
5. Convert semantic actions, such as `[forward, turn]`, into actuator controls.
|
|
98
|
+
6. Write controls into `mjData.ctrl`, then call `mj_step`.
|
|
99
|
+
7. Compute reward, termination, truncation, and diagnostics.
|
|
100
|
+
8. Let PPO, SAC, PufferLib, MJX, Isaac Lab, or another trainer update the policy.
|
|
101
|
+
9. Save checkpoints and evaluate them with deterministic playback, randomized starts, friction/mass perturbations, and force-push tests.
|
|
102
|
+
|
|
103
|
+
For the Scout Cam Rover balance example, the first serious action space should usually stay semantic:
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
action = [semanticForward, semanticTurn]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
That keeps the RL task independent from left/right motor sign conventions. The generated environment already calibrates mirrored wheel axes and maps semantic commands to actuator controls. If a later task needs raw motor commands, add that deliberately after the semantic baseline works.
|
|
110
|
+
|
|
111
|
+
The reward should encode the actual job, not just survival. For a self-balancing rover, a practical first reward includes uprightness, low tilt rate, target velocity or position tracking, bounded control effort, and clear fall termination. Randomize initial tilt, mass, friction, and push disturbances once the baseline can solve the nominal case.
|
|
112
|
+
|
|
113
|
+
## Training A Stable-Baselines3 Policy
|
|
114
|
+
|
|
115
|
+
The MJCF package includes an editable SB3 starter that reuses `BalanceEnv` and the generated semantic diff-drive action mapping:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cd /tmp/scout-cam-rover-mjcf
|
|
119
|
+
uv run --python 3.11 --with mujoco --with numpy --with gymnasium --with stable-baselines3 --with pillow python scripts/train_sb3.py \
|
|
120
|
+
--timesteps 50000 \
|
|
121
|
+
--output policies/sb3_balance_ppo.zip \
|
|
122
|
+
--render-gif sb3_balance.gif \
|
|
123
|
+
--fps 10
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
That command writes `policies/sb3_balance_ppo.zip` plus `policies/sb3_balance_ppo.json`. The JSON sidecar records the algorithm, observation/action shape, drive calibration, and short deterministic evaluation summary. Use `--algo sac` when you want the SAC template instead of PPO.
|
|
127
|
+
|
|
128
|
+
## Pushing The Trained Policy
|
|
129
|
+
|
|
130
|
+
After training, run the policy with a force pulse:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cd /tmp/scout-cam-rover-mjcf
|
|
134
|
+
uv run --python 3.11 --with mujoco --with numpy --with gymnasium --with pillow python scripts/play_balance.py \
|
|
135
|
+
--policy policies/balance_linear.json \
|
|
136
|
+
--push-y 1 \
|
|
137
|
+
--push-at 2 \
|
|
138
|
+
--push-duration 0.15 \
|
|
139
|
+
--render-gif balance_push.gif \
|
|
140
|
+
--fps 10
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`play_balance.py` applies force or torque through MuJoCo's `xfrc_applied` array on the root body. It reports the frame count, approximate GIF duration, push vector, final tilt, and survival time.
|
|
144
|
+
|
|
145
|
+
Use these options to push from other directions:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
--push-x 2
|
|
149
|
+
--push-y -4
|
|
150
|
+
--push-z 1
|
|
151
|
+
--torque-x 0.1
|
|
152
|
+
--torque-y -0.1
|
|
153
|
+
--torque-z 0.1
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Increase `--push-y` or `--push-x` when you want a failure/stress test instead of a recovery demo.
|
|
157
|
+
|
|
158
|
+
Stable-Baselines3 zip policies use the same playback path:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
cd /tmp/scout-cam-rover-mjcf
|
|
162
|
+
uv run --python 3.11 --with mujoco --with numpy --with gymnasium --with stable-baselines3 --with pillow python scripts/play_balance.py \
|
|
163
|
+
--policy policies/sb3_balance_ppo.zip \
|
|
164
|
+
--push-y 1 \
|
|
165
|
+
--push-at 2 \
|
|
166
|
+
--push-duration 0.15 \
|
|
167
|
+
--render-gif sb3_balance_push.gif \
|
|
168
|
+
--fps 10
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## MuJoCo App Vs Policy Control
|
|
172
|
+
|
|
173
|
+
MuJoCo's standalone viewer and MuJoCo.app load models, not trained policies. Open `scene.xml` when you want to inspect the exported robot, geometry, joints, contacts, actuators, and passive dynamics:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
uv run --python 3.11 --with mujoco python -m mujoco.viewer --mjcf=scene.xml
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
A trained policy is control code. The official MuJoCo control path is to set controls before stepping the simulator, or to install a control callback. In Python, the common interactive pattern is the passive viewer: the user script owns the loop, computes policy actions, writes `mjData.ctrl`, calls `mj_step`, and then calls `viewer.sync()`. On macOS, passive-viewer scripts should run with `mjpython`, which is installed with the `mujoco` Python package.
|
|
180
|
+
|
|
181
|
+
Conceptually:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
model = mujoco.MjModel.from_xml_path("scene.xml")
|
|
185
|
+
data = mujoco.MjData(model)
|
|
186
|
+
|
|
187
|
+
with mujoco.viewer.launch_passive(model, data) as viewer:
|
|
188
|
+
while viewer.is_running():
|
|
189
|
+
action = policy(observation_from(data))
|
|
190
|
+
data.ctrl[:] = action_to_actuator_controls(action)
|
|
191
|
+
mujoco.mj_step(model, data)
|
|
192
|
+
viewer.sync()
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`scripts/play_balance.py` is the generated scripted playback path. It loads the policy JSON, applies calibrated actuator commands, optionally applies force or torque pulses through `xfrc_applied`, and can render a diagnostic GIF. It is not the same thing as loading the policy into MuJoCo.app.
|
|
196
|
+
|
|
197
|
+
To run a policy in an interactive window, use the generated passive-viewer script. On macOS, run it through `mjpython`:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
cd /tmp/scout-cam-rover-mjcf
|
|
201
|
+
uv run --python 3.11 --with mujoco --with numpy --with gymnasium --with stable-baselines3 mjpython scripts/live_policy_viewer.py \
|
|
202
|
+
--policy policies/sb3_balance_ppo.zip
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## GIF Timing
|
|
206
|
+
|
|
207
|
+
The generated scripts treat GIF duration as:
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
duration ~= frame_count / fps
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
For a 6-second run at 10 FPS, expect about 60 frames. If the GIF is much shorter, the renderer is undersampling frames or the policy terminated early. The summary JSON prints both `frames` and `gifDurationSecondsApprox` for playback scripts.
|
|
214
|
+
|
|
215
|
+
Keep FPS modest. A 10 FPS diagnostic GIF is usually easier to review, smaller than a 30 FPS one, and maps to a clean 100 ms GIF frame delay in common viewers.
|
|
216
|
+
|
|
217
|
+
## What Belongs In ForgeCAD
|
|
218
|
+
|
|
219
|
+
ForgeCAD should own:
|
|
220
|
+
|
|
221
|
+
- physical metadata on parts
|
|
222
|
+
- colliders and contact intent
|
|
223
|
+
- joints and drive limits
|
|
224
|
+
- named controller metadata
|
|
225
|
+
- simulator package manifests
|
|
226
|
+
- export to URDF, SDF, and MJCF; native USD/USDZ export is future work
|
|
227
|
+
- generated starter envs, smoke tests, trainers, playback, and perturbation scripts
|
|
228
|
+
- offline source-level readiness checks
|
|
229
|
+
|
|
230
|
+
ForgeCAD should not own:
|
|
231
|
+
|
|
232
|
+
- a `selfBalancingDiffDrive(...)` API
|
|
233
|
+
- a single blessed RL algorithm
|
|
234
|
+
- task-specific rewards
|
|
235
|
+
- PufferLib-specific or IsaacLab-specific core CAD primitives
|
|
236
|
+
- deployment-specific training infrastructure
|
|
237
|
+
|
|
238
|
+
The durable product promise is: model once, export a working simulation lab, then swap the control stack freely.
|
|
239
|
+
|
|
240
|
+
## Isaac Lab Fit
|
|
241
|
+
|
|
242
|
+
Isaac Lab integration is plausible, but not turnkey in this slice.
|
|
243
|
+
|
|
244
|
+
The current NVIDIA Isaac Lab docs describe custom robot training as a multi-step flow: import and tune the robot in Isaac Sim, then define the Isaac Lab interface needed to clone the robot, drive joints, and reset state. Their "Adding a New Robot" tutorial names `AssetBaseCfg` as the interface between the USD articulation and learning algorithms. Their asset configuration guide shows `ArticulationCfg` with `UsdFileCfg`, and notes that `UrdfFileCfg` can replace `UsdFileCfg` for URDF-backed articulations.
|
|
245
|
+
|
|
246
|
+
Relevant docs:
|
|
247
|
+
|
|
248
|
+
- https://isaac-sim.github.io/IsaacLab/main/source/tutorials/01_assets/add_new_robot.html
|
|
249
|
+
- https://isaac-sim.github.io/IsaacLab/main/source/how-to/write_articulation_cfg.html
|
|
250
|
+
|
|
251
|
+
ForgeCAD has the information Isaac Lab needs in pieces:
|
|
252
|
+
|
|
253
|
+
- URDF export for an articulation import path
|
|
254
|
+
- SimReady manifest with root part, joints, drives, materials, contacts, and controller metadata
|
|
255
|
+
- SDF/MJCF packages for fast local physics iteration
|
|
256
|
+
|
|
257
|
+
ForgeCAD does not currently have a native USD/USDZ export command. For Isaac workflows today, use URDF as the import artifact and carry the richer ForgeCAD simulation intent through `simready-manifest.json`.
|
|
258
|
+
|
|
259
|
+
The missing ForgeCAD work for a clean Isaac Lab path is:
|
|
260
|
+
|
|
261
|
+
- direct USD authoring or a generated Isaac Sim URDF import/conversion script
|
|
262
|
+
- generated `ArticulationCfg` scaffold from `simready-manifest.json`
|
|
263
|
+
- actuator config generation from `Sim.drive.*(...)`
|
|
264
|
+
- reset/observation/action scaffolds equivalent to the MuJoCo Gym env
|
|
265
|
+
- coordinate/frame audit for Isaac's stage conventions and imported URDF axes
|
|
266
|
+
- Isaac Sim/Isaac Lab validation on a machine with those tools installed
|
|
267
|
+
|
|
268
|
+
Until that exists, the recommended path is:
|
|
269
|
+
|
|
270
|
+
1. Use MuJoCo export for fast local control-loop iteration.
|
|
271
|
+
2. Export URDF for Isaac Sim and keep `simready-manifest.json` beside it.
|
|
272
|
+
3. Generate or hand-write Isaac Lab `ArticulationCfg` and task code from the ForgeCAD manifests.
|
|
273
|
+
4. Validate imported articulation axes, actuators, masses, colliders, and reset behavior in Isaac Lab.
|
package/dist/index.html
CHANGED
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
84
84
|
html, body, #root { width: 100%; min-height: 100%; background: var(--fc-bg); color: var(--fc-text); font-family: system-ui, -apple-system, sans-serif; }
|
|
85
85
|
</style>
|
|
86
|
-
<script type="module" crossorigin src="/assets/app-
|
|
87
|
-
<link rel="stylesheet" crossorigin href="/assets/app-
|
|
86
|
+
<script type="module" crossorigin src="/assets/app-C9ct2hRD.js"></script>
|
|
87
|
+
<link rel="stylesheet" crossorigin href="/assets/app-CjsbDlb7.css">
|
|
88
88
|
</head>
|
|
89
89
|
<body>
|
|
90
90
|
<div id="root"></div>
|
package/dist/sitemap.xml
CHANGED
|
@@ -2,79 +2,91 @@
|
|
|
2
2
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
3
|
<url>
|
|
4
4
|
<loc>https://forgecad.io/</loc>
|
|
5
|
-
<lastmod>2026-06-
|
|
5
|
+
<lastmod>2026-06-12</lastmod>
|
|
6
6
|
<changefreq>weekly</changefreq>
|
|
7
7
|
<priority>1.0</priority>
|
|
8
8
|
</url>
|
|
9
9
|
<url>
|
|
10
10
|
<loc>https://forgecad.io/docs</loc>
|
|
11
|
-
<lastmod>2026-06-
|
|
11
|
+
<lastmod>2026-06-12</lastmod>
|
|
12
12
|
<changefreq>weekly</changefreq>
|
|
13
13
|
<priority>0.8</priority>
|
|
14
14
|
</url>
|
|
15
15
|
<url>
|
|
16
16
|
<loc>https://forgecad.io/docs/ai-native-cad</loc>
|
|
17
|
-
<lastmod>2026-06-
|
|
17
|
+
<lastmod>2026-06-12</lastmod>
|
|
18
18
|
<changefreq>weekly</changefreq>
|
|
19
19
|
<priority>0.9</priority>
|
|
20
20
|
</url>
|
|
21
21
|
<url>
|
|
22
22
|
<loc>https://forgecad.io/docs/ai-usage</loc>
|
|
23
|
-
<lastmod>2026-06-
|
|
23
|
+
<lastmod>2026-06-12</lastmod>
|
|
24
24
|
<changefreq>weekly</changefreq>
|
|
25
25
|
<priority>0.8</priority>
|
|
26
26
|
</url>
|
|
27
27
|
<url>
|
|
28
28
|
<loc>https://forgecad.io/docs/cli</loc>
|
|
29
|
-
<lastmod>2026-06-
|
|
29
|
+
<lastmod>2026-06-12</lastmod>
|
|
30
30
|
<changefreq>weekly</changefreq>
|
|
31
31
|
<priority>0.7</priority>
|
|
32
32
|
</url>
|
|
33
|
+
<url>
|
|
34
|
+
<loc>https://forgecad.io/docs/simready-quickstart</loc>
|
|
35
|
+
<lastmod>2026-06-12</lastmod>
|
|
36
|
+
<changefreq>weekly</changefreq>
|
|
37
|
+
<priority>0.8</priority>
|
|
38
|
+
</url>
|
|
39
|
+
<url>
|
|
40
|
+
<loc>https://forgecad.io/docs/simulation-workflow</loc>
|
|
41
|
+
<lastmod>2026-06-12</lastmod>
|
|
42
|
+
<changefreq>weekly</changefreq>
|
|
43
|
+
<priority>0.8</priority>
|
|
44
|
+
</url>
|
|
33
45
|
<url>
|
|
34
46
|
<loc>https://forgecad.io/docs/skills/forgecad-make-a-model</loc>
|
|
35
|
-
<lastmod>2026-06-
|
|
47
|
+
<lastmod>2026-06-12</lastmod>
|
|
36
48
|
<changefreq>weekly</changefreq>
|
|
37
49
|
<priority>0.7</priority>
|
|
38
50
|
</url>
|
|
39
51
|
<url>
|
|
40
52
|
<loc>https://forgecad.io/benchmark</loc>
|
|
41
|
-
<lastmod>2026-06-
|
|
53
|
+
<lastmod>2026-06-12</lastmod>
|
|
42
54
|
<changefreq>weekly</changefreq>
|
|
43
55
|
<priority>0.7</priority>
|
|
44
56
|
</url>
|
|
45
57
|
<url>
|
|
46
58
|
<loc>https://forgecad.io/blog</loc>
|
|
47
|
-
<lastmod>2026-06-
|
|
59
|
+
<lastmod>2026-06-12</lastmod>
|
|
48
60
|
<changefreq>weekly</changefreq>
|
|
49
61
|
<priority>0.6</priority>
|
|
50
62
|
</url>
|
|
51
63
|
<url>
|
|
52
64
|
<loc>https://forgecad.io/pricing</loc>
|
|
53
|
-
<lastmod>2026-06-
|
|
65
|
+
<lastmod>2026-06-12</lastmod>
|
|
54
66
|
<changefreq>monthly</changefreq>
|
|
55
67
|
<priority>0.5</priority>
|
|
56
68
|
</url>
|
|
57
69
|
<url>
|
|
58
70
|
<loc>https://forgecad.io/terms</loc>
|
|
59
|
-
<lastmod>2026-06-
|
|
71
|
+
<lastmod>2026-06-12</lastmod>
|
|
60
72
|
<changefreq>yearly</changefreq>
|
|
61
73
|
<priority>0.3</priority>
|
|
62
74
|
</url>
|
|
63
75
|
<url>
|
|
64
76
|
<loc>https://forgecad.io/privacy</loc>
|
|
65
|
-
<lastmod>2026-06-
|
|
77
|
+
<lastmod>2026-06-12</lastmod>
|
|
66
78
|
<changefreq>yearly</changefreq>
|
|
67
79
|
<priority>0.3</priority>
|
|
68
80
|
</url>
|
|
69
81
|
<url>
|
|
70
82
|
<loc>https://forgecad.io/license</loc>
|
|
71
|
-
<lastmod>2026-06-
|
|
83
|
+
<lastmod>2026-06-12</lastmod>
|
|
72
84
|
<changefreq>yearly</changefreq>
|
|
73
85
|
<priority>0.3</priority>
|
|
74
86
|
</url>
|
|
75
87
|
<url>
|
|
76
88
|
<loc>https://forgecad.io/blog/hello-forgecad-io</loc>
|
|
77
|
-
<lastmod>2026-06-
|
|
89
|
+
<lastmod>2026-06-12</lastmod>
|
|
78
90
|
<changefreq>monthly</changefreq>
|
|
79
91
|
<priority>0.5</priority>
|
|
80
92
|
</url>
|