forgecad 0.9.16 → 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.
- package/dist/assets/{AdminPage-CXvls4-J.js → AdminPage-DcCnj0qo.js} +1 -1
- package/dist/assets/{BenchmarkPage-B27zk8xL.js → BenchmarkPage-BVEpJSVk.js} +1 -1
- package/dist/assets/{BlogPage-CMAVvgQL.js → BlogPage-DHaGP50_.js} +1 -1
- package/dist/assets/{DocsPage-knf4I4h7.js → DocsPage-CDoxHkz8.js} +40 -859
- package/dist/assets/EditorApp-BJ0Dloyh.js +16446 -0
- package/dist/assets/{EmbedViewer-D7ZGlFjx.js → EmbedViewer-CRKZbY0y.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-CnevhTE8.js → LandingPageProofDriven-BxHkYRE7.js} +1 -1
- package/dist/assets/{LegalPage-BPTUmqeg.js → LegalPage-B-u6FrVv.js} +1 -1
- package/dist/assets/{PricingPage-B0D4goG_.js → PricingPage-CzpZ6-Ce.js} +1 -1
- package/dist/assets/{SettingsPage-CFF-UgjI.js → SettingsPage-CIZSSAd0.js} +1 -1
- package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
- package/dist/assets/{app-T0pDcSX4.js → app-DaTMg3nH.js} +1310 -290
- package/dist/assets/cli/{render-C5pcIISc.js → render-DPf4AYJK.js} +55 -60
- package/dist/assets/{constructionHistoryWorker-Ba2Hm58b.js → constructionHistoryWorker-AwMMWSxg.js} +1103 -349
- package/dist/assets/{evalWorker-vkx310U2.js → evalWorker-CjZZWRWW.js} +5209 -2643
- package/dist/assets/{inspectWorker-BuTJDVX6.js → inspectWorker-CZsCFtQT.js} +1163 -409
- package/dist/assets/{jointPose-B_Cgedn9.js → jointPose-DzQOViQH.js} +1 -1
- package/dist/assets/{manifold-BWgsjmAM.js → manifold-BYlzU521.js} +1 -1
- package/dist/assets/{manifold-D6IFSkhH.js → manifold-DgXo0T5P.js} +2 -2
- package/dist/assets/{manifold-rZexZI0G.js → manifold-K1SkarlQ.js} +1 -1
- package/dist/assets/{reportWorker-0AGij1Ru.js → reportWorker-B9nWwSrB.js} +8501 -3393
- package/dist/assets/{scalar-sampling-budget-J5cuzxT1.js → scalar-sampling-budget-prBw_s8t.js} +6067 -3479
- package/dist/assets/{scanProxyWorker-Vl4Wxa1y.js → scanProxyWorker-2GtDLk-R.js} +1 -1
- package/dist/assets/{javascript-1kQXfVaz.js → typescript-DBQ6RN5l.js} +874 -22
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +3 -3
- package/dist/docs-raw/AI/usage.md +1 -1
- package/dist/docs-raw/CLI.md +77 -240
- package/dist/docs-raw/README.md +6 -0
- package/dist/docs-raw/component-model.md +17 -150
- package/dist/docs-raw/generated/assembly.md +188 -582
- package/dist/docs-raw/generated/concepts.md +259 -3501
- package/dist/docs-raw/generated/core.md +283 -1250
- package/dist/docs-raw/generated/curves.md +387 -1608
- package/dist/docs-raw/generated/legacy.md +162 -0
- package/dist/docs-raw/generated/lib.md +227 -85
- package/dist/docs-raw/generated/output.md +35 -99
- package/dist/docs-raw/generated/runtime-names.md +23 -23
- package/dist/docs-raw/generated/sdf.md +68 -284
- package/dist/docs-raw/generated/sheet-metal.md +68 -335
- package/dist/docs-raw/generated/sketch.md +240 -1161
- package/dist/docs-raw/generated/viewport.md +75 -316
- package/dist/docs-raw/generated/wood.md +21 -49
- package/dist/docs-raw/guides/coordinate-system.md +4 -42
- package/dist/docs-raw/guides/inspection-bundles.md +44 -442
- package/dist/docs-raw/guides/joint-design.md +18 -79
- package/dist/docs-raw/guides/positioning.md +21 -143
- package/dist/docs-raw/guides/scene-presentation.md +89 -0
- package/dist/docs-raw/guides/simready-quickstart.md +171 -0
- package/dist/docs-raw/simulation-workflow.md +273 -0
- package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +25 -111
- package/dist/docs-raw/skills/forgecad-blockout-model.md +20 -117
- package/dist/docs-raw/skills/forgecad-component-model.md +23 -107
- package/dist/docs-raw/skills/forgecad-high-level-spec.md +47 -155
- package/dist/docs-raw/skills/forgecad-image-replicator.md +26 -143
- package/dist/docs-raw/skills/forgecad-lld.md +19 -113
- package/dist/docs-raw/skills/forgecad-make-a-model.md +112 -532
- package/dist/docs-raw/skills/forgecad-model-grader.md +38 -108
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +24 -211
- package/dist/docs-raw/skills/forgecad-project.md +13 -131
- package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +42 -134
- package/dist/docs-raw/skills/forgecad-render-inspect.md +27 -174
- package/dist/docs-raw/skills/forgecad-visual-spec.md +32 -112
- package/dist/docs-raw/skills/forgecad.md +19 -18
- package/dist/docs-raw/skills/index.md +2 -0
- package/dist/docs-raw/welcome.md +2 -2
- package/dist/index.html +2 -2
- package/dist/llms.txt +1 -2
- package/dist/sitemap.xml +25 -13
- package/dist-cli/{check-compiler-SYQ2PWOB.js → check-compiler-II7NLPAB.js} +1 -1
- package/dist-cli/{check-query-propagation-HIAGV62W.js → check-query-propagation-7462TR3R.js} +1 -1
- package/dist-cli/{chunk-SPZE3DUY.js → chunk-UWTJCGXF.js} +5848 -2915
- package/dist-cli/forgecad.js +3496 -703
- package/dist-skill/CONTEXT.md +1797 -7963
- package/dist-skill/SKILL.md +15 -15
- package/dist-skill/docs/API/core/concepts.md +27 -157
- package/dist-skill/docs/CLI.md +77 -240
- package/dist-skill/docs/generated/assembly.md +182 -532
- package/dist-skill/docs/generated/core.md +283 -1250
- package/dist-skill/docs/generated/curves.md +387 -1609
- package/dist-skill/docs/generated/lib.md +227 -85
- package/dist-skill/docs/generated/output.md +35 -99
- package/dist-skill/docs/generated/runtime-names.md +16 -21
- package/dist-skill/docs/generated/sdf.md +68 -284
- package/dist-skill/docs/generated/sheet-metal.md +68 -335
- package/dist-skill/docs/generated/sketch.md +240 -1160
- package/dist-skill/docs/generated/viewport.md +75 -223
- package/dist-skill/docs/generated/wood.md +21 -49
- package/dist-skill/docs/guides/coordinate-system.md +4 -42
- package/dist-skill/docs/guides/inspection-bundles.md +44 -442
- package/dist-skill/docs/guides/joint-design.md +18 -79
- package/dist-skill/docs/guides/positioning.md +21 -143
- package/dist-skill/docs/guides/scene-presentation.md +89 -0
- package/dist-skill/docs/guides/surface-members.md +26 -0
- package/dist-skill/library/forgecad-3d-reconstruction/SKILL.md +23 -111
- package/dist-skill/library/forgecad-blockout-model/SKILL.md +18 -117
- package/dist-skill/library/forgecad-component-model/SKILL.md +21 -107
- package/dist-skill/library/forgecad-high-level-spec/SKILL.md +45 -155
- package/dist-skill/library/forgecad-image-replicator/SKILL.md +24 -143
- package/dist-skill/library/forgecad-lld/SKILL.md +17 -113
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +110 -532
- package/dist-skill/library/forgecad-model-grader/SKILL.md +36 -108
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +35 -224
- package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +43 -271
- package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +30 -99
- package/dist-skill/library/forgecad-project/SKILL.md +13 -133
- package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +29 -123
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +25 -174
- package/dist-skill/library/forgecad-visual-spec/SKILL.md +30 -111
- package/dist-skill/website/skills/forgecad-3d-reconstruction.md +58 -0
- package/dist-skill/website/skills/forgecad-blockout-model.md +49 -0
- package/dist-skill/website/skills/forgecad-component-model.md +53 -0
- package/dist-skill/website/skills/forgecad-high-level-spec.md +101 -0
- package/dist-skill/website/skills/forgecad-image-replicator.md +63 -0
- package/dist-skill/website/skills/forgecad-lld.md +41 -0
- package/dist-skill/website/skills/forgecad-make-a-model.md +186 -0
- package/dist-skill/website/skills/forgecad-model-grader.md +82 -0
- package/dist-skill/website/skills/forgecad-prepare-prompt.md +63 -0
- package/dist-skill/website/skills/forgecad-project.md +26 -0
- package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +60 -0
- package/dist-skill/website/skills/forgecad-render-inspect.md +80 -0
- package/dist-skill/website/skills/forgecad-visual-spec.md +71 -0
- package/dist-skill/website/skills/forgecad.md +122 -0
- package/dist-skill/website/skills/index.md +26 -0
- package/examples/api/comparison-imported-sphere-candidate.forge.js +1 -1
- package/examples/api/conformal-product-ribbon.forge.js +1 -1
- package/examples/api/exact-sheet-shell-assembly.forge.js +1 -1
- package/examples/api/extrude-options.forge.js +4 -2
- package/examples/api/field-loft-drive-tip.forge.js +40 -0
- package/examples/api/guided-loft-olive-oil-bottle.forge.js +1 -1
- package/examples/api/highlight-debug.forge.js +10 -10
- package/examples/api/mesh-import-slats.forge.js +1 -1
- package/examples/api/real-product-curves.forge.js +1 -1
- package/examples/api/sculpt-box-circle-booleans.forge.js +1 -1
- package/examples/api/sdf-shapes.forge.js +2 -5
- package/examples/api/sketch-rounding-strategies.forge.js +6 -6
- package/examples/api/surface-member-bottle-cage.forge.js +3 -3
- package/examples/api/surface-member-conformal-product-ribbon.forge.js +3 -3
- package/examples/api/surface-member-razor-inlay.forge.js +1 -1
- package/examples/api/variable-sweep-test.forge.js +3 -3
- package/examples/mechanical/airplane-propeller.forge.js +74 -39
- package/examples/nurbs-surface.forge.js +1 -1
- package/examples/products/iphone.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 +1 -1
- package/dist/assets/EditorApp-BHMQlJ-D.js +0 -14686
- package/dist/docs-raw/guides/geometry-conventions.md +0 -52
- package/dist/docs-raw/guides/modeling-recipes.md +0 -78
- package/dist-skill/docs/guides/geometry-conventions.md +0 -52
- package/dist-skill/docs/guides/modeling-recipes.md +0 -78
- package/dist-skill/library/forgecad-visual-spec/references/prompt-template.md +0 -79
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-forgecad-skill.mjs — do not edit. Edit docs/permanent/ instead. -->
|
|
2
|
+
|
|
3
|
+
# forgecad
|
|
4
|
+
|
|
5
|
+
ForgeCAD model authoring, editing, debugging, and execution guidance for .forge.js, SVG-import, assembly, and CLI workflows. Use when building or modifying ForgeCAD geometry, structuring multi-file projects, validating scripts, or using ForgeCAD export/render tooling.
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Installed by | `forgecad skill install` |
|
|
10
|
+
| Source | Generated from `docs/permanent/` by `scripts/build-forgecad-skill.mjs` |
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ForgeCAD
|
|
15
|
+
|
|
16
|
+
Author or modify ForgeCAD models, sketches, assemblies, and CLI workflows. Prefer documented primitives, import rules, placement strategies, and CLI commands over inventing new APIs.
|
|
17
|
+
|
|
18
|
+
### Workflow
|
|
19
|
+
|
|
20
|
+
1. Identify the artifact: `.forge.js`, SVG asset, or CLI/export task.
|
|
21
|
+
2. **If the model has any moving parts, load the `assembly` group and `docs/permanent/guides/joint-design.md` upfront** — do not defer the kinematic structure to a refactor pass.
|
|
22
|
+
3. Load only the docs the task needs (see Source Map below). Start from the top group, add others as needed, and prefer these docs and recipes over ad-hoc repo examples.
|
|
23
|
+
4. If any two parts are intended to touch or mate in the final model, load `docs/permanent/guides/positioning.md` immediately and default to connectors + `matchTo()`.
|
|
24
|
+
5. Default to a concrete first pass — easy iteration beats speculative design review.
|
|
25
|
+
6. If an existing model is broken, replace the weak structure rather than preserving bad architecture.
|
|
26
|
+
7. Validate with `forgecad run <file>` (add `--debug-imports` for import chain issues; pass `--backend manifold|occt|truck` when the backend matters).
|
|
27
|
+
8. For moving assemblies, return the `Assembly` directly so runtime controls re-solve the link/edge kinematics model instead of stacking viewport-only transforms.
|
|
28
|
+
9. Model the physical artifact, not an educational diagram. No explanatory labels, arrows, legends, or text plaques unless the user explicitly asks for a presentation or teaching view; product markings only where the real object would carry them.
|
|
29
|
+
10. Build the real closed CAD first. Never bake cutaways, sectioned shells, permanently exploded layouts, or hidden-parts views into the default model just to show internals — use viewer-only cut planes, `explodeView`, object hiding, transparency, or `inspect sections` after the artifact exists.
|
|
30
|
+
|
|
31
|
+
#### Import and Composition
|
|
32
|
+
|
|
33
|
+
- Always include the extension in relative imports: `require("./file.forge.js", { Param: value })` for model files, `require("./helpers.js")` for plain helper modules. Extensionless imports such as `require("./file")` do not resolve; ForgeCAD resolves project imports by exact path.
|
|
34
|
+
- ForgeCAD APIs are injected globals in `.forge.js` files. Use `bom()`, `box()`, `scene()`, `Shape`, etc. directly; never destructure those names from helpers (`const { bom } = require("./bom.js")`). Import helper files under a project-specific name such as `const bomHelpers = require("./bom.js")`.
|
|
35
|
+
- For static multi-part models, connectors + `matchTo()` are the default way to assemble touching parts.
|
|
36
|
+
- Top-level scripts can return `Assembly` or `SolvedAssembly` directly. Do not call `.toGroup()` just to render an assembly; use it only when you need `ShapeGroup` composition, transforms, or named-child lookup.
|
|
37
|
+
- `Import.svgSketch()` loads SVG files (file format loader, not a module import).
|
|
38
|
+
- `.placeReference('bottom', [0,0,0])` aligns any built-in anchor to a world coordinate; also works with custom `.withReferences()`.
|
|
39
|
+
- Plain `.js` modules hold shared helpers/constants (not model imports).
|
|
40
|
+
|
|
41
|
+
### Source Map
|
|
42
|
+
|
|
43
|
+
Load groups top-to-bottom, stopping when you have what the task needs.
|
|
44
|
+
|
|
45
|
+
#### 1. Core API (always read first)
|
|
46
|
+
|
|
47
|
+
Execution model, colors, coordinate system, primitives, booleans, patterns, imports, parameters, topology, edge queries.
|
|
48
|
+
|
|
49
|
+
- `docs/permanent/API/core/concepts.md`
|
|
50
|
+
- `docs/permanent/generated/runtime-names.md`
|
|
51
|
+
- `docs/permanent/generated/core.md`
|
|
52
|
+
|
|
53
|
+
#### 2. Static Assembly and Positioning (for any multi-part model)
|
|
54
|
+
|
|
55
|
+
Axis conventions, winding rules, and placement strategy. If parts should touch in the final model, read this group before writing placement code. Connectors + `matchTo()` are the default for mating interfaces; raw `translate()` and `rotate()` are for free offsets, not assembly contracts.
|
|
56
|
+
|
|
57
|
+
- `docs/permanent/guides/coordinate-system.md`
|
|
58
|
+
- `docs/permanent/guides/positioning.md`
|
|
59
|
+
|
|
60
|
+
#### 3. Sketch APIs
|
|
61
|
+
|
|
62
|
+
2D construction, transforms, booleans, paths, on-face sketching, extrusion, anchors, text, regions.
|
|
63
|
+
|
|
64
|
+
- `docs/permanent/generated/sketch.md`
|
|
65
|
+
|
|
66
|
+
#### 4. Curves and Surfacing (for lofts, sweeps, splines)
|
|
67
|
+
|
|
68
|
+
Smooth curves, Hermite splines, lofted and swept solids. For straps, inlays, guards, brace members, vents, or physical bands that live on a carrier surface, use `Carrier` + `SurfaceBody` surface-member primitives before reaching for `variableSweep`, SDF sculpting, or manual boolean overlap recipes.
|
|
69
|
+
|
|
70
|
+
- `docs/permanent/guides/surface-members.md`
|
|
71
|
+
- `docs/permanent/generated/curves.md`
|
|
72
|
+
|
|
73
|
+
#### 5. Assemblies and Mechanisms (for joints or kinematics)
|
|
74
|
+
|
|
75
|
+
Assembly graph, joint types, couplings, validation, robot export.
|
|
76
|
+
|
|
77
|
+
- `docs/permanent/generated/assembly.md`
|
|
78
|
+
|
|
79
|
+
#### 6. Sheet Metal (for bent parts, K-factor, flat patterns)
|
|
80
|
+
|
|
81
|
+
Bend operations, flat pattern unfolding, K-factor configuration.
|
|
82
|
+
|
|
83
|
+
- `docs/permanent/generated/sheet-metal.md`
|
|
84
|
+
|
|
85
|
+
#### 7. Output and Export (for STL/3MF/STEP, BOM, dimensions)
|
|
86
|
+
|
|
87
|
+
Mesh export, exact geometry export, bill of materials, dimension annotations.
|
|
88
|
+
|
|
89
|
+
- `docs/permanent/generated/output.md`
|
|
90
|
+
|
|
91
|
+
#### 8. Toolbox (fasteners and standard parts)
|
|
92
|
+
|
|
93
|
+
Parametric bolts, nuts, washers, standard hardware, gears, pipes, and structural profiles.
|
|
94
|
+
|
|
95
|
+
- `docs/permanent/generated/lib.md`
|
|
96
|
+
- `docs/permanent/generated/wood.md`
|
|
97
|
+
|
|
98
|
+
#### 9. Runtime Viewport APIs (for cut planes, exploded views, hiding, and animation playback)
|
|
99
|
+
|
|
100
|
+
Viewer-only APIs such as cutPlane, explodeView, render labels, comparison references, and runtime display behavior.
|
|
101
|
+
|
|
102
|
+
- `docs/permanent/generated/viewport.md`
|
|
103
|
+
|
|
104
|
+
#### 10. Recipes and Debugging (for patterns and troubleshooting)
|
|
105
|
+
|
|
106
|
+
Modeling patterns, debugging tactics, copyable snippets.
|
|
107
|
+
|
|
108
|
+
- `docs/permanent/guides/scene-presentation.md`
|
|
109
|
+
- `docs/permanent/guides/joint-design.md`
|
|
110
|
+
|
|
111
|
+
#### 11. CLI (for validation/render/export tasks)
|
|
112
|
+
|
|
113
|
+
Test-run, export pipelines, debug flags.
|
|
114
|
+
|
|
115
|
+
- `docs/permanent/CLI.md`
|
|
116
|
+
- `docs/permanent/guides/inspection-bundles.md`
|
|
117
|
+
|
|
118
|
+
#### SDF Modeling (smooth booleans, TPMS, deformations, fromFunction)
|
|
119
|
+
|
|
120
|
+
Primitives, smooth booleans, TPMS lattices, twist/bend/displace, morph, custom functions, gotchas. The doc preamble's precision caution applies to every SDF workflow.
|
|
121
|
+
|
|
122
|
+
- `docs/permanent/generated/sdf.md`
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-forgecad-skill.mjs — do not edit. Edit agent-skill-library/*/SKILL.md instead. -->
|
|
2
|
+
|
|
3
|
+
# ForgeCAD Skills
|
|
4
|
+
|
|
5
|
+
These are the agent skills shipped in the ForgeCAD npm package. The default install path installs the core `forgecad` skill plus the companion workflow skills listed here.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
forgecad skill install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| Skill | Installed by | Purpose |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| [forgecad](/docs/skills/forgecad) | `forgecad skill install` | ForgeCAD model authoring, editing, debugging, and execution guidance for .forge.js, SVG-import, assembly, and CLI workflows. Use when building or modifying ForgeCAD geometry, structuring multi-file projects, validating scripts, or using ForgeCAD export/render tooling. |
|
|
14
|
+
| [forgecad-3d-reconstruction](/docs/skills/forgecad-3d-reconstruction) | `forgecad skill install` | Reconstruct a parametric ForgeCAD model from an existing 3D CAD or mesh file such as STL, OBJ, 3MF, STEP, or STP; inspect the source asset directly, author real ForgeCAD geometry, and iteratively compare the candidate with `forgecad compare 3d`. |
|
|
15
|
+
| [forgecad-blockout-model](/docs/skills/forgecad-blockout-model) | `forgecad skill install` | Create rough high-level ForgeCAD concept models from simple primitives to explore layout, proportions, motion, and part relationships without production detail. Use when asked for a quick model sketch, blockout, spatial mockup, or intuitive low-detail 3D concept. |
|
|
16
|
+
| [forgecad-component-model](/docs/skills/forgecad-component-model) | `forgecad skill install` | Enforce the ForgeCAD Component Model when building multi-part assemblies. Parts build at origin, connectors position them, data flows down from parent. Use when building or reviewing any multi-file ForgeCAD project. |
|
|
17
|
+
| [forgecad-high-level-spec](/docs/skills/forgecad-high-level-spec) | `forgecad skill install` | Write a high-level design document (HLD) for a model, mechanism, or assembly before detailed specification or coding. Use when starting a new design, rethinking an existing one, or when the user asks to spec out, plan, or think through a model at a high level. Works backwards from requirements — defines the problem, explores alternatives, records decisions. Produces a right-sized design document for review and iteration. |
|
|
18
|
+
| [forgecad-image-replicator](/docs/skills/forgecad-image-replicator) | `forgecad skill install` | Build real ForgeCAD geometry from one or more reference images by treating images as evidence, inferring the object, then validating against both reference-matched and canonical views. |
|
|
19
|
+
| [forgecad-lld](/docs/skills/forgecad-lld) | `forgecad skill install` | Write a Low-Level Design (LLD) for a CAD model — exact dimensions, constraints, parameters, and verification criteria. Use after a High-Level Design (HLD) exists and decisions are locked, or for simple parts that don't need an HLD. The detailed design document that code implements. |
|
|
20
|
+
| [forgecad-make-a-model](/docs/skills/forgecad-make-a-model) | `forgecad skill install` | Create manufacture-realistic prototype ForgeCAD (.forge.js) models in the active CAD project. Handles file placement, invokes the forgecad skill for API guidance, and validates the result. |
|
|
21
|
+
| [forgecad-model-grader](/docs/skills/forgecad-model-grader) | `forgecad skill install` | Analyze, verify, and grade ForgeCAD or CAD-as-code models against a user requirement, design brief, prompt, reference, or acceptance criteria. Use when asked to evaluate, judge, QA, benchmark, score, rate, or compare a CAD model; render it from multiple angles, run targeted inspections when needed, visually verify the evidence, and produce a 0-10 score with concise justification. |
|
|
22
|
+
| [forgecad-prepare-prompt](/docs/skills/forgecad-prepare-prompt) | `forgecad skill install` | Turn a fuzzy physical product, mechanism, or CAD artifact request into a concrete manufacture-realistic prototype ForgeCAD build brief and a single master prompt for the modeling pass. Use when the engineering brief is incomplete, manufacturing/process choice is underspecified, or the work needs a specific operating story to avoid generic toy solutions. |
|
|
23
|
+
| [forgecad-project](/docs/skills/forgecad-project) | `forgecad skill install` | ForgeCAD project CLI workflow — creating, managing, syncing projects and files on forgecad.io. Covers init, push, pull, file operations, member management, publishing, and sharing. |
|
|
24
|
+
| [forgecad-reconstruction-benchmark](/docs/skills/forgecad-reconstruction-benchmark) | `forgecad skill install` | Solve ForgeCAD CAD reconstruction benchmark or RL episodes in a prepared workspace by rebuilding a visible reference asset as readable parametric ForgeCAD in the fixed submission path, using visual and geometric self-checks while respecting sandbox limits. |
|
|
25
|
+
| [forgecad-render-inspect](/docs/skills/forgecad-render-inspect) | `forgecad skill install` | Run and interpret ForgeCAD inspection bundles for model verification. Use when asked to inspect a ForgeCAD model, analyze an inspection bundle, validate collisions, wall thickness, connectivity, floating bodies, sections, masks, depth, normals, or Zebra stripes. |
|
|
26
|
+
| [forgecad-visual-spec](/docs/skills/forgecad-visual-spec) | `forgecad skill install` | Turn a concrete ForgeCAD artifact, build brief, HLD, or existing model into builder-honest image prompts for AI image models. Use when the user wants visual-spec renders that show the final product while keeping mechanisms, seams, hardware, and build cues visible instead of drifting into concept art. |
|
|
@@ -11,7 +11,7 @@ compareWith("../assets/sphere.stl", {
|
|
|
11
11
|
label: "Original STL Sphere",
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
const source =
|
|
14
|
+
const source = Import.mesh("../assets/sphere.stl");
|
|
15
15
|
const bb = source.boundingBox();
|
|
16
16
|
const width = bb.max[0] - bb.min[0];
|
|
17
17
|
const depth = bb.max[1] - bb.min[1];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const width = param('Width', 112);
|
|
2
2
|
const depth = param('Depth', 78);
|
|
3
3
|
const height = param('Height', 44);
|
|
4
|
-
const showConstruction =
|
|
4
|
+
const showConstruction = Param.bool('Show Construction Sheets', false);
|
|
5
5
|
const constructionOffset = param('Construction Offset', 22);
|
|
6
6
|
|
|
7
7
|
const materials = {
|
|
@@ -23,8 +23,10 @@ const tapered = circle2d(r).extrude(h, { scaleTop: taper })
|
|
|
23
23
|
.translate(2 * spacing, 0, 0)
|
|
24
24
|
.color('#44cc88');
|
|
25
25
|
|
|
26
|
-
// 4. Combined: twist + taper
|
|
27
|
-
const
|
|
26
|
+
// 4. Combined: twist + taper, on a five-pointed star built from layout primitives
|
|
27
|
+
const tips = polygonVertices(5, r, { startDeg: -90 });
|
|
28
|
+
const valleys = polygonVertices(5, r * 0.5, { startDeg: -90 + 180 / 5 });
|
|
29
|
+
const combo = polygon(tips.flatMap((tip, i) => [tip, valleys[i]])).extrude(h, {
|
|
28
30
|
twist: twist,
|
|
29
31
|
scaleTop: taper,
|
|
30
32
|
divisions: 32,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field-interpolated loft for profiles that change character.
|
|
3
|
+
*
|
|
4
|
+
* Red uses the regular stitched loft. Green uses Loft.field(), which blends
|
|
5
|
+
* profile signed-distance fields and avoids boundary-point correspondence.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
scene({
|
|
9
|
+
camera: { position: [80, -105, 55], target: [0, 0, 9], fov: 36 },
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const heights = [0, 5, 11, 17];
|
|
13
|
+
const shank = circle2d(6, 80);
|
|
14
|
+
|
|
15
|
+
function crossProfile(radius, armWidth) {
|
|
16
|
+
return union2d(rect(radius * 2.05, armWidth), rect(armWidth, radius * 2.05));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const profiles = [
|
|
20
|
+
shank,
|
|
21
|
+
shank,
|
|
22
|
+
crossProfile(6, 3),
|
|
23
|
+
crossProfile(4.3, 2.1),
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const stitched = loft(profiles, heights, { edgeLength: 0.22 })
|
|
27
|
+
.translate(-12, 0, 0)
|
|
28
|
+
.color('#c2574b');
|
|
29
|
+
|
|
30
|
+
const field = Loft.field(profiles, heights, {
|
|
31
|
+
edgeLength: 0.14,
|
|
32
|
+
maxTriangles: 5000,
|
|
33
|
+
})
|
|
34
|
+
.translate(12, 0, 0)
|
|
35
|
+
.color('#4c8f73');
|
|
36
|
+
|
|
37
|
+
return [
|
|
38
|
+
{ name: 'regular loft', shape: stitched },
|
|
39
|
+
{ name: 'field loft', shape: field },
|
|
40
|
+
];
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Debug Highlight API — visual debugging for any geometry
|
|
3
3
|
*
|
|
4
|
-
* highlight() accepts points, edges, planes, faces, shapes, and sketch entities.
|
|
4
|
+
* Viewport.highlight() accepts points, edges, planes, faces, shapes, and sketch entities.
|
|
5
5
|
* Each highlighted item renders as a colored overlay in the viewport.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const b = box(30, 20, 15);
|
|
9
9
|
|
|
10
10
|
// Point highlight — rendered as a small sphere
|
|
11
|
-
highlight([0, 0, 0], { color: 'cyan', label: 'origin' });
|
|
12
|
-
highlight([30, 20, 15], { color: 'cyan', label: 'corner' });
|
|
11
|
+
Viewport.highlight([0, 0, 0], { color: 'cyan', label: 'origin' });
|
|
12
|
+
Viewport.highlight([30, 20, 15], { color: 'cyan', label: 'corner' });
|
|
13
13
|
|
|
14
14
|
// Edge highlight — rendered as a line segment with endpoint spheres
|
|
15
|
-
highlight([[0, 0, 0], [30, 20, 15]], { color: 'yellow', label: 'diagonal' });
|
|
15
|
+
Viewport.highlight([[0, 0, 0], [30, 20, 15]], { color: 'yellow', label: 'diagonal' });
|
|
16
16
|
|
|
17
17
|
// Plane highlight — rendered as a semi-transparent disc with normal arrow
|
|
18
|
-
highlight({ normal: [0, 0, 1], offset: 7.5 }, { color: 'lime', label: 'z=7.5', size: 30 });
|
|
18
|
+
Viewport.highlight({ normal: [0, 0, 1], offset: 7.5 }, { color: 'lime', label: 'z=7.5', size: 30 });
|
|
19
19
|
|
|
20
20
|
// Plane from normal + point
|
|
21
|
-
highlight({ normal: [1, 0, 0], point: [15, 0, 0] }, { color: 'orange', label: 'x=15' });
|
|
21
|
+
Viewport.highlight({ normal: [1, 0, 0], point: [15, 0, 0] }, { color: 'orange', label: 'x=15' });
|
|
22
22
|
|
|
23
23
|
// Face reference — highlight a named face from a tracked shape
|
|
24
|
-
highlight(b.face('top'), { color: 'red', label: 'top face' });
|
|
24
|
+
Viewport.highlight(b.face('top'), { color: 'red', label: 'top face' });
|
|
25
25
|
|
|
26
26
|
// Edge reference — highlight a named edge
|
|
27
|
-
highlight(b.edge('top-right'), { color: 'blue', label: 'top-right edge' });
|
|
27
|
+
Viewport.highlight(b.edge('top-right'), { color: 'blue', label: 'top-right edge' });
|
|
28
28
|
|
|
29
29
|
// Shape highlight — transparent colored overlay on the entire shape
|
|
30
|
-
highlight(b, { color: '#ff00ff' });
|
|
30
|
+
Viewport.highlight(b, { color: '#ff00ff' });
|
|
31
31
|
|
|
32
32
|
// Intermediary shape highlight — snapshot a temporary shape that is not returned
|
|
33
33
|
const temporary = b.translate(40, 0, 0);
|
|
34
|
-
highlight(temporary, { color: '#00ffff', label: 'temporary snapshot' });
|
|
34
|
+
Viewport.highlight(temporary, { color: '#00ffff', label: 'temporary snapshot' });
|
|
35
35
|
|
|
36
36
|
return b;
|
|
@@ -8,7 +8,7 @@ const slatThickness = Param.number("Slat thickness", 1.2, { min: 0.4, max: 3, un
|
|
|
8
8
|
const gap = Param.number("Gap", 1.5, { min: 0.5, max: 5, unit: "mm" });
|
|
9
9
|
|
|
10
10
|
// Import an external mesh file
|
|
11
|
-
const mesh =
|
|
11
|
+
const mesh = Import.mesh("assets/sphere.stl");
|
|
12
12
|
const bb = mesh.boundingBox();
|
|
13
13
|
const size = [bb.max[0] - bb.min[0], bb.max[1] - bb.min[1], bb.max[2] - bb.min[2]];
|
|
14
14
|
const center = [(bb.min[0] + bb.max[0]) / 2, (bb.min[1] + bb.max[1]) / 2, (bb.min[2] + bb.max[2]) / 2];
|
|
@@ -22,7 +22,7 @@ const slab = Sculpt.box(86, 44, 20, { radius: 8 }).polish({
|
|
|
22
22
|
reflectivity: 0.66,
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
const circularWindow = Sculpt.
|
|
25
|
+
const circularWindow = Sculpt.disk(13, 34).tilt(90, 'x').at(-22, 0, 0);
|
|
26
26
|
const ringGroove = Sculpt.torus(25, 2.8).tilt(90, 'x').at(20, 0, 0);
|
|
27
27
|
const diagonalBite = Sculpt.curve(
|
|
28
28
|
[
|
|
@@ -18,11 +18,8 @@ const smoothCarve = sdf.smoothDifference(
|
|
|
18
18
|
.translate(40, 0, 0)
|
|
19
19
|
.color('#cc4444');
|
|
20
20
|
|
|
21
|
-
const morphed = sdf.
|
|
22
|
-
sdf.
|
|
23
|
-
sdf.box(20, 20, 20),
|
|
24
|
-
0.5,
|
|
25
|
-
)
|
|
21
|
+
const morphed = sdf.sphere(12)
|
|
22
|
+
.morph(sdf.box(20, 20, 20), 0.5)
|
|
26
23
|
.translate(80, 0, 0)
|
|
27
24
|
.color('#44cc88');
|
|
28
25
|
|
|
@@ -41,16 +41,16 @@ const mergedCircles = union2d(
|
|
|
41
41
|
circle2d(radius).translate(bodyWidth - shoulderInset, bodyHeight + shoulderRise),
|
|
42
42
|
),
|
|
43
43
|
).color('#7f5af0');
|
|
44
|
-
const selectiveFillet =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
const selectiveFillet = polygon(roofPoints)
|
|
45
|
+
.filletCorner([bodyWidth - shoulderInset, bodyHeight + shoulderRise], radius)
|
|
46
|
+
.filletCorner([bodyWidth / 2, bodyHeight + peakRise], radius)
|
|
47
|
+
.filletCorner([shoulderInset, bodyHeight + shoulderRise], radius)
|
|
48
|
+
.color('#e63946');
|
|
49
49
|
|
|
50
50
|
return [
|
|
51
51
|
{ name: "Raw polygon", sketch: rawProfile },
|
|
52
52
|
{ name: "offset(-r).offset(+r)", sketch: roundedAllCorners.translate(gap, 0) },
|
|
53
53
|
{ name: "stroke(..., 'Round')", sketch: strokedCenterline.translate(gap * 2, 0) },
|
|
54
54
|
{ name: "union2d() of circles", sketch: mergedCircles.translate(gap * 3, 0) },
|
|
55
|
-
{ name: "
|
|
55
|
+
{ name: ".filletCorner([x, y], r)", sketch: selectiveFillet.translate(gap * 4, 0) },
|
|
56
56
|
];
|
|
@@ -7,8 +7,8 @@ const cage = SurfaceBody('road-bike-bottle-cage')
|
|
|
7
7
|
.at(bottle.back({ offset: 7 }))
|
|
8
8
|
.size(30, 118)
|
|
9
9
|
.section({ thickness: 5.4, edgeRadius: 1 })
|
|
10
|
-
.slot('upper-mount-slot',
|
|
11
|
-
.slot('lower-mount-slot',
|
|
10
|
+
.slot('upper-mount-slot', SurfaceMembers.roundedSlot({ length: 12, width: 5.7 }).verticalTravel(6).at({ z: 82 }))
|
|
11
|
+
.slot('lower-mount-slot', SurfaceMembers.roundedSlot({ length: 12, width: 5.7 }).verticalTravel(6).at({ z: 18 }))
|
|
12
12
|
.member('left-arm')
|
|
13
13
|
.band()
|
|
14
14
|
.path(bottle.path().from({ angle: -145, z: 18 }).through({ angle: -116, z: 42 }).through({ angle: -72, z: 78 }).to({ angle: -34, z: 112 }))
|
|
@@ -24,7 +24,7 @@ const cage = SurfaceBody('road-bike-bottle-cage')
|
|
|
24
24
|
.band()
|
|
25
25
|
.path(bottle.path().from({ angle: -135, z: 92 }).through({ angle: 180, z: 54 }).to({ angle: 135, z: 92 }))
|
|
26
26
|
.section({ width: 12, thickness: 5, edgeRadius: 1.3 })
|
|
27
|
-
.cutout('lightening-slot',
|
|
27
|
+
.cutout('lightening-slot', SurfaceMembers.roundedSlot({ length: 54, width: 7 }))
|
|
28
28
|
.join('left-arm', 'lower-cup').blend({ radius: 6, style: 'structural' })
|
|
29
29
|
.join('right-arm', 'lower-cup').blend({ radius: 6, style: 'structural' })
|
|
30
30
|
.join('front-sweep', ['left-arm', 'right-arm']).blend({ radius: 5 })
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Product.scenePreset('product');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
scene({
|
|
10
10
|
camera: { position: [78, -180, 88], target: [0, 0, 58], fov: 31 },
|
|
11
11
|
});
|
|
12
12
|
|
|
@@ -39,12 +39,12 @@ const inserts = SurfaceBody('surface-member-conformal-inserts')
|
|
|
39
39
|
.band()
|
|
40
40
|
.path(handle.surface('left').path().from({ u: 0.3, v: 0.18 }).through({ u: 0.34, v: 0.48 }).to({ u: 0.32, v: 0.78 }))
|
|
41
41
|
.section({ width: 5.8, thickness: 0.9, material: rubber })
|
|
42
|
-
.features(
|
|
42
|
+
.features(SurfaceMembers.ribs({ count: 10, height: 0.28 }))
|
|
43
43
|
.member('right-grip')
|
|
44
44
|
.band()
|
|
45
45
|
.path(handle.surface('right').path().from({ u: 0.7, v: 0.18 }).through({ u: 0.66, v: 0.48 }).to({ u: 0.68, v: 0.78 }))
|
|
46
46
|
.section({ width: 5.8, thickness: 0.9, material: rubber })
|
|
47
|
-
.features(
|
|
47
|
+
.features(SurfaceMembers.ribs({ count: 10, height: 0.28 }))
|
|
48
48
|
.build();
|
|
49
49
|
|
|
50
50
|
return group(
|
|
@@ -15,7 +15,7 @@ const inlay = SurfaceBody('razor-overmold')
|
|
|
15
15
|
.band()
|
|
16
16
|
.path(handle.surface('left').path().from({ u: 0.35, v: 0.12 }).through({ u: 0.22, v: 0.55 }).to({ u: 0.42, v: 0.9 }))
|
|
17
17
|
.section({ width: 8, thickness: 0.9, edgeRadius: 0.45, material: Product.materials.softRubber({ color: '#22262a' }) })
|
|
18
|
-
.features(
|
|
18
|
+
.features(SurfaceMembers.ribs({ count: 18, height: 0.35 }))
|
|
19
19
|
.member('right-grip')
|
|
20
20
|
.mirrorOf('left-grip')
|
|
21
21
|
.join('left-grip', 'right-grip').blend({ radius: 2, style: 'separate-overmold' })
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Variable Sweep test — tapered tube along a curved spine
|
|
2
2
|
// Demonstrates variableSweep() with different cross-sections at different t values
|
|
3
3
|
|
|
4
|
-
const spine = Curve.
|
|
4
|
+
const spine = Curve.Fit([
|
|
5
5
|
[0, 0, 0],
|
|
6
6
|
[20, 0, 10],
|
|
7
7
|
[40, 10, 20],
|
|
8
8
|
[60, 10, 30],
|
|
9
|
-
]
|
|
9
|
+
]);
|
|
10
10
|
|
|
11
11
|
const smallCircle = circle2d(3);
|
|
12
12
|
const largeCircle = circle2d(8);
|
|
@@ -20,6 +20,6 @@ const tapered = variableSweep(spine, [
|
|
|
20
20
|
samples: 64,
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
verify.boundingBoxSize('variableSweep along Curve.
|
|
23
|
+
verify.boundingBoxSize('variableSweep along Curve.Fit keeps the tapered tube envelope', tapered, [66, 22, 36], 8);
|
|
24
24
|
|
|
25
25
|
return tapered.color('#8899aa');
|
|
@@ -121,20 +121,28 @@ function twistAt(r) {
|
|
|
121
121
|
return Math.atan(Math.tan(pitchAngle * DEG) * 0.75 * R / r) / DEG;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
// Chord: GA propeller distribution — structural root + Betz-optimal taper
|
|
125
|
-
// Peaks near 25% span, maintains width
|
|
126
|
-
//
|
|
124
|
+
// Chord: GA propeller distribution — structural root + Betz-optimal taper,
|
|
125
|
+
// closed by an elliptical rounded tip. Peaks near 25% span, maintains width
|
|
126
|
+
// at root for structural attachment, cosine taper to 92% span, then an
|
|
127
|
+
// elliptical arc rounds the tip in planform (real propellers never taper to
|
|
128
|
+
// a razor point — the tip cap needs finite chord to close cleanly).
|
|
129
|
+
const TIP_ROUND_START = 0.92;
|
|
130
|
+
|
|
127
131
|
function chordAt(rNorm) {
|
|
128
|
-
const r = rNorm;
|
|
132
|
+
const r = Math.min(rNorm, TIP_ROUND_START);
|
|
129
133
|
// Root: rises from 55% at hub to 100% at ~25% span
|
|
130
134
|
const rootRise = r < 0.25
|
|
131
135
|
? 0.55 + 0.45 * Math.pow(r / 0.25, 0.7)
|
|
132
136
|
: 1.0;
|
|
133
|
-
// Cosine taper from peak to
|
|
137
|
+
// Cosine taper from peak to the start of the tip round
|
|
134
138
|
const tipTaper = r < 0.25
|
|
135
139
|
? 1.0
|
|
136
140
|
: Math.pow(Math.cos(Math.PI / 2 * ((r - 0.25) / 0.75)), 0.5);
|
|
137
|
-
|
|
141
|
+
const base = maxChord * rootRise * tipTaper;
|
|
142
|
+
if (rNorm <= TIP_ROUND_START) return base;
|
|
143
|
+
// Elliptical closure from 92% span to the tip, floored for a clean cap
|
|
144
|
+
const s = (rNorm - TIP_ROUND_START) / (1 - TIP_ROUND_START);
|
|
145
|
+
return Math.max(base * Math.sqrt(Math.max(0, 1 - s * s)), maxChord * 0.05);
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
// Thickness ratio: thick root for structure, thin tip for aero.
|
|
@@ -163,27 +171,41 @@ function camberAt(rNorm) {
|
|
|
163
171
|
// - Zero pitch → chord along tangential (Y in world)
|
|
164
172
|
// - Positive β → leading edge tilts toward thrust axis (+Z)
|
|
165
173
|
|
|
166
|
-
const NUM_STATIONS =
|
|
174
|
+
const NUM_STATIONS = 24;
|
|
167
175
|
const NACA_PTS = 40; // points per airfoil surface (80 total outline)
|
|
168
176
|
const CAMBER_POS = 0.4; // max camber at 40% chord (standard for props)
|
|
177
|
+
const rRoot = rHub * 0.55; // blade shank starts inside the spinner
|
|
169
178
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const r = rHub + rNorm * (R - rHub);
|
|
176
|
-
|
|
177
|
-
const chord = Math.max(chordAt(rNorm), 5); // minimum 5mm to avoid degenerate polygon
|
|
178
|
-
const thick = thicknessRatioAt(rNorm);
|
|
179
|
+
// Airfoil section at a given normalized span position (clamped to the
|
|
180
|
+
// aerodynamic range — the shank inside the spinner reuses the hub section).
|
|
181
|
+
function bladeSection(rNorm, r) {
|
|
182
|
+
const chord = chordAt(rNorm);
|
|
183
|
+
const thick = thicknessRatioAt(rNorm);
|
|
179
184
|
const camber = camberAt(rNorm);
|
|
180
|
-
const twist
|
|
185
|
+
const twist = twistAt(Math.max(r, rHub));
|
|
181
186
|
|
|
182
|
-
// Generate NACA points and rotate to blade orientation
|
|
183
187
|
let pts = nacaPoints(camber, CAMBER_POS, thick, chord, NACA_PTS);
|
|
184
188
|
pts = rotate2D(pts, 90 + twist); // 90° aligns chord with tangential, +twist adds pitch
|
|
189
|
+
return polygon(pts);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const profiles = [];
|
|
193
|
+
const heights = [];
|
|
185
194
|
|
|
186
|
-
|
|
195
|
+
// Shank stations buried inside the spinner — a constant hub section carries
|
|
196
|
+
// the blade to a structural attachment instead of floating at the hub surface.
|
|
197
|
+
for (const r of [rRoot, rHub * 0.92]) {
|
|
198
|
+
profiles.push(bladeSection(0, rHub));
|
|
199
|
+
heights.push(r);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Aerodynamic stations from the hub surface to the tip. Cosine spacing
|
|
203
|
+
// clusters stations at the root (where the BEMT twist gradient is steepest)
|
|
204
|
+
// and at the tip (where the planform rounds off).
|
|
205
|
+
for (let i = 0; i <= NUM_STATIONS; i++) {
|
|
206
|
+
const rNorm = 0.5 - 0.5 * Math.cos(Math.PI * i / NUM_STATIONS);
|
|
207
|
+
const r = rHub + rNorm * (R - rHub);
|
|
208
|
+
profiles.push(bladeSection(rNorm, r));
|
|
187
209
|
heights.push(r);
|
|
188
210
|
}
|
|
189
211
|
|
|
@@ -195,24 +217,27 @@ const blade = bladeRaw.rotateY(90);
|
|
|
195
217
|
|
|
196
218
|
// ─── Spinner / Hub ──────────────────────────────────────────────
|
|
197
219
|
//
|
|
198
|
-
// Ogive spinner:
|
|
220
|
+
// Ogive spinner: elliptical nose + cylindrical body + rear taper.
|
|
199
221
|
// Built as a loft of circular profiles along the propeller axis (Z).
|
|
222
|
+
// The cylindrical body is centered on the blade plane (z = 0) so the
|
|
223
|
+
// blade shanks bury fully into the spinner at maximum radius.
|
|
200
224
|
|
|
201
225
|
const spinnerLen = hubDiameter * 1.3;
|
|
202
226
|
const spinnerR = rHub * 0.95;
|
|
203
|
-
const HUB_STATIONS =
|
|
227
|
+
const HUB_STATIONS = 36;
|
|
204
228
|
|
|
205
229
|
const hubProfiles = [];
|
|
206
230
|
const hubHeights = [];
|
|
207
231
|
|
|
208
232
|
for (let i = 0; i <= HUB_STATIONS; i++) {
|
|
209
|
-
|
|
233
|
+
// Cosine clustering at the nose, where curvature is highest
|
|
234
|
+
const t = 1 - Math.cos(Math.PI / 2 * i / HUB_STATIONS); // 0 = nose (front), 1 = back
|
|
210
235
|
|
|
211
236
|
let r;
|
|
212
237
|
if (t < 0.35) {
|
|
213
|
-
// Nose:
|
|
214
|
-
const tn = t / 0.35;
|
|
215
|
-
r = spinnerR * Math.sqrt(tn);
|
|
238
|
+
// Nose: elliptical ogive — rounds to a closed dome instead of a flat disc
|
|
239
|
+
const tn = (0.35 - t) / 0.35;
|
|
240
|
+
r = spinnerR * Math.sqrt(Math.max(0, 1 - tn * tn));
|
|
216
241
|
} else if (t < 0.7) {
|
|
217
242
|
// Cylindrical body where blades attach
|
|
218
243
|
r = spinnerR;
|
|
@@ -222,41 +247,51 @@ for (let i = 0; i <= HUB_STATIONS; i++) {
|
|
|
222
247
|
r = spinnerR * (1 - 0.25 * tb * tb);
|
|
223
248
|
}
|
|
224
249
|
|
|
225
|
-
hubProfiles.push(circle2d(Math.max(r,
|
|
226
|
-
hubHeights.push(-spinnerLen * 0.
|
|
250
|
+
hubProfiles.push(circle2d(Math.max(r, 0.8)));
|
|
251
|
+
hubHeights.push(-spinnerLen * 0.525 + t * spinnerLen);
|
|
227
252
|
}
|
|
228
253
|
|
|
229
254
|
const spinner = loft(hubProfiles, hubHeights, { edgeLength: meshRes });
|
|
230
255
|
|
|
231
256
|
// ─── Assembly ───────────────────────────────────────────────────
|
|
257
|
+
//
|
|
258
|
+
// Real propellers are built from separate parts: the blades bolt into an
|
|
259
|
+
// internal hub barrel, and the spinner is a thin dome with blade-shaped
|
|
260
|
+
// cutouts (with running clearance) that the blades pass through. Modeling
|
|
261
|
+
// it that way also keeps the aerodynamic skin free of boolean cuts and
|
|
262
|
+
// near-tangent surface overlaps.
|
|
232
263
|
|
|
233
264
|
const allBlades = circularPattern(blade, numBlades);
|
|
234
265
|
|
|
266
|
+
// Internal hub barrel the blade shanks merge into (hidden by the spinner)
|
|
267
|
+
const hubBarrel = cylinder(spinnerLen * 0.5, rHub * 0.62)
|
|
268
|
+
.translate(0, 0, -spinnerLen * 0.2);
|
|
269
|
+
|
|
270
|
+
const bladeAssembly = union(allBlades, hubBarrel);
|
|
271
|
+
|
|
272
|
+
// Blade-shaped cutouts through the spinner with a small running
|
|
273
|
+
// clearance: the blade cross-section is grown in chord and thickness
|
|
274
|
+
// (x/y before the radial rotation), keeping the span direction exact.
|
|
275
|
+
const bladeCutout = bladeRaw.scale([1.04, 1.04, 1]).rotateY(90);
|
|
276
|
+
const spinnerCutouts = circularPattern(bladeCutout, numBlades);
|
|
277
|
+
|
|
235
278
|
// ─── Optional: Airfoil Section Visualization ────────────────────
|
|
236
279
|
//
|
|
237
280
|
// When enabled, shows thin extrusions of each airfoil cross-section
|
|
238
281
|
// along the blade, revealing the twist and chord distribution.
|
|
239
282
|
|
|
240
283
|
const result = [
|
|
241
|
-
{ name: "Blades", shape:
|
|
242
|
-
{ name: "Spinner", shape: spinner.color('#c8c8cc') },
|
|
284
|
+
{ name: "Blades", shape: bladeAssembly.color('#2d2d30') },
|
|
285
|
+
{ name: "Spinner", shape: spinner.subtract(spinnerCutouts).color('#c8c8cc') },
|
|
243
286
|
];
|
|
244
287
|
|
|
245
288
|
if (showSections) {
|
|
246
289
|
const sectionShapes = [];
|
|
247
290
|
for (let i = 0; i <= NUM_STATIONS; i++) {
|
|
248
|
-
const rNorm = i / NUM_STATIONS;
|
|
291
|
+
const rNorm = 0.5 - 0.5 * Math.cos(Math.PI * i / NUM_STATIONS);
|
|
249
292
|
const r = rHub + rNorm * (R - rHub);
|
|
250
293
|
|
|
251
|
-
const
|
|
252
|
-
const thick = thicknessRatioAt(rNorm);
|
|
253
|
-
const camber = camberAt(rNorm);
|
|
254
|
-
const twist = twistAt(r);
|
|
255
|
-
|
|
256
|
-
let pts = nacaPoints(camber, CAMBER_POS, thick, chord, NACA_PTS);
|
|
257
|
-
pts = rotate2D(pts, 90 + twist);
|
|
258
|
-
|
|
259
|
-
const section = polygon(pts).extrude(2)
|
|
294
|
+
const section = bladeSection(rNorm, r).extrude(2)
|
|
260
295
|
.rotateY(90)
|
|
261
296
|
.translate(r, 0, 0)
|
|
262
297
|
.color('#ff4422');
|