forgecad 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/assets/{evalWorker-1m873KWd.js → evalWorker-CbAa_9qg.js} +108 -108
- package/dist/assets/{index-Dvz3nSDc.js → index-DFa4fntx.js} +335 -335
- package/dist/assets/{manifold-C38sUiKu.js → manifold-BGMOVi-H.js} +1 -1
- package/dist/assets/{manifold-rOWQW9fU.js → manifold-BNJTX3qf.js} +1 -1
- package/dist/assets/{manifold-Dk2u-lhj.js → manifold-CL39pgcA.js} +1 -1
- package/dist/assets/{reportWorker-Cj587shw.js → reportWorker-C5Ot2xSE.js} +122 -122
- package/dist/index.html +1 -1
- package/dist-cli/forgecad.js +59 -0
- package/dist-skill/CONTEXT.md +102 -7
- package/dist-skill/docs/API/model-building/assembly.md +41 -0
- package/dist-skill/docs/API/model-building/reference.md +61 -7
- package/dist-skill/docs/VISION.md +2 -1
- package/examples/api/import-assembly-source.forge.js +22 -0
- package/examples/api/import-assembly.forge.js +26 -0
- package/package.json +1 -1
package/dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
9
|
html, body, #root { width: 100%; height: 100%; overflow: hidden; background: var(--fc-bg); color: var(--fc-text); font-family: system-ui, -apple-system, sans-serif; }
|
|
10
10
|
</style>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-DFa4fntx.js"></script>
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="root"></div>
|
package/dist-cli/forgecad.js
CHANGED
|
@@ -14746,6 +14746,42 @@ var Assembly = class {
|
|
|
14746
14746
|
function assembly(name) {
|
|
14747
14747
|
return new Assembly(name);
|
|
14748
14748
|
}
|
|
14749
|
+
var ImportedAssembly = class {
|
|
14750
|
+
constructor(_assembly) {
|
|
14751
|
+
this._assembly = _assembly;
|
|
14752
|
+
}
|
|
14753
|
+
/** The underlying Assembly — use for sweepJoint, addPart into parent, etc. */
|
|
14754
|
+
get assembly() {
|
|
14755
|
+
return this._assembly;
|
|
14756
|
+
}
|
|
14757
|
+
/** Solve the assembly at the given joint state (defaults to each joint's default value). */
|
|
14758
|
+
solve(state) {
|
|
14759
|
+
return this._assembly.solve(state);
|
|
14760
|
+
}
|
|
14761
|
+
/**
|
|
14762
|
+
* Return a specific named part positioned at the given joint state.
|
|
14763
|
+
* Result type mirrors SolvedAssembly.getPart(): Shape, TrackedShape, or ShapeGroup.
|
|
14764
|
+
*/
|
|
14765
|
+
part(name, state) {
|
|
14766
|
+
return this._assembly.solve(state).getPart(name);
|
|
14767
|
+
}
|
|
14768
|
+
/**
|
|
14769
|
+
* Convert all assembly parts to a ShapeGroup with named children.
|
|
14770
|
+
* Child names match the part names used in the assembly.
|
|
14771
|
+
* Useful for embedding a solved sub-assembly in a parent group or assembly.
|
|
14772
|
+
*/
|
|
14773
|
+
toGroup(state) {
|
|
14774
|
+
const solved = this._assembly.solve(state);
|
|
14775
|
+
const def = this._assembly.describe();
|
|
14776
|
+
const children = [];
|
|
14777
|
+
const childNames = [];
|
|
14778
|
+
for (const p of def.parts) {
|
|
14779
|
+
children.push(solved.getPart(p.name));
|
|
14780
|
+
childNames.push(p.name);
|
|
14781
|
+
}
|
|
14782
|
+
return new ShapeGroup(children, childNames);
|
|
14783
|
+
}
|
|
14784
|
+
};
|
|
14749
14785
|
|
|
14750
14786
|
// src/forge/robotExport.ts
|
|
14751
14787
|
var _collectedRobotExport = null;
|
|
@@ -17869,6 +17905,8 @@ function describeScriptResultType(value) {
|
|
|
17869
17905
|
if (value instanceof Sketch) return "Sketch";
|
|
17870
17906
|
if (value instanceof TrackedShape) return "TrackedShape";
|
|
17871
17907
|
if (value instanceof ShapeGroup) return "ShapeGroup";
|
|
17908
|
+
if (value instanceof Assembly) return "Assembly";
|
|
17909
|
+
if (value instanceof ImportedAssembly) return "ImportedAssembly";
|
|
17872
17910
|
if (Array.isArray(value)) return "Array";
|
|
17873
17911
|
if (typeof value === "object" && typeof value.toShape === "function") {
|
|
17874
17912
|
try {
|
|
@@ -18288,6 +18326,26 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
18288
18326
|
logImportTrace(fileName, scope, options, "importGroup", resolvedPath, "error", { requested: name, got });
|
|
18289
18327
|
throw new Error(`"${resolvedPath}" did not return a ShapeGroup (got ${got}). Use group(...) as the return value, or use importPart() for single-shape files.`);
|
|
18290
18328
|
};
|
|
18329
|
+
const importAssembly = (name, paramOverrides) => {
|
|
18330
|
+
const { source: src, lookupKey, resolvedPath } = resolveImportSource(fileName, name, allFiles, options);
|
|
18331
|
+
const localOverrides = parseImportParamArgs("importAssembly", name, paramOverrides);
|
|
18332
|
+
const childScope = { namePrefix: makeChildScopePrefix(resolvedPath), localOverrides };
|
|
18333
|
+
logImportTrace(fileName, scope, options, "importAssembly", resolvedPath, "start", { requested: name, overrides: localOverrides });
|
|
18334
|
+
let result;
|
|
18335
|
+
try {
|
|
18336
|
+
result = executeFile(src, lookupKey, allFiles, visited, childScope, options);
|
|
18337
|
+
} catch (error) {
|
|
18338
|
+
logImportTrace(fileName, scope, options, "importAssembly", resolvedPath, "error", { requested: name, error: formatLogError(error) });
|
|
18339
|
+
throw error;
|
|
18340
|
+
}
|
|
18341
|
+
if (result instanceof Assembly) {
|
|
18342
|
+
logImportTrace(fileName, scope, options, "importAssembly", resolvedPath, "success", { requested: name, got: "Assembly" });
|
|
18343
|
+
return new ImportedAssembly(result);
|
|
18344
|
+
}
|
|
18345
|
+
const got = describeScriptResultType(result);
|
|
18346
|
+
logImportTrace(fileName, scope, options, "importAssembly", resolvedPath, "error", { requested: name, got });
|
|
18347
|
+
throw new Error(`"${resolvedPath}" did not return an Assembly (got ${got}). Return the assembly() instance directly (before calling .solve()).`);
|
|
18348
|
+
};
|
|
18291
18349
|
const unwrap2 = (s) => s instanceof TrackedShape ? s.toShape() : s;
|
|
18292
18350
|
const wrappedUnion = (...shapes) => union(...shapes.map(unwrap2));
|
|
18293
18351
|
const wrappedDifference = (...shapes) => difference(...shapes.map(unwrap2));
|
|
@@ -18375,6 +18433,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
18375
18433
|
importSketch,
|
|
18376
18434
|
importPart,
|
|
18377
18435
|
importGroup,
|
|
18436
|
+
importAssembly,
|
|
18378
18437
|
importSvgSketch,
|
|
18379
18438
|
dim,
|
|
18380
18439
|
dimLine,
|
package/dist-skill/CONTEXT.md
CHANGED
|
@@ -1334,14 +1334,67 @@ const rightOnly = bracketB.child("Bracket Right").translate(200, 0, 0);
|
|
|
1334
1334
|
return [bracketA, leftOnly, rightOnly];
|
|
1335
1335
|
```
|
|
1336
1336
|
|
|
1337
|
-
|
|
1337
|
+
### `importAssembly(fileName, paramOverrides?)`
|
|
1338
|
+
Executes another file and returns its result as an `ImportedAssembly`. The target file **must** return an `Assembly` instance directly (before calling `.solve()`). Use this when the source file is authored with `assembly()` and you want to access named parts or kinematic structure without rewriting it as a `group()`.
|
|
1338
1339
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1340
|
+
**Parameters:**
|
|
1341
|
+
- `fileName` (string) — Import path (e.g. `"./sub-arm.forge.js"`)
|
|
1342
|
+
- `paramOverrides` (optional object) — Import-time parameter overrides by param name
|
|
1343
|
+
|
|
1344
|
+
**Returns:** `ImportedAssembly` with the following methods:
|
|
1345
|
+
- `.assembly` — the raw `Assembly` for `sweepJoint`, `describe`, etc.
|
|
1346
|
+
- `.solve(state?)` — delegate to `Assembly.solve()`, returns `SolvedAssembly`
|
|
1347
|
+
- `.part(name, state?)` — returns the named part positioned at the given joint state (defaults to each joint's `default` value)
|
|
1348
|
+
- `.toGroup(state?)` — converts all parts to a `ShapeGroup` with children named after the assembly part names
|
|
1349
|
+
|
|
1350
|
+
```javascript
|
|
1351
|
+
// sub-arm.forge.js ← the source file — returns Assembly, not solved
|
|
1352
|
+
const mech = assembly("Sub Arm")
|
|
1353
|
+
.addPart("Base", box(60, 60, 16, true))
|
|
1354
|
+
.addPart("Link", box(120, 20, 20).translate(0, -10, -10))
|
|
1355
|
+
.addRevolute("shoulder", "Base", "Link", {
|
|
1356
|
+
axis: [0, 1, 0],
|
|
1357
|
+
min: -45,
|
|
1358
|
+
max: 120,
|
|
1359
|
+
default: 30,
|
|
1360
|
+
frame: Transform.identity().translate(0, 0, 16),
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
return mech; // return Assembly directly
|
|
1364
|
+
```
|
|
1365
|
+
|
|
1366
|
+
```javascript
|
|
1367
|
+
// scene.forge.js ← the consumer
|
|
1368
|
+
const angle = param("Angle", 45, { unit: "°" });
|
|
1369
|
+
|
|
1370
|
+
const subArm = importAssembly("sub-arm.forge.js", { "Link Length": 100 });
|
|
1371
|
+
|
|
1372
|
+
// Access a specific part positioned at a given state
|
|
1373
|
+
const baseShape = subArm.part("Base");
|
|
1374
|
+
const linkShape = subArm.part("Link", { shoulder: angle });
|
|
1375
|
+
|
|
1376
|
+
// Convert to a named ShapeGroup — children match assembly part names
|
|
1377
|
+
const asGroup = subArm.toGroup({ shoulder: angle });
|
|
1378
|
+
const base = asGroup.child("Base");
|
|
1379
|
+
|
|
1380
|
+
// Full kinematic access via the underlying assembly
|
|
1381
|
+
const swept = subArm.assembly.sweepJoint("shoulder", -45, 120, 20);
|
|
1382
|
+
|
|
1383
|
+
// Place two copies at different joint states
|
|
1384
|
+
const copy1 = subArm.toGroup({ shoulder: 45 });
|
|
1385
|
+
const copy2 = subArm.toGroup({ shoulder: -20 }).translate(200, 0, 0);
|
|
1386
|
+
return [copy1, copy2];
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
**When to use `importGroup` vs `importAssembly`:**
|
|
1390
|
+
|
|
1391
|
+
| | `importPart` | `importGroup` | `importAssembly` |
|
|
1392
|
+
|---|---|---|---|
|
|
1393
|
+
| Source returns | `Shape` or `TrackedShape` | `ShapeGroup` via `group(...)` | `Assembly` (unsolved) |
|
|
1394
|
+
| Result type | `Shape` | `ShapeGroup` | `ImportedAssembly` |
|
|
1395
|
+
| Access children | Not possible | `.child("Name")` | `.part("Name", state?)` or `.toGroup().child("Name")` |
|
|
1396
|
+
| Kinematic access | None | None | `.solve()`, `.assembly.sweepJoint()` |
|
|
1397
|
+
| Multiple poses | No | No | Yes — call `.toGroup(state)` with different states |
|
|
1345
1398
|
|
|
1346
1399
|
### Import Rules
|
|
1347
1400
|
- Circular imports are detected and throw an error
|
|
@@ -1351,6 +1404,7 @@ return [bracketA, leftOnly, rightOnly];
|
|
|
1351
1404
|
- Relative imports (`./` / `../`) are resolved from the current file path
|
|
1352
1405
|
- `importPart()` accepts `Shape` or `TrackedShape` results and always returns a chainable `Shape`
|
|
1353
1406
|
- `importGroup()` accepts only `ShapeGroup` results; use `group(...)` as the return value in the source file
|
|
1407
|
+
- `importAssembly()` accepts only `Assembly` results; return the `assembly(...)` instance before calling `.solve()`
|
|
1354
1408
|
- Source files can attach placement references with `.withReferences({ points, edges, surfaces, objects })` — works on both `Shape` and `ShapeGroup`
|
|
1355
1409
|
- Imported tracked solids keep their named faces/edges as `surfaces.<faceName>` and `edges.<edgeName>` references
|
|
1356
1410
|
- SVG import supports deterministic region filtering (`regionSelection`, `maxRegions`, area thresholds)
|
|
@@ -2741,9 +2795,50 @@ show(solved.toScene());
|
|
|
2741
2795
|
|
|
2742
2796
|
That keeps mechanism setup in earlier cells and collision/sweep investigation in the current preview cell.
|
|
2743
2797
|
|
|
2798
|
+
## Importing assemblies from other files
|
|
2799
|
+
|
|
2800
|
+
Use `importAssembly(fileName, paramOverrides?)` to import an assembly defined in another file. The source file must `return` the `Assembly` instance directly (not `.solve()`).
|
|
2801
|
+
|
|
2802
|
+
```javascript
|
|
2803
|
+
// arm.forge.js — source file
|
|
2804
|
+
const mech = assembly("Arm")
|
|
2805
|
+
.addPart("Base", box(80, 80, 20, true))
|
|
2806
|
+
.addPart("Link", box(140, 24, 24).translate(0, -12, -12))
|
|
2807
|
+
.addRevolute("shoulder", "Base", "Link", {
|
|
2808
|
+
axis: [0, 1, 0],
|
|
2809
|
+
min: -30,
|
|
2810
|
+
max: 120,
|
|
2811
|
+
default: 25,
|
|
2812
|
+
frame: Transform.identity().translate(0, 0, 20),
|
|
2813
|
+
});
|
|
2814
|
+
|
|
2815
|
+
return mech; // return Assembly, not mech.solve()
|
|
2816
|
+
```
|
|
2817
|
+
|
|
2818
|
+
```javascript
|
|
2819
|
+
// scene.forge.js — consumer
|
|
2820
|
+
const arm = importAssembly("arm.forge.js");
|
|
2821
|
+
|
|
2822
|
+
// Access named parts by name (positioned at default or given joint state)
|
|
2823
|
+
const base = arm.part("Base");
|
|
2824
|
+
const link = arm.part("Link", { shoulder: 60 });
|
|
2825
|
+
|
|
2826
|
+
// Convert to a ShapeGroup — children named after assembly part names
|
|
2827
|
+
const g = arm.toGroup({ shoulder: 45 });
|
|
2828
|
+
const baseChild = g.child("Base");
|
|
2829
|
+
|
|
2830
|
+
// Full kinematic access
|
|
2831
|
+
arm.assembly.sweepJoint("shoulder", -30, 120, 24);
|
|
2832
|
+
const solved = arm.solve({ shoulder: 45 });
|
|
2833
|
+
console.log(solved.bom());
|
|
2834
|
+
|
|
2835
|
+
return arm.toGroup({ shoulder: 45 });
|
|
2836
|
+
```
|
|
2837
|
+
|
|
2744
2838
|
## Common pitfalls
|
|
2745
2839
|
- If parts vanish in the viewport, check whether a cut plane is active before debugging kinematics. The viewer-side APIs live in [../runtime/viewport.md](../runtime/viewport.md).
|
|
2746
2840
|
- If a returned object is empty, Forge logs a warning in script output.
|
|
2841
|
+
- `importAssembly()` requires the source file to return the `Assembly` object before calling `.solve()`. If you call `.solve()` in the source file and return a `SolvedAssembly`, use `importGroup()` instead (convert with `.toScene()` → group).
|
|
2747
2842
|
|
|
2748
2843
|
## Metadata
|
|
2749
2844
|
- `addPart(..., { metadata })` attaches per-part metadata to an assembly part.
|
|
@@ -131,9 +131,50 @@ show(solved.toScene());
|
|
|
131
131
|
|
|
132
132
|
That keeps mechanism setup in earlier cells and collision/sweep investigation in the current preview cell.
|
|
133
133
|
|
|
134
|
+
## Importing assemblies from other files
|
|
135
|
+
|
|
136
|
+
Use `importAssembly(fileName, paramOverrides?)` to import an assembly defined in another file. The source file must `return` the `Assembly` instance directly (not `.solve()`).
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
// arm.forge.js — source file
|
|
140
|
+
const mech = assembly("Arm")
|
|
141
|
+
.addPart("Base", box(80, 80, 20, true))
|
|
142
|
+
.addPart("Link", box(140, 24, 24).translate(0, -12, -12))
|
|
143
|
+
.addRevolute("shoulder", "Base", "Link", {
|
|
144
|
+
axis: [0, 1, 0],
|
|
145
|
+
min: -30,
|
|
146
|
+
max: 120,
|
|
147
|
+
default: 25,
|
|
148
|
+
frame: Transform.identity().translate(0, 0, 20),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return mech; // return Assembly, not mech.solve()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// scene.forge.js — consumer
|
|
156
|
+
const arm = importAssembly("arm.forge.js");
|
|
157
|
+
|
|
158
|
+
// Access named parts by name (positioned at default or given joint state)
|
|
159
|
+
const base = arm.part("Base");
|
|
160
|
+
const link = arm.part("Link", { shoulder: 60 });
|
|
161
|
+
|
|
162
|
+
// Convert to a ShapeGroup — children named after assembly part names
|
|
163
|
+
const g = arm.toGroup({ shoulder: 45 });
|
|
164
|
+
const baseChild = g.child("Base");
|
|
165
|
+
|
|
166
|
+
// Full kinematic access
|
|
167
|
+
arm.assembly.sweepJoint("shoulder", -30, 120, 24);
|
|
168
|
+
const solved = arm.solve({ shoulder: 45 });
|
|
169
|
+
console.log(solved.bom());
|
|
170
|
+
|
|
171
|
+
return arm.toGroup({ shoulder: 45 });
|
|
172
|
+
```
|
|
173
|
+
|
|
134
174
|
## Common pitfalls
|
|
135
175
|
- If parts vanish in the viewport, check whether a cut plane is active before debugging kinematics. The viewer-side APIs live in [../runtime/viewport.md](../runtime/viewport.md).
|
|
136
176
|
- If a returned object is empty, Forge logs a warning in script output.
|
|
177
|
+
- `importAssembly()` requires the source file to return the `Assembly` object before calling `.solve()`. If you call `.solve()` in the source file and return a `SolvedAssembly`, use `importGroup()` instead (convert with `.toScene()` → group).
|
|
137
178
|
|
|
138
179
|
## Metadata
|
|
139
180
|
- `addPart(..., { metadata })` attaches per-part metadata to an assembly part.
|
|
@@ -1284,14 +1284,67 @@ const rightOnly = bracketB.child("Bracket Right").translate(200, 0, 0);
|
|
|
1284
1284
|
return [bracketA, leftOnly, rightOnly];
|
|
1285
1285
|
```
|
|
1286
1286
|
|
|
1287
|
-
|
|
1287
|
+
### `importAssembly(fileName, paramOverrides?)`
|
|
1288
|
+
Executes another file and returns its result as an `ImportedAssembly`. The target file **must** return an `Assembly` instance directly (before calling `.solve()`). Use this when the source file is authored with `assembly()` and you want to access named parts or kinematic structure without rewriting it as a `group()`.
|
|
1288
1289
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1290
|
+
**Parameters:**
|
|
1291
|
+
- `fileName` (string) — Import path (e.g. `"./sub-arm.forge.js"`)
|
|
1292
|
+
- `paramOverrides` (optional object) — Import-time parameter overrides by param name
|
|
1293
|
+
|
|
1294
|
+
**Returns:** `ImportedAssembly` with the following methods:
|
|
1295
|
+
- `.assembly` — the raw `Assembly` for `sweepJoint`, `describe`, etc.
|
|
1296
|
+
- `.solve(state?)` — delegate to `Assembly.solve()`, returns `SolvedAssembly`
|
|
1297
|
+
- `.part(name, state?)` — returns the named part positioned at the given joint state (defaults to each joint's `default` value)
|
|
1298
|
+
- `.toGroup(state?)` — converts all parts to a `ShapeGroup` with children named after the assembly part names
|
|
1299
|
+
|
|
1300
|
+
```javascript
|
|
1301
|
+
// sub-arm.forge.js ← the source file — returns Assembly, not solved
|
|
1302
|
+
const mech = assembly("Sub Arm")
|
|
1303
|
+
.addPart("Base", box(60, 60, 16, true))
|
|
1304
|
+
.addPart("Link", box(120, 20, 20).translate(0, -10, -10))
|
|
1305
|
+
.addRevolute("shoulder", "Base", "Link", {
|
|
1306
|
+
axis: [0, 1, 0],
|
|
1307
|
+
min: -45,
|
|
1308
|
+
max: 120,
|
|
1309
|
+
default: 30,
|
|
1310
|
+
frame: Transform.identity().translate(0, 0, 16),
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
return mech; // return Assembly directly
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
```javascript
|
|
1317
|
+
// scene.forge.js ← the consumer
|
|
1318
|
+
const angle = param("Angle", 45, { unit: "°" });
|
|
1319
|
+
|
|
1320
|
+
const subArm = importAssembly("sub-arm.forge.js", { "Link Length": 100 });
|
|
1321
|
+
|
|
1322
|
+
// Access a specific part positioned at a given state
|
|
1323
|
+
const baseShape = subArm.part("Base");
|
|
1324
|
+
const linkShape = subArm.part("Link", { shoulder: angle });
|
|
1325
|
+
|
|
1326
|
+
// Convert to a named ShapeGroup — children match assembly part names
|
|
1327
|
+
const asGroup = subArm.toGroup({ shoulder: angle });
|
|
1328
|
+
const base = asGroup.child("Base");
|
|
1329
|
+
|
|
1330
|
+
// Full kinematic access via the underlying assembly
|
|
1331
|
+
const swept = subArm.assembly.sweepJoint("shoulder", -45, 120, 20);
|
|
1332
|
+
|
|
1333
|
+
// Place two copies at different joint states
|
|
1334
|
+
const copy1 = subArm.toGroup({ shoulder: 45 });
|
|
1335
|
+
const copy2 = subArm.toGroup({ shoulder: -20 }).translate(200, 0, 0);
|
|
1336
|
+
return [copy1, copy2];
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
**When to use `importGroup` vs `importAssembly`:**
|
|
1340
|
+
|
|
1341
|
+
| | `importPart` | `importGroup` | `importAssembly` |
|
|
1342
|
+
|---|---|---|---|
|
|
1343
|
+
| Source returns | `Shape` or `TrackedShape` | `ShapeGroup` via `group(...)` | `Assembly` (unsolved) |
|
|
1344
|
+
| Result type | `Shape` | `ShapeGroup` | `ImportedAssembly` |
|
|
1345
|
+
| Access children | Not possible | `.child("Name")` | `.part("Name", state?)` or `.toGroup().child("Name")` |
|
|
1346
|
+
| Kinematic access | None | None | `.solve()`, `.assembly.sweepJoint()` |
|
|
1347
|
+
| Multiple poses | No | No | Yes — call `.toGroup(state)` with different states |
|
|
1295
1348
|
|
|
1296
1349
|
### Import Rules
|
|
1297
1350
|
- Circular imports are detected and throw an error
|
|
@@ -1301,6 +1354,7 @@ return [bracketA, leftOnly, rightOnly];
|
|
|
1301
1354
|
- Relative imports (`./` / `../`) are resolved from the current file path
|
|
1302
1355
|
- `importPart()` accepts `Shape` or `TrackedShape` results and always returns a chainable `Shape`
|
|
1303
1356
|
- `importGroup()` accepts only `ShapeGroup` results; use `group(...)` as the return value in the source file
|
|
1357
|
+
- `importAssembly()` accepts only `Assembly` results; return the `assembly(...)` instance before calling `.solve()`
|
|
1304
1358
|
- Source files can attach placement references with `.withReferences({ points, edges, surfaces, objects })` — works on both `Shape` and `ShapeGroup`
|
|
1305
1359
|
- Imported tracked solids keep their named faces/edges as `surfaces.<faceName>` and `edges.<edgeName>` references
|
|
1306
1360
|
- SVG import supports deterministic region filtering (`regionSelection`, `maxRegions`, area thresholds)
|
|
@@ -37,6 +37,7 @@ The kernel is not the product. The modeling layer on top is.
|
|
|
37
37
|
- **View controls** — Render modes (solid/wireframe/overlay), projection (perspective/orthographic), named views (front/back/left/right/top/bottom/iso), fit-to-view, zoom-to-selection
|
|
38
38
|
- **STL export** — Binary STL export from the browser UI
|
|
39
39
|
- **Cut planes** — `cutPlane()` defines named section planes for inspection. Viewport sectioning uses `trimByPlane()` for capped solids, with GPU clipping fallback on trim failures
|
|
40
|
+
- **Compile plan inspector** — selecting a shape opens a Construction panel showing its build tree (Union → Box, Cylinder, Fillet, …). Clicking any node previews that sub-shape as an X-ray ghost in the viewport (visible through the parent solid). Navigate with arrow keys; Escape or clicking elsewhere exits.
|
|
40
41
|
|
|
41
42
|
### Gaps to close (Fusion360 parity)
|
|
42
43
|
|
|
@@ -51,7 +52,7 @@ The kernel is not the product. The modeling layer on top is.
|
|
|
51
52
|
| Thread/helix | Helical sweep for threads, springs | Mechanical fasteners | Medium (threads done via SDF, general helix sweep still missing) |
|
|
52
53
|
|
|
53
54
|
### What we deliberately skip
|
|
54
|
-
- **
|
|
55
|
+
- **Editable history tree / timeline** — code IS the history. You read it top to bottom. No need for a separate feature tree when the script is the tree. (Note: the compile plan inspector above is read-only — it shows what the code produced, not a parallel editable feature history.)
|
|
55
56
|
- **Direct modeling** — push/pull faces interactively. Not relevant for code-first CAD.
|
|
56
57
|
- **Full GUI-style assembly mate solving** — Forge now supports code-level assembly graphs (`assembly()`, revolute/prismatic/fixed joints, collision checks, BOM metadata), but not full interactive face-mate workflows like Fusion's assembly workspace.
|
|
57
58
|
- **Photorealistic rendering** — not a rendering tool. Basic viewport materials are sufficient. Export to STL for slicing or external renderers.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Source file for import-assembly demo.
|
|
2
|
+
// Returns an Assembly directly (not solved) so the importer controls state.
|
|
3
|
+
|
|
4
|
+
const linkLen = param("Link Length", 120, { min: 60, max: 200 });
|
|
5
|
+
|
|
6
|
+
const base = box(60, 60, 16, true).translate(0, 0, 8).color("#6e7b88");
|
|
7
|
+
const link = box(linkLen, 20, 20)
|
|
8
|
+
.translate(0, -10, -10)
|
|
9
|
+
.color("#5f87c6");
|
|
10
|
+
|
|
11
|
+
const mech = assembly("Sub Arm")
|
|
12
|
+
.addPart("Base", base, { metadata: { material: "PETG", qty: 1 } })
|
|
13
|
+
.addPart("Link", link, { metadata: { material: "PETG-CF", qty: 1 } })
|
|
14
|
+
.addRevolute("shoulder", "Base", "Link", {
|
|
15
|
+
axis: [0, 1, 0],
|
|
16
|
+
min: -45,
|
|
17
|
+
max: 120,
|
|
18
|
+
default: 30,
|
|
19
|
+
frame: Transform.identity().translate(0, 0, 16),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return mech;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// importAssembly() demo
|
|
2
|
+
// Shows how to import an Assembly, access named parts, and convert to a group.
|
|
3
|
+
|
|
4
|
+
const angle = param("Shoulder Angle", 45, { min: -45, max: 120, unit: "°" });
|
|
5
|
+
|
|
6
|
+
// Import the sub-assembly — get back an ImportedAssembly
|
|
7
|
+
const subArm = importAssembly("api/import-assembly-source.forge.js", { "Link Length": 100 });
|
|
8
|
+
|
|
9
|
+
// Access a specific part by name (positioned at default joint state)
|
|
10
|
+
const baseShape = subArm.part("Base");
|
|
11
|
+
const linkShape = subArm.part("Link", { shoulder: angle });
|
|
12
|
+
|
|
13
|
+
// Solve the whole sub-assembly at a specific joint state
|
|
14
|
+
const solved = subArm.solve({ shoulder: angle });
|
|
15
|
+
console.log("BOM:", solved.bom());
|
|
16
|
+
|
|
17
|
+
// Convert to a named ShapeGroup — children match part names
|
|
18
|
+
const asGroup = subArm.toGroup({ shoulder: angle });
|
|
19
|
+
const groupedBase = asGroup.child("Base");
|
|
20
|
+
const groupedLink = asGroup.child("Link");
|
|
21
|
+
|
|
22
|
+
// Place a copy of the full sub-assembly, and a shifted copy
|
|
23
|
+
const copy1 = subArm.toGroup({ shoulder: angle });
|
|
24
|
+
const copy2 = subArm.toGroup({ shoulder: -20 }).translate(200, 0, 0);
|
|
25
|
+
|
|
26
|
+
return [copy1, copy2];
|