forgecad 0.9.6 → 0.9.7

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 (76) hide show
  1. package/dist/assets/{AdminPage-Da6hhpJx.js → AdminPage-DX0mpSZT.js} +1 -1
  2. package/dist/assets/{BlogPage-Bl_sKeWb.js → BlogPage-CI_P0_Pf.js} +1 -1
  3. package/dist/assets/{DocsPage-Blz3Tp4j.js → DocsPage-DLhIIZyJ.js} +3 -3
  4. package/dist/assets/{EditorApp-CuiPbtn5.js → EditorApp-BujZvuwX.js} +140 -20
  5. package/dist/assets/{EditorApp-DS0AIUrZ.css → EditorApp-DfFT2Dn8.css} +1 -0
  6. package/dist/assets/{EmbedViewer-BFG6-Ufm.js → EmbedViewer-0S0qXKog.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-DB9fQd5P.js → LandingPageProofDriven-O_yMtAri.js} +1 -1
  8. package/dist/assets/{PricingPage-BMxYT_F0.js → PricingPage-DGkX3Ahr.js} +1 -1
  9. package/dist/assets/{SettingsPage-VVQNrCAg.js → SettingsPage-DBsqTB_y.js} +82 -22
  10. package/dist/assets/{app-Dl9ymBWC.js → app-BE2nD6Yz.js} +1056 -258
  11. package/dist/assets/cli/{render-CFtwKCCY.js → render-iP9qh475.js} +1533 -207
  12. package/dist/assets/{evalWorker-CRvbzTXm.js → evalWorker-Ds5U4xtN.js} +2178 -30
  13. package/dist/assets/inspectWorker-Dll4eVyD.js +12620 -0
  14. package/dist/assets/{manifold-DpBXFS2K.js → manifold-Bk26ViCr.js} +1 -1
  15. package/dist/assets/{manifold-DzZ4VRPs.js → manifold-DjYsd7A_.js} +2 -2
  16. package/dist/assets/{manifold-B9QSr-qP.js → manifold-sJ-axdXM.js} +1 -1
  17. package/dist/assets/{renderSceneState-BuAXF2jh.js → renderSceneState-Bngp5MrQ.js} +1 -1
  18. package/dist/assets/{reportWorker-BNWEnRg1.js → reportWorker-CU8RZ4O0.js} +2161 -30
  19. package/dist/assets/{distance-BEC2RjJi.js → sectionPlaneMath-BdTjyVfs.js} +2539 -1187
  20. package/dist/cli/render.html +1 -1
  21. package/dist/docs/index.html +1 -1
  22. package/dist/docs-raw/AI/usage.md +7 -2
  23. package/dist/docs-raw/CLI.md +82 -53
  24. package/dist/docs-raw/beta-operations.md +5 -0
  25. package/dist/docs-raw/coding.md +1 -1
  26. package/dist/docs-raw/generated/concepts.md +59 -2
  27. package/dist/docs-raw/generated/core.md +206 -1
  28. package/dist/docs-raw/generated/lib.md +17 -1
  29. package/dist/docs-raw/generated/viewport.md +1 -1
  30. package/dist/docs-raw/guides/inspection-bundles.md +36 -13
  31. package/dist/docs-raw/platform/auth.md +2 -0
  32. package/dist/docs-raw/platform/google-oauth-setup.md +4 -0
  33. package/dist/docs-raw/skills/forgecad-make-a-model.md +87 -8
  34. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +14 -6
  35. package/dist/docs-raw/skills/forgecad-render-inspect.md +1 -1
  36. package/dist/docs-raw/skills/index.md +2 -2
  37. package/dist/index.html +1 -1
  38. package/dist/sitemap.xml +6 -6
  39. package/dist-cli/forgecad.js +7975 -4528
  40. package/dist-cli/forgecad.js.map +1 -1
  41. package/dist-skill/CONTEXT.md +260 -16
  42. package/dist-skill/docs/CLI.md +82 -53
  43. package/dist-skill/docs/generated/core.md +206 -1
  44. package/dist-skill/docs/generated/lib.md +17 -1
  45. package/dist-skill/docs/generated/viewport.md +1 -1
  46. package/dist-skill/docs/guides/inspection-bundles.md +36 -13
  47. package/dist-skill/docs-dev/CLI.md +82 -53
  48. package/dist-skill/docs-dev/coding.md +1 -1
  49. package/dist-skill/docs-dev/generated/core.md +206 -1
  50. package/dist-skill/docs-dev/generated/lib.md +17 -1
  51. package/dist-skill/docs-dev/generated/viewport.md +1 -1
  52. package/dist-skill/docs-dev/guides/inspection-bundles.md +36 -13
  53. package/dist-skill/library/forgecad-make-a-model/SKILL.md +87 -8
  54. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +14 -6
  55. package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +5 -3
  56. package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +7 -5
  57. package/dist-skill/library/forgecad-render-inspect/SKILL.md +1 -1
  58. package/examples/api/bolted-service-cover.forge.js +17 -0
  59. package/examples/api/cable-gland-anchor.forge.js +14 -0
  60. package/examples/api/captured-cartridge-guide.forge.js +14 -0
  61. package/examples/api/captured-linear-slide.forge.js +13 -0
  62. package/examples/api/clevis-pin-joint.forge.js +13 -0
  63. package/examples/api/datum-enclosure.forge.js +16 -0
  64. package/examples/api/hose-barb-port.forge.js +14 -0
  65. package/examples/api/intentional-overlap-overmold.forge.js +16 -0
  66. package/examples/api/knuckled-hinge-assembly.forge.js +15 -0
  67. package/examples/api/living-hinge-cover.forge.js +14 -0
  68. package/examples/api/pcb-terminal-block.forge.js +22 -0
  69. package/examples/api/pinned-lever-pivot-stack.forge.js +14 -0
  70. package/examples/api/retained-shaft-knob-stack.forge.js +15 -0
  71. package/examples/api/routed-tube-clip.forge.js +15 -0
  72. package/examples/api/seated-bearing-stack.forge.js +30 -0
  73. package/examples/api/snap-latch-cover.forge.js +14 -0
  74. package/examples/api/static-assembly-connectors.forge.js +14 -16
  75. package/examples/api/thumb-screw-clamp.forge.js +15 -0
  76. package/package.json +1 -1
@@ -18,7 +18,7 @@ skill-order: 100
18
18
  - [Grouping & Local Coordinates](#grouping-local-coordinates) — `group`
19
19
  - [Section & Projection](#section-projection) — `intersectWithPlane`, `faceProfile`, `projectToPlane`
20
20
  - [Transforms](#transforms) — `composeChain`
21
- - [Verification](#verification) — `spec`
21
+ - [Verification](#verification) — `verify.that`, `verify.equal`, `verify.notEqual`, `verify.greaterThan`, `verify.lessThan`, `verify.inRange`, `verify.centersCoincide`, `verify.connectorDistance`, `verify.physicalComponentCount`, `verify.intentionalOverlap`, `verify.notColliding`, `verify.minClearance`, `verify.clearanceBetween`, `verify.parallel`, `verify.perpendicular`, `verify.coplanar`, `verify.faceAt`, `verify.sameDirection`, `verify.isEmpty`, `verify.notEmpty`, `verify.volumeApprox`, `verify.areaApprox`, `verify.boundingBoxSize`, `verify.edgeContinuity`, `verify.noTinyEdges`, `verify.noSliverFaces`, `verify.noSelfIntersection`, `spec`
22
22
  - [Shape](#shape) — Appearance, Face Topology, Edge Topology, Transforms, Booleans & Cutting, Features, Placement, Connectors, References, Measurement
23
23
  - [Transform](#transform)
24
24
  - [ShapeGroup](#shapegroup) — Children, Transforms, Placement, Connectors, References
@@ -669,6 +669,207 @@ composeChain(...steps: TransformInput[]): Transform
669
669
 
670
670
  ### Verification
671
671
 
672
+ #### `verify.that()` — Custom predicate check.
673
+
674
+ ```ts
675
+ verify.that(label: string, check: () => boolean, message?: string): void
676
+ ```
677
+
678
+ #### `verify.equal()` — Check that two numbers are approximately equal (within tolerance).
679
+
680
+ ```ts
681
+ verify.equal(label: string, actual: number, expected: number, tolerance?: number, message?: string): void
682
+ ```
683
+
684
+ #### `verify.notEqual()` — Check that two numbers are NOT equal (differ by more than tolerance).
685
+
686
+ ```ts
687
+ verify.notEqual(label: string, actual: number, unexpected: number, tolerance?: number, message?: string): void
688
+ ```
689
+
690
+ #### `verify.greaterThan()` — Check that actual > min.
691
+
692
+ ```ts
693
+ verify.greaterThan(label: string, actual: number, min: number, message?: string): void
694
+ ```
695
+
696
+ #### `verify.lessThan()` — Check that actual < max.
697
+
698
+ ```ts
699
+ verify.lessThan(label: string, actual: number, max: number, message?: string): void
700
+ ```
701
+
702
+ #### `verify.inRange()` — Check that min <= actual <= max.
703
+
704
+ ```ts
705
+ verify.inRange(label: string, actual: number, min: number, max: number, message?: string): void
706
+ ```
707
+
708
+ #### `verify.centersCoincide()` — Check that the bounding-box centers of two shapes coincide within tolerance (mm).
709
+
710
+ ```ts
711
+ verify.centersCoincide(label: string, a: ShapeLike, b: ShapeLike, tolerance?: number): void
712
+ ```
713
+
714
+ `ShapeLike`: `{ min: number[], max: number[] }`
715
+
716
+ #### `verify.connectorDistance()` — Check the distance between two named connectors on a shape or group.
717
+
718
+ Use this when connectors + `matchTo()` define a static assembly interface. It proves the mate at runtime, unlike a plain source-level connector declaration. The common case is `expected = 0`, meaning the two connector origins should coincide after placement.
719
+
720
+ ```ts
721
+ verify.connectorDistance("leg is seated", bench, "Rail.leg_0", "Leg0.head", 0, 0.01);
722
+ ```
723
+
724
+ ```ts
725
+ verify.connectorDistance(label: string, target: ConnectorDistanceLike, connectorA: string, connectorB: string, expected?: number, tolerance?: number): void
726
+ ```
727
+
728
+ #### `verify.physicalComponentCount()` — Declare the expected physical connectivity component count for the returned visible model.
729
+
730
+ Use this for generated mechanical models that should have a clear component graph: one connected fixture, a purchased part plus a removable cartridge, a root assembly plus named intentional ghosts, and so on. `forgecad inspect mechanical-integrity` resolves the returned visible objects with the same physical-connectivity analysis used in the quality gate and fails if the actual component count differs.
731
+
732
+ This catches the common generated-CAD failure where a script returns a visually plausible artifact but the handle, screw, washer, cover, or terminal block is actually a separate island.
733
+
734
+ ```ts
735
+ verify.physicalComponentCount("vise is one connected installed assembly", 1);
736
+ ```
737
+
738
+ ```ts
739
+ verify.physicalComponentCount(label: string, expected: number): void
740
+ ```
741
+
742
+ #### `verify.intentionalOverlap()` — Declare that two visible objects intentionally overlap because the overlap is real manufacturing intent.
743
+
744
+ Use this only for overlaps that a mechanical reviewer would accept as actual matter sharing volume: welded/fused regions, overmolded inserts, potted electronics, cast-in hardware, or deliberately bonded laminations. This is not a shortcut for screws without holes, shafts without bores, covers without pockets, or parts placed with collision as a positioning hack.
745
+
746
+ `forgecad inspect mechanical-integrity --collisions` only honors this declaration when both shapes are returned as visible objects and the exact collision report finds that same object pair. Unused or non-visible declarations fail the quality gate so annotations cannot hide unrelated collisions.
747
+
748
+ ```ts
749
+ verify.intentionalOverlap("rubber grip is overmolded on handle", rubberGrip, handleCore, "overmolded insert");
750
+ ```
751
+
752
+ ```ts
753
+ verify.intentionalOverlap(label: string, a: ShapeLike, b: ShapeLike, reason: string): void
754
+ ```
755
+
756
+ #### `verify.notColliding()` — Check that two shapes do not collide (minGap > 0).
757
+
758
+ ```ts
759
+ verify.notColliding(label: string, a: ShapeLike, b: ShapeLike, searchLength?: number): void
760
+ ```
761
+
762
+ #### `verify.minClearance()` — Check that a minimum clearance gap exists between two shapes.
763
+
764
+ ```ts
765
+ verify.minClearance(label: string, a: ShapeLike, b: ShapeLike, minGap: number, searchLength?: number): void
766
+ ```
767
+
768
+ #### `verify.clearanceBetween()` — Check that the clearance gap between two shapes is inside an allowed range.
769
+
770
+ Use this for seated and retained interfaces where a part must be close enough to be mechanically accountable, but must not collide beyond the allowed minimum. It catches both failure modes that make generated CAD look fake: parts floating away from their receiver, and parts intersecting their receiver because the pocket, bore, or running clearance was not modeled.
771
+
772
+ For contact, use a narrow range such as `[-0.01, 0.05]` to tolerate tiny numerical noise. For a running fit, use the intended clearance band.
773
+
774
+ Manifold-backed shapes use exact min-gap distance. Other backends use a mesh-derived min-gap check and say so in the verification message; keep `forgecad inspect mechanical-integrity --collisions` in the acceptance gate for positive-volume interference.
775
+
776
+ ```ts
777
+ verify.clearanceBetween("cover is seated on gasket", cover, gasket, -0.01, 0.05);
778
+ verify.clearanceBetween("carriage runs inside rail", carriage, rail, 0.2, 0.5);
779
+ ```
780
+
781
+ ```ts
782
+ verify.clearanceBetween(label: string, a: ShapeLike, b: ShapeLike, minGap: number, maxGap: number, searchLength?: number): void
783
+ ```
784
+
785
+ #### `verify.parallel()` — Check that two face normals are parallel (within toleranceDeg degrees).
786
+
787
+ ```ts
788
+ verify.parallel(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void
789
+ ```
790
+
791
+ `FaceRefLike`: `{ normal: [ number, number, number ], center: [ number, number, number ] }`
792
+
793
+ #### `verify.perpendicular()` — Check that two face normals are perpendicular (within toleranceDeg degrees).
794
+
795
+ ```ts
796
+ verify.perpendicular(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void
797
+ ```
798
+
799
+ #### `verify.coplanar()` — Check that a face is coplanar with (same plane as) another face, meaning they are parallel AND their centers lie on the same plane.
800
+
801
+ ```ts
802
+ verify.coplanar(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number, toleranceMm?: number): void
803
+ ```
804
+
805
+ #### `verify.faceAt()` — Check that a face center lies at a specific position (within toleranceMm).
806
+
807
+ ```ts
808
+ verify.faceAt(label: string, face: FaceRefLike, expectedPos: [ number, number, number ], toleranceMm?: number): void
809
+ ```
810
+
811
+ #### `verify.sameDirection()` — Check that two face normals point in the same direction (not antiparallel). Stricter than parallel — both |angle| AND sign must match.
812
+
813
+ ```ts
814
+ verify.sameDirection(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void
815
+ ```
816
+
817
+ #### `verify.isEmpty()` — Check that a shape is empty.
818
+
819
+ ```ts
820
+ verify.isEmpty(label: string, shape: ShapeLike, message?: string): void
821
+ ```
822
+
823
+ #### `verify.notEmpty()` — Check that a shape is NOT empty.
824
+
825
+ ```ts
826
+ verify.notEmpty(label: string, shape: ShapeLike, message?: string): void
827
+ ```
828
+
829
+ #### `verify.volumeApprox()` — Check that a shape's volume is approximately equal to expected (mm³).
830
+
831
+ ```ts
832
+ verify.volumeApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void
833
+ ```
834
+
835
+ #### `verify.areaApprox()` — Check that a shape's surface area is approximately equal to expected (mm²).
836
+
837
+ ```ts
838
+ verify.areaApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void
839
+ ```
840
+
841
+ #### `verify.boundingBoxSize()` — Check that a shape's bounding box has approximately the given size.
842
+
843
+ ```ts
844
+ verify.boundingBoxSize(label: string, shape: ShapeLike, expectedSize: [ number, number, number ], tolerance?: number): void
845
+ ```
846
+
847
+ #### `verify.edgeContinuity()` — Check that every sampled seam on a shape meets a requested continuity threshold.
848
+
849
+ ```ts
850
+ verify.edgeContinuity(label: string, shape: ShapeLike, options?: EdgeContinuityThresholds): void
851
+ ```
852
+
853
+ **`EdgeContinuityThresholds`**: `continuity?: SurfaceContinuity`, `samples?: number`, `positionTolerance?: number`, `tangentToleranceDeg?: number`, `curvatureTolerance?: number`
854
+
855
+ #### `verify.noTinyEdges()` — Check that a shape has no tiny edges below the requested threshold.
856
+
857
+ ```ts
858
+ verify.noTinyEdges(label: string, shape: ShapeLike, threshold?: number): void
859
+ ```
860
+
861
+ #### `verify.noSliverFaces()` — Check that a shape has no sliver faces below the requested score threshold.
862
+
863
+ ```ts
864
+ verify.noSliverFaces(label: string, shape: ShapeLike, threshold?: number): void
865
+ ```
866
+
867
+ #### `verify.noSelfIntersection()` — Best-effort exact-shape validity guard for self-intersections or broken B-Rep topology.
868
+
869
+ ```ts
870
+ verify.noSelfIntersection(label: string, shape: ShapeLike): void
871
+ ```
872
+
672
873
  #### `spec()` — Create a named, reusable bundle of verification checks.
673
874
 
674
875
  A spec groups related `verify.*` calls under a collapsible header in the Checks panel. This makes large check suites scannable. Specs can be applied to multiple shapes and can check relationships between parts.
@@ -1859,8 +2060,12 @@ toString(): string
1859
2060
  - `lessThan(label: string, actual: number, max: number, message?: string): void` — Check that actual < max.
1860
2061
  - `inRange(label: string, actual: number, min: number, max: number, message?: string): void` — Check that min <= actual <= max.
1861
2062
  - `centersCoincide(label: string, a: ShapeLike, b: ShapeLike, tolerance?: number): void` — Check that the bounding-box centers of two shapes coincide within tolerance (mm).
2063
+ - `connectorDistance(label: string, target: ConnectorDistanceLike, connectorA: string, connectorB: string, expected?: number, tolerance?: number): void` — Check the distance between two named connectors on a shape or group. Use this when connectors + `matchTo()` define a static assembly interface. It proves the mate at runtime, unlike a plain source-level connector declaration. The common case is `expected = 0`, meaning the two connector origins should coincide after placement. **Example** ```ts verify.connectorDistance("leg is seated", bench, "Rail.leg_0", "Leg0.head", 0, 0.01); ```
2064
+ - `physicalComponentCount(label: string, expected: number): void` — Declare the expected physical connectivity component count for the returned visible model. **Details** Use this for generated mechanical models that should have a clear component graph: one connected fixture, a purchased part plus a removable cartridge, a root assembly plus named intentional ghosts, and so on. `forgecad inspect mechanical-integrity` resolves the returned visible objects with the same physical-connectivity analysis used in the quality gate and fails if the actual component count differs. This catches the common generated-CAD failure where a script returns a visually plausible artifact but the handle, screw, washer, cover, or terminal block is actually a separate island. **Example** ```ts verify.physicalComponentCount("vise is one connected installed assembly", 1); ```
2065
+ - `intentionalOverlap(label: string, a: ShapeLike, b: ShapeLike, reason: string): void` — Declare that two visible objects intentionally overlap because the overlap is real manufacturing intent. **Details** Use this only for overlaps that a mechanical reviewer would accept as actual matter sharing volume: welded/fused regions, overmolded inserts, potted electronics, cast-in hardware, or deliberately bonded laminations. This is not a shortcut for screws without holes, shafts without bores, covers without pockets, or parts placed with collision as a positioning hack. `forgecad inspect mechanical-integrity --collisions` only honors this declaration when both shapes are returned as visible objects and the exact collision report finds that same object pair. Unused or non-visible declarations fail the quality gate so annotations cannot hide unrelated collisions. **Example** ```ts verify.intentionalOverlap("rubber grip is overmolded on handle", rubberGrip, handleCore, "overmolded insert"); ```
1862
2066
  - `notColliding(label: string, a: ShapeLike, b: ShapeLike, searchLength?: number): void` — Check that two shapes do not collide (minGap > 0).
1863
2067
  - `minClearance(label: string, a: ShapeLike, b: ShapeLike, minGap: number, searchLength?: number): void` — Check that a minimum clearance gap exists between two shapes.
2068
+ - `clearanceBetween(label: string, a: ShapeLike, b: ShapeLike, minGap: number, maxGap: number, searchLength?: number): void` — Check that the clearance gap between two shapes is inside an allowed range. **Details** Use this for seated and retained interfaces where a part must be close enough to be mechanically accountable, but must not collide beyond the allowed minimum. It catches both failure modes that make generated CAD look fake: parts floating away from their receiver, and parts intersecting their receiver because the pocket, bore, or running clearance was not modeled. For contact, use a narrow range such as `[-0.01, 0.05]` to tolerate tiny numerical noise. For a running fit, use the intended clearance band. Manifold-backed shapes use exact min-gap distance. Other backends use a mesh-derived min-gap check and say so in the verification message; keep `forgecad inspect mechanical-integrity --collisions` in the acceptance gate for positive-volume interference. **Example** ```ts verify.clearanceBetween("cover is seated on gasket", cover, gasket, -0.01, 0.05); verify.clearanceBetween("carriage runs inside rail", carriage, rail, 0.2, 0.5); ```
1864
2069
  - `parallel(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals are parallel (within toleranceDeg degrees).
1865
2070
  - `perpendicular(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void` — Check that two face normals are perpendicular (within toleranceDeg degrees).
1866
2071
  - `coplanar(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number, toleranceMm?: number): void` — Check that a face is coplanar with (same plane as) another face, meaning they are parallel AND their centers lie on the same plane.
@@ -87,7 +87,7 @@ Pre-built parametric parts available in user scripts as `lib.*`.
87
87
 
88
88
  Every key in this object becomes a method or namespace on the `lib` object exposed to `.forge.js` scripts. The catalog includes:
89
89
 
90
- **Fasteners:** `bolt`, `nut`, `washer`, `fastenerSet`, `fastenerHole`, `boltHole`, `counterbore`, `hexNut`, `holePattern`
90
+ **Fasteners and hardware patterns:** `bolt`, `nut`, `washer`, `fastenerSet`, `boltedServiceCover`, `datumEnclosureAssembly`, `snapLatchCoverAssembly`, `pinnedLeverAssembly`, `retainedShaftAssembly`, `capturedLinearSlide`, `capturedCartridgeGuideAssembly`, `livingHingeCoverAssembly`, `knuckledHingeAssembly`, `clevisPinJointAssembly`, `seatedBearingAssembly`, `cableGlandAnchorAssembly`, `hoseBarbPortAssembly`, `routedTubeClipAssembly`, `pcbTerminalBlockAssembly`, `thumbScrewClampAssembly`, `fastenerHole`, `boltHole`, `counterbore`, `hexNut`, `holePattern`
91
91
 
92
92
  **Structure:** `tube`, `pipe`, `bracket`, `pipeRoute`, `elbow`, `tSlotProfile`, `tSlotExtrusion`, `profile2020BSlot6Profile`, `profile2020BSlot6`
93
93
 
@@ -121,6 +121,22 @@ Sizes outside the supported ranges will throw at runtime with a descriptive erro
121
121
  - `nut(diameter: number, options?: { pitch?: number; height?: number; acrossFlats?: number; segments?: number; }): Shape` — ISO-style hex nut with a threaded bore. **Details** Constructed from the intersection of three rotated slabs with a cylindrical bore subtracted. The nut is centered at the origin, height along Z. Default proportions follow ISO 4032 loosely: height ≈ 0.8×diameter, across-flats ≈ 1.6×diameter. The bore is a clearance bore (not modelled with helical threads) for rendering efficiency. For standard M-size nuts pre-configured for a complete joint, use { **Example** ```ts const n = lib.nut(5); // M5 nut ```
122
122
  - `washer(size: MetricSize, options?: { standard?: WasherStandard; segments?: number; }): Shape` — ISO metric flat washer (DIN 125-A). **Details** Returns a flat ring centered at the origin, thickness along Z. Dimensions are taken from { **Example** ```ts const w = lib.washer('M5'); // DIN 125-A M5 washer ```
123
123
  - `fastenerSet(size: MetricSize, boltLength: number, options?: FastenerSetOptions): FastenerSetResult` — Complete ISO metric fastener set — bolt, nut, optional washers, and matching hole cutters. **Details** Returns all geometry for one bolted joint: the bolt, nut, up to two washers, a clearance-hole cutter, and a tap-drill cutter. All shapes are returned **un-positioned** (each on the Z-axis). Place them with `.translate()`. Sizes outside M4–M10 are supported for the washer (M2–M10); unsupported combinations will throw. **Example** ```ts const hw = lib.fastenerSet('M5', 20); const topPlate = box(60, 40, 8).translate(0, 0, 12) .subtract(hw.clearanceHole.translate(15, 10, 12)); const botPlate = box(60, 40, 8) .subtract(hw.clearanceHole.translate(15, 10, 0)); return [ { name: 'Top Plate', shape: topPlate }, { name: 'Bot Plate', shape: botPlate }, { name: 'Bolt', shape: hw.bolt.translate(15, 10, 20) }, { name: 'Nut', shape: hw.nut.translate(15, 10, -4) }, ]; ```
124
+ - `boltedServiceCover(options: BoltedServiceCoverOptions): BoltedServiceCoverResult` — Bolted service-cover interface with real seats, aligned holes, gasket, fused pull tabs, and installed screws. **Details** This is a higher-level mechanical pattern for the common "removable service cover" failure mode. It creates the parent ledge, cover, gasket, and screws from one shared bolt pattern so agents do not place decorative screw heads or floating pull tabs by eye. Coordinate convention: the parent frame sits from `z=0` to `parentThickness`, the gasket sits on the ledge, the cover sits above the gasket, and screw shafts run downward through the cover into the parent. All parts are centered on the XY origin. **Example** ```ts const cover = lib.boltedServiceCover({ width: 90, depth: 56, screwSize: 'M4', ledgeWidth: 10, boltInset: [6, 6], }); verify.equal('four retained cover screws', cover.screws.length, 4); return cover.parts; ```
125
+ - `datumEnclosureAssembly(options: DatumEnclosureAssemblyOptions): DatumEnclosureAssemblyResult` — Datum-driven enclosure tray with shared wall, ledge, standoff, cover, gasket, port, and screw geometry. **Details** This pattern is for electronics boxes, thermostat backplates, service-stack housings, camera housings, and small fixtures where generated models often place panels, ribs, bosses, ports, and covers by eye. The tray, internal ledges, standoffs, ribs, service port, gasket, cover holes, and installed screws all come from one datum system. This keeps screw axes, boss locations, wall thickness, and service openings aligned instead of relying on independent magic numbers. Coordinate convention: X/Y are the enclosure footprint, Z is up. The base tray starts at `z=0` and rises to `height`; the gasket and cover sit above the top ledge with small explicit face clearances. **Example** ```ts const enclosure = lib.datumEnclosureAssembly({ width: 96, depth: 64, height: 18, }); verify.notColliding('cover clears enclosure gasket', enclosure.cover, enclosure.gasket); verify.inRange('cover stack has small seating clearance', enclosure.dims.faceClearance, 0.01, 0.08); return enclosure.parts; ```
126
+ - `snapLatchCoverAssembly(options: SnapLatchCoverAssemblyOptions): SnapLatchCoverAssemblyResult` — Snap-retained cover with a receiver frame, latch windows, underside catch lands, and fused snap hooks. **Details** This pattern is for covers, cartridges, clasps, and small housings where agents often add decorative tabs without a catch. The receiver has a real service opening plus two clearance latch windows. The cover is one fused part with two flexible-looking snap fingers that pass through the windows and barb under the receiver underside. Nothing intersects in the final assembly; the hook geometry sits close enough to the catch lands to prove retention intent. Coordinate convention: the receiver frame sits from `z=0` to `parentThickness`; the cover is seated just above the receiver on +Z. Two snap hooks sit on the +/-Y ledges and tuck under the receiver. **Example** ```ts const snapCover = lib.snapLatchCoverAssembly({ width: 72, depth: 44, }); verify.notColliding('snap hooks clear receiver windows', snapCover.cover, snapCover.parent); verify.inRange('snap cover has small seating clearance', snapCover.dims.faceClearance, 0.01, 0.08); return snapCover.parts; ```
127
+ - `pinnedLeverAssembly(options: PinnedLeverAssemblyOptions): PinnedLeverAssemblyResult` — Retained pinned lever stack with a fused hub/arm/grip, low stop land, pivot pin, bore cutters, and thrust washers. **Details** This pattern is for the common handle/lever failure mode where a visual arm, hub, washer, and pin are placed near each other but never form a credible mechanism. The lever body is one fused part, the pin runs through aligned bores, washers sit on both sides of the lever, and the support includes a bearing land plus an optional low stop land beside the lever path. Coordinate convention: pivot axis is +Z at the XY origin. The support starts at `z=0`, the lower washer sits on top of the support, the lever sits on the lower washer, the upper washer sits on the lever, and the retained pin spans the full stack. **Example** ```ts const lever = lib.pinnedLeverAssembly({ armLength: 54, armWidth: 10, pinDiameter: 5, }); verify.equal('lever stack has five retained parts', lever.parts.length, 5); return lever.parts; ```
128
+ - `retainedShaftAssembly(options: RetainedShaftAssemblyOptions): RetainedShaftAssemblyResult` — Retained shaft, washer, knob, and support-cheek stack for trunnions, pivots, and adjustable clamps. **Details** This pattern replaces the common "pin, washers, and knob are near each other" visual shortcut with a mechanically accountable shaft stack. The two support cheeks get matching clearance bores, the through shaft spans the whole stack, washers and knobs share the same axis, and retaining heads keep the knobs from reading as loose floating cylinders. Coordinate convention: the shaft axis is +X through the world origin. Support cheeks are centered at `x = +/- supportSpacing / 2`. The supports are bored for clearance, so collision inspection should report no support/shaft overlap while the connectivity audit still sees one retained stack. **Example** ```ts const trunnion = lib.retainedShaftAssembly({ supportSpacing: 96, shaftDiameter: 8, supportHeight: 42, }); verify.equal('retained shaft stack has seven parts', trunnion.parts.length, 7); return trunnion.parts; ```
129
+ - `capturedLinearSlide(options: CapturedLinearSlideOptions): CapturedLinearSlideResult` — Captured linear slide with a U-channel rail, return lips, end stops, and a carriage posed inside the guide. **Details** This pattern is for drawer-slide, quick-release plate, and guided-carriage models where agents often place rail details and a moving block near each other without a capture relationship. The rail is one fused part with side walls, inward lips, and end stops; the carriage is wider than the lip throat but narrower than the inner rail width, so it is mechanically captured while retaining explicit clearance. Coordinate convention: rail length is along X, width is along Y, and Z is up. The rail base starts at `z=0`; the carriage sits above the base and below the return lips. `travel=0` places the carriage at the negative-X end of travel, and `travel=maxTravel` places it at the positive-X end. **Example** ```ts const slide = lib.capturedLinearSlide({ length: 160, carriageLength: 52, travel: 42, }); verify.greaterThan('carriage is captured by return lips', slide.dims.carriageWidth, slide.dims.throatWidth); return slide.parts; ```
130
+ - `capturedCartridgeGuideAssembly(options: CapturedCartridgeGuideAssemblyOptions): CapturedCartridgeGuideAssemblyResult` — Captured removable cartridge guide with return lips, rear stop, wide cartridge flange, and pull tab. **Details** This pattern is for pump cartridges, filter cassettes, skeg cassettes, battery cartridges, and slide-in service modules where generated models often place a tray and a loose block near each other. The guide is one fused part with side walls, inward return lips, and a rear stop. The cartridge has a wide lower flange captured under the lips and a narrower body that passes through the throat, so the model has a real retention contract without manual coordinate tuning. Coordinate convention: insertion travel is along +X. The open entry is at −X, the rear stop is at +X, the guide base starts at `z=0`, and `insertion=0` places the cartridge at the front travel limit. **Example** ```ts const cassette = lib.capturedCartridgeGuideAssembly({ length: 150, cartridgeLength: 72, }); verify.notColliding('cartridge clears guide rails', cassette.cartridge, cassette.guide); verify.greaterThan('cartridge flange is captured by lips', cassette.dims.cartridgeWidth, cassette.dims.throatWidth); return cassette.parts; ```
131
+ - `livingHingeCoverAssembly(options: LivingHingeCoverAssemblyOptions): LivingHingeCoverAssemblyResult` — One-piece molded living-hinge cover strip with a fixed leaf, thin flexible web, cover leaf, pull lip, snap barb, and catch land. **Details** This pattern is for small polypropylene-style lids, battery doors, sample covers, blister latches, and molded service flaps where generated models often draw a decorative hinge strip between two disconnected plates. It returns one fused molded part in its as-molded flat state: fixed mounting leaf, thin hinge web, moving cover leaf, pull lip, raised snap barb, and catch land. The flexible web is intentionally much thinner than the rigid leaves and shares material with both leaves. Coordinate convention: X is hinge length/part width, Y runs from fixed leaf through hinge web to cover leaf, and Z is thickness. The hinge web is centered on `y=0`; the fixed leaf lies at −Y and the cover leaf at +Y. **Example** ```ts const livingCover = lib.livingHingeCoverAssembly({ width: 64, coverDepth: 42, }); verify.greaterThan('living hinge is much thinner than rigid leaves', livingCover.dims.flexRatio, 3); return livingCover.parts; ```
132
+ - `knuckledHingeAssembly(options: KnuckledHingeAssemblyOptions): KnuckledHingeAssemblyResult` — Alternating knuckle hinge with two fused leaves and a retained pin. **Details** This pattern replaces hand-placed hinge barrels and pin ghosts with a mechanically accountable hinge. The fixed leaf owns every other knuckle, the moving leaf owns the alternating knuckles, all knuckles share one bore size, and the retained pin spans the full stack with heads outside the barrels. Coordinate convention: the hinge pin axis is +X through the world origin. The fixed leaf extends toward +Y. The moving leaf extends toward -Y and rotates about +X by `openAngleDeg`. **Example** ```ts const hinge = lib.knuckledHingeAssembly({ length: 70, leafLength: 28, openAngleDeg: 45, }); verify.equal('hinge has two leaves and one retained pin', hinge.parts.length, 3); return hinge.parts; ```
133
+ - `clevisPinJointAssembly(options?: ClevisPinJointAssemblyOptions): ClevisPinJointAssemblyResult` — Clevis-style pin joint with bored yoke ears, a center link eye, and a retained pin. **Details** This pattern is for crank links, damper rod ends, pump crossheads, capo/cam pivots, and small mechanism joints where agents often place an eyelet and a pin near a bracket without modeling the captured load path. The clevis is one fused part with two bored ears and a rear bridge, the center link has a real eye and arm, and the retained pin spans the full stack with heads outside the ears. Coordinate convention: the pin axis is +Y through the world origin. The center link arm extends toward +X. The clevis bridge sits behind the eye on -X, leaving the link eye clear inside the yoke. **Example** ```ts const clevis = lib.clevisPinJointAssembly({ pinDiameter: 4, linkArmLength: 38, }); verify.equal('clevis joint has three retained parts', clevis.parts.length, 3); return clevis.parts; ```
134
+ - `seatedBearingAssembly(options: SeatedBearingAssemblyOptions): SeatedBearingAssemblyResult` — Seated radial-bearing support with a real counterbore, shoulder, through shaft, and retaining collars. **Details** This pattern is for purchased bearings, rollers, burr-cartridge shafts, and small spindle supports where agents often place a ring and a shaft near a block without modelling the pocket that locates the bearing. The housing includes a through-bore and a larger counterbore that leaves a shoulder for the bearing outer race. The shaft is smaller than the bearing bore and carries collars outside the housing, so collision checks can distinguish intended clearance from impossible overlap. Coordinate convention: the shaft axis is +Z through the world origin. The housing block starts at `z=0`, the raised boss is on top of the block, the bearing is seated from the top counterbore, and the shaft extends above and below the housing. **Example** ```ts const bearingStack = lib.seatedBearingAssembly({ bearingOuterDiameter: 22, bearingInnerDiameter: 8, bearingWidth: 7, }); verify.greaterThan('housing has wall around bearing pocket', bearingStack.dims.bossOuterDiameter - bearingStack.dims.pocketDiameter, 4); return bearingStack.parts; ```
135
+ - `cableGlandAnchorAssembly(options: CableGlandAnchorAssemblyOptions): CableGlandAnchorAssemblyResult` — Cable, wire, or tube gland anchor with a real panel hole, hollow gland body, compression nut, and routed cable. **Details** This pattern is for pumps, filters, electronics boxes, vents, monitors, and fixtures where generated models often leave hoses or cables terminating in space. It creates the receiving panel hole, a hollow gland body with a panel-side flange seated in a shallow pocket, a hollow compression nut, and a cable/tube that runs through the gland bore with explicit clearance. Coordinate convention: the cable axis is +X through the world origin. The panel is centered around `x=0` with thickness along X; the flange sits on the +X side of the panel and the compression nut sits on the −X side. The cable spans the full anchor. **Example** ```ts const anchor = lib.cableGlandAnchorAssembly({ cableDiameter: 6, panelThickness: 3, }); verify.notColliding('cable clears gland bore', anchor.cable, anchor.gland); verify.clearanceBetween('gland flange is seated at panel pocket', anchor.gland, anchor.panel, 0.01, 0.2); return anchor.parts; ```
136
+ - `hoseBarbPortAssembly(options: HoseBarbPortAssemblyOptions): HoseBarbPortAssemblyResult` — Hose-barb pump/filter port with a bored receiver, shoulder, barb ridges, installed hose, and clamp band. **Details** This pattern is for pump heads, filters, vents, lab cartridges, and fluid fittings where generated models often leave tubes ending near a block. The receiver has a real through-port and raised boss, the fitting is hollow with a shoulder and multiple barb ridges, and the hose is modeled as an installed tube over the barb envelope with a clamp band. The hose bore is sized for the deformed installed hose, so collision checks distinguish the retained interface from impossible solid overlap. Coordinate convention: the fluid axis is +X through the world origin. The receiver block is centered around `x=0`; the raised boss and hose are on the +X side. **Example** ```ts const hosePort = lib.hoseBarbPortAssembly({ hoseInnerDiameter: 6, hoseOuterDiameter: 10, }); verify.notColliding('hose clears barb peaks', hosePort.hose, hosePort.fitting); verify.inRange('fitting shoulder seats near boss face', hosePort.dims.faceClearance, 0.01, 0.08); return hosePort.parts; ```
137
+ - `routedTubeClipAssembly(options: RoutedTubeClipAssemblyOptions): RoutedTubeClipAssemblyResult` — Routed tube or cable retained by saddle clips with real bores, screw holes, and installed screws. **Details** This pattern is for hoses, wires, pump tubes, sensor leads, and appliance cable runs where generated models often draw a cylinder near a wall without clips or strain relief. The base panel has receiving screw envelopes, each saddle clip has a real through-bore around the tube and vertical screw clearances, and the installed screws share those positions. Coordinate convention: the routed tube runs along +X through the world origin. The base panel starts at `z=0`; clips sit on top of the panel, and the tube passes through their bores. **Example** ```ts const route = lib.routedTubeClipAssembly({ tubeDiameter: 6, clipCount: 3, }); verify.notColliding('tube clears clip bores', route.tube, union(...route.clips)); verify.notColliding('clip screws clear retained stack', union(...route.screws), union(route.panel, ...route.clips)); return route.parts; ```
138
+ - `pcbTerminalBlockAssembly(options?: PcbTerminalBlockAssemblyOptions): PcbTerminalBlockAssemblyResult` — PCB terminal-block stack with a backplate, standoffs, mounting screws, pin holes, and a seated terminal block. **Details** This pattern is for thermostat backplates, appliance control panels, sensor boards, and small electronics where generated models often place a terminal block, screw heads, and holes as independent decorations. The PCB mounting holes, fused standoffs, installed screws, terminal pins, and PCB pin clearances all come from one shared datum system so the purchased block is mechanically seated and the board is actually mounted. Coordinate convention: X/Y are the board footprint, Z is up. The backplate starts at `z=0`, standoffs rise from the plate, the PCB rests on the standoffs, and the terminal block sits on top of the PCB near the front edge. **Example** ```ts const terminalStack = lib.pcbTerminalBlockAssembly({ terminalCount: 5, screwSize: 'M3', }); verify.notColliding('terminal pins clear PCB holes', terminalStack.terminalBlock, terminalStack.pcb); verify.notColliding('mounting screws clear PCB and standoff holes', union(...terminalStack.screws), union(terminalStack.pcb, terminalStack.backplate)); return terminalStack.parts; ```
139
+ - `thumbScrewClampAssembly(options?: ThumbScrewClampAssemblyOptions): ThumbScrewClampAssemblyResult` — Thumb-screw clamp with a C-frame, threaded boss, captive pressure pad, knob, and clamped workpiece. **Details** This pattern is for bench clamps, monitor-arm desk clamps, small vise screws, capo pressure screws, fixture hold-downs, and service brackets where generated models often place a loose screw, knob, or pressure pad near a bracket. The helper creates a one-piece clamp frame with a fixed anvil pad, a bored threaded support and boss, an installed screw with a captive pressure pad and hand knob, and a representative clamped workpiece seated between the pads. Coordinate convention: the clamp screw runs along +X. The fixed anvil is on the -X side, the threaded support and knob are on the +X side, and Z is up from the base bridge. **Example** ```ts const clamp = lib.thumbScrewClampAssembly({ screwSize: 'M6', workpieceThickness: 20, }); verify.notColliding('thumb screw clears threaded boss', clamp.clampScrew, clamp.frame); verify.clearanceBetween('pressure pad is seated on workpiece', clamp.clampScrew, clamp.workpiece, -0.01, 0.05); return clamp.parts; ```
124
140
  - `pipeRoute(points: [ number, number, number ][], radius: number, options?: { bendRadius?: number; wall?: number; segments?: number; }): Shape` — Route a pipe (solid or hollow) through 3D waypoints with smooth bends. Each interior waypoint gets a torus-section bend. Straight segments connect them. Returns a single unioned Shape.
125
141
  - `elbow(pipeRadius: number, bendRadius: number, angle?: number | { ... }, options?: { ... }): Shape` — Pipe elbow — a curved pipe section (torus arc) for connecting two pipe directions. By default creates a bend in the XZ plane: incoming along +Z, outgoing rotated by `angle`. The bend starts at the origin, curving away from it.
126
142
  - `beltDrive(options: BeltDriveOptions): BeltDriveResult` — Create a flat open-belt body around two pulley pitch circles. The belt is generated as a tangent loop in the XY plane and extruded along +Z by `beltWidth`. The result includes the solid belt, the 2D belt profile, a thin pitch-path sketch for visualization, total belt length, tangent spans, and wrap metadata for each pulley. For more than two pulleys, the API intentionally asks for route intent before geometry is created. Use `route: "outer"` for the future outside-envelope mode, or an ordered route for future serpentine/idler layouts. ```ts const drive = lib.beltDrive({ pulleys: [ { name: "motor", center: [0, 0], pitchRadius: 12 }, { name: "output", center: [80, 0], pitchRadius: 28 }, ], beltWidth: 8, beltThickness: 2, }); return drive.belt; ```
@@ -366,7 +366,7 @@ Mock objects appear in the viewport and spatial analysis when you run a file dir
366
366
 
367
367
  The shape is returned unchanged, so you can reference it for alignment, dimensioning, and `verify` checks.
368
368
 
369
- Mock objects participate in `forgecad run` collision detection and spatial analysis. Their names appear with a `(mock)` suffix in reports.
369
+ Mock objects participate in `forgecad run --spatial bounded|exact` collision detection and spatial analysis. Their names appear with a `(mock)` suffix in reports.
370
370
 
371
371
  In the viewport, mock objects render at reduced opacity so they are visually distinct from real geometry.
372
372
 
@@ -26,7 +26,7 @@ cross-sections.
26
26
  forgecad render inspect examples/api/static-assembly-connectors.forge.js --channels rgb,mask
27
27
  forgecad render inspect model.forge.js out/model-inspect --channels rgb,section --force
28
28
  forgecad render inspect model.forge.js --channels rgb,mask,section
29
- forgecad render inspect model.forge.js --channels collisions --focus Bench
29
+ forgecad render inspect model.forge.js --channels collisions --focus "Bench.*"
30
30
  forgecad render inspect model.forge.js --channels rgb,mask --hide "Bench.Slat0,Bench.Slat1"
31
31
  forgecad render inspect model.forge.js --channels thickness --min-thickness 1.2 --warn-thickness 2.0
32
32
  forgecad render inspect channels
@@ -43,7 +43,9 @@ question so heavy analyses do not run unnecessarily.
43
43
  `--focus` and `--hide` use the same object-name filtering semantics as
44
44
  `forgecad run` and `forgecad render 3d`. A bare `--focus` hides mock objects;
45
45
  `--focus name1,name2` emits only matching objects; `--hide name1,name2` removes
46
- matching objects from an otherwise visible scene.
46
+ matching objects from an otherwise visible scene. Matching is case-insensitive
47
+ and supports `*` / `?` globs, so grouped child objects are usually best matched
48
+ with patterns such as `Bench.*`.
47
49
 
48
50
  ## Bundle Layout
49
51
 
@@ -148,6 +150,12 @@ defects. Use this channel to spot spiky tessellation, accidental faceting,
148
150
  jagged boolean residue, and dense sharp-corner regions without losing the
149
151
  silhouette of otherwise smooth surfaces.
150
152
 
153
+ The channel also writes `channels/roughness/point-cloud.json`. Each point sample
154
+ stores object identity, object-local position, normal, dihedral angle, class,
155
+ RGB color, and represented surface area. The PNG renders those samples over
156
+ muted source geometry so the visual evidence stays point-level instead of
157
+ painting a whole object.
158
+
151
159
  `mask` emits one object-color image per view. Black is background. Non-black
152
160
  pixels resolve through `manifest.channels.mask.objects`, which includes object
153
161
  index, RGB color, object id, name, group, tree path, and mock flag. Edge pixels
@@ -161,9 +169,9 @@ background. Non-black pixels resolve through
161
169
  Connectivity is computed from visible scene objects:
162
170
 
163
171
  ```text
164
- bbox overlap edge = bbox interiors overlap
165
- touching edge = bbox contact gap <= 0.05 model units
166
- component = transitive closure over overlap/touching edges
172
+ bbox candidate = bbox interiors overlap or bbox contact gap <= 0.05 model units
173
+ overlap edge = exact boolean intersection volume > 0.1 model units^3
174
+ component = transitive closure over exact overlap edges
167
175
  ```
168
176
 
169
177
  The manifest stores the edge list, component list, per-object body counts, and
@@ -172,8 +180,11 @@ multiple disconnected kernel bodies and the caller supplied a body count, the
172
180
  manifest reports `bodyCount > 1` but the PNG cannot color those internal bodies
173
181
  separately yet.
174
182
 
175
- Connectivity is a fast bbox-neighborhood graph. Use the `collisions` channel
176
- when you need exact positive-volume boolean overlap evidence.
183
+ Connectivity uses bbox only as a broadphase. Bbox contact is not enough to merge
184
+ separate scene objects, which keeps concave assemblies such as cages and captive
185
+ balls from being falsely colored as one component. Use the `collisions` channel
186
+ when you need positive-volume overlap evidence as a defect report rather than a
187
+ component grouping.
177
188
 
178
189
  `distance` emits one rooted physical-component-distance heatmap per view. Black
179
190
  is background. Non-black pixels resolve through
@@ -222,9 +233,10 @@ with the ghosted source geometry. If `--focus PartA,PartB` is used, everything
222
233
  except those objects is hidden, `PartA` and `PartB` are ghosted, and their
223
234
  overlap volume is highlighted if present.
224
235
 
225
- `thickness` emits one local wall-thickness heatmap per view. The renderer
226
- samples visible mesh triangles, casts through the object along each triangle
227
- normal, and colors the surface by the first opposite-surface distance:
236
+ `thickness` emits one local wall-thickness heatmap per view. The renderer places
237
+ deterministic area-weighted point samples across visible mesh surfaces, casts
238
+ through the object along each sample normal, and colors each point by the first
239
+ opposite-surface distance:
228
240
 
229
241
  ```text
230
242
  red = thickness <= minThickness
@@ -237,7 +249,7 @@ gray = unresolved sample
237
249
  The default thresholds are `minThickness=1.2`, `warnThickness=2.0`, and
238
250
  `maxThickness=6.0` model units. Override them with `--min-thickness`,
239
251
  `--warn-thickness`, and `--max-thickness`. Use `--thickness-samples` to raise or
240
- lower the maximum sampled triangles per object.
252
+ lower the maximum thickness point samples per object.
241
253
 
242
254
  The manifest stores the method, thresholds, palette, object list, per-object
243
255
  triangle counts, sampled-triangle counts, minimum, p05, median, mean, maximum,
@@ -245,6 +257,17 @@ critical-area percentage, warning-area percentage, below-warning percentage, and
245
257
  unresolved-area percentage. This makes the PNG useful for visual debugging while
246
258
  the manifest remains the machine-readable source of truth.
247
259
 
260
+ The channel also writes `channels/thickness/point-cloud.json`. Each point sample
261
+ stores object identity, object-local position, normal, measured thickness,
262
+ class, RGB color, and represented surface area. The PNG renders those samples
263
+ over muted source geometry, so local evidence survives even when neighboring
264
+ triangles have very different values.
265
+
266
+ `roughness` uses the same area-weighted point placement. Point colors are local
267
+ to nearby physical feature edges: smooth tessellation diagonals do not become
268
+ visible roughness lines. Use `--roughness-samples` to raise or lower the maximum
269
+ roughness point samples per object.
270
+
248
271
  `section` emits five interior slices per principal plane. The current slicing
249
272
  policy is:
250
273
 
@@ -282,8 +305,8 @@ per-view depth ranges, and object-mask mappings live.
282
305
  not infer identity from object order alone.
283
306
  - Connectivity is object-level. It reports disconnected kernel bodies in the
284
307
  manifest, but the PNG does not split a single scene object into per-body colors.
285
- - Bbox contact is intentionally simple and may over-connect concave shapes whose
286
- bounding boxes touch while surfaces do not. Boolean-overlap edges are exact.
308
+ - Bbox contact is only broadphase evidence and does not merge separate scene
309
+ objects by default. Boolean-overlap edges are exact.
287
310
  - Distance is a physical-component bbox-gap metric in v1, not exact nearest
288
311
  mesh-surface distance. Concave components and loose bounding boxes can make the
289
312
  reported gap smaller than the real closest-surface distance.
@@ -75,6 +75,8 @@ FORGECAD_TOKEN=fc_pat_... forgecad project push
75
75
 
76
76
  Passing the token directly as `forgecad login --token fc_pat_...` is accepted for non-interactive setup, but the interactive prompt is preferred so tokens do not land in shell history.
77
77
 
78
+ For local development or self-hosted experiments, `forgecad login --server <url>` accepts a clean server origin only. Use HTTPS for remote servers; plain HTTP is accepted only for localhost. License activation remains tied to forgecad.io accounts.
79
+
78
80
  ---
79
81
 
80
82
  ## API Endpoints
@@ -39,6 +39,10 @@ This is what users see when they click "Sign in with Google."
39
39
 
40
40
  ## 3. Create OAuth Credentials
41
41
 
42
+ Use the Google Auth Platform / Credentials page for ForgeCAD web sign-in.
43
+ The `gcloud iam oauth-clients` command is for IAM/IAP OAuth application
44
+ resources and is not the right client type for this app's Google Sign-In flow.
45
+
42
46
  1. In the left sidebar: **APIs & Services → Credentials**
43
47
  2. Click **+ Create Credentials → OAuth client ID**
44
48
  3. **Application type**: Web application
@@ -1,6 +1,6 @@
1
1
  # forgecad-make-a-model
2
2
 
3
- Create new ForgeCAD (.forge.js) models in the active CAD project. Handles file placement, invokes the forgecad skill for API guidance, and validates the result.
3
+ 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.
4
4
 
5
5
  | Field | Value |
6
6
  | --- | --- |
@@ -13,6 +13,17 @@ Create new ForgeCAD (.forge.js) models in the active CAD project. Handles file p
13
13
 
14
14
  Create new ForgeCAD models in the user's active ForgeCAD project.
15
15
 
16
+ ### Default Output Standard
17
+
18
+ Unless the user explicitly asks for a rough blockout, toy, pure visual study, mass-production release package, or specific manufacturing process, the output is a **manufacture-realistic prototype**.
19
+
20
+ That means the model should look and behave like a serious prototype someone could fabricate, buy parts for, assemble, inspect, and iterate in a real shop or product lab. It is not a casual concept sketch, not a universal 3D-printing exercise, and not a claim of certified production readiness.
21
+
22
+ - Choose processes that fit the artifact: machined, sheet-metal, tube, wood/composite, laser-cut, molded-look prototype, printed, or hybrid purchased-hardware construction as appropriate.
23
+ - Include prototype-real features: wall thickness, fastener stacks, bosses, ribs, flanges, bends, seats, bushings, gaskets, cable exits, service access, toleranced clearances, and believable purchased parts.
24
+ - Use production cues only when they help prototype realism. Do not invent expensive production tooling details, certification claims, or safety ratings unless the user asked for that level.
25
+ - If the user asks for `production-realistic`, push toward manufacturing DFM and production-intent materials. If they ask for `printable`, make the selected printed parts honest. If they ask for `visual-CAD`, keep it clearly visual rather than pretending it is build-ready.
26
+
16
27
  ### File Placement
17
28
 
18
29
  All new `.forge.js` files go under the date-based directory structure:
@@ -40,6 +51,7 @@ Use today's date for the directory. Use the user's current ForgeCAD project when
40
51
  1. Load the ForgeCAD skill — always invoke the `forgecad` skill first to get API docs and authoring guidance. Read at minimum the Core API reference. If any two parts are intended to touch or mate in the final model, read the positioning guide immediately and default to connectors + `matchTo()`.
41
52
  2. Create the directory — `mkdir -p YYYY/MM/DD/[folder]` as needed.
42
53
  3. Write the model — create the `.forge.js` file(s) following ForgeCAD conventions:
54
+ - Treat the default build profile as `manufacture-realistic prototype`; choose and encode the artifact's manufacturing/process cues before adding styling detail
43
55
  - Declare `param()` / `boolParam()` for all tunable dimensions
44
56
  - If the model is split across files, use `main.forge.js` as the primary entry point, import renderable parts from neighboring `.forge.js` files, and keep only pure helpers/constants in plain `.js` modules
45
57
  - When there are multiple versions of the same object, expose the version as a choice parameter and render one selected version at a time
@@ -52,12 +64,12 @@ Use today's date for the directory. Use the user's current ForgeCAD project when
52
64
  - Return the final geometry (single shape, array, or named objects array)
53
65
  - Treat `fillet(shape, r)` and `chamfer(shape, r)` as experimental edge treatments: Manifold can produce incorrect results and OCCT can be very slow. Prefer simpler primitive profiles, lower segment counts, targeted edge selectors, and inspection before relying on the result.
54
66
  4. Validate — run `forgecad run <file>` to check for errors. For multi-file projects, always validate `main.forge.js`.
55
- 5. Verify geometry — render the result, run `forgecad run --connectivity` when the model has multiple returned objects or visible attachments, and run `forgecad render inspect` with the relevant channels for the task (see Final Acceptance Gate and Render-Verify Loop below). For multi-file projects, render and inspect `main.forge.js`.
67
+ 5. Verify geometry — render the result, run `forgecad run --connectivity` when the model has multiple returned objects or visible attachments, run `forgecad debug assembly --fail-on warning` when the script uses `assembly()`, run `forgecad inspect mechanical-integrity <project-or-file> --collisions` before sharing generated mechanical work, and run `forgecad render inspect` with the relevant channels for the task (see Final Acceptance Gate and Render-Verify Loop below). For multi-file projects, render and inspect `main.forge.js`.
56
68
 
57
69
  ### Manufacturing Process Is Not Assumed
58
70
 
59
71
  Do not interpret every ForgeCAD model as a printable object.
60
- Choose the manufacturing/process cues that fit the artifact unless the user explicitly asked for a specific process.
72
+ Choose the manufacturing/process cues that fit the artifact unless the user explicitly asked for a specific process. A manufacture-realistic prototype can be CNC-machined, bent sheet, tube-and-plate, wood/composite, molded-look low-volume, printed, or hybrid; the right answer depends on the load path and operating story.
61
73
 
62
74
  - For rideable products such as scooters, bikes, skateboards, carts, or mobility-adjacent devices, use realistic metal/composite/wood structural members, purchased wheels/bearings/axles/brakes/grips, and standard hardware unless the user asked for a printable toy/model.
63
75
  - For furniture and load-bearing structures, consider wood, sheet goods, tube, metal brackets, conventional joinery, and printed parts only where they are honest secondary components.
@@ -109,6 +121,52 @@ Do not reveal hidden structure by permanently cutting away the production geomet
109
121
 
110
122
  When internals are hidden by the final exterior, verify them with exploration tools instead of changing the artifact: render underside or alternate camera views, use `render inspect` section channels, use viewer-only cut planes or explode controls, temporarily make a shell transparent, or add named ghost objects for fit checks. Those views are diagnostic/presentation modes; they must not replace the real model unless the user explicitly asked for a cutaway teaching model.
111
123
 
124
+ ### Mechanical Assembly Contract
125
+
126
+ For mechanical models, a ForgeCAD script is not done when it merely looks assembled. Every visible piece must have a believable physical reason to be where it is: fused material, contact faces, a screw stack, a pin in a bore, a tab in a slot, a gasket on a land, a bearing in a seat, a cable in a channel, or a named intentional ghost.
127
+
128
+ - For bespoke fixed assemblies that do not match an existing `lib.*` helper, start from `examples/api/static-assembly-connectors.forge.js`: build each part in local coordinates, pick one root part, place every other touching part with `matchTo()`, and verify the mate with `verify.connectorDistance(...)`. Do not use final `translate()` calls as assembly contracts.
129
+ - If you use `assembly().addPart()`, do not treat `addFixed()`, `addRevolute()`, or `addPrismatic()` with a manual `frame: Transform.identity().translate(...)` as a physical contract. Manual joint frames are acceptable only as a temporary scaffold. Before delivery, convert mating interfaces to connectors with `connect()` / `match()` where the interfaces physically meet, or prove the manual joint with `forgecad debug assembly --fail-on warning` and documented geometry.
130
+ - A named assembly part should not contain unintentional disconnected bodies. If a "cover plate" contains floating pull tabs, loose screw heads, or a separate gasket, either boolean-join the manufactured features, model the fasteners/seals as separate named parts, or provide the receiving holes/lands that explain the separation.
131
+ - Screws are not decoration. A screw needs a clearance/counterbore in the cover, a receiving threaded hole/boss or through stack in the parent, enough material around both, and aligned axes from one shared bolt pattern.
132
+ - Handles and levers need a load path. Model the hub-to-arm connection, pivot pin/bore, thrust washers or shoulders, stops/detents where relevant, and the connected follower/contact surface. A handle tangent to a hub is a failed mechanism.
133
+ - Covers, doors, cartridges, and service panels need seats. Model ledges, gasket grooves, bosses, snap hooks, tabs, or hinge barrels, then show how the removable part is retained.
134
+ - Cables, wires, and tubes need receiving geometry. Model a gland, grommet, clamp, socket, ferrule, routed channel, or hose barb; do not let a cylinder end in open space.
135
+ - Purchased loose parts may remain separate bodies, but they should be named as purchased hardware or consumables and should sit in believable sockets, bores, races, guides, or fastener stacks.
136
+ - Encode interface intent with `verify.*`, not only comments. Use `verify.clearanceBetween("cover is seated on gasket", cover, gasket, -0.01, 0.05)` for contact/seated fits and clearance bands, `verify.minClearance(...)` or `verify.notColliding(...)` for keep-out/running gaps, and `verify.connectorDistance(...)` for connector-authored mates. Part counts and generic dimensions are useful supporting checks, but they do not prove an interface by themselves.
137
+
138
+ For ordinary removable covers, prefer `lib.boltedServiceCover(...)` before hand-placing plates, tabs, screw heads, gaskets, and holes. It creates the parent ledge, gasket, cover plate with fused pull tabs, shared bolt pattern, and installed screws as one mechanically accountable interface.
139
+
140
+ For electronics boxes, backplates, service-stack housings, and camera/monitor enclosures, prefer `lib.datumEnclosureAssembly(...)` before independently placing panels, ribs, bosses, ports, covers, and screws. It creates the tray, ledges, standoffs, ribs, service port, gasket, cover, and screws from one shared datum system.
141
+
142
+ For PCB-mounted terminal blocks, thermostat backplates, control boards, and wire-entry electronics panels, prefer `lib.pcbTerminalBlockAssembly(...)` before placing a loose green block near a board or cover. It creates the backplate, fused standoffs, PCB mounting screws, PCB pin holes, terminal pins, and seated purchased terminal block from one shared datum system.
143
+
144
+ For snap-retained covers, cartridges, small clasps, and housings, prefer `lib.snapLatchCoverAssembly(...)` before drawing decorative snap tabs. It creates latch windows, underside catch lands, fused snap hooks, barbs, and clearance checks so the cover is retained by real geometry.
145
+
146
+ For ordinary pinned handles, cam levers, release levers, and latch arms, prefer `lib.pinnedLeverAssembly(...)` before hand-placing a hub, arm, washers, and pin. It creates a fused lever body, aligned pivot bore, retained pin, thrust washers, support land, and low stop land as one mechanically accountable pivot stack.
147
+
148
+ For trunnions, side knobs, adjustable pivots, and clamp shafts, prefer `lib.retainedShaftAssembly(...)` before hand-placing rods, washers, and knobs. It creates bored support cheeks, a through shaft, thrust washers, knobs, retaining heads, and shared bore dimensions as one mechanically accountable stack.
149
+
150
+ For thumb screws, desk clamps, vise screws, capo pressure screws, and small fixture hold-downs, prefer `lib.thumbScrewClampAssembly(...)` before hand-placing a knob, screw cylinder, pressure pad, and bracket jaw. It creates the C-frame, threaded boss/bore, captive pressure pad, hand knob, and seated workpiece contact from one shared datum system.
151
+
152
+ For drawer slides, quick-release plates, and guided linear carriages, prefer `lib.capturedLinearSlide(...)` before hand-placing rails and a block. It creates a U-channel rail with return lips, end stops, a captured carriage, and explicit travel/clearance dimensions.
153
+
154
+ For pump cartridges, filter cassettes, battery cartridges, skeg cassettes, and removable slide-in modules, prefer `lib.capturedCartridgeGuideAssembly(...)` before placing a loose tray and block. It creates return lips, a rear stop, a captured cartridge flange, pull tab, insertion travel, and explicit clearance dimensions.
155
+
156
+ For molded flexible battery doors, sample covers, blister latches, and polypropylene-style service flaps, prefer `lib.livingHingeCoverAssembly(...)` before drawing two plates with a decorative hinge strip. It creates one fused molded strip with fixed leaf, thin flexible web, moving cover leaf, pull lip, snap barb, catch land, and web-thickness checks.
157
+
158
+ For doors, barn-door leaves, lids, locket leaves, and small hinged access panels, prefer `lib.knuckledHingeAssembly(...)` before hand-placing barrels and a pin. It creates alternating fused knuckles, two leaves, a shared bore, and a retained pin as one mechanically accountable hinge.
159
+
160
+ For crank links, damper rod ends, crossheads, and clevis-yoke pivots, prefer `lib.clevisPinJointAssembly(...)` before hand-placing an eyelet and pin. It creates bored clevis ears, a captured center link eye, a rear bridge, and a retained pin as one mechanically accountable load path.
161
+
162
+ For bearings, rollers, burr cartridges, spindle supports, and purchased radial bearings, prefer `lib.seatedBearingAssembly(...)` before hand-placing a ring and shaft near a block. It creates a bored housing, counterbore pocket, bearing shoulder, seated bearing, shaft, collars, and shared clearance dimensions as one mechanically accountable support.
163
+
164
+ For cables, wires, hoses, pump tubes, and panel pass-throughs, prefer `lib.cableGlandAnchorAssembly(...)` before hand-placing a loose cylinder near a wall. It creates the panel clearance hole, hollow gland body, compression nut, and routed cable/tube as one mechanically accountable pass-through.
165
+
166
+ For routed cables, wires, hoses, pump tubes, and sensor leads that run along a surface, prefer `lib.routedTubeClipAssembly(...)` before drawing a tube that floats between endpoints. It creates a base panel, saddle clip bores, clip screw holes, installed screws, and the retained tube route from one shared datum system.
167
+
168
+ For fluid hoses, pump inlets/outlets, filter ports, and lab tubing, prefer `lib.hoseBarbPortAssembly(...)` before drawing a tube that stops at a block. It creates the bored receiver, raised boss, hollow barbed fitting, installed hose, and clamp band as one accountable hose-port interface.
169
+
112
170
  ### Final Geometry Should Be Physically Plausible
113
171
 
114
172
  Treat each returned part as real matter occupying space. In the final build, separate parts should not intersect unless the intersection is the actual manufacturing intent, such as a welded/fused region, an overmolded insert, or a boolean-unioned solid that is no longer a separate part.
@@ -117,7 +175,9 @@ Do not use final interpenetration as a placement shortcut. For joints and interf
117
175
 
118
176
  Temporary collisions during construction are fine when they are part of how the model is made or verified: oversized cutter solids before `difference()`, overlapping primitives before `union()`, transparent ghost parts for fit checks, or exploratory joint layouts. Those temporary bodies should be consumed, hidden, named as ghosts, or isolated with inspection filters so final collision findings stay meaningful.
119
177
 
120
- Before delivery on any multi-part, internal, or mechanical model, run the collision inspection, read the collision channel PNGs, and check `manifest.json`. Fix unexpected overlaps. If a collision is intentional, document it in the model naming/comments or isolate that inspection with `--focus` / `--hide` so the remaining collision report proves the final assembly is real.
178
+ If a final overlap is real manufacturing intent, document the exact visible object pair with `verify.intentionalOverlap("rubber grip is overmolded on handle core", rubberGrip, handleCore, "overmolded bonded grip")`. Use this only for welded, fused, overmolded, cast-in, potted, or bonded matter. The mechanical-integrity gate honors it only when the same visible object pair has a confirmed exact collision; unused or non-visible declarations still fail.
179
+
180
+ Before delivery on any multi-part, internal, or mechanical model, run the collision inspection, read the collision channel PNGs, and check `manifest.json`. Fix unexpected overlaps. If a collision is intentional, declare the exact visible pair with `verify.intentionalOverlap(...)` or isolate that inspection with `--focus` / `--hide` so the remaining collision report proves the final assembly is real.
121
181
 
122
182
  ### Final Acceptance Gate
123
183
 
@@ -131,6 +191,24 @@ Before telling the user the model is done, prove both technical validity and vis
131
191
 
132
192
  The reported component count must match the design intent. Treat unexpected islands, accidental fusion, or bbox-only "touching" that does not make physical sense as model bugs.
133
193
 
194
+ If the script uses `assembly()`, also run:
195
+
196
+ ```bash
197
+ forgecad debug assembly model.forge.js --fail-on warning
198
+ ```
199
+
200
+ Fix warnings about multiple roots, manual joint contracts, disconnected bodies inside parts, unused connectors, solve warnings, and collisions before delivery. If a warning is truly intentional, rename the part or add a short code comment so a reviewer can see the physical reason.
201
+
202
+ For generated mechanical projects or batches, also run:
203
+
204
+ ```bash
205
+ forgecad inspect mechanical-integrity . --collisions
206
+ ```
207
+
208
+ This is the shareability gate. It fails on missing `verify.*` checks, missing mechanical-interface verification, fragmented named groups, uncontracted manual assemblies, positive-volume object collisions, timeouts, and runtime failures. Do not share a generated mechanical model while this gate is red unless the user explicitly asked for a rough concept/blockout.
209
+
210
+ The model should include at least one verification that proves a mechanical interface, not just object count. Prefer checks such as `verify.clearanceBetween("bearing is seated in pocket", bearing, housing, -0.01, 0.1)`, `verify.minClearance("carriage clears rail", carriage, rail, 0.15)`, `verify.notColliding("cover screw clears parent hole", screw, parent)`, or `verify.connectorDistance("leg connector is seated", bench, "Rail.leg_0", "Leg0.head", 0, 0.01)`.
211
+
134
212
  2. Run a collision bundle and read both the manifest and images:
135
213
 
136
214
  ```bash
@@ -245,13 +323,14 @@ return [
245
323
 
246
324
  This immediately reveals: does it fit? Does it collide with walls? Does the shaft clear the opening?
247
325
 
248
- #### Use console.log for dimension checks
326
+ #### Use verify for acceptance, console.log for traces
249
327
 
250
- Print derived dimensions and clearances to verify arithmetic before rendering:
328
+ Use `verify.*` for dimensions and clearances that decide whether the model is acceptable. Use `console.log()` only for explanatory traces that help you read the run output.
251
329
 
252
330
  ```js
253
- console.log("wall remaining:", ((outerW - slotW) / 2).toFixed(1)); // must be > 0
254
- console.log("hole clearance from edge:", (flangeW/2 - holeX - holeDia/2).toFixed(1));
331
+ verify.greaterThan("wall remains around slot", (outerW - slotW) / 2, 1.6);
332
+ verify.greaterThan("hole clears flange edge", flangeW / 2 - holeX - holeDia / 2, 2.0);
333
+ console.log("wall remaining:", ((outerW - slotW) / 2).toFixed(1));
255
334
  ```
256
335
 
257
336
  Output appears under "Script output:" in `forgecad run`.