forgecad 0.9.4 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/assets/{AdminPage-jwoEgwE_.js → AdminPage-uTtcSXtn.js} +1 -1
  2. package/dist/assets/{BlogPage-Ck7g3ue2.js → BlogPage-DYJMjWx3.js} +1 -1
  3. package/dist/assets/{DocsPage-9WaRC14b.js → DocsPage-C58f0K5v.js} +1 -6
  4. package/dist/assets/{EditorApp-Dja2jMmW.js → EditorApp-DNH1TEz1.js} +282 -62
  5. package/dist/assets/{EmbedViewer-37_PfMwv.js → EmbedViewer-CMXWA2LX.js} +2 -2
  6. package/dist/assets/{LandingPageProofDriven-CO8WL0CY.js → LandingPageProofDriven-CAu2OZFn.js} +1 -1
  7. package/dist/assets/{PricingPage-DADKGuOa.js → PricingPage-BIgW7m3X.js} +1 -1
  8. package/dist/assets/{SettingsPage-DKKI4W49.js → SettingsPage-N1l1tMXO.js} +1 -1
  9. package/dist/assets/{app-CwI02pTA.js → app-CFy7g5WP.js} +74 -12
  10. package/dist/assets/cli/{render-Kw5hLEcL.js → render-BrVVdj_T.js} +453 -41
  11. package/dist/assets/{evalWorker-D6ub3kfS.js → evalWorker-c_SB9gg3.js} +2057 -446
  12. package/dist/assets/{manifold-lru0jwVw.js → manifold-CRoBhJKH.js} +2 -2
  13. package/dist/assets/{manifold-CwDdMKyc.js → manifold-Cjk7WhRs.js} +1 -1
  14. package/dist/assets/{manifold-DTvmxSDf.js → manifold-Dp6pvFr6.js} +1 -1
  15. package/dist/assets/{renderSceneState-tvtNKNRi.js → renderSceneState-3DfsSASX.js} +1 -1
  16. package/dist/assets/{reportWorker-DeqktDGt.js → reportWorker-BLkuIoS8.js} +2052 -443
  17. package/dist/assets/{sectionPlaneMath-C8N0w8o3.js → sectionPlaneMath-CykEnkvQ.js} +2258 -518
  18. package/dist/cli/render.html +1 -1
  19. package/dist/docs/index.html +2 -2
  20. package/dist/docs-raw/AI/usage.md +0 -1
  21. package/dist/docs-raw/API/core/concepts.md +11 -1
  22. package/dist/docs-raw/CLI.md +64 -13
  23. package/dist/docs-raw/generated/assembly.md +8 -3
  24. package/dist/docs-raw/generated/concepts.md +44 -41
  25. package/dist/docs-raw/generated/core.md +97 -47
  26. package/dist/docs-raw/generated/curves.md +6 -580
  27. package/dist/docs-raw/generated/lib.md +40 -3
  28. package/dist/docs-raw/generated/output.md +6 -1
  29. package/dist/docs-raw/generated/sdf.md +50 -4
  30. package/dist/docs-raw/generated/viewport.md +1 -9
  31. package/dist/docs-raw/guides/inspection-bundles.md +31 -6
  32. package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -0
  33. package/dist/docs-raw/skills/forgecad-image-replicator.md +3 -1
  34. package/dist/docs-raw/skills/forgecad-make-a-model.md +48 -4
  35. package/dist/docs-raw/skills/forgecad-render-inspect.md +3 -1
  36. package/dist/docs-raw/skills/forgecad-visual-spec.md +2 -0
  37. package/dist/docs-raw/skills/forgecad.md +2 -1
  38. package/dist/docs-raw/skills/index.md +0 -1
  39. package/dist/index.html +1 -1
  40. package/dist/sitemap.xml +6 -6
  41. package/dist-cli/blender/render.py +43 -8
  42. package/dist-cli/forgecad.js +4941 -1758
  43. package/dist-cli/forgecad.js.map +1 -1
  44. package/dist-skill/CONTEXT.md +255 -656
  45. package/dist-skill/SKILL-dev.md +2 -1
  46. package/dist-skill/SKILL.md +2 -1
  47. package/dist-skill/docs/API/core/concepts.md +11 -1
  48. package/dist-skill/docs/CLI.md +64 -13
  49. package/dist-skill/docs/generated/assembly.md +8 -3
  50. package/dist-skill/docs/generated/core.md +97 -47
  51. package/dist-skill/docs/generated/curves.md +6 -580
  52. package/dist-skill/docs/generated/lib.md +40 -3
  53. package/dist-skill/docs/generated/output.md +6 -1
  54. package/dist-skill/docs/generated/sdf.md +50 -4
  55. package/dist-skill/docs/generated/viewport.md +1 -9
  56. package/dist-skill/docs/guides/inspection-bundles.md +31 -6
  57. package/dist-skill/docs-dev/API/core/concepts.md +11 -1
  58. package/dist-skill/docs-dev/CLI.md +64 -13
  59. package/dist-skill/docs-dev/generated/assembly.md +8 -3
  60. package/dist-skill/docs-dev/generated/core.md +97 -47
  61. package/dist-skill/docs-dev/generated/curves.md +6 -580
  62. package/dist-skill/docs-dev/generated/lib.md +40 -3
  63. package/dist-skill/docs-dev/generated/output.md +6 -1
  64. package/dist-skill/docs-dev/generated/sdf.md +50 -4
  65. package/dist-skill/docs-dev/generated/viewport.md +1 -9
  66. package/dist-skill/docs-dev/guides/inspection-bundles.md +31 -6
  67. package/dist-skill/library/README.md +0 -1
  68. package/dist-skill/library/forgecad-blockout-model/SKILL.md +1 -0
  69. package/dist-skill/library/forgecad-image-replicator/SKILL.md +3 -1
  70. package/dist-skill/library/forgecad-make-a-model/SKILL.md +48 -4
  71. package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
  72. package/dist-skill/library/forgecad-visual-spec/SKILL.md +2 -0
  73. package/examples/api/drive-wheel-regions.forge.js +43 -0
  74. package/examples/api/sdf-circular-array-knurling.forge.js +19 -0
  75. package/examples/api/sdf-pattern2d-ceramic-ripple-set.forge.js +83 -0
  76. package/examples/api/sdf-pattern2d-grip-tread.forge.js +72 -0
  77. package/examples/api/sdf-pattern2d-orbital-jewelry.forge.js +62 -0
  78. package/examples/api/sdf-surface-basket-weave.forge.js +67 -0
  79. package/examples/api/sector-gear-body.forge.js +34 -0
  80. package/package.json +1 -1
  81. package/dist/docs-raw/skills/forgecad-api-dogfood.md +0 -130
  82. package/dist-skill/library/forgecad-api-dogfood/SKILL.md +0 -125
@@ -10,6 +10,7 @@ Pre-built fasteners, gears, pipes, structural profiles, and utility shapes. Acce
10
10
  ## Contents
11
11
 
12
12
  - [TangentLoop2D](#tangentloop2d)
13
+ - [DriveWheelBuilder](#drivewheelbuilder)
13
14
  - [lib](#lib)
14
15
 
15
16
  ---
@@ -50,6 +51,32 @@ toProfile(): Sketch
50
51
  offsetBand(thickness: number): Sketch
51
52
  ```
52
53
 
54
+ ### `DriveWheelBuilder`
55
+
56
+ #### `addSpurTeethBetween()` — Add an involute spur-tooth window on part of the pitch circle.
57
+
58
+ ```ts
59
+ addSpurTeethBetween(options: DriveWheelSpurTeethRegionOptions): this
60
+ ```
61
+
62
+ #### `addSolidArcBetween()` — Add a constant-radius solid arc region such as a dwell, stop, or pusher.
63
+
64
+ ```ts
65
+ addSolidArcBetween(options: DriveWheelSolidArcRegionOptions): this
66
+ ```
67
+
68
+ #### `addShapeRegion()` — Add a fully custom region shape while preserving region metadata.
69
+
70
+ ```ts
71
+ addShapeRegion(name: string, shape: Shape, options?: DriveWheelShapeRegionOptions): this
72
+ ```
73
+
74
+ #### `build()` — Build the final wheel shape with a bore connector and region metadata.
75
+
76
+ ```ts
77
+ build(): Shape
78
+ ```
79
+
53
80
  ---
54
81
 
55
82
  ## Constants
@@ -58,7 +85,7 @@ offsetBand(thickness: number): Sketch
58
85
 
59
86
  Pre-built parametric parts available in user scripts as `lib.*`.
60
87
 
61
- Every key in this object becomes a method on the `lib` namespace exposed to `.forge.js` scripts. The catalog includes:
88
+ Every key in this object becomes a method or namespace on the `lib` object exposed to `.forge.js` scripts. The catalog includes:
62
89
 
63
90
  **Fasteners:** `bolt`, `nut`, `washer`, `fastenerSet`, `fastenerHole`, `boltHole`, `counterbore`, `hexNut`, `holePattern`
64
91
 
@@ -68,7 +95,9 @@ Every key in this object becomes a method on the `lib` namespace exposed to `.fo
68
95
 
69
96
  **Threads:** `thread`
70
97
 
71
- **Gears:** `spurGear`, `bevelGear`, `faceGear`, `sideGear`, `ringGear`, `rackGear`, `gearPair`, `bevelGearPair`, `faceGearPair`, `sideGearPair`
98
+ **Gears:** `spurGear`, `sectorGear`, `driveWheel`, `bevelGear`, `faceGear`, `sideGear`, `ringGear`, `rackGear`, `gearPair`, `bevelGearPair`, `faceGearPair`, `sideGearPair`
99
+
100
+ **Gear bodies:** `gearBodies.disk`, `gearBodies.diskWithHub`, `gearBodies.spoked`, `gearBodies.fromProfile` plus direct aliases `gearBodyDisk`, `gearBodyDiskWithHub`, `gearBodySpoked`, `gearBodyFromProfile`
72
101
 
73
102
  **Gear ratios (pure math helpers):** `gearRatio`, `rackRatio`, `planetaryRatio`
74
103
 
@@ -76,7 +105,7 @@ Every key in this object becomes a method on the `lib` namespace exposed to `.fo
76
105
 
77
106
  **Utilities:** `explode`
78
107
 
79
- Extend this by adding new entries here and registering the corresponding runner binding in `runner.ts`. Sizes outside the supported ranges will throw at runtime with a descriptive error.
108
+ Sizes outside the supported ranges will throw at runtime with a descriptive error.
80
109
 
81
110
  - `boltHole(diameter: number, depth: number): Shape` — Simple cylindrical through-hole cutter centered on Z=0. Subtract the result from a part to produce a plain cylindrical clearance hole. For ISO metric sizes with fit classes and counterbore/countersink, use {
82
111
  - `fastenerHole(opts: FastenerHoleOptions): Shape` — ISO metric fastener hole cutter with optional counterbore or countersink. **Details** Returns a cutter shape (subtract from a solid to produce the hole). Sizes outside M2–M10 will throw. Extend `METRIC_HOLE_TABLE` in this file to add new sizes. **Example** ```ts const plate = box(60, 40, 8) .subtract(lib.fastenerHole({ size: 'M5', fit: 'normal', depth: 8 }) .translate(15, 10, 4)); ```
@@ -114,3 +143,11 @@ Extend this by adding new entries here and registering the corresponding runner
114
143
  - `rackRatio(module: number, pinionTeeth: number): number` — Coupling ratio between a pinion and a rack. When the pinion rotates by `θ` degrees, the rack slides by `θ × (π × module × teeth / 360)` mm. Equivalently, 1mm of rack travel = `180 / (π × pitchRadius)` degrees of pinion rotation.
115
144
  - `planetaryRatio(sunTeeth: number, ringTeeth: number): number` — Planetary gear reduction ratio when the ring is held fixed. Input: sun. Output: carrier. Ratio: `1 + ringTeeth / sunTeeth`. One turn of the sun produces `1 / ratio` turns of the carrier.
116
145
  - `boltPattern(options: BoltPatternOptions): BoltPattern` — Define a bolt pattern once and cut it from multiple parts. const base = bolts.cut(box(60, 50, 10), 12, { from: -1 }); const cover = bolts.cut(box(60, 50, 3), 5, { from: -1 }); // Same positions in both parts — guaranteed aligned. ```
146
+ - `driveWheel(options?: DriveWheelOptions): DriveWheelBuilder` — Start a composable exceptional gear or drive wheel.
147
+ - `readDriveWheelMeta(shape: Shape): DriveWheelMeta | null` — Read the functional-region metadata attached by `driveWheel().build()`.
148
+ - `sectorGear(options: SectorGearOptions): Shape` — Involute sector gear with teeth on only part of the pitch circle. Specify the full-circle pitch as `teethOnFullCircle`, then choose the active tooth window with `firstTooth` and `toothCount`. The body is separate from the tooth region: pass a `gearBody...` shape for spokes, hubs, and product styling, or omit it for a simple root-radius disk. **Example** ```ts const body = lib.gearBodies.spoked({ outerRadius: 22, rimWidth: 3, hubDiameter: 10, spokeCount: 5, spokeWidth: 2.5, faceWidth: 8, boreDiameter: 5, }); const sector = lib.sectorGear({ module: 1.25, teethOnFullCircle: 36, toothCount: 10, faceWidth: 8, body, }); ```
149
+ - `gearBodies: { ... }` — Gear body preset namespace: disk, diskWithHub, spoked, and fromProfile.
150
+ - `gearBodyDisk(options: GearBodyDiskOptions): Shape` — Solid disk/ring gear body, independent from any tooth geometry.
151
+ - `gearBodyDiskWithHub(options: GearBodyDiskWithHubOptions): Shape` — Disk gear body with a raised center hub.
152
+ - `gearBodySpoked(options: GearBodySpokedOptions): Shape` — Spoked gear body with an outer rim, center hub, and radial spokes.
153
+ - `gearBodyFromProfile(profile: Sketch, options: GearBodyFromProfileOptions): Shape` — Extrude a custom 2D profile into a gear body.
@@ -144,7 +144,12 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
144
144
 
145
145
  `AssemblyPartDef`: `{ name: string, part: AssemblyPart, base: Transform, metadata?: PartMetadata }`
146
146
 
147
- **`PartMetadata`**: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`
147
+ **`PartMetadata`**
148
+
149
+ | Option | Type | Description |
150
+ |--------|------|-------------|
151
+ | `tags?` | `string \| readonly string[]` | Viewport organization tags applied to scene objects produced from this part. |
152
+ | `material?`, `process?`, `tolerance?`, `qty?`, `notes?`, `densityKgM3?`, `massKg?` | | — |
148
153
 
149
154
  **`AssemblyJointDef`**: `name: string`, `type: JointType`, `parent: string`, `child: string`, `frame: Transform`, `axis: Vec3`, `min?: number`, `max?: number`, `defaultValue: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `connectorRefs?: JointConnectorRefs`
150
155
 
@@ -191,6 +191,36 @@ intersect(...others: SdfShape[]): SdfShape
191
191
  clipBox(x: number, y: number, z: number): SdfShape
192
192
  ```
193
193
 
194
+ #### `fillWith()` — Keep only the material where this shape overlaps another SDF pattern.
195
+
196
+ ```ts
197
+ fillWith(pattern: SdfShape): SdfShape
198
+ ```
199
+
200
+ #### `fillWithGyroid()` — Keep only the gyroid lattice inside this shape.
201
+
202
+ ```ts
203
+ fillWithGyroid(options: TpmsOptions): SdfShape
204
+ ```
205
+
206
+ #### `fillWithSchwarzP()` — Keep only the Schwarz-P lattice inside this shape.
207
+
208
+ ```ts
209
+ fillWithSchwarzP(options: TpmsOptions): SdfShape
210
+ ```
211
+
212
+ #### `fillWithDiamond()` — Keep only the diamond TPMS lattice inside this shape.
213
+
214
+ ```ts
215
+ fillWithDiamond(options: TpmsOptions): SdfShape
216
+ ```
217
+
218
+ #### `fillWithLidinoid()` — Keep only the lidinoid TPMS lattice inside this shape.
219
+
220
+ ```ts
221
+ fillWithLidinoid(options: TpmsOptions): SdfShape
222
+ ```
223
+
194
224
  #### `smoothUnion()` — Smooth union — blends shapes together with a smooth radius.
195
225
 
196
226
  ```ts
@@ -269,6 +299,14 @@ bend(radius: number): SdfShape
269
299
  repeat(spacing: Vec3, count?: Vec3): SdfShape
270
300
  ```
271
301
 
302
+ #### `circularArray()` — Arrange this SDF in a circular array around the Z axis.
303
+
304
+ The source shape is translated by `offset` in +X before arraying. This uses angular domain folding, so evaluation stays O(1): the source SDF is sampled twice no matter how many copies are requested.
305
+
306
+ ```ts
307
+ circularArray(count: number, offset?: number): SdfShape
308
+ ```
309
+
272
310
  #### `shell()` — Hollow out, keeping only a shell of given thickness.
273
311
 
274
312
  ```ts
@@ -281,8 +319,8 @@ shell(thickness: number): SdfShape
281
319
  // Function displacement
282
320
  shape.displace((x, y, z) => Math.sin(x) * 0.5)
283
321
 
284
- // Pattern displacement (e.g. basketWeave)
285
- shape.displace(sdf.basketWeave({ threads: 16, spacing: 3 }))
322
+ // Pattern displacement from a 3D SDF field
323
+ shape.displace(sdf.knurl({ pitch: 2, depth: 0.3 }))
286
324
  ```
287
325
 
288
326
  ```ts
@@ -295,10 +333,16 @@ Automatically detects the shape's UV parametrization (sphere, cylinder, torus) f
295
333
 
296
334
  UV coordinates are in **surface millimeters** — patterns defined with `spacing: 3` always produce 3mm spacing, regardless of shape size.
297
335
 
336
+ Prefer `sdf.pattern2d()` or built-in surface patterns when the relief should stay on the native shader and meshing path. Callback functions are supported for experimentation, but they are opaque to the typed pattern optimizer.
337
+
298
338
  ```js
299
- // Surface-following basket weave — auto-detects sphere UV
339
+ // Native typed pattern — auto-detects sphere UV
340
+ const p = sdf.pattern2d()
341
+ const ribs = p.stripes({ spacing: 3, width: 0.8, depth: 0.35 })
342
+ .add(p.sineWave({ direction: [0, 1], wavelength: 14, amplitude: 0.08 }))
343
+
300
344
  sdf.sphere(27).shell(3)
301
- .surfaceDisplace(sdf.basketWeave({ spacing: 3, depth: 0.8 }))
345
+ .surfaceDisplace(ribs)
302
346
  .toShape()
303
347
 
304
348
  // Custom 2D pattern via function
@@ -370,9 +414,11 @@ return {
370
414
  - `brick(options?: BrickOptions): SdfShape` — Brick/stone wall pattern — running bond with mortar grooves.
371
415
  - `weave(options?: WeaveOptions): SdfShape` — Grid lattice pattern — two families of infinite slabs crossing at 90°.
372
416
  - `basketWeave(options?: BasketWeaveOptions): SurfacePattern` — Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`.
417
+ - `pattern2d(): Pattern2DBuilder` — Create typed, composable 2D surface patterns for `.surfaceDisplace()`.
373
418
  - `twist(shape: SdfShape, degreesPerUnit: number): SdfShape` — Twist an SDF shape around the Z axis.
374
419
  - `bend(shape: SdfShape, radius: number): SdfShape` — Bend an SDF shape around the Z axis.
375
420
  - `repeat(shape: SdfShape, spacing: Vec3, count?: Vec3): SdfShape` — Repeat an SDF shape in space.
421
+ - `circularArray(shape: SdfShape, count: number, offset?: number): SdfShape` — Arrange an SDF shape in a circular array around the Z axis with O(1) folded-domain evaluation.
376
422
  - `SurfacePattern: typeof SurfacePattern` — A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`.
377
423
  - `fromFunction(fn: SdfFunctionSource, options: SdfFunctionOptions): SdfShape` — Create a custom SDF from one expression; shader-safe expressions raymarch directly.
378
424
  - `Sculpt: { sphere: (radius: number) => SdfShape; box: (x: number, y: number, z: number, options?: SculptBoxOptions) => SdfShape; cylinder: (height: number, radius: number) => SdfShape; disk: (radius: number, thickness?: number) => SdfShape; circle: (radius: number, thickness?: number) => SdfShape; capsule: (height: number, radius: number) => SdfShape; torus: (majorRadius: number, minorRadius: number) => SdfShape; cone: (height: number, radius: number) => SdfShape; tube: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape; curve: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape; path: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape; blend: (first?: SculptBlendInput | SculptBlendOptions, optionsOrShape?: SculptBlendInput | SculptBlendOptions, ...rest: (SculptBlendInput | SculptBlendOptions)[]) => SdfShape; union: (first?: SculptBlendInput, ...rest: SculptBlendInput[]) => SdfShape; carve: (base: SdfShape, cutters: SculptBlendInput, options?: SculptBlendOptions) => SdfShape; keep: (first?: SculptBlendInput | SculptBlendOptions, optionsOrShape?: SculptBlendInput | SculptBlendOptions, ...rest: (SculptBlendInput | SculptBlendOptions)[]) => SdfShape; polish: (shape: SdfShape, input?: SculptPolishInput) => SdfShape; material: (input?: SculptPolishInput) => ShapeMaterialProps & { color?: string; }; look: (preset?: SculptLookPreset) => SceneOptions; knownMaterials: typeof knownSculptMaterialPresets; }` — Sculpt-like facade: friendly liquid-modeling verbs backed by the same SDF kernel.
@@ -59,7 +59,7 @@ When `lights` is specified, **all** default lights are removed. You must include
59
59
 
60
60
  Setting `camera.position` overrides auto-framing — the viewport will no longer auto-fit the geometry on script reload.
61
61
 
62
- Named render views let scripts check in repeatable cameras next to the model code. The canonical shape is `{ camera: { position, target } }`, and a direct camera shorthand `{ position, target }` is also accepted. Use the canonical shape when you may add view metadata later. Use it from the CLI with `forgecad render 3d model.forge.js --view hero`.
62
+ Named render views let scripts check in repeatable cameras next to the model code. The canonical shape is `{ camera: { position, target } }`, and a direct camera shorthand `{ position, target }` is also accepted. Use the canonical shape when you may add view metadata later. Use it from the CLI with `--view hero` on `forgecad render 3d`, `forgecad render hq`, or `forgecad capture`.
63
63
 
64
64
  Model journeys let scripts check in a compact guided path through named objects. Each journey has ordered `steps`; each step can name a `focus` target by object name/tree path, provide a caption, and optionally provide an explicit camera. In the viewer, journeys are opt-in: they appear as a small Explore control and do not move the camera until the user starts them. Use `forgecad run model.forge.js --journeys` or `--journeys-json` to inspect resolved targets.
65
65
 
@@ -128,7 +128,6 @@ scene(options: SceneOptions): void
128
128
  | `behavior?` | `"opt-in" \| "auto"` | Whether the viewer should offer or auto-open the journey. First slice supports opt-in. |
129
129
  | `steps` | `SceneJourneyStepConfig[]` | Ordered journey spine. Branches can be added later without changing this core contract. |
130
130
  | `valid?` | `boolean` | True unless any journey or step diagnostic has level "error". |
131
- | `diagnostics?` | `SceneJourneyDiagnostic[]` | Whole-journey diagnostics, including unresolved startsAt and step target diagnostics. |
132
131
 
133
132
  **`SceneJourneyStepConfig`**
134
133
 
@@ -141,9 +140,6 @@ scene(options: SceneOptions): void
141
140
  | `camera?` | `SceneViewCameraConfig` | Optional explicit camera for this step. When omitted, the viewer fits `focus`. |
142
141
  | `resolvedFocusId?` | `string \| null` | Resolved object id after script execution, when `focus` matched exactly one object. |
143
142
  | `resolvedFocusPath?` | `string \| null` | Resolved object tree path or name after script execution. |
144
- | `diagnostics?` | `SceneJourneyDiagnostic[]` | Resolution diagnostics for this step. |
145
-
146
- `SceneJourneyDiagnostic`: `{ level: SceneJourneyDiagnosticLevel, message: string, stepId?: string, suggestions?: string[] }`
147
143
 
148
144
  **`SceneLightConfig`**
149
145
 
@@ -219,10 +215,6 @@ viewConfig({
219
215
  viewConfig(options?: ViewConfigOptions): void
220
216
  ```
221
217
 
222
- `ViewConfigOptions`: `{ jointOverlay?: JointOverlayViewConfigOptions }`
223
-
224
- **`JointOverlayViewConfigOptions`**: `enabled?: boolean`, `axisColor?: string`, `axisCoreColor?: string`, `arcColor?: string`, `zeroColor?: string`, `arcVisualLimitDeg?: number`, `axisLengthScale?: number`, `axisLengthMin?: number`, `axisLineRadiusScale?: number`, `axisLineRadiusMin?: number`, `axisLineRadiusMax?: number`, `spokeLineRadiusScale?: number`, `spokeLineRadiusMin?: number`, `spokeLineRadiusMax?: number`, `arcLineRadiusScale?: number`, `arcLineRadiusMin?: number`, `arcLineRadiusMax?: number`, `axisDotRadiusScale?: number`, `axisDotRadiusMin?: number`, `axisArrowRadiusScale?: number`, `axisArrowRadiusMin?: number`, `axisArrowLengthScale?: number`, `axisArrowLengthMin?: number`, `axisArrowOffsetFactor?: number`, `arcRadiusScale?: number`, `arcRadiusMin?: number`, `arcDotRadiusScale?: number`, `arcDotRadiusMin?: number`, `arcArrowRadiusScale?: number`, `arcArrowRadiusMin?: number`, `arcArrowLengthScale?: number`, `arcArrowLengthMin?: number`, `arcArrowOffsetFactor?: number`, `arcStepDeg?: number`, `arcMinSteps?: number`, `arcTubeSegmentsMin?: number`, `arcTubeSegmentsFactor?: number`, `arcTubeRadialSegments?: number`
225
-
226
218
  #### `explodeView()` — Configure how the viewport explode slider offsets returned objects.
227
219
 
228
220
  Offsets are resolved from the returned object tree, not a flat list. In `radial` mode each node follows its parent branch direction, then fans locally from the immediate parent center — nested assemblies peel apart level by level. In fixed-axis or fixed-vector modes, the branch follows that axis/vector but nested descendants fan out perpendicular by default.
@@ -7,8 +7,9 @@ skill-order: 2
7
7
 
8
8
  `forgecad render inspect` writes a deterministic directory bundle for agents,
9
9
  tests, and automation. Use it when a single shaded PNG is too ambiguous and the
10
- consumer needs geometry-aware signals such as depth, normals, part identity,
11
- physical connected components, collisions, local thickness, or cross-sections.
10
+ consumer needs geometry-aware signals such as depth, normals, surface roughness,
11
+ part identity, physical connected components, collisions, local thickness, or
12
+ cross-sections.
12
13
 
13
14
  ## When To Use It
14
15
 
@@ -28,14 +29,16 @@ forgecad render inspect model.forge.js --channels rgb,mask,section
28
29
  forgecad render inspect model.forge.js --channels collisions --focus Bench
29
30
  forgecad render inspect model.forge.js --channels rgb,mask --hide "Bench.Slat0,Bench.Slat1"
30
31
  forgecad render inspect model.forge.js --channels thickness --min-thickness 1.2 --warn-thickness 2.0
32
+ forgecad render inspect channels
31
33
  ```
32
34
 
33
35
  The default output directory is `<script-name>-inspect/` next to the input file.
34
36
  Pass `--force` to replace an existing bundle directory.
35
37
 
36
38
  There are no default channels. Pass `--channels` every time as a
37
- comma-separated subset. Keep bundles targeted to the current question so heavy
38
- analyses do not run unnecessarily.
39
+ comma-separated subset. Run `forgecad render inspect channels` to list the
40
+ supported channels in the installed CLI. Keep bundles targeted to the current
41
+ question so heavy analyses do not run unnecessarily.
39
42
 
40
43
  `--focus` and `--hide` use the same object-name filtering semantics as
41
44
  `forgecad run` and `forgecad render 3d`. A bare `--focus` hides mock objects;
@@ -91,12 +94,13 @@ implemented channel in one bundle:
91
94
 
92
95
  ```bash
93
96
  forgecad render inspect model.forge.js --channels depth,normals
97
+ forgecad render inspect model.forge.js --channels rgb,roughness
94
98
  forgecad render inspect model.forge.js --channels rgb,mask,collisions
95
99
  forgecad render inspect model.forge.js --channels rgb,section,thickness
96
100
  ```
97
101
 
98
- Supported channels are `rgb`, `depth`, `normals`, `mask`, `connectivity`,
99
- `distance`, `collisions`, `thickness`, and `section`.
102
+ Supported channels are `rgb`, `depth`, `normals`, `roughness`, `mask`,
103
+ `connectivity`, `distance`, `collisions`, `thickness`, and `section`.
100
104
 
101
105
  ## Channel Semantics
102
106
 
@@ -123,6 +127,27 @@ normal = normalize((rgb / 255) * 2 - 1)
123
127
 
124
128
  Background pixels are black and should be treated as `null`.
125
129
 
130
+ `roughness` emits a mesh-dihedral surface-quality heatmap. Smooth and gently
131
+ curved triangles render as a faint translucent shadow over black, while
132
+ triangles adjacent to sharp, harsh, boundary, or non-manifold mesh edges render
133
+ in orange or magenta:
134
+
135
+ ```text
136
+ shadow = max adjacent angle < sharpAngleDeg
137
+ orange = sharpAngleDeg <= angle < harshAngleDeg
138
+ magenta = angle >= harshAngleDeg, boundary, or non-manifold
139
+ ```
140
+
141
+ The default thresholds are `smoothAngleDeg=5`, `sharpAngleDeg=30`, and
142
+ `harshAngleDeg=90`. The manifest stores the method, thresholds, palette, object
143
+ list, per-object triangle and edge counts, area percentages by smooth,
144
+ moderate, sharp, and harsh classes, angle percentiles, maximum angle, quality
145
+ score, and warnings. Moderate angles are reported in the manifest but stay in
146
+ the shadow layer by default so intentionally curved surfaces do not light up as
147
+ defects. Use this channel to spot spiky tessellation, accidental faceting,
148
+ jagged boolean residue, and dense sharp-corner regions without losing the
149
+ silhouette of otherwise smooth surfaces.
150
+
126
151
  `mask` emits one object-color image per view. Black is background. Non-black
127
152
  pixels resolve through `manifest.channels.mask.objects`, which includes object
128
153
  index, RGB color, object id, name, group, tree path, and mock flag. Edge pixels
@@ -8,7 +8,6 @@ forgecad skill install
8
8
 
9
9
  The installed skill names are namespaced to avoid collisions with a user's existing generic skills.
10
10
 
11
- - `forgecad-api-dogfood`
12
11
  - `forgecad-blockout-model`
13
12
  - `forgecad-component-model`
14
13
  - `forgecad-high-level-spec`
@@ -67,6 +67,7 @@ Use today's date for the directory. Use the user's current ForgeCAD project when
67
67
  - Use at most a handful of top-level `param()` values when comparing rough proportions. Do not parameterize every dimension.
68
68
  - Name uncertainty honestly: `armLengthGuess`, `baseWidthApprox`, `screenVolume`, `clearanceEnvelope`.
69
69
  - Do not add text labels, callout arrows, legends, coordinate labels, or explanatory plaques to the model. Use named return objects and a short written note outside the geometry.
70
+ - Do not cut away a shell, remove covers, or permanently explode parts just to show hidden intent. Even a blockout should represent the object in its normal assembled state unless the user explicitly asks for a presentation view.
70
71
  - Use transparent ghost geometry for:
71
72
  - sweep arcs
72
73
  - keep-out volumes
@@ -12,6 +12,8 @@ The reference image is evidence. It is not the deliverable.
12
12
 
13
13
  The deliverable is a real parametric object that remains believable from front, back, side, top, bottom, and reference camera views. A model that matches one image but falls apart from other angles has failed, even if the comparison board looks close.
14
14
 
15
+ If a reference image is cutaway, sectioned, exploded, partly hidden, or transparent, treat that as evidence about the complete object. Do not make the default ForgeCAD result a permanently cutaway or exploded display unless the user explicitly asked for a teaching/display model. Build the closed artifact first, then use ForgeCAD viewer/inspection tools to recreate explanatory views.
16
+
15
17
  ## Required Companion Skills
16
18
 
17
19
  - Use `forgecad` for API docs, model authoring, and renderer behavior.
@@ -80,7 +82,7 @@ Reference matching is a validation step after the object exists.
80
82
  When multiple images are attached, do not choose one as the target and ignore the rest. Assign each image a camera, evidence list, and confidence level. Optimize one shared geometry against the whole set. If an image is decorative, distorted, or contradictory, state how it was weighted.
81
83
 
82
84
  10. Inspect the final object.
83
- Run `forgecad run`, render the reference comparison boards, render canonical views, and use `forgecad render inspect` with relevant channels. For multi-part, mechanical, internal, or fit-sensitive models, include collisions and section views.
85
+ Run `forgecad run`, render the reference comparison boards, render canonical views, and use `forgecad render inspect` with relevant channels. For multi-part, mechanical, internal, or fit-sensitive models, include collisions and section-channel inspection, but keep the delivered model as the complete closed artifact.
84
86
 
85
87
  ## Renderer Camera Support
86
88
 
@@ -40,12 +40,14 @@ Use today's date for the directory. Use the user's current ForgeCAD project when
40
40
  - When there are multiple versions of the same object, expose the version as a choice parameter and render one selected version at a time
41
41
  - Use clear variable names
42
42
  - Build any implied internal structure as real geometry, even when it will be hidden in the final view
43
+ - Build the complete physical artifact first: closed shells, installed covers, real part positions, and all meaningful internal structure in place
43
44
  - Make final mating geometry physically plausible: parts may touch, clear each other, or be boolean-joined, but should not unintentionally pass through each other
44
45
  - Model the physical artifact, not an educational diagram: no explanatory arrows, floating labels, section labels, legends, or text plaques unless the user explicitly requested a presentation/teaching view
46
+ - Do not make the default returned model a cutaway, sectioned shell, permanently exploded assembly, or hidden-parts teaching view. ForgeCAD gives the user viewer and inspection tools for slicing, exploding, hiding, and looking inside after the real CAD exists.
45
47
  - Return the final geometry (single shape, array, or named objects array)
46
- - Avoid expensive global edge treatment on repeated decorative geometry: do not call `fillet(shape, r)` or `chamfer(shape, r)` on every edge of large unioned/repeated parts unless the render/run loop proves it is fast enough. Prefer simpler primitive profiles, lower segment counts, or targeted edge selectors.
48
+ - 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.
47
49
  4. Validate — run `forgecad run <file>` to check for errors. For multi-file projects, always validate `main.forge.js`.
48
- 5. Verify geometry — render the result and run `forgecad render inspect` with the relevant channels for the task (see Render-Verify Loop below). For multi-file projects, render and inspect `main.forge.js`.
50
+ 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`.
49
51
 
50
52
  ## Manufacturing Process Is Not Assumed
51
53
 
@@ -98,7 +100,9 @@ Build hidden features as actual geometry:
98
100
  - Mechanism clearances, travel envelopes, stops, guides, rails, hinges, gear spaces, and service access
99
101
  - Process-specific features such as bends, tubes, sheet-metal flanges, machined bosses, cast ribs, molded draft, weld tabs, laser-cut slots, or print-oriented ribs where appropriate
100
102
 
101
- When internals are hidden by the final exterior, add a temporary verification view: transparent shell, exploded view, cutaway, underside render, or named ghost objects. Use that view to check fit and collisions, but keep the final returned model faithful to the real closed artifact unless the user asked for a visible cutaway.
103
+ Do not reveal hidden structure by permanently cutting away the production geometry. Keep the returned default model faithful to the real closed artifact, with covers installed and parts in their actual assembled positions.
104
+
105
+ 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.
102
106
 
103
107
  ## Final Geometry Should Be Physically Plausible
104
108
 
@@ -110,6 +114,46 @@ Temporary collisions during construction are fine when they are part of how the
110
114
 
111
115
  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.
112
116
 
117
+ ## Final Acceptance Gate
118
+
119
+ Before telling the user the model is done, prove both technical validity and visual plausibility. A model can pass `forgecad run` and still be wrong because a rail, cable, trim line, handle, or fastener is merely hovering over a curved surface. Use this gate for any model with multiple bodies, surface-mounted details, cables/strings, rails/tracks, handles, product skins, visible hardware, or hidden mating geometry.
120
+
121
+ 1. State the intended physical component graph. Decide whether the final artifact should be one connected component, several intentionally separate components, or a selected assembly plus named ghosts. Then run:
122
+
123
+ ```bash
124
+ forgecad run model.forge.js --connectivity
125
+ ```
126
+
127
+ 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.
128
+
129
+ 2. Run a collision bundle and read both the manifest and images:
130
+
131
+ ```bash
132
+ forgecad render inspect model.forge.js /tmp/model-inspect --channels rgb,mask,collisions --force --size 700
133
+ jq '.channels.collisions | {collisionCount, collisions, warnings}' /tmp/model-inspect/manifest.json
134
+ ```
135
+
136
+ `collisionCount` should be zero unless an overlap is deliberately manufactured, fused, welded, overmolded, or isolated with `--focus` / `--hide`. Do not ignore the channel PNGs; visually inspect where the findings or warnings appear.
137
+
138
+ 3. Render risk-specific views, not only a hero shot. At minimum, render one hero view plus the orthographic views that expose the likely failure modes:
139
+
140
+ - long products, vehicles, weapons, rails, handles, and tools: side and top
141
+ - sockets, underside joins, stands, brackets, and handles: side plus underside; use section-channel inspection only when hidden geometry must be checked
142
+ - cables, strings, belts, tubes, and hoses: top plus side so endpoints and sag/clearance are visible
143
+ - surface details on curved ProductSkin bodies: grazing side view plus top/iso to confirm they conform or are embedded as intended
144
+
145
+ 4. Do a visual attachment audit. For every detail that should be connected, ask: "Where does this physically enter, seat, wrap, terminate, or fasten?" Check that view directly. Common failures to fix before delivery:
146
+
147
+ - a flat rail or arrow bed sitting on top of a curved shell instead of being recessed, saddled, socketed, or structurally blended into the body
148
+ - strings/cables that pass through space without terminal knots, hooks, holes, posts, ferrules, pulleys, or anchors
149
+ - decorative brass/trim lines floating above the body instead of following a ProductSkin surface or being built as inset/raised strips with believable thickness
150
+ - handles/grips touching only by a tangent or thin face instead of having a neck, bridge, socket, screws, or overmolded landing
151
+ - small hardware or gems that are bbox-connected but visually read as levitating; replace with flush/inset seats or explicit brackets
152
+
153
+ 5. Treat ProductSkin and surface-member limitations honestly. If `render inspect` reports boolean-test warnings because a sampled `Product.skin` loft has boundary edges, distinguish that from real collision findings. You may still deliver if `collisionCount` is clean, the intended connectivity is correct, and the visual attachment audit passes. Mention the residual warning briefly in the final response.
154
+
155
+ 6. Final response must name the evidence: commands run, component count, collision count, and any residual warnings or intentional exceptions. Do not just say "validated."
156
+
113
157
  ## Render-Verify Loop
114
158
 
115
159
  You are building blind unless you render. `forgecad run` only checks that code executes — it cannot tell you a hole is in the wrong place or a part doesn't fit. Render and look at the result.
@@ -231,7 +275,7 @@ Key primitives:
231
275
 
232
276
  - `box(x, y, z)`, `cylinder(h, r, rTop?, segments?)`, `sphere(r)`, `torus(R, r)`
233
277
  - `union()`, `difference()`, `intersection()`
234
- - `.fillet()`, `.chamfer()` for edge treatments
278
+ - `.fillet()`, `.chamfer()` for experimental edge treatments only
235
279
  - `param(name, default, opts)`, `boolParam(name, default)`
236
280
  - Return `[{ name, shape, color }]` for multi-part colored models
237
281
 
@@ -10,6 +10,8 @@ Use `forgecad render inspect` when a shaded viewport render is too ambiguous and
10
10
 
11
11
  This skill owns the inspection workflow: choosing channels, generating the bundle, reading the manifest, visually inspecting the relevant PNGs, and turning the findings into model fixes or a verification report.
12
12
 
13
+ Inspection is not a substitute artifact. Use section, mask, transparency, focus, and hide channels to look inside a real model; do not edit the model into a cutaway or exploded default just to make the inspection easier.
14
+
13
15
  ## Trigger Boundary
14
16
 
15
17
  Use this skill for:
@@ -143,7 +145,7 @@ Manifest fields to check first:
143
145
  - `channels.thickness.objects`: inspect `minThickness`, `p05Thickness`, critical/warning percentages, and unresolved area.
144
146
  - `channels.connectivity.componentCount`: compare to the expected number of physical components.
145
147
  - `channels.distance.maxRootDistance` and per-object `nearestGap`: check suspicious isolation or spacing.
146
- - `channels.section.planes`: look for missing slices, wrong path counts, or empty internal cuts.
148
+ - `channels.section.planes`: look for missing slices, wrong path counts, or empty internal cuts. These are inspection views, not instructions to section the returned production geometry.
147
149
 
148
150
  PNG review order:
149
151
 
@@ -32,6 +32,7 @@ The image should not:
32
32
  - smooth away the mechanism into a fake consumer shell
33
33
  - invent flashy sci-fi styling that hides how it works
34
34
  - pretend to be a CAD drawing, dimensioned blueprint, or engineering diagram
35
+ - turn the artifact into a cutaway, sectioned shell, or exploded teaching view unless the user explicitly asks for that representation
35
36
 
36
37
  ## Default Strategy
37
38
 
@@ -119,6 +120,7 @@ Bias harder toward interfaces, seams, mounted actuators, and subsystem boundarie
119
120
  Use when the user wants assembly logic or modular breakdown.
120
121
 
121
122
  Keep the explosion restrained. Separate only major modules, not every screw.
123
+ This is a visual-spec support image, not a default ForgeCAD modeling instruction; the CAD artifact should still be the complete assembled product.
122
124
 
123
125
  ### Workshop Prototype Realism
124
126
 
@@ -0,0 +1,43 @@
1
+ const moduleSize = Param.number("Module", 1.15, { min: 0.8, max: 2.2, step: 0.05, unit: "mm" });
2
+ const teethOnFullCircle = Param.number("Pitch Teeth", 40, { min: 24, max: 72, integer: true });
3
+ const toothCount = Param.number("Active Teeth", 9, { min: 2, max: 20, integer: true });
4
+ const firstTooth = Param.number("First Tooth", 29, { min: 0, max: 71, integer: true });
5
+ const faceWidth = Param.number("Face Width", 8, { min: 4, max: 14, unit: "mm" });
6
+
7
+ const pitchRadius = (moduleSize * teethOnFullCircle) / 2;
8
+ const rootRadius = pitchRadius - moduleSize * 1.25;
9
+ const rimWidth = Math.max(2.4, moduleSize * 2.2);
10
+ const hubDiameter = Math.max(9, moduleSize * 8);
11
+ const clampedFirstTooth = Math.min(firstTooth, teethOnFullCircle - 1);
12
+ const clampedToothCount = Math.min(toothCount, teethOnFullCircle - clampedFirstTooth);
13
+
14
+ const body = lib.gearBodies.spoked({
15
+ outerRadius: rootRadius,
16
+ rimWidth,
17
+ hubDiameter,
18
+ spokeCount: 5,
19
+ spokeWidth: Math.max(2, moduleSize * 1.8),
20
+ faceWidth,
21
+ boreDiameter: 5,
22
+ });
23
+
24
+ const wheel = lib
25
+ .driveWheel({ body, faceWidth })
26
+ .addSpurTeethBetween({
27
+ name: "drive teeth",
28
+ module: moduleSize,
29
+ teethOnFullCircle,
30
+ firstTooth: clampedFirstTooth,
31
+ toothCount: Math.max(1, clampedToothCount),
32
+ pressureAngleDeg: 20,
33
+ })
34
+ .addSolidArcBetween({
35
+ name: "dwell shoulder",
36
+ fromAngleDeg: 100,
37
+ toAngleDeg: 170,
38
+ innerRadius: rootRadius - rimWidth,
39
+ outerRadius: rootRadius + moduleSize * 0.9,
40
+ })
41
+ .build();
42
+
43
+ return [{ name: "Exceptional Drive Wheel", shape: wheel.color("#d5a15f") }];
@@ -0,0 +1,19 @@
1
+ // SDF circular array — fogleman-style folded-domain knurling.
2
+ // circularArray evaluates the source groove twice no matter how many copies it creates.
3
+
4
+ const body = sdf.cylinder(42, 11);
5
+
6
+ const rightHandGroove = sdf.box(2.4, 1.2, 48)
7
+ .rotateZ(45)
8
+ .circularArray(32, 11.6)
9
+ .twist(2.2);
10
+
11
+ const leftHandGroove = sdf.box(2.4, 1.2, 48)
12
+ .rotateZ(-45)
13
+ .circularArray(32, 11.6)
14
+ .twist(-2.2);
15
+
16
+ return body
17
+ .subtract(rightHandGroove, leftHandGroove)
18
+ .color('#8da2ad')
19
+ .material({ roughness: 0.38, metalness: 0.18 });
@@ -0,0 +1,83 @@
1
+ // Native Pattern2D relief on ceramic forms: flutes, bands, and woven bowl texture.
2
+
3
+ scene({
4
+ background: { top: '#fff8f1', bottom: '#d8e4ea' },
5
+ camera: { position: [90, -126, 66], target: [0, 0, 8], fov: 34 },
6
+ environment: { preset: 'studio', intensity: 1.28 },
7
+ lights: [
8
+ { type: 'ambient', color: '#ffffff', intensity: 0.28 },
9
+ { type: 'point', position: [54, -72, 90], color: '#fff1d0', intensity: 2.2, distance: 280, decay: 1.12 },
10
+ { type: 'point', position: [-76, 48, 62], color: '#84d8ff', intensity: 1.15, distance: 240, decay: 1.18 },
11
+ { type: 'directional', position: [30, -56, 150], color: '#ffffff', intensity: 0.58 },
12
+ ],
13
+ postProcessing: {
14
+ toneMappingExposure: 1.12,
15
+ bloom: { intensity: 0.08, threshold: 0.84, radius: 0.38 },
16
+ vignette: { darkness: 0.12, offset: 0.62 },
17
+ },
18
+ });
19
+
20
+ const ceramic = (shape, color) =>
21
+ shape.color(color).material({
22
+ roughness: 0.46,
23
+ metalness: 0,
24
+ clearcoat: 0.72,
25
+ clearcoatRoughness: 0.18,
26
+ });
27
+
28
+ const p = sdf.pattern2d();
29
+
30
+ const flutedPorcelain = p
31
+ .sineWave({ direction: [1, 0], wavelength: 4.6, amplitude: 0.28 })
32
+ .add(p.sineWave({ direction: [0, 1], wavelength: 18, amplitude: 0.08 }))
33
+ .clamp(-0.34, 0.34);
34
+
35
+ const carvedBands = p
36
+ .stripes({ direction: [0, 1], spacing: 7.5, width: 0.9, depth: 0.2 })
37
+ .add(p.sineWave({ direction: [1, 1], wavelength: 9, amplitude: 0.08 }))
38
+ .clamp(-0.27, 0.07);
39
+
40
+ const wovenGlaze = p
41
+ .overUnderWeave({ spacing: 2.45, threadWidth: 1.08, depth: 0.34 })
42
+ .add(p.sineWave({ direction: [1, -1], wavelength: 13, amplitude: 0.055 }))
43
+ .clamp(-0.42, 0.06);
44
+
45
+ const tallVase = ceramic(
46
+ sdf.cylinder(58, 11)
47
+ .shell(1.35)
48
+ .surfaceDisplace(flutedPorcelain, { uv: 'cylinder' })
49
+ .subtract(sdf.cylinder(66, 8.2))
50
+ .translate(-35, 0, 3),
51
+ '#f2efe2',
52
+ );
53
+
54
+ const bandedCup = ceramic(
55
+ sdf.cylinder(38, 14)
56
+ .shell(1.25)
57
+ .surfaceDisplace(carvedBands, { uv: 'cylinder' })
58
+ .subtract(sdf.cylinder(46, 10.6))
59
+ .translate(0, 0, -7),
60
+ '#8fb7c7',
61
+ );
62
+
63
+ const lowBowl = ceramic(
64
+ sdf.sphere(22)
65
+ .shell(1.4)
66
+ .surfaceDisplace(wovenGlaze)
67
+ .subtract(sdf.box(68, 68, 40).translate(0, 0, 17))
68
+ .translate(37, 0, -8),
69
+ '#c97462',
70
+ );
71
+
72
+ const footRings = ceramic(
73
+ sdf
74
+ .torus(10.5, 0.75)
75
+ .translate(-35, 0, -27)
76
+ .union(
77
+ sdf.torus(12.5, 0.72).translate(0, 0, -26),
78
+ sdf.torus(14.5, 0.72).translate(37, 0, -25),
79
+ ),
80
+ '#d8c59f',
81
+ );
82
+
83
+ return { tallVase, bandedCup, lowBowl, footRings };