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.
Files changed (162) hide show
  1. package/dist/assets/{AdminPage-CXvls4-J.js → AdminPage-DcCnj0qo.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-B27zk8xL.js → BenchmarkPage-BVEpJSVk.js} +1 -1
  3. package/dist/assets/{BlogPage-CMAVvgQL.js → BlogPage-DHaGP50_.js} +1 -1
  4. package/dist/assets/{DocsPage-knf4I4h7.js → DocsPage-CDoxHkz8.js} +40 -859
  5. package/dist/assets/EditorApp-BJ0Dloyh.js +16446 -0
  6. package/dist/assets/{EmbedViewer-D7ZGlFjx.js → EmbedViewer-CRKZbY0y.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-CnevhTE8.js → LandingPageProofDriven-BxHkYRE7.js} +1 -1
  8. package/dist/assets/{LegalPage-BPTUmqeg.js → LegalPage-B-u6FrVv.js} +1 -1
  9. package/dist/assets/{PricingPage-B0D4goG_.js → PricingPage-CzpZ6-Ce.js} +1 -1
  10. package/dist/assets/{SettingsPage-CFF-UgjI.js → SettingsPage-CIZSSAd0.js} +1 -1
  11. package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
  12. package/dist/assets/{app-T0pDcSX4.js → app-DaTMg3nH.js} +1310 -290
  13. package/dist/assets/cli/{render-C5pcIISc.js → render-DPf4AYJK.js} +55 -60
  14. package/dist/assets/{constructionHistoryWorker-Ba2Hm58b.js → constructionHistoryWorker-AwMMWSxg.js} +1103 -349
  15. package/dist/assets/{evalWorker-vkx310U2.js → evalWorker-CjZZWRWW.js} +5209 -2643
  16. package/dist/assets/{inspectWorker-BuTJDVX6.js → inspectWorker-CZsCFtQT.js} +1163 -409
  17. package/dist/assets/{jointPose-B_Cgedn9.js → jointPose-DzQOViQH.js} +1 -1
  18. package/dist/assets/{manifold-BWgsjmAM.js → manifold-BYlzU521.js} +1 -1
  19. package/dist/assets/{manifold-D6IFSkhH.js → manifold-DgXo0T5P.js} +2 -2
  20. package/dist/assets/{manifold-rZexZI0G.js → manifold-K1SkarlQ.js} +1 -1
  21. package/dist/assets/{reportWorker-0AGij1Ru.js → reportWorker-B9nWwSrB.js} +8501 -3393
  22. package/dist/assets/{scalar-sampling-budget-J5cuzxT1.js → scalar-sampling-budget-prBw_s8t.js} +6067 -3479
  23. package/dist/assets/{scanProxyWorker-Vl4Wxa1y.js → scanProxyWorker-2GtDLk-R.js} +1 -1
  24. package/dist/assets/{javascript-1kQXfVaz.js → typescript-DBQ6RN5l.js} +874 -22
  25. package/dist/cli/render.html +1 -1
  26. package/dist/docs/index.html +3 -3
  27. package/dist/docs-raw/AI/usage.md +1 -1
  28. package/dist/docs-raw/CLI.md +77 -240
  29. package/dist/docs-raw/README.md +6 -0
  30. package/dist/docs-raw/component-model.md +17 -150
  31. package/dist/docs-raw/generated/assembly.md +188 -582
  32. package/dist/docs-raw/generated/concepts.md +259 -3501
  33. package/dist/docs-raw/generated/core.md +283 -1250
  34. package/dist/docs-raw/generated/curves.md +387 -1608
  35. package/dist/docs-raw/generated/legacy.md +162 -0
  36. package/dist/docs-raw/generated/lib.md +227 -85
  37. package/dist/docs-raw/generated/output.md +35 -99
  38. package/dist/docs-raw/generated/runtime-names.md +23 -23
  39. package/dist/docs-raw/generated/sdf.md +68 -284
  40. package/dist/docs-raw/generated/sheet-metal.md +68 -335
  41. package/dist/docs-raw/generated/sketch.md +240 -1161
  42. package/dist/docs-raw/generated/viewport.md +75 -316
  43. package/dist/docs-raw/generated/wood.md +21 -49
  44. package/dist/docs-raw/guides/coordinate-system.md +4 -42
  45. package/dist/docs-raw/guides/inspection-bundles.md +44 -442
  46. package/dist/docs-raw/guides/joint-design.md +18 -79
  47. package/dist/docs-raw/guides/positioning.md +21 -143
  48. package/dist/docs-raw/guides/scene-presentation.md +89 -0
  49. package/dist/docs-raw/guides/simready-quickstart.md +171 -0
  50. package/dist/docs-raw/simulation-workflow.md +273 -0
  51. package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +25 -111
  52. package/dist/docs-raw/skills/forgecad-blockout-model.md +20 -117
  53. package/dist/docs-raw/skills/forgecad-component-model.md +23 -107
  54. package/dist/docs-raw/skills/forgecad-high-level-spec.md +47 -155
  55. package/dist/docs-raw/skills/forgecad-image-replicator.md +26 -143
  56. package/dist/docs-raw/skills/forgecad-lld.md +19 -113
  57. package/dist/docs-raw/skills/forgecad-make-a-model.md +112 -532
  58. package/dist/docs-raw/skills/forgecad-model-grader.md +38 -108
  59. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +24 -211
  60. package/dist/docs-raw/skills/forgecad-project.md +13 -131
  61. package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +42 -134
  62. package/dist/docs-raw/skills/forgecad-render-inspect.md +27 -174
  63. package/dist/docs-raw/skills/forgecad-visual-spec.md +32 -112
  64. package/dist/docs-raw/skills/forgecad.md +19 -18
  65. package/dist/docs-raw/skills/index.md +2 -0
  66. package/dist/docs-raw/welcome.md +2 -2
  67. package/dist/index.html +2 -2
  68. package/dist/llms.txt +1 -2
  69. package/dist/sitemap.xml +25 -13
  70. package/dist-cli/{check-compiler-SYQ2PWOB.js → check-compiler-II7NLPAB.js} +1 -1
  71. package/dist-cli/{check-query-propagation-HIAGV62W.js → check-query-propagation-7462TR3R.js} +1 -1
  72. package/dist-cli/{chunk-SPZE3DUY.js → chunk-UWTJCGXF.js} +5848 -2915
  73. package/dist-cli/forgecad.js +3496 -703
  74. package/dist-skill/CONTEXT.md +1797 -7963
  75. package/dist-skill/SKILL.md +15 -15
  76. package/dist-skill/docs/API/core/concepts.md +27 -157
  77. package/dist-skill/docs/CLI.md +77 -240
  78. package/dist-skill/docs/generated/assembly.md +182 -532
  79. package/dist-skill/docs/generated/core.md +283 -1250
  80. package/dist-skill/docs/generated/curves.md +387 -1609
  81. package/dist-skill/docs/generated/lib.md +227 -85
  82. package/dist-skill/docs/generated/output.md +35 -99
  83. package/dist-skill/docs/generated/runtime-names.md +16 -21
  84. package/dist-skill/docs/generated/sdf.md +68 -284
  85. package/dist-skill/docs/generated/sheet-metal.md +68 -335
  86. package/dist-skill/docs/generated/sketch.md +240 -1160
  87. package/dist-skill/docs/generated/viewport.md +75 -223
  88. package/dist-skill/docs/generated/wood.md +21 -49
  89. package/dist-skill/docs/guides/coordinate-system.md +4 -42
  90. package/dist-skill/docs/guides/inspection-bundles.md +44 -442
  91. package/dist-skill/docs/guides/joint-design.md +18 -79
  92. package/dist-skill/docs/guides/positioning.md +21 -143
  93. package/dist-skill/docs/guides/scene-presentation.md +89 -0
  94. package/dist-skill/docs/guides/surface-members.md +26 -0
  95. package/dist-skill/library/forgecad-3d-reconstruction/SKILL.md +23 -111
  96. package/dist-skill/library/forgecad-blockout-model/SKILL.md +18 -117
  97. package/dist-skill/library/forgecad-component-model/SKILL.md +21 -107
  98. package/dist-skill/library/forgecad-high-level-spec/SKILL.md +45 -155
  99. package/dist-skill/library/forgecad-image-replicator/SKILL.md +24 -143
  100. package/dist-skill/library/forgecad-lld/SKILL.md +17 -113
  101. package/dist-skill/library/forgecad-make-a-model/SKILL.md +110 -532
  102. package/dist-skill/library/forgecad-model-grader/SKILL.md +36 -108
  103. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +35 -224
  104. package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +43 -271
  105. package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +30 -99
  106. package/dist-skill/library/forgecad-project/SKILL.md +13 -133
  107. package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +29 -123
  108. package/dist-skill/library/forgecad-render-inspect/SKILL.md +25 -174
  109. package/dist-skill/library/forgecad-visual-spec/SKILL.md +30 -111
  110. package/dist-skill/website/skills/forgecad-3d-reconstruction.md +58 -0
  111. package/dist-skill/website/skills/forgecad-blockout-model.md +49 -0
  112. package/dist-skill/website/skills/forgecad-component-model.md +53 -0
  113. package/dist-skill/website/skills/forgecad-high-level-spec.md +101 -0
  114. package/dist-skill/website/skills/forgecad-image-replicator.md +63 -0
  115. package/dist-skill/website/skills/forgecad-lld.md +41 -0
  116. package/dist-skill/website/skills/forgecad-make-a-model.md +186 -0
  117. package/dist-skill/website/skills/forgecad-model-grader.md +82 -0
  118. package/dist-skill/website/skills/forgecad-prepare-prompt.md +63 -0
  119. package/dist-skill/website/skills/forgecad-project.md +26 -0
  120. package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +60 -0
  121. package/dist-skill/website/skills/forgecad-render-inspect.md +80 -0
  122. package/dist-skill/website/skills/forgecad-visual-spec.md +71 -0
  123. package/dist-skill/website/skills/forgecad.md +122 -0
  124. package/dist-skill/website/skills/index.md +26 -0
  125. package/examples/api/comparison-imported-sphere-candidate.forge.js +1 -1
  126. package/examples/api/conformal-product-ribbon.forge.js +1 -1
  127. package/examples/api/exact-sheet-shell-assembly.forge.js +1 -1
  128. package/examples/api/extrude-options.forge.js +4 -2
  129. package/examples/api/field-loft-drive-tip.forge.js +40 -0
  130. package/examples/api/guided-loft-olive-oil-bottle.forge.js +1 -1
  131. package/examples/api/highlight-debug.forge.js +10 -10
  132. package/examples/api/mesh-import-slats.forge.js +1 -1
  133. package/examples/api/real-product-curves.forge.js +1 -1
  134. package/examples/api/sculpt-box-circle-booleans.forge.js +1 -1
  135. package/examples/api/sdf-shapes.forge.js +2 -5
  136. package/examples/api/sketch-rounding-strategies.forge.js +6 -6
  137. package/examples/api/surface-member-bottle-cage.forge.js +3 -3
  138. package/examples/api/surface-member-conformal-product-ribbon.forge.js +3 -3
  139. package/examples/api/surface-member-razor-inlay.forge.js +1 -1
  140. package/examples/api/variable-sweep-test.forge.js +3 -3
  141. package/examples/mechanical/airplane-propeller.forge.js +74 -39
  142. package/examples/nurbs-surface.forge.js +1 -1
  143. package/examples/products/iphone.forge.js +1 -1
  144. package/examples/robotics/README.md +46 -0
  145. package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
  146. package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
  147. package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
  148. package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
  149. package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
  150. package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
  151. package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
  152. package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
  153. package/examples/robotics/simready-asset-crate.forge.js +79 -0
  154. package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
  155. package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
  156. package/package.json +1 -1
  157. package/dist/assets/EditorApp-BHMQlJ-D.js +0 -14686
  158. package/dist/docs-raw/guides/geometry-conventions.md +0 -52
  159. package/dist/docs-raw/guides/modeling-recipes.md +0 -78
  160. package/dist-skill/docs/guides/geometry-conventions.md +0 -52
  161. package/dist-skill/docs/guides/modeling-recipes.md +0 -78
  162. 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 = importMesh("../assets/sphere.stl");
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];
@@ -5,7 +5,7 @@
5
5
 
6
6
  Product.scenePreset('product');
7
7
 
8
- viewConfig({
8
+ scene({
9
9
  camera: { position: [78, -180, 88], target: [0, 0, 58], fov: 31 },
10
10
  });
11
11
 
@@ -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 = boolParam('Show Construction Sheets', false);
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 combo = star(5, r, r * 0.5).extrude(h, {
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
+ ];
@@ -7,7 +7,7 @@
7
7
 
8
8
  Product.scenePreset('product');
9
9
 
10
- viewConfig({
10
+ scene({
11
11
  camera: { position: [190, -250, 150], target: [8, 0, 88], fov: 34 },
12
12
  });
13
13
 
@@ -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 = importMesh("assets/sphere.stl");
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];
@@ -8,7 +8,7 @@
8
8
 
9
9
  Product.scenePreset('product');
10
10
 
11
- viewConfig({
11
+ scene({
12
12
  camera: { position: [190, -265, 160], target: [0, -18, 70], fov: 34 },
13
13
  });
14
14
 
@@ -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.circle(13, 34).tilt(90, 'x').at(-22, 0, 0);
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.morph(
22
- sdf.sphere(12),
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 = filletCorners(roofPoints, [
45
- { index: 3, radius },
46
- { index: 4, radius },
47
- { index: 5, radius },
48
- ]).color('#e63946');
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: "filletCorners()", sketch: selectiveFillet.translate(gap * 4, 0) },
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', Slot.rounded({ length: 12, width: 5.7 }).verticalTravel(6).at({ z: 82 }))
11
- .slot('lower-mount-slot', Slot.rounded({ length: 12, width: 5.7 }).verticalTravel(6).at({ z: 18 }))
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', Slot.rounded({ length: 54, width: 7 }))
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
- viewConfig({
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(Ribs.repeated({ count: 10, height: 0.28 }))
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(Ribs.repeated({ count: 10, height: 0.28 }))
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(Ribs.repeated({ count: 18, height: 0.35 }))
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.Spline([
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
- ], { tension: 0.4 });
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.Spline keeps the tapered tube envelope', tapered, [66, 22, 36], 8);
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 at root for structural attachment,
126
- // smooth cosine taper to tip.
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 tip (exponent < 1 for fuller planform)
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
- return maxChord * rootRise * tipTaper;
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 = 12;
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
- const profiles = [];
171
- const heights = [];
172
-
173
- for (let i = 0; i <= NUM_STATIONS; i++) {
174
- const rNorm = i / NUM_STATIONS;
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 = twistAt(r);
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
- profiles.push(polygon(pts));
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: parabolic nose + cylindrical body + rear taper.
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 = 10;
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
- const t = i / HUB_STATIONS; // 0 = nose (front), 1 = back
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: parabolic ogive for low drag
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, 3)));
226
- hubHeights.push(-spinnerLen * 0.35 + t * spinnerLen);
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: allBlades.color('#2d2d30') },
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 chord = Math.max(chordAt(rNorm), 5);
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');
@@ -5,4 +5,4 @@ const grid = [
5
5
  [[0,20,1], [10,20,6], [20,20,6], [30,20,1]],
6
6
  [[0,30,0], [10,30,2], [20,30,2], [30,30,0]],
7
7
  ];
8
- return nurbsSurface(grid, { thickness: 1.5 });
8
+ return Surface.Nurbs(grid, { thickness: 1.5 });