forgecad 0.1.0

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 (119) hide show
  1. package/LICENSE +97 -0
  2. package/README.md +354 -0
  3. package/dist/assets/evalWorker-BYHXxh15.js +461 -0
  4. package/dist/assets/index--CYbOPKS.js +5797 -0
  5. package/dist/assets/manifold-65fIQlgQ.js +20 -0
  6. package/dist/assets/manifold-B85M7kop.js +20 -0
  7. package/dist/assets/manifold-B8h_vZ5O.js +16 -0
  8. package/dist/assets/manifold-D9yvTBHx.wasm +0 -0
  9. package/dist/assets/manifold-d1UpyLJ8.js +20 -0
  10. package/dist/assets/reportWorker-B1Zdrz9l.js +494 -0
  11. package/dist/index.html +16 -0
  12. package/dist-cli/forgecad.js +44464 -0
  13. package/dist-skill/SKILL.md +4635 -0
  14. package/examples/3d-printer.forge.js +328 -0
  15. package/examples/5-figen-robot-hand.forge.js +283 -0
  16. package/examples/ac-unit-glm47.forge.js +108 -0
  17. package/examples/ac-unit-glm5.forge.js +174 -0
  18. package/examples/ac-unit-kimi25.forge.js +236 -0
  19. package/examples/ac-unit-minimax.forge.js +123 -0
  20. package/examples/ac-unit.forge.js +126 -0
  21. package/examples/adjustable-table.forge.js +191 -0
  22. package/examples/api/assembly-gear-coupling.forge.js +32 -0
  23. package/examples/api/assembly-mechanism.forge.js +111 -0
  24. package/examples/api/attachTo-basics.forge.js +45 -0
  25. package/examples/api/benchy-style-hull.forge.js +89 -0
  26. package/examples/api/bill-of-materials.forge.js +46 -0
  27. package/examples/api/boolean-operations.forge.js +48 -0
  28. package/examples/api/bounding-box-visualizer.forge.js +58 -0
  29. package/examples/api/brep-exportable.forge.js +19 -0
  30. package/examples/api/center-true-vs-false.forge.js +40 -0
  31. package/examples/api/clone-duplicate.forge.js +41 -0
  32. package/examples/api/colors-union-vs-array.forge.js +27 -0
  33. package/examples/api/coordinate-system.forge.js +54 -0
  34. package/examples/api/curves-surfacing-basics.forge.js +91 -0
  35. package/examples/api/dimensioned-bracket.forge.js +19 -0
  36. package/examples/api/elbow-test.forge.js +23 -0
  37. package/examples/api/exploded-view.forge.js +60 -0
  38. package/examples/api/extrude-options.forge.js +44 -0
  39. package/examples/api/face-gears.forge.js +44 -0
  40. package/examples/api/face-transformation-history.forge.js +45 -0
  41. package/examples/api/feature-created-faces.forge.js +47 -0
  42. package/examples/api/folded-service-panel-cover.forge.js +3 -0
  43. package/examples/api/folded-service-panel-cover.js +117 -0
  44. package/examples/api/gears-bevel-face-joints.forge.js +157 -0
  45. package/examples/api/gears-tier1.forge.js +57 -0
  46. package/examples/api/geometry-info.forge.js +49 -0
  47. package/examples/api/group-test.forge.js +34 -0
  48. package/examples/api/group-vs-union.forge.js +25 -0
  49. package/examples/api/import-args-unit.forge.js +5 -0
  50. package/examples/api/import-args.forge.js +16 -0
  51. package/examples/api/import-dimensions-follow.forge.js +18 -0
  52. package/examples/api/import-placement-references.forge.js +18 -0
  53. package/examples/api/import-placement-widget-source.forge.js +30 -0
  54. package/examples/api/import-relative-paths.forge.js +18 -0
  55. package/examples/api/import-svg-sketch-shape.svg +15 -0
  56. package/examples/api/import-svg-sketch.forge.js +28 -0
  57. package/examples/api/js-module-imports.forge.js +9 -0
  58. package/examples/api/js-module-pillars.js +25 -0
  59. package/examples/api/js-module-scene.js +9 -0
  60. package/examples/api/notebook-assembly-debug.forge-notebook.json +90 -0
  61. package/examples/api/notebook-iteration.forge-notebook.json +75 -0
  62. package/examples/api/patterns.forge.js +32 -0
  63. package/examples/api/pointAlong-orientation.forge.js +52 -0
  64. package/examples/api/profile-2020-b-slot6.forge.js +36 -0
  65. package/examples/api/rotate-around-to.forge.js +31 -0
  66. package/examples/api/runtime-joints-view.forge.js +116 -0
  67. package/examples/api/sdf-rover-demo.forge.js +159 -0
  68. package/examples/api/section-plane-visualization.forge.js +38 -0
  69. package/examples/api/sketch-basics.forge.js +48 -0
  70. package/examples/api/sketch-on-face.forge.js +56 -0
  71. package/examples/api/sketch-rounding-strategies.forge.js +56 -0
  72. package/examples/api/spatial-recipes.forge.js +129 -0
  73. package/examples/bathroom.forge.js +197 -0
  74. package/examples/bolt-and-nut.forge.js +39 -0
  75. package/examples/bolt-pattern.forge.js +18 -0
  76. package/examples/bottle.forge.js +101 -0
  77. package/examples/chair.forge.js +62 -0
  78. package/examples/chess-set.forge.js +232 -0
  79. package/examples/classical-piano.forge.js +203 -0
  80. package/examples/clock.forge.js +169 -0
  81. package/examples/compiler-corpus/README.md +88 -0
  82. package/examples/compiler-corpus/edge-finished-mount.forge.js +18 -0
  83. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +24 -0
  84. package/examples/compiler-corpus/fastener-plate-variants.forge.js +42 -0
  85. package/examples/compiler-corpus/folded-service-panel-cover.forge.js +5 -0
  86. package/examples/compiler-corpus/motor-mount-plate.forge.js +32 -0
  87. package/examples/compiler-corpus/projection-relay-cover.forge.js +16 -0
  88. package/examples/compiler-corpus/sensor-bracket.forge.js +35 -0
  89. package/examples/compiler-corpus/service-panel-cover.forge.js +53 -0
  90. package/examples/compiler-corpus/trimmed-access-cover.forge.js +26 -0
  91. package/examples/cup.forge.js +25 -0
  92. package/examples/cut-plane-demo.forge.js +28 -0
  93. package/examples/door-with-hinges.forge.js +54 -0
  94. package/examples/frame.sketch.js +4 -0
  95. package/examples/headphone-hanger-profile.sketch.js +18 -0
  96. package/examples/headphone-hanger-v2.forge.js +88 -0
  97. package/examples/headphone-hanger.forge.js +5 -0
  98. package/examples/iphone-stand.forge.js +72 -0
  99. package/examples/iphone.forge.js +114 -0
  100. package/examples/ironman-helmet.js +79 -0
  101. package/examples/kitchen.forge.js +231 -0
  102. package/examples/lamp-shade.sketch.js +17 -0
  103. package/examples/laptop.forge.js +144 -0
  104. package/examples/liquid-soap-dispenser.forge.js +159 -0
  105. package/examples/modern-tv.forge.js +86 -0
  106. package/examples/picture-frame.forge.js +34 -0
  107. package/examples/robot_hand.forge.js +393 -0
  108. package/examples/robot_hand_2.forge.js +622 -0
  109. package/examples/sandbox.forge.js +3 -0
  110. package/examples/shelf/container.forge.js +30 -0
  111. package/examples/shelf/shelf-unit.forge.js +62 -0
  112. package/examples/shoe-rack-doors.forge.js +107 -0
  113. package/examples/shoe-rack.forge.js +65 -0
  114. package/examples/spiderman-cake.forge.js +92 -0
  115. package/examples/table-lamp.forge.js +33 -0
  116. package/examples/table.forge.js +44 -0
  117. package/examples/test-colors.forge.js +19 -0
  118. package/examples/tv-stand.forge.js +21 -0
  119. package/package.json +69 -0
@@ -0,0 +1,48 @@
1
+ // Boolean operations — union, difference, intersection.
2
+ //
3
+ // union(a, b) → combined volume
4
+ // difference(a, b) → a minus b (subtract b from a)
5
+ // intersection(a, b) → only the overlapping volume
6
+ //
7
+ // Method syntax also accepts multiple cutters/operands:
8
+ // a.add(b, c), a.subtract(b, c), a.intersect(b, c)
9
+ // and the helper functions also accept arrays:
10
+ // difference([a, b, c]), union([a, b, c]), intersection([a, b, c])
11
+
12
+ const size = param("Size", 30, { min: 15, max: 50, unit: "mm" });
13
+ const overlap = param("Overlap", 15, { min: 0, max: 30, unit: "mm" });
14
+ const spacing = 80;
15
+
16
+ // Two overlapping shapes for each demo
17
+ function makePair(offsetX) {
18
+ const a = box(size, size, size, true).translate(offsetX, 0, 0).color('#4488cc');
19
+ const b = sphere(size * 0.6).translate(offsetX + size - overlap, 0, 0).color('#cc4444');
20
+ return [a, b];
21
+ }
22
+
23
+ // 1. Union — combined
24
+ const [u1, u2] = makePair(0);
25
+ const unioned = union(u1, u2).color('#8866cc');
26
+
27
+ // 2. Difference — box minus sphere and cross-bore
28
+ const [d1, d2] = makePair(spacing);
29
+ const d3 = cylinder(size * 1.2, size * 0.14, undefined, undefined, true)
30
+ .pointAlong([0, 1, 0])
31
+ .translate(spacing, 0, 0);
32
+ const diffed = d1.subtract(d2, d3);
33
+
34
+ // 3. Intersection — only overlap
35
+ const [i1, i2] = makePair(2 * spacing);
36
+ const intersected = intersection(i1, i2).color('#cc8844');
37
+
38
+ // Show the original shapes (translucent-ish via separate objects) for reference
39
+ const refA = box(size, size, size, true).translate(3 * spacing, 0, 0).color('#4488cc');
40
+ const refB = sphere(size * 0.6).translate(3 * spacing + size - overlap, 0, 0).color('#cc4444');
41
+
42
+ return [
43
+ { name: "Union", shape: unioned },
44
+ { name: "Difference (box - sphere - bore)", shape: diffed },
45
+ { name: "Intersection", shape: intersected },
46
+ { name: "Original Box", shape: refA },
47
+ { name: "Original Sphere", shape: refB },
48
+ ];
@@ -0,0 +1,58 @@
1
+ // Visualize bounding boxes — useful for debugging positioning.
2
+ //
3
+ // boundingBox() returns { min: [x,y,z], max: [x,y,z] }.
4
+ // This example draws thin cylinders along the 12 edges of the bbox.
5
+
6
+ const edgeR = 0.5; // wireframe edge radius
7
+
8
+ function vizBBox(shape) {
9
+ const bb = shape.boundingBox();
10
+ const [x0, y0, z0] = bb.min;
11
+ const [x1, y1, z1] = bb.max;
12
+ const dx = x1 - x0, dy = y1 - y0, dz = z1 - z0;
13
+
14
+ const edges = [];
15
+ // 4 edges along X (at each combination of Y,Z corners)
16
+ for (const y of [y0, y1]) {
17
+ for (const z of [z0, z1]) {
18
+ edges.push(cylinder(dx, edgeR).pointAlong([1, 0, 0]).translate(x0, y, z));
19
+ }
20
+ }
21
+ // 4 edges along Y
22
+ for (const x of [x0, x1]) {
23
+ for (const z of [z0, z1]) {
24
+ edges.push(cylinder(dy, edgeR).pointAlong([0, 1, 0]).translate(x, y0, z));
25
+ }
26
+ }
27
+ // 4 edges along Z
28
+ for (const x of [x0, x1]) {
29
+ for (const y of [y0, y1]) {
30
+ edges.push(cylinder(dz, edgeR).translate(x, y, z0));
31
+ }
32
+ }
33
+ return union(...edges);
34
+ }
35
+
36
+ // --- Demo shapes ---
37
+
38
+ // A rotated box — bbox is larger than the shape itself
39
+ const angle = param("Rotation", 30, { min: 0, max: 90, unit: "°" });
40
+ const rotBox = box(40, 30, 20, true).rotate(0, 0, angle).color('#4488cc');
41
+ const rotBBox = vizBBox(rotBox).color('#cc4444');
42
+
43
+ // A sphere — bbox is a perfect cube around it
44
+ const sph = sphere(20).translate(80, 0, 0).color('#44cc44');
45
+ const sphBBox = vizBBox(sph).color('#cc4444');
46
+
47
+ // A tilted cylinder — bbox shows the extent
48
+ const tiltCyl = cylinder(50, 10).rotate(30, 0, 0).translate(0, 80, 0).color('#cc88ff');
49
+ const cylBBox = vizBBox(tiltCyl).color('#cc4444');
50
+
51
+ return [
52
+ { name: "Rotated Box", shape: rotBox },
53
+ { name: "Box BBox", shape: rotBBox },
54
+ { name: "Sphere", shape: sph },
55
+ { name: "Sphere BBox", shape: sphBBox },
56
+ { name: "Tilted Cylinder", shape: tiltCyl },
57
+ { name: "Cylinder BBox", shape: cylBBox },
58
+ ];
@@ -0,0 +1,19 @@
1
+ // Exact-exportable subset demo for STEP/BREP.
2
+ // Run: forgecad export step examples/api/brep-exportable.forge.js
3
+
4
+ const plate = rect(120, 80, true).extrude(10).color('#748b99');
5
+ const boss = cylinder(24, 18).translate(0, 0, 10).color('#b7c4cc');
6
+
7
+ const leftHole = cylinder(18, 5).translate(-34, 0, -4);
8
+ const rightHole = cylinder(18, 5).translate(34, 0, -4);
9
+ const centerBore = cylinder(34, 8).translate(0, 0, 6);
10
+
11
+ const exactPart = union(plate.toShape(), boss)
12
+ .subtract(leftHole)
13
+ .subtract(rightHole)
14
+ .subtract(centerBore)
15
+ .color('#9db1bd');
16
+
17
+ return [
18
+ { name: 'Exact Export Demo', shape: exactPart },
19
+ ];
@@ -0,0 +1,40 @@
1
+ // center=true vs center=false — the #1 source of positioning confusion.
2
+ //
3
+ // box(w, d, h) → corner at origin, extends into +X, +Y, +Z
4
+ // box(w, d, h, true) → centered at origin
5
+ //
6
+ // Same applies to cylinder(h, r) vs cylinder(h, r, r, undefined, true).
7
+
8
+ const w = 40, d = 30, h = 20;
9
+
10
+ // --- Side-by-side comparison ---
11
+
12
+ // Left: center=false (default). Red sphere marks the origin [0,0,0].
13
+ const cornerBox = box(w, d, h).color('#4488cc').translate(-60, 0, 0);
14
+ const cornerOrigin = sphere(2).color('#cc0000').translate(-60, 0, 0);
15
+
16
+ // Right: center=true. Red sphere marks the origin [0,0,0].
17
+ const centeredBox = box(w, d, h, true).color('#44cc88').translate(60, 0, 0);
18
+ const centeredOrigin = sphere(2).color('#cc0000').translate(60, 0, 0);
19
+
20
+ // --- Practical impact: placing a cylinder on top of a base ---
21
+
22
+ // With center=false: cylinder must go to (w/2, d/2, h)
23
+ const base1 = box(w, d, h).color('#888888').translate(-60, 60, 0);
24
+ const cyl1 = cylinder(15, 6).color('#cc8844').translate(-60 + w/2, 60 + d/2, h);
25
+
26
+ // With center=true + attachTo: no math needed
27
+ const base2 = box(w, d, h, true).color('#888888').translate(60, 60 + d/2, h/2);
28
+ const cyl2 = cylinder(15, 6).color('#cc8844')
29
+ .attachTo(base2, 'top', 'bottom');
30
+
31
+ return [
32
+ { name: "Corner Box (center=false)", shape: cornerBox },
33
+ { name: "Corner Origin ●", shape: cornerOrigin },
34
+ { name: "Centered Box (center=true)", shape: centeredBox },
35
+ { name: "Centered Origin ●", shape: centeredOrigin },
36
+ { name: "Base (corner)", shape: base1 },
37
+ { name: "Cylinder (manual math)", shape: cyl1 },
38
+ { name: "Base (centered)", shape: base2 },
39
+ { name: "Cylinder (attachTo)", shape: cyl2 },
40
+ ];
@@ -0,0 +1,41 @@
1
+ // clone() / duplicate() — explicit copy helpers for Shape, TrackedShape, Sketch, and ShapeGroup.
2
+
3
+ const spacing = param("Spacing", 90, { min: 40, max: 180, unit: "mm" });
4
+
5
+ // --- Shape clone ---
6
+ const block = box(36, 20, 12, true).color("#4a90e2");
7
+ const blockL = block.clone().translate(-spacing / 2, 0, 0);
8
+ const blockR = block.duplicate().translate(spacing / 2, 0, 0);
9
+
10
+ // --- TrackedShape clone (topology preserved) ---
11
+ const post = cylinder(36, 6).color("#49b675");
12
+ const postCopy = post.clone().translate(0, 45, 0);
13
+
14
+ // --- Sketch clone ---
15
+ const slotProfile = slot(30, 10).color("#e98b39");
16
+ const slotL = slotProfile.clone().translate(-spacing / 2, -35);
17
+ const slotR = slotProfile.duplicate().translate(spacing / 2, -35);
18
+
19
+ // --- ShapeGroup clone (with named children) ---
20
+ const moduleGroup = group(
21
+ { name: "Block", shape: block },
22
+ { name: "Post", shape: post.attachTo(block, "top", "bottom") }
23
+ );
24
+ const moduleL = moduleGroup.clone().translate(-spacing / 2, 95, 0);
25
+ const moduleR = moduleGroup.duplicate().translate(spacing / 2, 95, 0).color("#c85a54");
26
+
27
+ return [
28
+ { name: "Shape clone/duplicate", group: [
29
+ { name: "Block L", shape: blockL },
30
+ { name: "Block R", shape: blockR },
31
+ ] },
32
+ { name: "TrackedShape clone", shape: postCopy },
33
+ { name: "Sketch clone/duplicate", group: [
34
+ { name: "Slot L", sketch: slotL },
35
+ { name: "Slot R", sketch: slotR },
36
+ ] },
37
+ { name: "ShapeGroup clone/duplicate", group: [
38
+ { name: "Module L", shape: moduleL },
39
+ { name: "Module R", shape: moduleR },
40
+ ] },
41
+ ];
@@ -0,0 +1,27 @@
1
+ // Colors: union() vs returning separate objects.
2
+ //
3
+ // ❌ union() merges into one mesh → only the first shape's color survives.
4
+ // ✅ Returning an array of {name, shape} → each keeps its own color.
5
+
6
+ const size = 25;
7
+ const gap = 5;
8
+
9
+ // --- Three colored boxes ---
10
+ const red = box(size, size, size, true).color('#cc4444');
11
+ const green = box(size, size, size, true).color('#44cc44').translate(size + gap, 0, 0);
12
+ const blue = box(size, size, size, true).color('#4444cc').translate(2 * (size + gap), 0, 0);
13
+
14
+ // BAD: union kills individual colors — result is all red (first shape's color)
15
+ const merged = union(red, green, blue).translate(-80, 0, 0);
16
+
17
+ // GOOD: separate objects keep their colors
18
+ const redSep = box(size, size, size, true).color('#cc4444').translate(80, 0, 0);
19
+ const greenSep = box(size, size, size, true).color('#44cc44').translate(80 + size + gap, 0, 0);
20
+ const blueSep = box(size, size, size, true).color('#4444cc').translate(80 + 2 * (size + gap), 0, 0);
21
+
22
+ return [
23
+ { name: "❌ Union (all one color)", shape: merged },
24
+ { name: "✅ Red (separate)", shape: redSep },
25
+ { name: "✅ Green (separate)", shape: greenSep },
26
+ { name: "✅ Blue (separate)", shape: blueSep },
27
+ ];
@@ -0,0 +1,54 @@
1
+ // Coordinate system — ForgeCAD uses Z-up, right-handed.
2
+ // X = right (+X), left (-X)
3
+ // Y = forward (+Y), back (-Y)
4
+ // Z = up (+Z), down (-Z)
5
+ //
6
+ // "front" = -Y face (camera default looks from -Y toward +Y)
7
+ // "back" = +Y face
8
+
9
+ const axisLen = 80;
10
+ const shaftR = 2;
11
+ const tipH = 10;
12
+ const tipR = 5;
13
+
14
+ // X axis — red, pointing right
15
+ const xShaft = cylinder(axisLen, shaftR).pointAlong([1, 0, 0]).color('#cc4444');
16
+ const xTip = cylinder(tipH, tipR, 0).pointAlong([1, 0, 0]).translate(axisLen, 0, 0).color('#cc4444');
17
+ const xMark = sphere(4).translate(axisLen + tipH + 5, 0, 0).color('#cc4444');
18
+
19
+ // Y axis — green, pointing forward
20
+ const yShaft = cylinder(axisLen, shaftR).pointAlong([0, 1, 0]).color('#44cc44');
21
+ const yTip = cylinder(tipH, tipR, 0).pointAlong([0, 1, 0]).translate(0, axisLen, 0).color('#44cc44');
22
+ const yMark = box(7, 7, 7, true).translate(0, axisLen + tipH + 5, 0).color('#44cc44');
23
+
24
+ // Z axis — blue, pointing up
25
+ const zShaft = cylinder(axisLen, shaftR).color('#4444cc');
26
+ const zTip = cylinder(tipH, tipR, 0).translate(0, 0, axisLen).color('#4444cc');
27
+ const zMark = ngon(6, 4).extrude(4, { center: true }).translate(0, 0, axisLen + tipH + 5).color('#4444cc');
28
+
29
+ // Origin
30
+ const origin = sphere(3).color('#ffffff');
31
+
32
+ // Reference box to show face names
33
+ const ref = box(30, 20, 15, true).translate(40, 40, 0).color('#888888');
34
+ // "front" face is at -Y, "right" face is at +X, "top" face is at +Z
35
+ const frontDot = sphere(3).color('#ffaa00')
36
+ .attachTo(ref, 'front', 'center', [0, -5, 0]);
37
+ const topDot = sphere(3).color('#ffaa00')
38
+ .attachTo(ref, 'top', 'center', [0, 0, 5]);
39
+
40
+ return [
41
+ { name: "X shaft (right)", shape: xShaft },
42
+ { name: "X tip", shape: xTip },
43
+ { name: "X mark ●", shape: xMark },
44
+ { name: "Y shaft (forward)", shape: yShaft },
45
+ { name: "Y tip", shape: yTip },
46
+ { name: "Y mark ■", shape: yMark },
47
+ { name: "Z shaft (up)", shape: zShaft },
48
+ { name: "Z tip", shape: zTip },
49
+ { name: "Z mark ⬡", shape: zMark },
50
+ { name: "Origin", shape: origin },
51
+ { name: "Reference Box", shape: ref },
52
+ { name: "Front dot (−Y)", shape: frontDot },
53
+ { name: "Top dot (+Z)", shape: topDot },
54
+ ];
@@ -0,0 +1,91 @@
1
+ // Curves + Surfacing basics
2
+ // Demonstrates reusable APIs for everyday products:
3
+ // - spline2d() for smooth section sketches
4
+ // - loft() for section-driven solids
5
+ // - spline3d() + sweep() for curved tubes/handles/details
6
+
7
+ const height = param("Bottle Height", 170, { min: 110, max: 260, unit: "mm" });
8
+ const bodyW = param("Body Width", 72, { min: 45, max: 110, unit: "mm" });
9
+ const bodyD = param("Body Depth", 48, { min: 30, max: 90, unit: "mm" });
10
+ const neckW = param("Neck Width", 28, { min: 18, max: 45, unit: "mm" });
11
+ const neckD = param("Neck Depth", 24, { min: 14, max: 40, unit: "mm" });
12
+ const corner = param("Corner Round", 8, { min: 2, max: 20, unit: "mm" });
13
+
14
+ const sectionAt = (w, d, pinch = 0) => spline2d([
15
+ [w * 0.5, 0],
16
+ [w * 0.42, d * 0.45],
17
+ [w * 0.2, d * 0.5 + pinch],
18
+ [0, d * 0.52 + pinch],
19
+ [-w * 0.2, d * 0.5 + pinch],
20
+ [-w * 0.42, d * 0.45],
21
+ [-w * 0.5, 0],
22
+ [-w * 0.42, -d * 0.45],
23
+ [-w * 0.2, -d * 0.5 + pinch],
24
+ [0, -d * 0.52 + pinch],
25
+ [w * 0.2, -d * 0.5 + pinch],
26
+ [w * 0.42, -d * 0.45],
27
+ ], {
28
+ closed: true,
29
+ samplesPerSegment: 10,
30
+ tension: 0.42,
31
+ }).offset(corner * 0.08, 'Round');
32
+
33
+ const z0 = 0;
34
+ const z1 = height * 0.25;
35
+ const z2 = height * 0.62;
36
+ const z3 = height * 0.9;
37
+ const z4 = height;
38
+
39
+ const body = loft(
40
+ [
41
+ sectionAt(bodyW * 0.86, bodyD * 0.84, -2),
42
+ sectionAt(bodyW, bodyD, 0),
43
+ sectionAt(bodyW * 0.92, bodyD * 0.94, 1),
44
+ sectionAt(neckW * 1.25, neckD * 1.2, 0.5),
45
+ sectionAt(neckW, neckD, 0),
46
+ ],
47
+ [z0, z1, z2, z3, z4],
48
+ { edgeLength: 1.1 },
49
+ );
50
+
51
+ // Hollow interior by lofting smaller inner sections.
52
+ const wall = 2.4;
53
+ const inner = loft(
54
+ [
55
+ sectionAt(bodyW * 0.78, bodyD * 0.76, -2.2),
56
+ sectionAt(bodyW - wall * 2, bodyD - wall * 2, -0.6),
57
+ sectionAt(bodyW * 0.86 - wall * 2, bodyD * 0.88 - wall * 2, 0.2),
58
+ sectionAt(neckW * 1.06 - wall, neckD * 1.06 - wall, 0),
59
+ sectionAt(neckW - wall, neckD - wall, 0),
60
+ ],
61
+ [z0 + 3, z1, z2, z3, z4 + 2],
62
+ { edgeLength: 1.1 },
63
+ );
64
+
65
+ let bottle = body.subtract(inner);
66
+ // Mild smoothing to reduce voxel-like artifacts on curved sections.
67
+ bottle = bottle.smoothOut(70, 0.25).refine(2);
68
+
69
+ // Curved spout/tube detail using sweep.
70
+ const spoutPath = spline3d(
71
+ [
72
+ [0, 0, z4 - 8],
73
+ [12, 0, z4 + 8],
74
+ [26, 0, z4 + 24],
75
+ [36, 0, z4 + 16],
76
+ ],
77
+ { tension: 0.45 },
78
+ );
79
+ const spout = sweep(circle2d(2.8, 20), spoutPath, {
80
+ samples: 36,
81
+ edgeLength: 0.65,
82
+ });
83
+
84
+ const topCap = circle2d(Math.max(neckW, neckD) * 0.34, 40).extrude(9)
85
+ .translate(0, 0, z4 - 1.5);
86
+
87
+ return [
88
+ { name: "Bottle Body", shape: bottle.color('#d8e5ec') },
89
+ { name: "Spout", shape: spout.color('#c0ccd4') },
90
+ { name: "Cap", shape: topCap.color('#4f5f70') },
91
+ ];
@@ -0,0 +1,19 @@
1
+ // Dimensioned L-bracket — shows how to add dimension annotations
2
+
3
+ const w = param("Width", 80, { min: 40, max: 150, unit: "mm" });
4
+ const h = param("Height", 60, { min: 30, max: 100, unit: "mm" });
5
+ const d = param("Depth", 40, { min: 20, max: 80, unit: "mm" });
6
+ const t = param("Thickness", 5, { min: 2, max: 15, unit: "mm" });
7
+
8
+ // Build the L-bracket
9
+ const base = box(w, d, t);
10
+ const wall = box(t, d, h).translate(0, 0, t);
11
+ const bracket = union(base, wall);
12
+
13
+ // Add dimensions — purely visual annotations
14
+ dim([0, 0, 0], [w, 0, 0], { label: "Width" });
15
+ dim([0, 0, 0], [0, d, 0], { label: "Depth", offset: 12 });
16
+ dim([0, 0, 0], [0, 0, h + t], { label: "Height", offset: 15 });
17
+ dim([0, 0, 0], [t, 0, 0], { label: "Wall", offset: -8, color: "#ffaa44" });
18
+
19
+ return bracket;
@@ -0,0 +1,23 @@
1
+ // Test lib.elbow() — pipe bend primitive
2
+ const pipeR = param("Pipe Radius", 5, { min: 2, max: 15, unit: "mm" });
3
+ const bendR = param("Bend Radius", 25, { min: 10, max: 60, unit: "mm" });
4
+ const angle = param("Angle", 90, { min: 15, max: 180, unit: "°" });
5
+
6
+ // Basic elbow at default orientation
7
+ const basic = lib.elbow(pipeR, bendR, angle).color('#B87333');
8
+
9
+ // Elbow with from/to directions
10
+ const oriented = lib.elbow(pipeR, bendR, {
11
+ from: [0, 0, 1],
12
+ to: [1, 0, 0],
13
+ }).translate(80, 0, 0).color('#4488cc');
14
+
15
+ // Hollow elbow
16
+ const hollow = lib.elbow(pipeR, bendR, angle, { wall: 1.5 })
17
+ .translate(0, 80, 0).color('#888888');
18
+
19
+ return [
20
+ { name: "Basic 90° Elbow", shape: basic },
21
+ { name: "Oriented Elbow (Z→X)", shape: oriented },
22
+ { name: "Hollow Elbow", shape: hollow },
23
+ ];
@@ -0,0 +1,60 @@
1
+ // Standard-library exploded view: staged offsets + per-part direction overrides.
2
+
3
+ const explodeAmt = param("Explode", 0, { min: 0, max: 36, unit: "mm" });
4
+
5
+ const base = box(120, 80, 10, true).color('#5f6d7a');
6
+ const pedestal = box(70, 40, 20, true).translate(0, 0, 15).color('#6f7f8f');
7
+
8
+ const motorBody = cylinder(55, 16, 16, 40, true)
9
+ .pointAlong([1, 0, 0])
10
+ .translate(0, 0, 32)
11
+ .color('#8f9eab');
12
+ const shaft = cylinder(80, 4, 4, 24, true)
13
+ .pointAlong([1, 0, 0])
14
+ .translate(0, 0, 32)
15
+ .color('#d1d7de');
16
+ const rotorCap = cylinder(8, 18, 18, 36, true)
17
+ .pointAlong([1, 0, 0])
18
+ .translate(31, 0, 32)
19
+ .color('#9eacb9');
20
+
21
+ const boltTemplate = lib.bolt(6, 26).rotate(180, 0, 0).color('#d8dde3');
22
+ const bolts = [
23
+ boltTemplate.translate(-45, -25, 10),
24
+ boltTemplate.translate(45, -25, 10),
25
+ boltTemplate.translate(-45, 25, 10),
26
+ boltTemplate.translate(45, 25, 10),
27
+ ];
28
+
29
+ const explodedParts = [
30
+ { name: "Base", shape: base },
31
+ { name: "Pedestal", shape: pedestal, explode: { stage: 0.35, direction: [0, 0, 1] } },
32
+ {
33
+ name: "Drive",
34
+ group: [
35
+ { name: "Motor Body", shape: motorBody },
36
+ { name: "Rotor Cap", shape: rotorCap, explode: { stage: 1.1, direction: [1, 0, 0] } },
37
+ { name: "Shaft", shape: shaft },
38
+ ],
39
+ },
40
+ {
41
+ name: "Fasteners",
42
+ group: bolts.map((b, i) => ({
43
+ name: `Bolt ${i + 1}`,
44
+ shape: b,
45
+ explode: { stage: 0.9, direction: 'z' },
46
+ })),
47
+ },
48
+ ];
49
+
50
+ cutPlane("Center Section", [0, 1, 0], 0);
51
+
52
+ return lib.explode(explodedParts, {
53
+ amount: explodeAmt,
54
+ stages: [0.35, 0.7, 1.0],
55
+ mode: 'radial',
56
+ byName: {
57
+ "Shaft": { direction: [1, 0, 0], stage: 1.4 },
58
+ "Fasteners": { axisLock: 'z', stage: 0.45 },
59
+ },
60
+ });
@@ -0,0 +1,44 @@
1
+ // Extrude options — twist, taper, center.
2
+ //
3
+ // .extrude(height) is the basic form.
4
+ // Options: { twist, divisions, scaleTop, center }
5
+
6
+ const r = param("Radius", 20, { min: 10, max: 40, unit: "mm" });
7
+ const h = param("Height", 60, { min: 20, max: 120, unit: "mm" });
8
+ const twist = param("Twist", 90, { min: 0, max: 360, unit: "°" });
9
+ const taper = param("Taper", 0.5, { min: 0.1, max: 1.0 });
10
+ const spacing = 60;
11
+
12
+ // 1. Plain extrude
13
+ const plain = ngon(6, r).extrude(h)
14
+ .color('#4488cc');
15
+
16
+ // 2. Twisted extrude — needs divisions for smooth twist
17
+ const twisted = ngon(6, r).extrude(h, { twist: twist, divisions: 32 })
18
+ .translate(spacing, 0, 0)
19
+ .color('#cc8844');
20
+
21
+ // 3. Tapered extrude — scaleTop shrinks the top face
22
+ const tapered = circle2d(r).extrude(h, { scaleTop: taper })
23
+ .translate(2 * spacing, 0, 0)
24
+ .color('#44cc88');
25
+
26
+ // 4. Centered extrude — shape is centered along Z instead of starting at Z=0
27
+ const centered = rect(r * 1.5, r, true).extrude(h, { center: true })
28
+ .translate(3 * spacing, 0, 0)
29
+ .color('#cc44cc');
30
+
31
+ // 5. Combined: twist + taper
32
+ const combo = star(5, r, r * 0.5).extrude(h, {
33
+ twist: twist,
34
+ scaleTop: taper,
35
+ divisions: 32,
36
+ }).translate(4 * spacing, 0, 0).color('#cccc44');
37
+
38
+ return [
39
+ { name: "Plain", shape: plain },
40
+ { name: "Twisted", shape: twisted },
41
+ { name: "Tapered", shape: tapered },
42
+ { name: "Centered (Z)", shape: centered },
43
+ { name: "Twist + Taper", shape: combo },
44
+ ];
@@ -0,0 +1,44 @@
1
+ // Face gear demo: teeth on one face + perpendicular vertical spur gear.
2
+
3
+ const moduleSize = param("Module", 1.25, { min: 0.6, max: 3.0, step: 0.05 });
4
+ const faceTeeth = param("Face Teeth", 36, { min: 18, max: 84, integer: true });
5
+ const verticalTeeth = param("Vertical Teeth", 12, { min: 8, max: 30, integer: true });
6
+ const faceWidth = param("Face Width", 8, { min: 4, max: 18, unit: "mm" });
7
+ const toothHeight = param("Face Tooth Height", 1.25, { min: 0.4, max: 3.5, step: 0.05, unit: "mm" });
8
+ const backlash = param("Backlash", 0.04, { min: 0, max: 0.2, step: 0.01, unit: "mm" });
9
+ const topFace = param("Top Face Teeth (1/0)", 1, { min: 0, max: 1, integer: true });
10
+
11
+ const pair = lib.faceGearPair({
12
+ face: {
13
+ module: moduleSize,
14
+ teeth: faceTeeth,
15
+ pressureAngleDeg: 20,
16
+ faceWidth,
17
+ toothHeight,
18
+ side: topFace === 0 ? "bottom" : "top",
19
+ boreDiameter: moduleSize * 5,
20
+ },
21
+ vertical: {
22
+ module: moduleSize,
23
+ teeth: verticalTeeth,
24
+ pressureAngleDeg: 20,
25
+ faceWidth,
26
+ boreDiameter: moduleSize * 3,
27
+ },
28
+ backlash,
29
+ });
30
+
31
+ for (const d of pair.diagnostics) {
32
+ const tag = `[${d.level}] ${d.code}`;
33
+ if (d.level === "error") console.error(tag, d.message);
34
+ else if (d.level === "warn") console.warn(tag, d.message);
35
+ else console.info(tag, d.message);
36
+ }
37
+
38
+ const faceColor = pair.status === "error" ? "#bf4b4b" : "#8aa8c2";
39
+ const verticalColor = pair.status === "error" ? "#c58b5d" : "#d5a15f";
40
+
41
+ return [
42
+ { name: "Face Gear", shape: pair.face.color(faceColor) },
43
+ { name: "Vertical Gear", shape: pair.vertical.color(verticalColor) },
44
+ ];
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Face Transformation History Demo
3
+ *
4
+ * Shows how to trace the transformation chain for each surface.
5
+ */
6
+
7
+ const base = box(100, 60, 20, true)
8
+ .translate(0, 0, 10)
9
+ .rotate(0, 0, 15);
10
+
11
+ const withHole = base
12
+ .hole('top', { diameter: 12, depth: 8, u: 20, v: 10 });
13
+
14
+ // Get the transformation history for the top face
15
+ const topHistory = withHole.faceHistory('top');
16
+
17
+ // Get history for the hole floor
18
+ const floorHistory = withHole.faceHistory('floor');
19
+
20
+ // Display the histories as dimensions for visibility
21
+ dim([0, 0, 0], [10, 0, 0], {
22
+ label: `Top: ${topHistory.origin.operation} → ${topHistory.transformations.length} transforms`,
23
+ });
24
+
25
+ dim([0, 0, 0], [0, 10, 0], {
26
+ label: `Floor: ${floorHistory.origin.operation} → ${floorHistory.transformations.length} transforms`,
27
+ });
28
+
29
+ // Also log to console for CLI output
30
+ console.log('=== Face Transformation History ===');
31
+ console.log('\nTop Face:');
32
+ console.log(' Origin:', topHistory.origin.operation);
33
+ console.log(' Transformations:');
34
+ topHistory.transformations.forEach((step, i) => {
35
+ console.log(` ${i + 1}. ${step.description}`);
36
+ });
37
+
38
+ console.log('\nHole Floor Face:');
39
+ console.log(' Origin:', floorHistory.origin.operation);
40
+ console.log(' Transformations:');
41
+ floorHistory.transformations.forEach((step, i) => {
42
+ console.log(` ${i + 1}. ${step.description}`);
43
+ });
44
+
45
+ return withHole;
@@ -0,0 +1,47 @@
1
+ const shellBase = roundedRect(90, 56, 6, true).extrude(28);
2
+ const cup = shellBase.shell(2.5, { openFaces: ['top'] }).color('#6f7b86');
3
+ const innerWallPad = roundedRect(16, 9, 1.8, true)
4
+ .onFace(cup, 'inner-side-right', { u: 0, v: -3, protrude: 0.05, selfAnchor: 'center' })
5
+ .extrude(1.4)
6
+ .toShape()
7
+ .color('#f2b16a');
8
+
9
+ const holeBase = roundedRect(72, 44, 5, true).extrude(20);
10
+ const drilled = holeBase.hole('top', { diameter: 8, u: 16, v: -8, depth: 10 }).color('#7a8792');
11
+ const floorBoss = circle2d(3)
12
+ .onFace(drilled, 'floor', { u: 0, v: 0, protrude: 0.05, selfAnchor: 'center' })
13
+ .extrude(1.2)
14
+ .toShape()
15
+ .color('#d46452');
16
+
17
+ const counterboreBase = roundedRect(72, 44, 5, true).extrude(20);
18
+ const counterboreExitFace = counterboreBase.face('bottom');
19
+ const counterbored = counterboreBase.hole('top', {
20
+ diameter: 6,
21
+ u: -14,
22
+ v: 10,
23
+ upToFace: counterboreExitFace,
24
+ counterbore: { diameter: 10, depth: 4 },
25
+ }).color('#748498');
26
+ const shoulderPad = rect(4, 3)
27
+ .onFace(counterbored, 'counterbore-floor', { u: 0, v: 0, protrude: 0.05, selfAnchor: 'center' })
28
+ .extrude(0.9)
29
+ .toShape()
30
+ .color('#f0c36c');
31
+
32
+ const cutBase = roundedRect(78, 46, 5, true).extrude(22);
33
+ const pocket = roundedRect(20, 12, 2, true)
34
+ .onFace(cutBase, 'front', { u: 0, v: 4, selfAnchor: 'center' });
35
+ const cut = cutBase.cutout(pocket, { depth: 8 }).color('#64707d');
36
+ const wallTab = rect(5, 4)
37
+ .onFace(cut, 'wall-right', { u: 0, v: 0, protrude: 0.05, selfAnchor: 'center' })
38
+ .extrude(1)
39
+ .toShape()
40
+ .color('#5ba6d6');
41
+
42
+ return [
43
+ { name: 'Shell Inner Wall Pad', shape: union(cup, innerWallPad).translate(-165, 0, 0) },
44
+ { name: 'Blind Hole Floor Boss', shape: union(drilled, floorBoss).translate(-55, 0, 0) },
45
+ { name: 'Counterbore Shoulder Pad', shape: union(counterbored, shoulderPad).translate(55, 0, 0) },
46
+ { name: 'Cut Wall Tab', shape: union(cut, wallTab).translate(165, 0, 0) },
47
+ ];
@@ -0,0 +1,3 @@
1
+ import { buildFoldedServicePanelCoverScene } from './folded-service-panel-cover.js';
2
+
3
+ return buildFoldedServicePanelCoverScene();