forgecad 0.9.14 → 0.9.16

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 (239) hide show
  1. package/LICENSE +6 -4
  2. package/README.md +8 -4
  3. package/dist/assets/{AdminPage-eWGs2K6H.js → AdminPage-CXvls4-J.js} +2 -2
  4. package/dist/assets/{BenchmarkPage-CTrLKfpo.js → BenchmarkPage-B27zk8xL.js} +4 -15
  5. package/dist/assets/{BlogPage-5nPesyds.js → BlogPage-CMAVvgQL.js} +2 -2
  6. package/dist/assets/{DocsPage-C4Y3nbYc.js → DocsPage-knf4I4h7.js} +9 -3
  7. package/dist/assets/EditorApp-BHMQlJ-D.js +14686 -0
  8. package/dist/assets/{EditorApp-BAnckbsk.css → EditorApp-BpjZgzk0.css} +846 -0
  9. package/dist/assets/{EmbedViewer-C8fB4n5U.js → EmbedViewer-D7ZGlFjx.js} +3 -3
  10. package/dist/assets/{LandingPageProofDriven-jSz0LaMM.js → LandingPageProofDriven-CnevhTE8.js} +36 -38
  11. package/dist/assets/LegalPage-BPTUmqeg.js +39 -0
  12. package/dist/assets/LegalPage-BRlScr9A.css +91 -0
  13. package/dist/assets/{PricingPage-B83B90zh.js → PricingPage-B0D4goG_.js} +19 -19
  14. package/dist/assets/{PricingPage-BMedqFef.css → PricingPage-BPF6HKyO.css} +25 -0
  15. package/dist/assets/{SettingsPage-DY889pcu.js → SettingsPage-CFF-UgjI.js} +2 -2
  16. package/dist/assets/app-CE3sYcV7.css +3890 -0
  17. package/dist/assets/{app-bEww1ic4.js → app-T0pDcSX4.js} +3382 -1069
  18. package/dist/assets/cli/{render-Cho2uKG_.js → render-C5pcIISc.js} +477 -29
  19. package/dist/assets/{constructionHistoryWorker-HYwzJY4m.js → constructionHistoryWorker-Ba2Hm58b.js} +928 -243
  20. package/dist/assets/{evalWorker-CjQwJSE-.js → evalWorker-vkx310U2.js} +8883 -6040
  21. package/dist/assets/{forgecad_geometry-CH2nvuLA.js → forgecad_geometry-Dgceylq9.js} +43 -1
  22. package/dist/assets/forgecad_geometry_bg-dD4RNQF1.wasm +0 -0
  23. package/dist/assets/{inspectWorker-DeRnMVv1.js → inspectWorker-BuTJDVX6.js} +1179 -273
  24. package/dist/assets/{javascript-70-4uGcz.js → javascript-1kQXfVaz.js} +1 -1
  25. package/dist/assets/{targets-D6PWsv6X.js → jointPose-B_Cgedn9.js} +71 -3
  26. package/dist/assets/landing-proof-driven-DiGqdtWa.js +18 -0
  27. package/dist/assets/{landing-proof-driven-oFYW6mjz.css → landing-proof-driven-ORyigZ6p.css} +13 -7
  28. package/dist/assets/legalContent-ZfFGMmi4.js +251 -0
  29. package/dist/assets/{manifold-rmfAcdwF.js → manifold-BWgsjmAM.js} +1 -1
  30. package/dist/assets/{manifold-uRzgk5O8.js → manifold-D6IFSkhH.js} +2 -2
  31. package/dist/assets/{manifold-CG9Fokx-.js → manifold-rZexZI0G.js} +1 -1
  32. package/dist/assets/{reportWorker-4cW_ZpoS.js → reportWorker-0AGij1Ru.js} +8659 -12771
  33. package/dist/assets/{scalar-sampling-budget-CfDiFvh7.js → scalar-sampling-budget-J5cuzxT1.js} +8050 -6203
  34. package/dist/assets/{scanProxyWorker-Bs2TDgLw.js → scanProxyWorker-Vl4Wxa1y.js} +50 -6
  35. package/dist/assets/{solver-DuJAO8S6.js → solver-BZ9LPTHs.js} +1 -1
  36. package/dist/assets/solver_bg-DAHZJ_rw.wasm +0 -0
  37. package/dist/assets/{vendor-react-Da3A2QmU.js → vendor-react-6j1Kke-Y.js} +6 -5
  38. package/dist/cli/render.html +1 -1
  39. package/dist/docs/index.html +2 -2
  40. package/dist/docs-raw/AI/ai-native-cad.md +50 -0
  41. package/dist/docs-raw/AI/usage.md +5 -12
  42. package/dist/docs-raw/CLI.md +34 -10
  43. package/dist/docs-raw/component-model.md +27 -11
  44. package/dist/docs-raw/generated/assembly.md +374 -187
  45. package/dist/docs-raw/generated/concepts.md +245 -237
  46. package/dist/docs-raw/generated/core.md +283 -6
  47. package/dist/docs-raw/generated/curves.md +274 -361
  48. package/dist/docs-raw/generated/lib.md +9 -19
  49. package/dist/docs-raw/generated/output.md +29 -4
  50. package/dist/docs-raw/generated/runtime-names.md +49 -0
  51. package/dist/docs-raw/generated/sdf.md +31 -0
  52. package/dist/docs-raw/generated/sheet-metal.md +9 -0
  53. package/dist/docs-raw/generated/sketch.md +44 -1
  54. package/dist/docs-raw/generated/viewport.md +11 -3
  55. package/dist/docs-raw/guides/coordinate-system.md +20 -16
  56. package/dist/docs-raw/guides/geometry-conventions.md +2 -2
  57. package/dist/docs-raw/guides/inspection-bundles.md +2 -1
  58. package/dist/docs-raw/guides/joint-design.md +24 -0
  59. package/dist/docs-raw/guides/positioning.md +13 -3
  60. package/dist/docs-raw/legal/privacy.md +63 -0
  61. package/dist/docs-raw/legal/software-license.md +55 -0
  62. package/dist/docs-raw/legal/terms.md +87 -0
  63. package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +1 -1
  64. package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
  65. package/dist/docs-raw/skills/forgecad-component-model.md +11 -2
  66. package/dist/docs-raw/skills/forgecad-high-level-spec.md +1 -1
  67. package/dist/docs-raw/skills/forgecad-image-replicator.md +8 -8
  68. package/dist/docs-raw/skills/forgecad-lld.md +1 -1
  69. package/dist/docs-raw/skills/forgecad-make-a-model.md +40 -39
  70. package/dist/docs-raw/skills/forgecad-model-grader.md +2 -2
  71. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +2 -2
  72. package/dist/docs-raw/skills/forgecad-project.md +3 -1
  73. package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +1 -1
  74. package/dist/docs-raw/skills/forgecad-render-inspect.md +4 -2
  75. package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
  76. package/dist/docs-raw/skills/forgecad.md +4 -3
  77. package/dist/docs-raw/welcome.md +2 -0
  78. package/dist/index.html +40 -12
  79. package/dist/llms.txt +8 -0
  80. package/dist/site.webmanifest +1 -1
  81. package/dist/sitemap.xml +49 -13
  82. package/dist-cli/{check-compiler-U5SOPN7X.js → check-compiler-SYQ2PWOB.js} +1 -2
  83. package/dist-cli/{check-query-propagation-XOKNSSYU.js → check-query-propagation-HIAGV62W.js} +1 -2
  84. package/dist-cli/{chunk-EXWGNL6K.js → chunk-SPZE3DUY.js} +20659 -17930
  85. package/dist-cli/forgecad.js +3568 -1250
  86. package/dist-cli/{forgecad_geometry-GYVNKPIE.js → forgecad_geometry-QOQIIP53.js} +42 -1
  87. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  88. package/dist-cli/{solver-46FFSK2U.js → solver-OK4HECRH.js} +0 -1
  89. package/dist-cli/solver_bg.wasm +0 -0
  90. package/dist-skill/CONTEXT.md +1192 -725
  91. package/dist-skill/SKILL.md +3 -2
  92. package/dist-skill/docs/API/core/concepts.md +64 -1
  93. package/dist-skill/docs/CLI.md +34 -10
  94. package/dist-skill/docs/generated/assembly.md +339 -213
  95. package/dist-skill/docs/generated/core.md +283 -6
  96. package/dist-skill/docs/generated/curves.md +272 -362
  97. package/dist-skill/docs/generated/lib.md +9 -19
  98. package/dist-skill/docs/generated/output.md +29 -4
  99. package/dist-skill/docs/generated/runtime-names.md +40 -0
  100. package/dist-skill/docs/generated/sdf.md +31 -0
  101. package/dist-skill/docs/generated/sheet-metal.md +9 -0
  102. package/dist-skill/docs/generated/sketch.md +44 -2
  103. package/dist-skill/docs/generated/viewport.md +2 -87
  104. package/dist-skill/docs/guides/coordinate-system.md +20 -16
  105. package/dist-skill/docs/guides/geometry-conventions.md +2 -2
  106. package/dist-skill/docs/guides/inspection-bundles.md +2 -1
  107. package/dist-skill/docs/guides/joint-design.md +24 -0
  108. package/dist-skill/docs/guides/positioning.md +13 -3
  109. package/dist-skill/library/forgecad-component-model/SKILL.md +10 -1
  110. package/dist-skill/library/forgecad-image-replicator/SKILL.md +6 -6
  111. package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.py +166 -0
  112. package/dist-skill/library/forgecad-make-a-model/SKILL.md +39 -38
  113. package/dist-skill/library/forgecad-model-grader/SKILL.md +1 -1
  114. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +1 -1
  115. package/dist-skill/library/forgecad-project/SKILL.md +2 -0
  116. package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
  117. package/examples/api/assembly-kinematics-foundation.forge.js +65 -0
  118. package/examples/api/assembly-kinematics-four-bar.forge.js +115 -0
  119. package/examples/api/assembly-kinematics-limb.forge.js +116 -0
  120. package/examples/api/connector-frame-rig-chain.forge.js +102 -0
  121. package/examples/api/exact-sheet-shell-assembly.forge.js +0 -2
  122. package/examples/api/exact-surface-studio.forge.js +6 -8
  123. package/examples/api/helix-basics.forge.js +8 -8
  124. package/examples/api/lean-foundations/README.md +12 -0
  125. package/examples/api/lean-foundations/curve-blend-exact.forge.js +22 -0
  126. package/examples/api/lean-foundations/curve-fit-interpolation.forge.js +18 -0
  127. package/examples/api/lean-foundations/curve-helix-canonicalization.forge.js +27 -0
  128. package/examples/api/lean-foundations/curve-route-canonicalization.forge.js +16 -0
  129. package/examples/api/lean-foundations/curve-trim-reverse.forge.js +24 -0
  130. package/examples/api/lean-foundations/exact-curve-arc.forge.js +36 -0
  131. package/examples/api/mixed-edge-finishes-proof.forge.js +8 -11
  132. package/examples/api/route3d-elbow.forge.js +71 -0
  133. package/examples/api/transition-curves.forge.js +44 -15
  134. package/examples/api/variable-sweep-test.forge.js +3 -1
  135. package/examples/api/y-blend-corner-showcase.forge.js +0 -2
  136. package/examples/generative/coral-vase.forge.js +1 -1
  137. package/examples/nurbs-tube.forge.js +1 -1
  138. package/package.json +17 -13
  139. package/dist/assets/EditorApp-lXv53A1m.js +0 -13610
  140. package/dist/assets/app-CsHnaBWt.css +0 -1789
  141. package/dist/assets/forgecad_geometry_bg-C5_E9Oa9.wasm +0 -0
  142. package/dist/assets/solver_bg-CWvv4lnN.wasm +0 -0
  143. package/dist/docs-raw/API/README.md +0 -16
  144. package/dist/docs-raw/API/core/concepts.md +0 -118
  145. package/dist/docs-raw/INDEX.md +0 -138
  146. package/dist/docs-raw/RELEASING.md +0 -87
  147. package/dist/docs-raw/agent-native-api.md +0 -27
  148. package/dist/docs-raw/beta-deployment.md +0 -304
  149. package/dist/docs-raw/beta-operations.md +0 -325
  150. package/dist/docs-raw/blueprint-first.md +0 -145
  151. package/dist/docs-raw/cli-monetization.md +0 -112
  152. package/dist/docs-raw/coding-best-practices.md +0 -120
  153. package/dist/docs-raw/coding.md +0 -340
  154. package/dist/docs-raw/deployment.md +0 -374
  155. package/dist/docs-raw/guides/skill-maintenance.md +0 -161
  156. package/dist/docs-raw/guides/surface-members.md +0 -82
  157. package/dist/docs-raw/harbor-cli.md +0 -854
  158. package/dist/docs-raw/internals/backend-vocabulary.md +0 -35
  159. package/dist/docs-raw/internals/compiler.md +0 -307
  160. package/dist/docs-raw/internals/constraint-solver-quality.md +0 -161
  161. package/dist/docs-raw/internals/constraint-solver.md +0 -176
  162. package/dist/docs-raw/internals/shape-from-slices.md +0 -152
  163. package/dist/docs-raw/internals/sketch-2d-pipeline.md +0 -108
  164. package/dist/docs-raw/platform/admin.md +0 -45
  165. package/dist/docs-raw/platform/architecture.md +0 -82
  166. package/dist/docs-raw/platform/auth.md +0 -139
  167. package/dist/docs-raw/platform/email.md +0 -67
  168. package/dist/docs-raw/platform/google-oauth-setup.md +0 -88
  169. package/dist/docs-raw/platform/observability.md +0 -197
  170. package/dist/docs-raw/platform/projects.md +0 -111
  171. package/dist/docs-raw/platform/sharing.md +0 -90
  172. package/dist/docs-raw/product/README.md +0 -39
  173. package/dist/docs-raw/product/api-as-product-language.md +0 -13
  174. package/dist/docs-raw/product/business-model.md +0 -15
  175. package/dist/docs-raw/product/competitive-positioning.md +0 -17
  176. package/dist/docs-raw/product/creative-manufacturing.md +0 -15
  177. package/dist/docs-raw/product/founder-story.md +0 -11
  178. package/dist/docs-raw/product/manufacturing-workflows.md +0 -15
  179. package/dist/docs-raw/product/onboarding-first-experience.md +0 -256
  180. package/dist/docs-raw/product/product-loop.md +0 -17
  181. package/dist/docs-raw/product/strategic-decisions.md +0 -22
  182. package/dist/docs-raw/product/user-outreach-email-templates.md +0 -161
  183. package/dist/docs-raw/product/user-segments.md +0 -15
  184. package/dist/docs-raw/product/vision.md +0 -26
  185. package/dist/docs-raw/rl-environments.md +0 -350
  186. package/dist/docs-raw/runbook.md +0 -611
  187. package/dist-cli/check-compiler-U5SOPN7X.js.map +0 -1
  188. package/dist-cli/check-query-propagation-XOKNSSYU.js.map +0 -1
  189. package/dist-cli/chunk-EXWGNL6K.js.map +0 -1
  190. package/dist-cli/forgecad.js.map +0 -1
  191. package/dist-cli/forgecad_geometry-GYVNKPIE.js.map +0 -1
  192. package/dist-cli/solver-46FFSK2U.js.map +0 -1
  193. package/dist-skill/SKILL-dev.md +0 -145
  194. package/dist-skill/docs-dev/API/core/concepts.md +0 -118
  195. package/dist-skill/docs-dev/CLI.md +0 -677
  196. package/dist-skill/docs-dev/agent-native-api.md +0 -27
  197. package/dist-skill/docs-dev/blueprint-first.md +0 -145
  198. package/dist-skill/docs-dev/coding-best-practices.md +0 -120
  199. package/dist-skill/docs-dev/coding.md +0 -340
  200. package/dist-skill/docs-dev/component-model.md +0 -164
  201. package/dist-skill/docs-dev/generated/assembly.md +0 -794
  202. package/dist-skill/docs-dev/generated/core.md +0 -2117
  203. package/dist-skill/docs-dev/generated/curves.md +0 -2583
  204. package/dist-skill/docs-dev/generated/lib.md +0 -169
  205. package/dist-skill/docs-dev/generated/output.md +0 -247
  206. package/dist-skill/docs-dev/generated/sdf.md +0 -446
  207. package/dist-skill/docs-dev/generated/sheet-metal.md +0 -504
  208. package/dist-skill/docs-dev/generated/sketch.md +0 -1811
  209. package/dist-skill/docs-dev/generated/viewport.md +0 -585
  210. package/dist-skill/docs-dev/generated/wood.md +0 -108
  211. package/dist-skill/docs-dev/guides/coordinate-system.md +0 -46
  212. package/dist-skill/docs-dev/guides/geometry-conventions.md +0 -52
  213. package/dist-skill/docs-dev/guides/inspection-bundles.md +0 -485
  214. package/dist-skill/docs-dev/guides/joint-design.md +0 -78
  215. package/dist-skill/docs-dev/guides/modeling-recipes.md +0 -78
  216. package/dist-skill/docs-dev/guides/positioning.md +0 -161
  217. package/dist-skill/docs-dev/guides/skill-maintenance.md +0 -161
  218. package/dist-skill/docs-dev/internals/backend-vocabulary.md +0 -35
  219. package/dist-skill/docs-dev/internals/compiler.md +0 -307
  220. package/dist-skill/docs-dev/internals/constraint-solver-quality.md +0 -161
  221. package/dist-skill/docs-dev/internals/constraint-solver.md +0 -176
  222. package/dist-skill/docs-dev/internals/sketch-2d-pipeline.md +0 -108
  223. package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.mjs +0 -289
  224. package/examples/api/bolted-service-cover.forge.js +0 -17
  225. package/examples/api/cable-gland-anchor.forge.js +0 -14
  226. package/examples/api/captured-cartridge-guide.forge.js +0 -14
  227. package/examples/api/captured-linear-slide.forge.js +0 -13
  228. package/examples/api/clevis-pin-joint.forge.js +0 -13
  229. package/examples/api/datum-enclosure.forge.js +0 -16
  230. package/examples/api/hose-barb-port.forge.js +0 -14
  231. package/examples/api/knuckled-hinge-assembly.forge.js +0 -15
  232. package/examples/api/living-hinge-cover.forge.js +0 -14
  233. package/examples/api/pcb-terminal-block.forge.js +0 -22
  234. package/examples/api/pinned-lever-pivot-stack.forge.js +0 -14
  235. package/examples/api/retained-shaft-knob-stack.forge.js +0 -15
  236. package/examples/api/routed-tube-clip.forge.js +0 -15
  237. package/examples/api/seated-bearing-stack.forge.js +0 -30
  238. package/examples/api/snap-latch-cover.forge.js +0 -14
  239. package/examples/api/thumb-screw-clamp.forge.js +0 -15
@@ -1,108 +0,0 @@
1
- ---
2
- skill-group: dev-compiler
3
- skill-order: 2
4
- ---
5
-
6
- # 2D Sketch Pipeline: Runtime Mesh ↔ ProfileCompilePlan ↔ Export Backend
7
-
8
- Captured from implementing sketch region selection, planar arrangement detection, and cross-sketch reference geometry (2026-03).
9
-
10
- ---
11
-
12
- ## The Two-Track Export Model
13
-
14
- Every 3D shape in ForgeCAD has two representations:
15
-
16
- 1. **Runtime mesh** — a watertight triangulated solid used for preview rendering and mesh-domain operations.
17
- 2. **`ProfileCompilePlan`** — a serializable Forge intent description (discriminated union with `kind: 'rect' | 'circle' | 'polygon' | 'boolean' | ...`) that the current export backend reads to produce STEP geometry.
18
-
19
- When exporting STEP, the system walks the `ProfileCompilePlan` tree. If a node has no plan (plan is `null`), it falls back to a faceted mesh-to-STEP conversion — useful for interchange, but it loses parametric intent.
20
-
21
- **Key invariant**: any sketch API that only calls `polygon()` and boolean ops (`add`/`subtract`) automatically stays on the maintained STEP path. No special backend-specific code is needed.
22
-
23
- ---
24
-
25
- ## What `Sketch` Is
26
-
27
- `Sketch` wraps a Manifold `CrossSection` (2D polygon set). It also carries:
28
- - An optional `ProfileCompilePlan` for the STEP path.
29
- - A `Placement3D` for face-mounted sketches (`.onFace()`).
30
-
31
- `ConstrainedSketchBuilder` → `.solve()` → `ConstraintSketch extends Sketch` holds the constraint definition alongside the solved `CrossSection`. The solved `CrossSection` is built from explicitly declared loops (`addLoop`); geometry outside loops is not included in the area — only in the constraint definition for arrangement detection.
32
-
33
- ---
34
-
35
- ## New 2D Sketch Surface-Selection APIs (2026-03)
36
-
37
- ### 1. `sketch.regions()` / `sketch.region(seed)`
38
-
39
- **File**: `src/forge/sketch/regions.ts`
40
-
41
- Decomposes a Manifold `CrossSection` into its distinct filled areas.
42
-
43
- - `CrossSection.toPolygons()` returns a flat list of contours. Positive signed area → outer boundary; negative → hole.
44
- - Holes are nested into their smallest containing outer boundary via `pointInPolygon`.
45
- - Each outer boundary + its holes is reassembled as a `polygon(outerPts, holePts)` sketch.
46
- - The `region(seed)` variant picks the one face whose outer boundary contains the seed and no hole does.
47
-
48
- **STEP export**: uses `polygon()` and potentially `boolean(difference)` for rings — both are handled by the current export backend without extra sketch-specific code.
49
-
50
- ### 2. `constraintSketch.detectArrangement()` / `detectArrangementRegion(seed)`
51
-
52
- **File**: `src/forge/sketch/arrangement.ts`
53
-
54
- DCEL-based planar arrangement detection from the raw line segments in a `ConstraintDefinition`.
55
-
56
- Algorithm:
57
- 1. Extract non-construction line segments from `def.lines`.
58
- 2. Split segments at all pairwise intersections — both X-crossings (`segSegT`) and **T-junctions** (`pointOnSegT`). T-junction support is critical: when a divider endpoint touches a boundary edge interior, only `pointOnSegT` detects it.
59
- 3. Snap nearby nodes and build a clean planar graph.
60
- 4. Build DCEL half-edges. At each node sort outgoing half-edges by polar angle.
61
- 5. `next(u→v)` = outgoing from v immediately preceding `twin(u→v)` in CCW order at v — i.e., `out[(pos−1+n) % n]`.
62
- 6. Traverse all face cycles; keep CCW faces (positive signed area). CW = unbounded outer face, excluded.
63
- 7. Return each face as a `polygon(pts)` sketch.
64
-
65
- No explicit loops needed from the caller. Works on any set of line constraints.
66
-
67
- **STEP export**: each face is a `polygon()` — handled by the maintained export path.
68
-
69
- ### 3. `builderB.referenceFrom(sketchA, entityId)` / `referenceAllFrom(sketchA)`
70
-
71
- **File**: `src/forge/sketch/constraints.ts`
72
-
73
- Import solved geometry from another `ConstraintSketch` as fixed construction references.
74
-
75
- - Fixed points (`fixed: true`) and construction lines (`construction: true`) participate in constraint solving but contribute zero area to the resulting CrossSection profile.
76
- - `referenceFrom(source, id)` looks up the entity by id in the source's `ConstraintDefinition` and creates a fixed copy in the current builder.
77
- - Enables constraints like `parallel(bBot, refBase)` to lock relationships between separate sketches.
78
-
79
- **STEP export**: construction elements are ignored by `buildSketchFromDefinition` when assembling loops → no impact on export path.
80
-
81
- ---
82
-
83
- ## Backend Agnosticism
84
-
85
- These APIs are fully backend-agnostic because they operate on:
86
- - **2D coordinates only** — no 3D kernel calls during region/arrangement detection.
87
- - **`polygon()`** — the lowest-level sketch primitive, available on every backend.
88
- - **Boolean ops** — available on every backend (Manifold, OCCT, …).
89
-
90
- No backend-specific code was added. Adding a new backend (e.g., CGAL, OpenCASCADE directly) automatically inherits all three APIs as long as it handles `polygon` and `boolean` plan kinds.
91
-
92
- The only Manifold-specific call is `CrossSection.toPolygons()` in `regions.ts`. If a future backend doesn't use `CrossSection`, `sketchRegions` would need an adapter — but the algorithm and public API shape would be unchanged.
93
-
94
- ---
95
-
96
- ## Gotchas
97
-
98
- ### Empty CrossSection for loop-less sketches
99
-
100
- `constrainedSketch().solve()` used to throw "at least one closed loop" when called without `addLoop()`. Users calling `.detectArrangement()` never add loops. Fix: return an empty `CrossSection` via `CrossSection.difference([unit, unit])` (not `new CrossSection([])` — Manifold's `polygons2vec` crashes on empty array).
101
-
102
- ### T-junctions
103
-
104
- Interior-only intersection detection (`segSegT`) misses T-junctions entirely. A 3×2 grid yields only 4 cells instead of 6 if T-junctions aren't split. Always run `pointOnSegT` for every endpoint of every other segment against every segment.
105
-
106
- ### DCEL `next` pointer direction
107
-
108
- The formula traces faces to the **LEFT** of each directed half-edge. CW winding = unbounded outer face. Keep only faces with positive signed area (CCW winding = bounded interior face).
@@ -1,289 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { existsSync } from 'node:fs';
4
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
5
- import { dirname, extname, resolve } from 'node:path';
6
- import { execFileSync } from 'node:child_process';
7
- import puppeteer from 'puppeteer-core';
8
-
9
- const CHROME_PATHS = [
10
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
11
- '/Applications/Chromium.app/Contents/MacOS/Chromium',
12
- '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
13
- '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
14
- '/usr/bin/google-chrome',
15
- '/usr/bin/google-chrome-stable',
16
- '/usr/bin/chromium',
17
- '/usr/bin/chromium-browser',
18
- '/snap/bin/chromium',
19
- ];
20
-
21
- const MIME_BY_EXT = new Map([
22
- ['.png', 'image/png'],
23
- ['.jpg', 'image/jpeg'],
24
- ['.jpeg', 'image/jpeg'],
25
- ['.webp', 'image/webp'],
26
- ['.gif', 'image/gif'],
27
- ['.bmp', 'image/bmp'],
28
- ['.svg', 'image/svg+xml'],
29
- ]);
30
-
31
- function usage() {
32
- return `Usage:
33
- compare_images.mjs <reference-image> <forgecad-render> <output.png> [options]
34
-
35
- Options:
36
- --height <px> Panel height in pixels (default: 900)
37
- --panel-width <px> Panel width in pixels (default: max input aspect at --height)
38
- --gap <px> Gap between panels (default: 16)
39
- --padding <px> Outer padding (default: 16)
40
- --background <color> Canvas background (default: #111111)
41
- --fit <contain|cover> Fit mode inside equal panels (default: contain)
42
- --labels <left,right> Labels (default: Reference,ForgeCAD)
43
- --no-labels Disable label band
44
- --chrome-path <path> Chrome or Chromium executable
45
- -h, --help Show help`;
46
- }
47
-
48
- function readValue(argv, index, flag) {
49
- const value = argv[index + 1];
50
- if (!value || value.startsWith('--')) {
51
- throw new Error(`Missing value for ${flag}`);
52
- }
53
- return value;
54
- }
55
-
56
- function parsePositiveInt(raw, label) {
57
- const value = Number.parseInt(raw, 10);
58
- if (!Number.isFinite(value) || value <= 0) {
59
- throw new Error(`${label} must be a positive integer.`);
60
- }
61
- return value;
62
- }
63
-
64
- function parseArgs(argv) {
65
- if (argv.includes('-h') || argv.includes('--help')) {
66
- console.log(usage());
67
- process.exit(0);
68
- }
69
-
70
- const positionals = [];
71
- const options = {
72
- height: 900,
73
- panelWidth: null,
74
- gap: 16,
75
- padding: 16,
76
- background: '#111111',
77
- fit: 'contain',
78
- labels: ['Reference', 'ForgeCAD'],
79
- chromePath: process.env.CHROME_PATH || null,
80
- };
81
-
82
- for (let i = 0; i < argv.length; i += 1) {
83
- const arg = argv[i];
84
- if (arg === '--height') {
85
- options.height = parsePositiveInt(readValue(argv, i, arg), '--height');
86
- i += 1;
87
- } else if (arg === '--panel-width') {
88
- options.panelWidth = parsePositiveInt(readValue(argv, i, arg), '--panel-width');
89
- i += 1;
90
- } else if (arg === '--gap') {
91
- options.gap = parsePositiveInt(readValue(argv, i, arg), '--gap');
92
- i += 1;
93
- } else if (arg === '--padding') {
94
- options.padding = parsePositiveInt(readValue(argv, i, arg), '--padding');
95
- i += 1;
96
- } else if (arg === '--background') {
97
- options.background = readValue(argv, i, arg);
98
- i += 1;
99
- } else if (arg === '--fit') {
100
- const fit = readValue(argv, i, arg);
101
- if (fit !== 'contain' && fit !== 'cover') {
102
- throw new Error('--fit must be contain or cover.');
103
- }
104
- options.fit = fit;
105
- i += 1;
106
- } else if (arg === '--labels') {
107
- const labels = readValue(argv, i, arg)
108
- .split(',')
109
- .map((entry) => entry.trim())
110
- .filter(Boolean);
111
- if (labels.length !== 2) {
112
- throw new Error('--labels must contain two comma-separated labels.');
113
- }
114
- options.labels = labels;
115
- i += 1;
116
- } else if (arg === '--no-labels') {
117
- options.labels = null;
118
- } else if (arg === '--chrome-path') {
119
- options.chromePath = readValue(argv, i, arg);
120
- i += 1;
121
- } else if (arg.startsWith('--')) {
122
- throw new Error(`Unknown option: ${arg}`);
123
- } else {
124
- positionals.push(arg);
125
- }
126
- }
127
-
128
- if (positionals.length !== 3) {
129
- throw new Error(`Expected reference, render, and output paths.\n\n${usage()}`);
130
- }
131
-
132
- return {
133
- referencePath: resolve(positionals[0]),
134
- renderPath: resolve(positionals[1]),
135
- outputPath: resolve(positionals[2]),
136
- ...options,
137
- };
138
- }
139
-
140
- function commandPath(name) {
141
- try {
142
- const found = execFileSync(process.platform === 'win32' ? 'where' : 'which', [name], {
143
- stdio: ['ignore', 'pipe', 'ignore'],
144
- })
145
- .toString()
146
- .trim()
147
- .split(/\r?\n/)[0];
148
- return found || null;
149
- } catch {
150
- return null;
151
- }
152
- }
153
-
154
- function resolveChromePath(explicitPath) {
155
- if (explicitPath && existsSync(explicitPath)) return explicitPath;
156
- for (const candidate of CHROME_PATHS) {
157
- if (existsSync(candidate)) return candidate;
158
- }
159
- for (const candidate of ['google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', 'brave-browser', 'microsoft-edge', 'chrome']) {
160
- const found = commandPath(candidate);
161
- if (found && existsSync(found)) return found;
162
- }
163
- return null;
164
- }
165
-
166
- async function imageDataUrl(path) {
167
- if (!existsSync(path)) {
168
- throw new Error(`Image not found: ${path}`);
169
- }
170
- const ext = extname(path).toLowerCase();
171
- const mime = MIME_BY_EXT.get(ext);
172
- if (!mime) {
173
- throw new Error(`Unsupported image extension "${ext}" for ${path}`);
174
- }
175
- const bytes = await readFile(path);
176
- return `data:${mime};base64,${bytes.toString('base64')}`;
177
- }
178
-
179
- async function main() {
180
- const options = parseArgs(process.argv.slice(2));
181
- const chromePath = resolveChromePath(options.chromePath);
182
- if (!chromePath) {
183
- throw new Error('Chrome or Chromium was not found. Pass --chrome-path or set CHROME_PATH.');
184
- }
185
-
186
- const [referenceUrl, renderUrl] = await Promise.all([imageDataUrl(options.referencePath), imageDataUrl(options.renderPath)]);
187
- const browser = await puppeteer.launch({
188
- executablePath: chromePath,
189
- headless: true,
190
- args: ['--no-sandbox', '--disable-gpu-sandbox'],
191
- });
192
-
193
- try {
194
- const page = await browser.newPage();
195
- const result = await page.evaluate(
196
- async (payload) => {
197
- const loadImage = (src) =>
198
- new Promise((resolveImage, rejectImage) => {
199
- const img = new Image();
200
- img.onload = () => resolveImage(img);
201
- img.onerror = () => rejectImage(new Error('Failed to decode image'));
202
- img.src = src;
203
- });
204
-
205
- const [reference, render] = await Promise.all([loadImage(payload.referenceUrl), loadImage(payload.renderUrl)]);
206
- const panelHeight = payload.height;
207
- const maxAspect = Math.max(reference.naturalWidth / reference.naturalHeight, render.naturalWidth / render.naturalHeight);
208
- const panelWidth = payload.panelWidth ?? Math.ceil(panelHeight * maxAspect);
209
- const labelHeight = payload.labels ? 34 : 0;
210
- const canvasWidth = payload.padding * 2 + panelWidth * 2 + payload.gap;
211
- const canvasHeight = payload.padding * 2 + labelHeight + panelHeight;
212
- const canvas = document.createElement('canvas');
213
- canvas.width = canvasWidth;
214
- canvas.height = canvasHeight;
215
- const ctx = canvas.getContext('2d');
216
- ctx.fillStyle = payload.background;
217
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
218
-
219
- const drawLabel = (text, x) => {
220
- ctx.fillStyle = 'rgba(255,255,255,0.9)';
221
- ctx.font = '600 18px system-ui, -apple-system, BlinkMacSystemFont, sans-serif';
222
- ctx.textBaseline = 'top';
223
- ctx.fillText(text, x, payload.padding + 4);
224
- };
225
-
226
- const drawPanel = (img, x, y) => {
227
- const scale =
228
- payload.fit === 'cover'
229
- ? Math.max(panelWidth / img.naturalWidth, panelHeight / img.naturalHeight)
230
- : Math.min(panelWidth / img.naturalWidth, panelHeight / img.naturalHeight);
231
- const width = img.naturalWidth * scale;
232
- const height = img.naturalHeight * scale;
233
- const dx = x + (panelWidth - width) * 0.5;
234
- const dy = y + (panelHeight - height) * 0.5;
235
-
236
- ctx.save();
237
- ctx.beginPath();
238
- ctx.rect(x, y, panelWidth, panelHeight);
239
- ctx.clip();
240
- ctx.drawImage(img, dx, dy, width, height);
241
- ctx.restore();
242
-
243
- ctx.strokeStyle = 'rgba(255,255,255,0.25)';
244
- ctx.lineWidth = 1;
245
- ctx.strokeRect(x + 0.5, y + 0.5, panelWidth - 1, panelHeight - 1);
246
- };
247
-
248
- const leftX = payload.padding;
249
- const rightX = payload.padding + panelWidth + payload.gap;
250
- const panelY = payload.padding + labelHeight;
251
- if (payload.labels) {
252
- drawLabel(payload.labels[0], leftX);
253
- drawLabel(payload.labels[1], rightX);
254
- }
255
- drawPanel(reference, leftX, panelY);
256
- drawPanel(render, rightX, panelY);
257
-
258
- return {
259
- png: canvas.toDataURL('image/png'),
260
- width: canvasWidth,
261
- height: canvasHeight,
262
- };
263
- },
264
- {
265
- referenceUrl,
266
- renderUrl,
267
- height: options.height,
268
- panelWidth: options.panelWidth,
269
- gap: options.gap,
270
- padding: options.padding,
271
- background: options.background,
272
- fit: options.fit,
273
- labels: options.labels,
274
- },
275
- );
276
-
277
- const png = Buffer.from(result.png.replace(/^data:image\/png;base64,/, ''), 'base64');
278
- await mkdir(dirname(options.outputPath), { recursive: true });
279
- await writeFile(options.outputPath, png);
280
- console.log(`Wrote ${options.outputPath} (${result.width}x${result.height})`);
281
- } finally {
282
- await browser.close();
283
- }
284
- }
285
-
286
- main().catch((error) => {
287
- console.error(error instanceof Error ? error.message : String(error));
288
- process.exit(1);
289
- });
@@ -1,17 +0,0 @@
1
- // Bolted service cover: parent ledge, gasket, fused pull tabs, aligned holes, and installed screws.
2
-
3
- const cover = lib.boltedServiceCover({
4
- width: 90,
5
- depth: 56,
6
- coverThickness: 3,
7
- parentThickness: 8,
8
- ledgeWidth: 10,
9
- screwSize: 'M4',
10
- boltInset: [6, 6],
11
- });
12
-
13
- verify.equal('four retained cover screws', cover.screws.length, 4);
14
- verify.greaterThan('cover has edge margin around service opening', cover.dims.ledgeWidth, 6);
15
- verify.notColliding('cover does not collide with gasket', cover.cover, cover.gasket);
16
-
17
- return cover.parts;
@@ -1,14 +0,0 @@
1
- const anchor = lib.cableGlandAnchorAssembly({
2
- cableDiameter: 6,
3
- panelThickness: 3,
4
- panelWidth: 62,
5
- panelHeight: 44,
6
- });
7
-
8
- verify.equal('cable gland anchor has four retained parts', anchor.parts.length, 4);
9
- verify.greaterThan('panel leaves material around gland flange', anchor.dims.panelHeight - anchor.dims.flangeDiameter, 8);
10
- verify.notColliding('cable clears gland bore', anchor.cable, anchor.gland);
11
- verify.notColliding('gland clears panel hole', anchor.gland, anchor.panel);
12
- verify.clearanceBetween('gland flange is seated at panel pocket', anchor.gland, anchor.panel, 0.01, 0.2);
13
-
14
- return anchor.parts;
@@ -1,14 +0,0 @@
1
- const cassette = lib.capturedCartridgeGuideAssembly({
2
- length: 150,
3
- cartridgeLength: 72,
4
- insertion: 28,
5
- });
6
-
7
- verify.equal('cartridge guide has guide and removable cassette', cassette.parts.length, 2);
8
- verify.notColliding('cartridge clears guide rails and rear stop', cassette.cartridge, cassette.guide);
9
- verify.greaterThan('cartridge flange is captured by guide lips', cassette.dims.cartridgeWidth, cassette.dims.throatWidth);
10
- verify.lessThan('cartridge still clears inner guide width', cassette.dims.cartridgeWidth, cassette.dims.innerWidth);
11
- verify.greaterThan('cartridge body passes through the guide throat', cassette.dims.throatWidth, cassette.dims.cartridgeBodyWidth);
12
- verify.inRange('cartridge insertion stays inside travel', cassette.dims.insertion, 0, cassette.dims.maxInsertion);
13
-
14
- return cassette.parts;
@@ -1,13 +0,0 @@
1
- const slide = lib.capturedLinearSlide({
2
- length: 160,
3
- railWidth: 38,
4
- carriageLength: 52,
5
- travel: 42,
6
- });
7
-
8
- verify.equal('captured slide has rail and carriage parts', slide.parts.length, 2);
9
- verify.greaterThan('carriage is wider than the throat opening', slide.dims.carriageWidth, slide.dims.throatWidth);
10
- verify.greaterThan('slide retains usable travel', slide.dims.maxTravel, 80);
11
- verify.minClearance('carriage clears captured rail', slide.carriage, slide.rail, 0.05);
12
-
13
- return slide.parts;
@@ -1,13 +0,0 @@
1
- const clevis = lib.clevisPinJointAssembly({
2
- pinDiameter: 4,
3
- linkThickness: 6,
4
- linkArmLength: 38,
5
- earThickness: 4,
6
- });
7
-
8
- verify.equal('clevis joint has three retained parts', clevis.parts.length, 3);
9
- verify.greaterThan('link eye has material around pin bore', clevis.dims.eyeOuterRadius - clevis.dims.boreDiameter / 2, 4);
10
- verify.greaterThan('clevis gap clears the link eye', clevis.dims.clevisGap, clevis.dims.linkThickness);
11
- verify.minClearance('link eye clears clevis yoke', clevis.link, clevis.clevis, 0.05);
12
-
13
- return clevis.parts;
@@ -1,16 +0,0 @@
1
- const enclosure = lib.datumEnclosureAssembly({
2
- width: 96,
3
- depth: 64,
4
- height: 18,
5
- screwSize: 'M3',
6
- });
7
-
8
- verify.equal('datum enclosure has base gasket cover and four screws', enclosure.parts.length, 7);
9
- verify.greaterThan('internal cavity keeps usable width', enclosure.dims.innerWidth, 80);
10
- verify.greaterThan('standoff wall remains around screw envelope', enclosure.dims.standoffDiameter - enclosure.dims.threadEnvelopeDia, 4);
11
- verify.greaterThan('service port leaves top wall material', enclosure.dims.height - enclosure.dims.baseThickness - enclosure.dims.portHeight, 6);
12
- verify.notColliding('cover clears seated gasket', enclosure.cover, enclosure.gasket);
13
- verify.notColliding('first screw clears standoff thread envelope', enclosure.screws[0], enclosure.base);
14
- verify.inRange('cover stack has small seating clearance', enclosure.dims.faceClearance, 0.01, 0.08);
15
-
16
- return enclosure.parts;
@@ -1,14 +0,0 @@
1
- const hosePort = lib.hoseBarbPortAssembly({
2
- hoseInnerDiameter: 6,
3
- hoseOuterDiameter: 10,
4
- barbCount: 3,
5
- });
6
-
7
- verify.equal('hose barb port has receiver fitting hose and clamp', hosePort.parts.length, 4);
8
- verify.greaterThan('barb peaks retain nominal hose ID', hosePort.dims.barbPeakDiameter, hosePort.dims.hoseInnerDiameter);
9
- verify.greaterThan('hose has wall around installed barb envelope', hosePort.dims.hoseOuterDiameter - hosePort.dims.installedHoseBoreDiameter, 1.2);
10
- verify.notColliding('hose clears barb peaks', hosePort.hose, hosePort.fitting);
11
- verify.notColliding('clamp clears hose outside', hosePort.clamp, hosePort.hose);
12
- verify.inRange('fitting shoulder seats near boss face', hosePort.dims.faceClearance, 0.01, 0.08);
13
-
14
- return hosePort.parts;
@@ -1,15 +0,0 @@
1
- const hinge = lib.knuckledHingeAssembly({
2
- length: 70,
3
- leafLength: 28,
4
- leafThickness: 1.8,
5
- barrelOuterRadius: 3.2,
6
- pinDiameter: 2,
7
- openAngleDeg: 42,
8
- });
9
-
10
- verify.equal('hinge has two leaves and one retained pin', hinge.parts.length, 3);
11
- verify.greaterThan('barrel wall remains around pin bore', hinge.dims.barrelOuterRadius - hinge.dims.boreDiameter / 2, 1.5);
12
- verify.greaterThan('hinge has meaningful knuckle length', hinge.dims.knuckleLength, hinge.dims.pinDiameter * 2);
13
- verify.minClearance('moving leaf clears fixed leaf', hinge.movingLeaf, hinge.fixedLeaf, 0.05);
14
-
15
- return hinge.parts;
@@ -1,14 +0,0 @@
1
- const livingCover = lib.livingHingeCoverAssembly({
2
- width: 64,
3
- coverDepth: 42,
4
- fixedLeafDepth: 18,
5
- });
6
-
7
- verify.equal('living hinge cover is one molded part', livingCover.parts.length, 1);
8
- verify.equal('one-piece living hinge cover has one body', livingCover.cover.numBodies(), 1);
9
- verify.greaterThan('living hinge web is much thinner than rigid leaves', livingCover.dims.flexRatio, 3.5);
10
- verify.greaterThan('hinge web spans the full cover width', livingCover.dims.width, 60);
11
- verify.greaterThan('snap barb has positive retention height', livingCover.dims.snapBarbHeight, 1);
12
- verify.greaterThan('catch land is present on fixed leaf', livingCover.dims.catchLandDepth, 2);
13
-
14
- return livingCover.parts;
@@ -1,22 +0,0 @@
1
- const terminalStack = lib.pcbTerminalBlockAssembly({
2
- terminalCount: 5,
3
- terminalPitch: 5.08,
4
- screwSize: 'M3',
5
- });
6
-
7
- verify.equal('terminal block exposes one pin per terminal', terminalStack.pinPositions.length, terminalStack.dims.terminalCount);
8
- verify.greaterThan(
9
- 'PCB leaves mounting margin around terminal block',
10
- terminalStack.dims.boardWidth - terminalStack.dims.terminalBlockWidth,
11
- 20,
12
- );
13
- verify.notColliding('terminal pins clear PCB holes', terminalStack.terminalBlock, terminalStack.pcb);
14
- verify.notColliding('mounting screws clear PCB and standoff holes', union(...terminalStack.screws), union(terminalStack.pcb, terminalStack.backplate));
15
- verify.greaterThan(
16
- 'standoffs retain wall around screw envelopes',
17
- terminalStack.dims.standoffDiameter - terminalStack.dims.standoffThreadEnvelopeDiameter,
18
- 3,
19
- );
20
- verify.greaterThan('pin holes leave PCB web between terminals', terminalStack.dims.terminalPitch - terminalStack.dims.pinHoleDiameter, 3);
21
-
22
- return terminalStack.parts;
@@ -1,14 +0,0 @@
1
- const lever = lib.pinnedLeverAssembly({
2
- armLength: 56,
3
- armWidth: 10,
4
- leverThickness: 5,
5
- pinDiameter: 5,
6
- supportThickness: 8,
7
- });
8
-
9
- verify.equal('pinned lever stack has five retained parts', lever.parts.length, 5);
10
- verify.greaterThan('thrust washers have real thickness', lever.dims.washerThickness, 0);
11
- verify.greaterThan('pivot hub has material around the bore', lever.dims.hubRadius - lever.dims.boreDiameter / 2, 3);
12
- verify.notColliding('lower washer does not collide with support', lever.washers.lower, lever.support);
13
-
14
- return lever.parts;
@@ -1,15 +0,0 @@
1
- const trunnion = lib.retainedShaftAssembly({
2
- supportSpacing: 96,
3
- shaftDiameter: 8,
4
- supportThickness: 6,
5
- supportHeight: 42,
6
- knobDiameter: 24,
7
- knobThickness: 8,
8
- });
9
-
10
- verify.equal('retained shaft stack has seven parts', trunnion.parts.length, 7);
11
- verify.greaterThan('support cheeks leave material around bore', trunnion.dims.supportHeight - trunnion.dims.boreDiameter, 20);
12
- verify.greaterThan('shaft is long enough to retain knobs', trunnion.dims.shaftLength, trunnion.dims.supportSpacing);
13
- verify.notColliding('left washer does not collide with support cheek', trunnion.washers.left, trunnion.supports.left);
14
-
15
- return trunnion.parts;
@@ -1,15 +0,0 @@
1
- const route = lib.routedTubeClipAssembly({
2
- tubeDiameter: 6,
3
- tubeLength: 130,
4
- clipCount: 3,
5
- screwSize: 'M3',
6
- });
7
-
8
- verify.equal('tube route has three retaining clips', route.clips.length, 3);
9
- verify.equal('each retaining clip has two installed screws', route.screws.length, route.clips.length * 2);
10
- verify.notColliding('tube clears saddle clip bores', route.tube, union(...route.clips));
11
- verify.notColliding('clip screws clear panel and clip holes', union(...route.screws), union(route.panel, ...route.clips));
12
- verify.greaterThan('clip wall leaves material around tube bore', route.dims.clipWallThickness, route.dims.screwHeadDiameter);
13
- verify.greaterThan('tube bore clears tube outside diameter', route.dims.tubeBoreDiameter - route.dims.tubeDiameter, 0.3);
14
-
15
- return route.parts;
@@ -1,30 +0,0 @@
1
- const bearingStack = lib.seatedBearingAssembly({
2
- bearingOuterDiameter: 22,
3
- bearingInnerDiameter: 8,
4
- bearingWidth: 7,
5
- shaftDiameter: 7.4,
6
- housingWidth: 54,
7
- housingDepth: 42,
8
- housingThickness: 10,
9
- });
10
-
11
- verify.equal('seated bearing stack has three retained parts', bearingStack.parts.length, 3);
12
- verify.greaterThan(
13
- 'housing boss leaves material around bearing pocket',
14
- bearingStack.dims.bossOuterDiameter - bearingStack.dims.pocketDiameter,
15
- 5,
16
- );
17
- verify.greaterThan(
18
- 'bearing bore clears the shaft',
19
- bearingStack.dims.bearingInnerDiameter,
20
- bearingStack.dims.shaftDiameter + bearingStack.dims.runningClearance,
21
- );
22
- verify.clearanceBetween(
23
- 'bearing is seated in counterbore',
24
- bearingStack.bearing,
25
- bearingStack.housing,
26
- -0.01,
27
- bearingStack.dims.runningClearance + 0.1,
28
- );
29
-
30
- return bearingStack.parts;
@@ -1,14 +0,0 @@
1
- const snapCover = lib.snapLatchCoverAssembly({
2
- width: 72,
3
- depth: 44,
4
- ledgeWidth: 8,
5
- parentThickness: 6,
6
- });
7
-
8
- verify.equal('snap latch cover has receiver and cover parts', snapCover.parts.length, 2);
9
- verify.greaterThan('snap hooks tuck under catch land', snapCover.dims.hookThrow, snapCover.dims.latchThickness);
10
- verify.greaterThan('receiver leaves a usable service opening', snapCover.dims.openingWidth, 48);
11
- verify.notColliding('snap hooks clear receiver windows', snapCover.cover, snapCover.parent);
12
- verify.inRange('snap cover has small seating clearance', snapCover.dims.faceClearance, 0.01, 0.08);
13
-
14
- return snapCover.parts;