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.
- package/LICENSE +97 -0
- package/README.md +354 -0
- package/dist/assets/evalWorker-BYHXxh15.js +461 -0
- package/dist/assets/index--CYbOPKS.js +5797 -0
- package/dist/assets/manifold-65fIQlgQ.js +20 -0
- package/dist/assets/manifold-B85M7kop.js +20 -0
- package/dist/assets/manifold-B8h_vZ5O.js +16 -0
- package/dist/assets/manifold-D9yvTBHx.wasm +0 -0
- package/dist/assets/manifold-d1UpyLJ8.js +20 -0
- package/dist/assets/reportWorker-B1Zdrz9l.js +494 -0
- package/dist/index.html +16 -0
- package/dist-cli/forgecad.js +44464 -0
- package/dist-skill/SKILL.md +4635 -0
- package/examples/3d-printer.forge.js +328 -0
- package/examples/5-figen-robot-hand.forge.js +283 -0
- package/examples/ac-unit-glm47.forge.js +108 -0
- package/examples/ac-unit-glm5.forge.js +174 -0
- package/examples/ac-unit-kimi25.forge.js +236 -0
- package/examples/ac-unit-minimax.forge.js +123 -0
- package/examples/ac-unit.forge.js +126 -0
- package/examples/adjustable-table.forge.js +191 -0
- package/examples/api/assembly-gear-coupling.forge.js +32 -0
- package/examples/api/assembly-mechanism.forge.js +111 -0
- package/examples/api/attachTo-basics.forge.js +45 -0
- package/examples/api/benchy-style-hull.forge.js +89 -0
- package/examples/api/bill-of-materials.forge.js +46 -0
- package/examples/api/boolean-operations.forge.js +48 -0
- package/examples/api/bounding-box-visualizer.forge.js +58 -0
- package/examples/api/brep-exportable.forge.js +19 -0
- package/examples/api/center-true-vs-false.forge.js +40 -0
- package/examples/api/clone-duplicate.forge.js +41 -0
- package/examples/api/colors-union-vs-array.forge.js +27 -0
- package/examples/api/coordinate-system.forge.js +54 -0
- package/examples/api/curves-surfacing-basics.forge.js +91 -0
- package/examples/api/dimensioned-bracket.forge.js +19 -0
- package/examples/api/elbow-test.forge.js +23 -0
- package/examples/api/exploded-view.forge.js +60 -0
- package/examples/api/extrude-options.forge.js +44 -0
- package/examples/api/face-gears.forge.js +44 -0
- package/examples/api/face-transformation-history.forge.js +45 -0
- package/examples/api/feature-created-faces.forge.js +47 -0
- package/examples/api/folded-service-panel-cover.forge.js +3 -0
- package/examples/api/folded-service-panel-cover.js +117 -0
- package/examples/api/gears-bevel-face-joints.forge.js +157 -0
- package/examples/api/gears-tier1.forge.js +57 -0
- package/examples/api/geometry-info.forge.js +49 -0
- package/examples/api/group-test.forge.js +34 -0
- package/examples/api/group-vs-union.forge.js +25 -0
- package/examples/api/import-args-unit.forge.js +5 -0
- package/examples/api/import-args.forge.js +16 -0
- package/examples/api/import-dimensions-follow.forge.js +18 -0
- package/examples/api/import-placement-references.forge.js +18 -0
- package/examples/api/import-placement-widget-source.forge.js +30 -0
- package/examples/api/import-relative-paths.forge.js +18 -0
- package/examples/api/import-svg-sketch-shape.svg +15 -0
- package/examples/api/import-svg-sketch.forge.js +28 -0
- package/examples/api/js-module-imports.forge.js +9 -0
- package/examples/api/js-module-pillars.js +25 -0
- package/examples/api/js-module-scene.js +9 -0
- package/examples/api/notebook-assembly-debug.forge-notebook.json +90 -0
- package/examples/api/notebook-iteration.forge-notebook.json +75 -0
- package/examples/api/patterns.forge.js +32 -0
- package/examples/api/pointAlong-orientation.forge.js +52 -0
- package/examples/api/profile-2020-b-slot6.forge.js +36 -0
- package/examples/api/rotate-around-to.forge.js +31 -0
- package/examples/api/runtime-joints-view.forge.js +116 -0
- package/examples/api/sdf-rover-demo.forge.js +159 -0
- package/examples/api/section-plane-visualization.forge.js +38 -0
- package/examples/api/sketch-basics.forge.js +48 -0
- package/examples/api/sketch-on-face.forge.js +56 -0
- package/examples/api/sketch-rounding-strategies.forge.js +56 -0
- package/examples/api/spatial-recipes.forge.js +129 -0
- package/examples/bathroom.forge.js +197 -0
- package/examples/bolt-and-nut.forge.js +39 -0
- package/examples/bolt-pattern.forge.js +18 -0
- package/examples/bottle.forge.js +101 -0
- package/examples/chair.forge.js +62 -0
- package/examples/chess-set.forge.js +232 -0
- package/examples/classical-piano.forge.js +203 -0
- package/examples/clock.forge.js +169 -0
- package/examples/compiler-corpus/README.md +88 -0
- package/examples/compiler-corpus/edge-finished-mount.forge.js +18 -0
- package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +24 -0
- package/examples/compiler-corpus/fastener-plate-variants.forge.js +42 -0
- package/examples/compiler-corpus/folded-service-panel-cover.forge.js +5 -0
- package/examples/compiler-corpus/motor-mount-plate.forge.js +32 -0
- package/examples/compiler-corpus/projection-relay-cover.forge.js +16 -0
- package/examples/compiler-corpus/sensor-bracket.forge.js +35 -0
- package/examples/compiler-corpus/service-panel-cover.forge.js +53 -0
- package/examples/compiler-corpus/trimmed-access-cover.forge.js +26 -0
- package/examples/cup.forge.js +25 -0
- package/examples/cut-plane-demo.forge.js +28 -0
- package/examples/door-with-hinges.forge.js +54 -0
- package/examples/frame.sketch.js +4 -0
- package/examples/headphone-hanger-profile.sketch.js +18 -0
- package/examples/headphone-hanger-v2.forge.js +88 -0
- package/examples/headphone-hanger.forge.js +5 -0
- package/examples/iphone-stand.forge.js +72 -0
- package/examples/iphone.forge.js +114 -0
- package/examples/ironman-helmet.js +79 -0
- package/examples/kitchen.forge.js +231 -0
- package/examples/lamp-shade.sketch.js +17 -0
- package/examples/laptop.forge.js +144 -0
- package/examples/liquid-soap-dispenser.forge.js +159 -0
- package/examples/modern-tv.forge.js +86 -0
- package/examples/picture-frame.forge.js +34 -0
- package/examples/robot_hand.forge.js +393 -0
- package/examples/robot_hand_2.forge.js +622 -0
- package/examples/sandbox.forge.js +3 -0
- package/examples/shelf/container.forge.js +30 -0
- package/examples/shelf/shelf-unit.forge.js +62 -0
- package/examples/shoe-rack-doors.forge.js +107 -0
- package/examples/shoe-rack.forge.js +65 -0
- package/examples/spiderman-cake.forge.js +92 -0
- package/examples/table-lamp.forge.js +33 -0
- package/examples/table.forge.js +44 -0
- package/examples/test-colors.forge.js +19 -0
- package/examples/tv-stand.forge.js +21 -0
- package/package.json +69 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Tool Shelf — multi-shelf unit with identical containers
|
|
2
|
+
// Demonstrates importPart() for reusing the same container across shelves
|
|
3
|
+
|
|
4
|
+
const shelfW = param("Shelf Width", 800, { min: 400, max: 1200, unit: "mm" });
|
|
5
|
+
const shelfD = param("Shelf Depth", 200, { min: 150, max: 350, unit: "mm" });
|
|
6
|
+
const totalH = param("Total Height", 1200, { min: 600, max: 1800, unit: "mm" });
|
|
7
|
+
const boardT = param("Board Thickness", 18, { min: 12, max: 25, unit: "mm" });
|
|
8
|
+
const rows = param("Shelf Rows", 4, { min: 2, max: 6, integer: true });
|
|
9
|
+
const cols = param("Containers/Row", 3, { min: 1, max: 6, integer: true });
|
|
10
|
+
|
|
11
|
+
// Container dimensions (must match container.forge.js defaults or be close)
|
|
12
|
+
const containerW = 120;
|
|
13
|
+
const containerD = 180;
|
|
14
|
+
const containerH = 100;
|
|
15
|
+
const lipH = 5;
|
|
16
|
+
|
|
17
|
+
// Derived
|
|
18
|
+
const innerW = shelfW - 2 * boardT;
|
|
19
|
+
const rowH = (totalH - boardT) / rows; // spacing between shelves
|
|
20
|
+
|
|
21
|
+
// --- Side panels ---
|
|
22
|
+
const leftPanel = box(boardT, shelfD, totalH);
|
|
23
|
+
const rightPanel = box(boardT, shelfD, totalH).translate(shelfW - boardT, 0, 0);
|
|
24
|
+
|
|
25
|
+
// --- Shelf boards (horizontal) ---
|
|
26
|
+
const shelfBoards = [];
|
|
27
|
+
for (let i = 0; i <= rows; i++) {
|
|
28
|
+
shelfBoards.push(
|
|
29
|
+
box(innerW, shelfD, boardT).translate(boardT, 0, i * rowH)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// --- Back panel (thin, full height) ---
|
|
34
|
+
const backPanel = box(innerW, boardT / 2, totalH)
|
|
35
|
+
.translate(boardT, shelfD - boardT / 2, 0);
|
|
36
|
+
|
|
37
|
+
const frame = union(leftPanel, rightPanel, backPanel, ...shelfBoards);
|
|
38
|
+
|
|
39
|
+
// --- Import and place containers ---
|
|
40
|
+
const container = importPart("shelf/container.forge.js");
|
|
41
|
+
|
|
42
|
+
const containers = [];
|
|
43
|
+
const gapX = (innerW - cols * (containerW + lipH * 2)) / (cols + 1);
|
|
44
|
+
const gapY = 5; // small gap from front edge
|
|
45
|
+
|
|
46
|
+
for (let row = 0; row < rows; row++) {
|
|
47
|
+
const shelfZ = row * rowH + boardT; // top of shelf board
|
|
48
|
+
for (let col = 0; col < cols; col++) {
|
|
49
|
+
const cx = boardT + gapX * (col + 1) + col * (containerW + lipH * 2) + lipH;
|
|
50
|
+
const cy = gapY;
|
|
51
|
+
containers.push(
|
|
52
|
+
container.translate(cx, cy, shelfZ)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const allContainers = union(...containers);
|
|
58
|
+
|
|
59
|
+
return [
|
|
60
|
+
{ name: "Frame", shape: frame, color: "#b08050" },
|
|
61
|
+
{ name: "Containers", shape: allContainers, color: "#4477aa" },
|
|
62
|
+
];
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Shoe Rack with Doors — 2-level shelves, sitting area, openable cabinet doors
|
|
2
|
+
//
|
|
3
|
+
// 4 doors (2 per shelf level), hinged at the outer edges.
|
|
4
|
+
// Each pair shares one angle slider for synchronized open/close.
|
|
5
|
+
|
|
6
|
+
const width = param("Width", 800, { min: 500, max: 1200, unit: "mm" });
|
|
7
|
+
const depth = param("Depth", 350, { min: 250, max: 500, unit: "mm" });
|
|
8
|
+
const totalH = param("Total Height", 500, { min: 350, max: 650, unit: "mm" });
|
|
9
|
+
|
|
10
|
+
const boardT = param("Board Thickness", 18, { min: 12, max: 25, unit: "mm" });
|
|
11
|
+
|
|
12
|
+
const seatPad = param("Seat Padding", 30, { min: 15, max: 60, unit: "mm" });
|
|
13
|
+
const seatOverhang = param("Seat Overhang", 10, { min: 0, max: 30, unit: "mm" });
|
|
14
|
+
|
|
15
|
+
const doorT = param("Door Thickness", 8, { min: 4, max: 15, unit: "mm" });
|
|
16
|
+
const doorGap = param("Door Gap", 2, { min: 1, max: 5, unit: "mm" });
|
|
17
|
+
|
|
18
|
+
// Derived
|
|
19
|
+
const innerW = width - 2 * boardT;
|
|
20
|
+
const shelfCount = 2;
|
|
21
|
+
const shelfSpacing = (totalH - boardT) / (shelfCount + 1);
|
|
22
|
+
const halfInnerW = innerW / 2;
|
|
23
|
+
|
|
24
|
+
// ── Frame (same as shoe-rack.forge.js + center divider always on) ──
|
|
25
|
+
const leftPanel = box(boardT, depth, totalH);
|
|
26
|
+
const rightPanel = box(boardT, depth, totalH).translate(width - boardT, 0, 0);
|
|
27
|
+
const bottom = box(innerW, depth, boardT).translate(boardT, 0, 0);
|
|
28
|
+
const topBoard = box(width, depth, boardT).translate(0, 0, totalH - boardT);
|
|
29
|
+
const backPanel = box(innerW, boardT / 2, totalH - boardT)
|
|
30
|
+
.translate(boardT, depth - boardT / 2, 0);
|
|
31
|
+
const divider = box(boardT, depth, totalH - boardT)
|
|
32
|
+
.translate(width / 2 - boardT / 2, 0, 0);
|
|
33
|
+
|
|
34
|
+
const shelves = [];
|
|
35
|
+
for (let i = 1; i <= shelfCount; i++) {
|
|
36
|
+
shelves.push(
|
|
37
|
+
box(innerW, depth, boardT).translate(boardT, 0, i * shelfSpacing)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const frame = union(
|
|
42
|
+
leftPanel, rightPanel, bottom, topBoard, backPanel, divider, ...shelves
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// ── Cushion ──
|
|
46
|
+
const cushionW = width + 2 * seatOverhang;
|
|
47
|
+
const cushionD = depth + seatOverhang;
|
|
48
|
+
const cushion = box(cushionW, cushionD, seatPad)
|
|
49
|
+
.translate(-seatOverhang, 0, totalH);
|
|
50
|
+
|
|
51
|
+
// ── Doors ──
|
|
52
|
+
// Each compartment gets one door. Door width = half inner width minus gaps.
|
|
53
|
+
const doorW = halfInnerW - boardT / 2 - doorGap;
|
|
54
|
+
|
|
55
|
+
// Door positions: [pivotX, hingeSign, shelfIndex]
|
|
56
|
+
// hingeSign: -1 = hinged on left edge (opens left), +1 = hinged on right edge (opens right)
|
|
57
|
+
const doorDefs = [
|
|
58
|
+
// Bottom-left: hinged at left panel, opens outward (negative Z rotation)
|
|
59
|
+
{ pivotX: boardT, sign: -1, level: 0 },
|
|
60
|
+
// Bottom-right: hinged at right panel, opens outward (positive Z rotation)
|
|
61
|
+
{ pivotX: width - boardT, sign: 1, level: 0 },
|
|
62
|
+
// Top-left
|
|
63
|
+
{ pivotX: boardT, sign: -1, level: 1 },
|
|
64
|
+
// Top-right
|
|
65
|
+
{ pivotX: width - boardT, sign: 1, level: 1 },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const doorParts = [];
|
|
69
|
+
|
|
70
|
+
for (const d of doorDefs) {
|
|
71
|
+
const z = d.level * shelfSpacing + boardT + doorGap;
|
|
72
|
+
const doorH = shelfSpacing - boardT - 2 * doorGap;
|
|
73
|
+
|
|
74
|
+
// Left-hinged: door extends to the right of pivot
|
|
75
|
+
// Right-hinged: door extends to the left of pivot
|
|
76
|
+
const doorX = d.sign === -1
|
|
77
|
+
? d.pivotX // left hinge: door starts at pivot, goes right
|
|
78
|
+
: d.pivotX - doorW; // right hinge: door ends at pivot, starts left
|
|
79
|
+
|
|
80
|
+
const panel = box(doorW, doorT, doorH)
|
|
81
|
+
.translate(doorX, -doorT, z);
|
|
82
|
+
|
|
83
|
+
const label = d.level === 0 ? "Bottom" : "Top";
|
|
84
|
+
const side = d.sign === -1 ? "Left" : "Right";
|
|
85
|
+
|
|
86
|
+
// Left doors: rotate negative (outward = clockwise from top = negative Z)
|
|
87
|
+
// Right doors: rotate positive (outward = counter-clockwise from top)
|
|
88
|
+
const minA = d.sign === -1 ? -120 : 0;
|
|
89
|
+
const maxA = d.sign === -1 ? 0 : 120;
|
|
90
|
+
const defA = 0;
|
|
91
|
+
|
|
92
|
+
const opened = joint(`${label} ${side} Door`, panel, [d.pivotX, 0, 0], {
|
|
93
|
+
axis: [0, 0, 1],
|
|
94
|
+
min: minA,
|
|
95
|
+
max: maxA,
|
|
96
|
+
default: defA,
|
|
97
|
+
reverse: d.sign === -1,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
doorParts.push({ name: `${label} ${side} Door`, shape: opened, color: "#a07850" });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return [
|
|
104
|
+
{ name: "Frame", shape: frame, color: "#c4956a" },
|
|
105
|
+
{ name: "Cushion", shape: cushion, color: "#5a4a3a" },
|
|
106
|
+
...doorParts,
|
|
107
|
+
];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Shoe Rack with Sitting Area — 2-level shelves + padded seat on top
|
|
2
|
+
//
|
|
3
|
+
// Structure: two side panels, bottom board, 2 shelves, top board,
|
|
4
|
+
// back panel for rigidity, and a cushion on top for sitting.
|
|
5
|
+
|
|
6
|
+
const width = param("Width", 800, { min: 500, max: 1200, unit: "mm" });
|
|
7
|
+
const depth = param("Depth", 350, { min: 250, max: 500, unit: "mm" });
|
|
8
|
+
const totalH = param("Total Height", 500, { min: 350, max: 650, unit: "mm" });
|
|
9
|
+
|
|
10
|
+
const boardT = param("Board Thickness", 18, { min: 12, max: 25, unit: "mm" });
|
|
11
|
+
|
|
12
|
+
const seatPad = param("Seat Padding", 30, { min: 15, max: 60, unit: "mm" });
|
|
13
|
+
const seatOverhang = param("Seat Overhang", 10, { min: 0, max: 30, unit: "mm" });
|
|
14
|
+
|
|
15
|
+
const divider = param("Center Divider", 1, { min: 0, max: 1, step: 1 });
|
|
16
|
+
|
|
17
|
+
// Derived
|
|
18
|
+
const innerW = width - 2 * boardT;
|
|
19
|
+
const shelfCount = 2;
|
|
20
|
+
const shelfSpacing = (totalH - boardT) / (shelfCount + 1);
|
|
21
|
+
|
|
22
|
+
// --- Side panels ---
|
|
23
|
+
const leftPanel = box(boardT, depth, totalH);
|
|
24
|
+
const rightPanel = box(boardT, depth, totalH).translate(width - boardT, 0, 0);
|
|
25
|
+
|
|
26
|
+
// --- Bottom board ---
|
|
27
|
+
const bottom = box(innerW, depth, boardT).translate(boardT, 0, 0);
|
|
28
|
+
|
|
29
|
+
// --- Shelves ---
|
|
30
|
+
const shelves = [];
|
|
31
|
+
for (let i = 1; i <= shelfCount; i++) {
|
|
32
|
+
shelves.push(
|
|
33
|
+
box(innerW, depth, boardT).translate(boardT, 0, i * shelfSpacing)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// --- Top board (seat base) ---
|
|
38
|
+
const topBoard = box(width, depth, boardT).translate(0, 0, totalH - boardT);
|
|
39
|
+
|
|
40
|
+
// --- Back panel (thin, full height, for rigidity) ---
|
|
41
|
+
const backPanel = box(innerW, boardT / 2, totalH - boardT)
|
|
42
|
+
.translate(boardT, depth - boardT / 2, 0);
|
|
43
|
+
|
|
44
|
+
// --- Optional center divider ---
|
|
45
|
+
const parts = [leftPanel, rightPanel, bottom, topBoard, backPanel, ...shelves];
|
|
46
|
+
|
|
47
|
+
if (divider >= 1) {
|
|
48
|
+
const divH = totalH - boardT; // from bottom to underside of top board
|
|
49
|
+
parts.push(
|
|
50
|
+
box(boardT, depth, divH).translate(width / 2 - boardT / 2, 0, 0)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Seat cushion (overhangs front and sides) ---
|
|
55
|
+
const cushionW = width + 2 * seatOverhang;
|
|
56
|
+
const cushionD = depth + seatOverhang;
|
|
57
|
+
const cushion = box(cushionW, cushionD, seatPad)
|
|
58
|
+
.translate(-seatOverhang, 0, totalH);
|
|
59
|
+
|
|
60
|
+
const frame = union(...parts);
|
|
61
|
+
|
|
62
|
+
return [
|
|
63
|
+
{ name: "Frame", shape: frame, color: "#c4956a" },
|
|
64
|
+
{ name: "Cushion", shape: cushion, color: "#5a4a3a" },
|
|
65
|
+
];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Spider-Man Birthday Cake
|
|
2
|
+
// A multi-tiered cake with Spider-Man colors and decorations.
|
|
3
|
+
|
|
4
|
+
const tierHeight = param("Tier Height", 30, { min: 20, max: 50, unit: "mm" });
|
|
5
|
+
const baseRadius = param("Base Radius", 60, { min: 40, max: 100, unit: "mm" });
|
|
6
|
+
|
|
7
|
+
// Colors
|
|
8
|
+
const spideyRed = '#e60000';
|
|
9
|
+
const spideyBlue = '#0055aa';
|
|
10
|
+
const spideyBlack = '#111111';
|
|
11
|
+
const spideyWhite = '#ffffff';
|
|
12
|
+
const candleYellow = '#ffff44';
|
|
13
|
+
const flameOrange = '#ff8800';
|
|
14
|
+
|
|
15
|
+
const cake = [];
|
|
16
|
+
|
|
17
|
+
// --- Bottom Tier (Blue) ---
|
|
18
|
+
const bottomTier = cylinder(tierHeight, baseRadius).color(spideyBlue);
|
|
19
|
+
cake.push({ name: "Bottom Tier", shape: bottomTier });
|
|
20
|
+
|
|
21
|
+
// --- Middle Tier (Red with Webbing) ---
|
|
22
|
+
const midRadius = baseRadius * 0.75;
|
|
23
|
+
const midTier = cylinder(tierHeight, midRadius).color(spideyRed)
|
|
24
|
+
.attachTo(bottomTier, 'top', 'bottom');
|
|
25
|
+
|
|
26
|
+
// Vertical webbing lines for middle tier
|
|
27
|
+
const webLine = cylinder(tierHeight, 0.6).color(spideyBlack);
|
|
28
|
+
const midWebs = circularPattern(webLine.translate(midRadius - 0.2, 0, 0), 12)
|
|
29
|
+
.attachTo(bottomTier, 'top', 'bottom');
|
|
30
|
+
|
|
31
|
+
cake.push({ name: "Middle Tier", shape: midTier });
|
|
32
|
+
cake.push({ name: "Middle Webbing", shape: midWebs });
|
|
33
|
+
|
|
34
|
+
// --- Top Tier (Red with Eyes) ---
|
|
35
|
+
const topRadius = midRadius * 0.7;
|
|
36
|
+
const topTier = cylinder(tierHeight, topRadius).color(spideyRed)
|
|
37
|
+
.attachTo(midTier, 'top', 'bottom');
|
|
38
|
+
|
|
39
|
+
// Vertical webbing lines for top tier
|
|
40
|
+
const topWebs = circularPattern(webLine.translate(topRadius - 0.2, 0, 0), 12)
|
|
41
|
+
.attachTo(midTier, 'top', 'bottom');
|
|
42
|
+
|
|
43
|
+
// Spider-Man Eyes (simplified as angled boxes on the front)
|
|
44
|
+
const eyeW = topRadius * 0.4;
|
|
45
|
+
const eyeH = tierHeight * 0.6;
|
|
46
|
+
const eyeT = 2;
|
|
47
|
+
|
|
48
|
+
const leftEye = box(eyeW, eyeT, eyeH, true).color(spideyWhite)
|
|
49
|
+
.rotate(0, 0, 20)
|
|
50
|
+
.onFace(topTier, 'front', { u: -topRadius * 0.35, v: 0, protrude: 1 });
|
|
51
|
+
|
|
52
|
+
const rightEye = box(eyeW, eyeT, eyeH, true).color(spideyWhite)
|
|
53
|
+
.rotate(0, 0, -20)
|
|
54
|
+
.onFace(topTier, 'front', { u: topRadius * 0.35, v: 0, protrude: 1 });
|
|
55
|
+
|
|
56
|
+
cake.push({ name: "Top Tier", shape: topTier });
|
|
57
|
+
cake.push({ name: "Top Webbing", shape: topWebs });
|
|
58
|
+
cake.push({ name: "Left Eye", shape: leftEye });
|
|
59
|
+
cake.push({ name: "Right Eye", shape: rightEye });
|
|
60
|
+
|
|
61
|
+
// --- Candles on Top ---
|
|
62
|
+
const candleR = 2;
|
|
63
|
+
const candleH = 15;
|
|
64
|
+
const candleCount = 6;
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < candleCount; i++) {
|
|
67
|
+
const angle = (i / candleCount) * 360;
|
|
68
|
+
const rad = topRadius * 0.6;
|
|
69
|
+
const x = rad * Math.cos(angle * Math.PI / 180);
|
|
70
|
+
const y = rad * Math.sin(angle * Math.PI / 180);
|
|
71
|
+
|
|
72
|
+
const candleBody = cylinder(candleH, candleR).color(candleYellow)
|
|
73
|
+
.attachTo(topTier, 'top', 'bottom', [x, y, 0]);
|
|
74
|
+
|
|
75
|
+
const flame = sphere(2.5).color(flameOrange)
|
|
76
|
+
.attachTo(candleBody, 'top', 'bottom', [0, 0, 1]);
|
|
77
|
+
|
|
78
|
+
cake.push({
|
|
79
|
+
name: `Candle ${i + 1}`,
|
|
80
|
+
group: [
|
|
81
|
+
{ name: "Body", shape: candleBody },
|
|
82
|
+
{ name: "Flame", shape: flame }
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// --- Cake Board ---
|
|
88
|
+
const board = cylinder(3, baseRadius + 10).color('#eeeeee')
|
|
89
|
+
.attachTo(bottomTier, 'bottom', 'top');
|
|
90
|
+
cake.push({ name: "Cake Board", shape: board });
|
|
91
|
+
|
|
92
|
+
return cake;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Table Lamp — base + stem + shade (multi-file: imports shade profile)
|
|
2
|
+
|
|
3
|
+
const baseR = param("Base Radius", 25, { min: 15, max: 40, unit: "mm" });
|
|
4
|
+
const baseH = param("Base Height", 6, { min: 3, max: 12, unit: "mm" });
|
|
5
|
+
const stemR = param("Stem Radius", 4, { min: 2, max: 8, unit: "mm" });
|
|
6
|
+
const stemH = param("Stem Height", 60, { min: 30, max: 100, unit: "mm" });
|
|
7
|
+
const shadeTopR = param("Shade Top Radius", 15, { min: 8, max: 30, unit: "mm" });
|
|
8
|
+
const shadeBottomR = param("Shade Bottom Radius", 30, { min: 15, max: 50, unit: "mm" });
|
|
9
|
+
const shadeH = param("Shade Height", 35, { min: 20, max: 60, unit: "mm" });
|
|
10
|
+
const shadeWall = param("Shade Wall", 1.5, { min: 0.5, max: 4, unit: "mm" });
|
|
11
|
+
|
|
12
|
+
// --- Base: flat cylinder ---
|
|
13
|
+
const base = cylinder(baseH, baseR);
|
|
14
|
+
|
|
15
|
+
// --- Stem: thin cylinder rising from base center ---
|
|
16
|
+
const stem = cylinder(stemH, stemR).translate(0, 0, baseH);
|
|
17
|
+
|
|
18
|
+
// --- Shade: revolve the imported profile ---
|
|
19
|
+
const shadeProfile = importSketch("lamp-shade.sketch.js", {
|
|
20
|
+
"Top Radius": shadeTopR,
|
|
21
|
+
"Bottom Radius": shadeBottomR,
|
|
22
|
+
"Shade Height": shadeH,
|
|
23
|
+
"Wall Thickness": shadeWall,
|
|
24
|
+
});
|
|
25
|
+
const shade = shadeProfile.revolve();
|
|
26
|
+
const shadeZ = baseH + stemH;
|
|
27
|
+
const shadePlaced = shade.translate(0, 0, shadeZ);
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
{ name: "Base", shape: base, color: "#2a2a2a" },
|
|
31
|
+
{ name: "Stem", shape: stem, color: "#888888" },
|
|
32
|
+
{ name: "Shade", shape: shadePlaced, color: "#f5e6c8" },
|
|
33
|
+
];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Four-Legged Table — parametric dining/work table
|
|
2
|
+
|
|
3
|
+
const topW = param("Top Width", 120, { min: 60, max: 200, unit: "mm" });
|
|
4
|
+
const topD = param("Top Depth", 80, { min: 40, max: 150, unit: "mm" });
|
|
5
|
+
const topH = param("Top Thickness", 4, { min: 2, max: 10, unit: "mm" });
|
|
6
|
+
const tableH = param("Table Height", 75, { min: 50, max: 110, unit: "mm" });
|
|
7
|
+
const legSide = param("Leg Width", 5, { min: 3, max: 12, unit: "mm" });
|
|
8
|
+
const inset = param("Leg Inset", 5, { min: 0, max: 20, unit: "mm" });
|
|
9
|
+
const stretcher = param("Stretcher", 1, { min: 0, max: 1, step: 1 });
|
|
10
|
+
const stretcherH = param("Stretcher Height", 15, { min: 5, max: 40, unit: "mm" });
|
|
11
|
+
const stretcherW = param("Stretcher Width", 3, { min: 1, max: 6, unit: "mm" });
|
|
12
|
+
|
|
13
|
+
const legH = tableH - topH;
|
|
14
|
+
|
|
15
|
+
// Tabletop
|
|
16
|
+
const top = box(topW, topD, topH).translate(0, 0, legH);
|
|
17
|
+
|
|
18
|
+
// Leg positions: inset from each corner
|
|
19
|
+
const legPositions = [
|
|
20
|
+
[inset, inset],
|
|
21
|
+
[topW - inset - legSide, inset],
|
|
22
|
+
[inset, topD - inset - legSide],
|
|
23
|
+
[topW - inset - legSide, topD - inset - legSide],
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const legs = union(
|
|
27
|
+
...legPositions.map(([x, y]) =>
|
|
28
|
+
box(legSide, legSide, legH).translate(x, y, 0)
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Optional stretchers between legs (long sides)
|
|
33
|
+
const parts = [top, legs];
|
|
34
|
+
|
|
35
|
+
if (stretcher >= 1) {
|
|
36
|
+
const strLen = topD - 2 * inset - 2 * legSide + stretcherW;
|
|
37
|
+
const leftStr = box(stretcherW, strLen, stretcherW)
|
|
38
|
+
.translate(inset + legSide / 2 - stretcherW / 2, inset + legSide, stretcherH);
|
|
39
|
+
const rightStr = box(stretcherW, strLen, stretcherW)
|
|
40
|
+
.translate(topW - inset - legSide / 2 - stretcherW / 2, inset + legSide, stretcherH);
|
|
41
|
+
parts.push(leftStr, rightStr);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return union(...parts);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Test color support
|
|
2
|
+
|
|
3
|
+
const redBox = box(20, 20, 20).color("#ff0000").translate(-25, 0, 0);
|
|
4
|
+
const blueSphere = sphere(12).color("#0000ff").translate(25, 0, 0);
|
|
5
|
+
|
|
6
|
+
// Union should preserve first operand color (red)
|
|
7
|
+
const combined = redBox.add(blueSphere).translate(0, 30, 0);
|
|
8
|
+
|
|
9
|
+
// Sketch colors
|
|
10
|
+
const greenRect = rect(30, 10).color("#00ff00").translate(0, -30);
|
|
11
|
+
const yellowCircle = circle2d(10).color("#ffff00").translate(0, -30);
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
{ name: "Red Box", shape: redBox },
|
|
15
|
+
{ name: "Blue Sphere", shape: blueSphere },
|
|
16
|
+
{ name: "Combined (Red)", shape: combined },
|
|
17
|
+
{ name: "Green Rect", sketch: greenRect },
|
|
18
|
+
{ name: "Yellow Circle", sketch: yellowCircle }
|
|
19
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Flat-Screen TV with Stand — Entity-based API demo
|
|
2
|
+
// Rectangle2D for named geometry, TrackedShape for topology
|
|
3
|
+
|
|
4
|
+
const tvWidth = param("TV Width", 1200, { min: 800, max: 1600, unit: "mm" });
|
|
5
|
+
const tvHeight = param("TV Height", 700, { min: 500, max: 1000, unit: "mm" });
|
|
6
|
+
const tvThick = param("TV Thickness", 40, { min: 20, max: 80, unit: "mm" });
|
|
7
|
+
|
|
8
|
+
const standW = param("Stand Width", 400, { min: 200, max: 600, unit: "mm" });
|
|
9
|
+
const standD = param("Stand Depth", 250, { min: 150, max: 400, unit: "mm" });
|
|
10
|
+
const standH = param("Stand Height", 60, { min: 30, max: 120, unit: "mm" });
|
|
11
|
+
|
|
12
|
+
// TV panel: wide in X, thin in Y, tall in Z
|
|
13
|
+
const panelRect = Rectangle2D.fromCenterAndDimensions(point(0, 0), tvWidth, tvThick);
|
|
14
|
+
const panel = panelRect.extrude(tvHeight).moveBy(0, 0, standH);
|
|
15
|
+
|
|
16
|
+
// Stand: wider in Y (depth), centered
|
|
17
|
+
const standRect = Rectangle2D.fromCenterAndDimensions(point(0, 0), standW, standD);
|
|
18
|
+
const stand = standRect.extrude(standH);
|
|
19
|
+
|
|
20
|
+
// union() auto-unwraps TrackedShape
|
|
21
|
+
return union(stand, panel);
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "forgecad",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Code-first parametric CAD for JavaScript/TypeScript, in the browser and CLI.",
|
|
5
|
+
"license": "BUSL-1.1",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"forgecad": "./dist-cli/forgecad.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist-cli/forgecad.js",
|
|
12
|
+
"dist",
|
|
13
|
+
"dist-skill",
|
|
14
|
+
"examples",
|
|
15
|
+
"!examples/todo.md",
|
|
16
|
+
"!examples/robot_hand_2_api_feedback.md",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "vite -- ./examples",
|
|
25
|
+
"dev:blank": "vite",
|
|
26
|
+
"build": "tsc && vite build && npm run build:cli",
|
|
27
|
+
"build:cli": "tsup cli/forgecad.ts --format esm --platform node --target node20 --out-dir dist-cli --clean --sourcemap --external typescript",
|
|
28
|
+
"test": "npm run test:prepare && node dist-cli/forgecad.js check suite",
|
|
29
|
+
"test:prepare": "tsc --noEmit && npm run build:cli",
|
|
30
|
+
"test:compiler": "npm run test:prepare && node dist-cli/forgecad.js check compiler",
|
|
31
|
+
"test:compiler:update": "npm run test:prepare && node dist-cli/forgecad.js check compiler --update",
|
|
32
|
+
"test:query-propagation": "npm run test:prepare && node dist-cli/forgecad.js check query-propagation",
|
|
33
|
+
"test:query-propagation:update": "npm run test:prepare && node dist-cli/forgecad.js check query-propagation --update",
|
|
34
|
+
"test:examples": "npm run test:prepare && node dist-cli/forgecad.js check examples",
|
|
35
|
+
"test:brep": "npm run test:prepare && node dist-cli/forgecad.js check brep",
|
|
36
|
+
"test:api": "npm run test:prepare && node dist-cli/forgecad.js check api",
|
|
37
|
+
"build:skill:forgecad": "node scripts/build-forgecad-skill.mjs",
|
|
38
|
+
"prepare": "npm run build:cli",
|
|
39
|
+
"prepublishOnly": "npm run build && npm run build:skill:forgecad",
|
|
40
|
+
"preview": "vite preview",
|
|
41
|
+
"prune:branches": "uv run cli/forge-prune-local-branches.py",
|
|
42
|
+
"meta:init": "bash scripts/local-meta.sh init",
|
|
43
|
+
"meta:status": "bash scripts/local-meta.sh status",
|
|
44
|
+
"meta:path": "bash scripts/local-meta.sh path"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"chokidar": "^4.0.0",
|
|
48
|
+
"gifenc": "^1.0.3",
|
|
49
|
+
"manifold-3d": "^3.3.2",
|
|
50
|
+
"pngjs": "^7.0.0",
|
|
51
|
+
"puppeteer-core": "^24.37.2",
|
|
52
|
+
"typescript": "^5.8.3"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@monaco-editor/react": "^4.7.0",
|
|
56
|
+
"@react-three/drei": "^10.0.6",
|
|
57
|
+
"@react-three/fiber": "^9.1.2",
|
|
58
|
+
"@types/react": "^19.1.6",
|
|
59
|
+
"@types/react-dom": "^19.1.6",
|
|
60
|
+
"@types/three": "^0.175.0",
|
|
61
|
+
"@vitejs/plugin-react": "^4.5.2",
|
|
62
|
+
"react": "^19.1.0",
|
|
63
|
+
"react-dom": "^19.1.0",
|
|
64
|
+
"three": "^0.175.0",
|
|
65
|
+
"tsup": "^8.5.0",
|
|
66
|
+
"vite": "^6.3.5",
|
|
67
|
+
"zustand": "^5.0.5"
|
|
68
|
+
}
|
|
69
|
+
}
|