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,114 @@
|
|
|
1
|
+
// iPhone — parametric model
|
|
2
|
+
// Tests ForgeCAD's sketch→extrude + smoothing workflow
|
|
3
|
+
|
|
4
|
+
// === Dimensions (roughly iPhone 15 Pro proportions) ===
|
|
5
|
+
const w = param("Width", 71.6, { min: 60, max: 80, unit: "mm" });
|
|
6
|
+
const h = param("Height", 146.6, { min: 120, max: 170, unit: "mm" });
|
|
7
|
+
const d = param("Depth", 8.25, { min: 6, max: 12, unit: "mm" });
|
|
8
|
+
const cornerR = param("Corner Radius", 10, { min: 2, max: 20, unit: "mm" });
|
|
9
|
+
const edgeR = param("Edge Radius", 1.5, { min: 0, max: 3, step: 0.1, unit: "mm" });
|
|
10
|
+
|
|
11
|
+
// === Body ===
|
|
12
|
+
// Strategy: create the body profile, extrude, then smooth edges.
|
|
13
|
+
// smoothOut + refine rounds the sharp 90° edges where top/bottom meet sides.
|
|
14
|
+
// We use low smoothness to avoid inflating the shape.
|
|
15
|
+
const bodyProfile = roundedRect(w, h, cornerR, true);
|
|
16
|
+
let body = bodyProfile.extrude(d, { center: true });
|
|
17
|
+
|
|
18
|
+
if (edgeR > 0) {
|
|
19
|
+
// smoothOut marks edges for rounding, refine subdivides to actually curve them
|
|
20
|
+
// minSharpAngle=80 catches the 90° edges but leaves shallow angles alone
|
|
21
|
+
// minSmoothness controls how much rounding (0=sharp, 1=full round)
|
|
22
|
+
const smoothness = Math.min(edgeR / 3, 1);
|
|
23
|
+
body = body.toShape().smoothOut(80, smoothness).refine(3);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// === Screen cutout (inset from front face) ===
|
|
27
|
+
const screenInset = param("Screen Inset", 2, { min: 1, max: 5, unit: "mm" });
|
|
28
|
+
const screenDepth = 0.4;
|
|
29
|
+
const screenProfile = roundedRect(w - screenInset * 2, h - screenInset * 2, cornerR - screenInset, true);
|
|
30
|
+
const screenCut = screenProfile.extrude(screenDepth + 1)
|
|
31
|
+
.translate(0, 0, d / 2 - screenDepth);
|
|
32
|
+
body = body.subtract(screenCut);
|
|
33
|
+
|
|
34
|
+
// === Camera island (back, top-left area) ===
|
|
35
|
+
const camSize = param("Camera Island", 36, { min: 25, max: 45, unit: "mm" });
|
|
36
|
+
const camR = 8;
|
|
37
|
+
const camBump = param("Camera Bump", 1.5, { min: 0.5, max: 3, unit: "mm" });
|
|
38
|
+
const camX = -w / 2 + camSize / 2 + 4;
|
|
39
|
+
const camY = h / 2 - camSize / 2 - 4;
|
|
40
|
+
|
|
41
|
+
const camProfile = roundedRect(camSize, camSize, camR, true).translate(camX, camY);
|
|
42
|
+
let camIsland = camProfile.extrude(camBump).translate(0, 0, -d / 2 - camBump);
|
|
43
|
+
if (edgeR > 0) {
|
|
44
|
+
camIsland = camIsland.toShape().smoothOut(80, 0.4).refine(2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Camera lenses (3 in L-pattern)
|
|
48
|
+
const lensR = param("Lens Radius", 6, { min: 3, max: 10, unit: "mm" });
|
|
49
|
+
const lensSpacing = 12;
|
|
50
|
+
const lensZ = -d / 2 - camBump;
|
|
51
|
+
|
|
52
|
+
const makeLens = (x, y) =>
|
|
53
|
+
circle2d(lensR).translate(x, y).extrude(camBump + 1).translate(0, 0, lensZ - 0.5);
|
|
54
|
+
|
|
55
|
+
const lens1 = makeLens(camX - lensSpacing / 2, camY + lensSpacing / 2);
|
|
56
|
+
const lens2 = makeLens(camX + lensSpacing / 2, camY + lensSpacing / 2);
|
|
57
|
+
const lens3 = makeLens(camX - lensSpacing / 2, camY - lensSpacing / 2);
|
|
58
|
+
|
|
59
|
+
// === Charging port (bottom edge) ===
|
|
60
|
+
const portW = param("Port Width", 9, { min: 6, max: 14, unit: "mm" });
|
|
61
|
+
const portH = 3;
|
|
62
|
+
const portDepth = 4;
|
|
63
|
+
const portCut = roundedRect(portW, portH, portH / 2, true)
|
|
64
|
+
.extrude(portDepth)
|
|
65
|
+
.rotate(90, 0, 0)
|
|
66
|
+
.translate(0, -h / 2, 0);
|
|
67
|
+
|
|
68
|
+
// === Speaker grille (bottom, right of port) ===
|
|
69
|
+
const holeR = 0.5;
|
|
70
|
+
const holeCount = param("Speaker Holes", 6, { min: 3, max: 10 });
|
|
71
|
+
const holeSpacing = 2.2;
|
|
72
|
+
const grillX0 = portW / 2 + 5;
|
|
73
|
+
const speakerHoles = [];
|
|
74
|
+
for (let i = 0; i < holeCount; i++) {
|
|
75
|
+
speakerHoles.push(
|
|
76
|
+
cylinder(portDepth, holeR, undefined, 12)
|
|
77
|
+
.rotate(90, 0, 0)
|
|
78
|
+
.translate(grillX0 + i * holeSpacing, -h / 2, 0)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Mic holes (bottom, left of port)
|
|
83
|
+
for (let i = 0; i < 2; i++) {
|
|
84
|
+
speakerHoles.push(
|
|
85
|
+
cylinder(portDepth, holeR, undefined, 12)
|
|
86
|
+
.rotate(90, 0, 0)
|
|
87
|
+
.translate(-grillX0 - i * holeSpacing, -h / 2, 0)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const grillCut = union(...speakerHoles);
|
|
91
|
+
|
|
92
|
+
// === Side buttons ===
|
|
93
|
+
const btnInset = 0.4;
|
|
94
|
+
const btnThick = 1.6;
|
|
95
|
+
|
|
96
|
+
const sideBtn = (bw, bx, by, side) => {
|
|
97
|
+
const profile = roundedRect(bw, btnThick, 0.5, true);
|
|
98
|
+
const btn = profile.extrude(btnInset);
|
|
99
|
+
if (side === 'left') return btn.rotate(90, 0, 90).translate(-w / 2 - edgeR - btnInset, by, 0);
|
|
100
|
+
return btn.rotate(90, 0, 90).translate(w / 2 + edgeR + btnInset, by, 0);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const volUp = sideBtn(14, 0, h / 2 - 35, 'left');
|
|
104
|
+
const volDown = sideBtn(14, 0, h / 2 - 53, 'left');
|
|
105
|
+
const actionBtn = sideBtn(8, 0, h / 2 - 13, 'left');
|
|
106
|
+
const powerBtn = sideBtn(18, 0, h / 2 - 40, 'right');
|
|
107
|
+
|
|
108
|
+
// === Assembly ===
|
|
109
|
+
let phone = union(body, camIsland);
|
|
110
|
+
phone = phone.subtract(lens1).subtract(lens2).subtract(lens3);
|
|
111
|
+
phone = phone.subtract(portCut).subtract(grillCut);
|
|
112
|
+
phone = union(phone, volUp, volDown, actionBtn, powerBtn);
|
|
113
|
+
|
|
114
|
+
return phone;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Iron Man Helmet - Parametric Design
|
|
2
|
+
|
|
3
|
+
// Parameters
|
|
4
|
+
const scale = param("Scale", 1.0, { min: 0.5, max: 1.5, step: 0.1 });
|
|
5
|
+
const chinWidth = param("Chin Width", 80, { min: 60, max: 100, unit: "mm" });
|
|
6
|
+
const eyeWidth = param("Eye Width", 35, { min: 25, max: 50, unit: "mm" });
|
|
7
|
+
const eyeHeight = param("Eye Height", 12, { min: 8, max: 20, unit: "mm" });
|
|
8
|
+
const mouthWidth = param("Mouth Width", 25, { min: 15, max: 40, unit: "mm" });
|
|
9
|
+
|
|
10
|
+
// Base dimensions (scaled)
|
|
11
|
+
const baseWidth = 180 * scale;
|
|
12
|
+
const baseHeight = 240 * scale;
|
|
13
|
+
const baseDepth = 200 * scale;
|
|
14
|
+
|
|
15
|
+
// Main helmet shell - elongated sphere for head shape
|
|
16
|
+
const mainShell = sphere(baseWidth / 2)
|
|
17
|
+
.scale([1, 1.3, 1.1])
|
|
18
|
+
.translate(0, 0, baseHeight / 3);
|
|
19
|
+
|
|
20
|
+
// Faceplate - angular front section
|
|
21
|
+
const faceplateProfile = polygon([
|
|
22
|
+
[0, 0],
|
|
23
|
+
[chinWidth / 2, 0],
|
|
24
|
+
[baseWidth / 2, baseHeight * 0.4],
|
|
25
|
+
[baseWidth / 2, baseHeight * 0.7],
|
|
26
|
+
[baseWidth / 2 - 20, baseHeight * 0.85],
|
|
27
|
+
[0, baseHeight * 0.9]
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const faceplate = union2d(
|
|
31
|
+
faceplateProfile,
|
|
32
|
+
faceplateProfile.mirror([1, 0])
|
|
33
|
+
).extrude(baseDepth / 2, { scaleTop: 0.7 });
|
|
34
|
+
|
|
35
|
+
// Eye cutouts - angular slits
|
|
36
|
+
const eyeLeft = rect(eyeWidth, eyeHeight)
|
|
37
|
+
.offset(2, 'Round')
|
|
38
|
+
.extrude(50)
|
|
39
|
+
.rotate(0, 10, -15)
|
|
40
|
+
.translate(-baseWidth / 4, baseHeight * 0.55, baseDepth / 2 - 10);
|
|
41
|
+
|
|
42
|
+
const eyeRight = eyeLeft.mirror([1, 0, 0]);
|
|
43
|
+
|
|
44
|
+
// Mouth/chin vent - triangular opening
|
|
45
|
+
const mouthVent = polygon([
|
|
46
|
+
[-mouthWidth / 2, 0],
|
|
47
|
+
[mouthWidth / 2, 0],
|
|
48
|
+
[0, -15]
|
|
49
|
+
]).extrude(30)
|
|
50
|
+
.translate(0, baseHeight * 0.15, baseDepth / 2 - 5);
|
|
51
|
+
|
|
52
|
+
// Forehead arc reactor glow slot
|
|
53
|
+
const foreheadSlot = rect(40, 8)
|
|
54
|
+
.offset(2, 'Round')
|
|
55
|
+
.extrude(20)
|
|
56
|
+
.translate(0, baseHeight * 0.75, baseDepth / 2 - 5);
|
|
57
|
+
|
|
58
|
+
// Cheek vents - angular cuts
|
|
59
|
+
const cheekVent = rect(15, 40)
|
|
60
|
+
.extrude(25)
|
|
61
|
+
.rotate(0, 15, 20)
|
|
62
|
+
.translate(baseWidth / 3, baseHeight * 0.35, baseDepth / 2 - 10);
|
|
63
|
+
|
|
64
|
+
const cheekVentRight = cheekVent.mirror([1, 0, 0]);
|
|
65
|
+
|
|
66
|
+
// Combine everything
|
|
67
|
+
const helmet = intersection(mainShell, faceplate)
|
|
68
|
+
.subtract(eyeLeft)
|
|
69
|
+
.subtract(eyeRight)
|
|
70
|
+
.subtract(mouthVent)
|
|
71
|
+
.subtract(foreheadSlot)
|
|
72
|
+
.subtract(cheekVent)
|
|
73
|
+
.subtract(cheekVentRight);
|
|
74
|
+
|
|
75
|
+
// Add chin detail - angular jaw line
|
|
76
|
+
const chinDetail = box(chinWidth - 10, 15, 20, true)
|
|
77
|
+
.translate(0, baseHeight * 0.08, baseDepth / 2 - 10);
|
|
78
|
+
|
|
79
|
+
return helmet.add(chinDetail);
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// Modern Kitchen — L-shaped counter with appliances
|
|
2
|
+
// Demonstrates: multi-object scene, colors, parametric layout
|
|
3
|
+
|
|
4
|
+
const counterW = param("Counter Width", 2400, { min: 1800, max: 3500, unit: "mm" });
|
|
5
|
+
const counterD = param("Counter Depth", 600, { min: 500, max: 800, unit: "mm" });
|
|
6
|
+
const counterH = param("Counter Height", 900, { min: 800, max: 1000, unit: "mm" });
|
|
7
|
+
const sideLen = param("Side Length", 1600, { min: 1000, max: 2500, unit: "mm" });
|
|
8
|
+
const cabinetH = param("Cabinet Height", 720, { min: 600, max: 850, unit: "mm" });
|
|
9
|
+
const topT = param("Countertop Thick", 30, { min: 15, max: 50, unit: "mm" });
|
|
10
|
+
const kickH = 100;
|
|
11
|
+
const kickD = 50;
|
|
12
|
+
|
|
13
|
+
// ─── Base cabinets (L-shape) ───
|
|
14
|
+
|
|
15
|
+
// Main run along back wall
|
|
16
|
+
const mainCab = box(counterW, counterD, cabinetH)
|
|
17
|
+
.translate(0, 0, counterH - cabinetH - topT);
|
|
18
|
+
|
|
19
|
+
// Side run perpendicular
|
|
20
|
+
const sideCab = box(counterD, sideLen - counterD, cabinetH)
|
|
21
|
+
.translate(0, counterD, counterH - cabinetH - topT);
|
|
22
|
+
|
|
23
|
+
// Kick plates (recessed at bottom)
|
|
24
|
+
const mainKick = box(counterW, counterD - kickD, kickH)
|
|
25
|
+
.translate(0, kickD, 0);
|
|
26
|
+
const sideKick = box(counterD - kickD, sideLen - counterD, kickH)
|
|
27
|
+
.translate(kickD, counterD, 0);
|
|
28
|
+
|
|
29
|
+
const cabinets = union(mainCab, sideCab).subtract(mainKick).subtract(sideKick);
|
|
30
|
+
|
|
31
|
+
// Cabinet doors — vertical lines
|
|
32
|
+
const doorGap = 2;
|
|
33
|
+
const doorW = 500;
|
|
34
|
+
const doorCount = Math.floor(counterW / doorW);
|
|
35
|
+
const doorLines = [];
|
|
36
|
+
for (let i = 1; i < doorCount; i++) {
|
|
37
|
+
doorLines.push(
|
|
38
|
+
box(doorGap, 3, cabinetH)
|
|
39
|
+
.translate(i * doorW, counterD - 1, counterH - cabinetH - topT)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
// Side cabinet doors
|
|
43
|
+
const sideDoorCount = Math.floor((sideLen - counterD) / doorW);
|
|
44
|
+
for (let i = 1; i < sideDoorCount; i++) {
|
|
45
|
+
doorLines.push(
|
|
46
|
+
box(3, doorGap, cabinetH)
|
|
47
|
+
.translate(counterD - 1, counterD + i * doorW, counterH - cabinetH - topT)
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Handles — horizontal bars on each door
|
|
52
|
+
const handles = [];
|
|
53
|
+
const handleW = 120;
|
|
54
|
+
const handleH = 10;
|
|
55
|
+
const handleZ = counterH - cabinetH - topT + cabinetH * 0.75;
|
|
56
|
+
for (let i = 0; i < doorCount; i++) {
|
|
57
|
+
handles.push(
|
|
58
|
+
box(handleW, 8, handleH, true)
|
|
59
|
+
.translate(i * doorW + doorW / 2, counterD + 4, handleZ)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
for (let i = 0; i < sideDoorCount; i++) {
|
|
63
|
+
handles.push(
|
|
64
|
+
box(8, handleW, handleH, true)
|
|
65
|
+
.translate(counterD + 4, counterD + i * doorW + doorW / 2, handleZ)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const allHandles = handles.length > 0 ? union(...handles) : box(1, 1, 1);
|
|
70
|
+
const allDoorLines = doorLines.length > 0 ? union(...doorLines) : box(1, 1, 1);
|
|
71
|
+
|
|
72
|
+
// ─── Countertop ───
|
|
73
|
+
|
|
74
|
+
const mainTop = box(counterW + 20, counterD + 20, topT)
|
|
75
|
+
.translate(-10, -10, counterH - topT);
|
|
76
|
+
const sideTop = box(counterD + 20, sideLen - counterD + 10, topT)
|
|
77
|
+
.translate(-10, counterD, counterH - topT);
|
|
78
|
+
const countertop = union(mainTop, sideTop);
|
|
79
|
+
|
|
80
|
+
// ─── Sink (in main counter) ───
|
|
81
|
+
|
|
82
|
+
const sinkW = param("Sink Width", 500, { min: 350, max: 700, unit: "mm" });
|
|
83
|
+
const sinkD = 400;
|
|
84
|
+
const sinkDepth = 180;
|
|
85
|
+
const sinkX = counterW * 0.4;
|
|
86
|
+
|
|
87
|
+
const sinkOuter = box(sinkW, sinkD, sinkDepth + topT, true);
|
|
88
|
+
const sinkInner = box(sinkW - 20, sinkD - 20, sinkDepth + topT + 2, true);
|
|
89
|
+
const sinkBowl = sinkOuter.subtract(sinkInner)
|
|
90
|
+
.translate(sinkX, counterD / 2, counterH - sinkDepth / 2);
|
|
91
|
+
|
|
92
|
+
// Drain
|
|
93
|
+
const drain = cylinder(sinkDepth + topT + 2, 20)
|
|
94
|
+
.translate(sinkX, counterD / 2, counterH - sinkDepth - topT);
|
|
95
|
+
|
|
96
|
+
// Faucet
|
|
97
|
+
const faucetBase = cylinder(40, 15)
|
|
98
|
+
.translate(sinkX, counterD * 0.15, counterH);
|
|
99
|
+
const faucetArm = box(12, 180, 12, true)
|
|
100
|
+
.translate(sinkX, counterD * 0.15 + 90, counterH + 40 + 6);
|
|
101
|
+
const faucetNeck = cylinder(50, 6)
|
|
102
|
+
.translate(sinkX, counterD * 0.15, counterH);
|
|
103
|
+
const faucetSpout = cylinder(30, 4)
|
|
104
|
+
.translate(sinkX, counterD / 2, counterH + 30);
|
|
105
|
+
|
|
106
|
+
const faucet = union(faucetBase, faucetNeck, faucetArm, faucetSpout);
|
|
107
|
+
|
|
108
|
+
// Cut sink hole in countertop
|
|
109
|
+
const sinkHole = box(sinkW - 10, sinkD - 10, topT + 2, true)
|
|
110
|
+
.translate(sinkX, counterD / 2, counterH - topT / 2);
|
|
111
|
+
|
|
112
|
+
// ─── Stovetop (right side of main counter) ───
|
|
113
|
+
|
|
114
|
+
const stoveX = counterW * 0.78;
|
|
115
|
+
const stoveW = 580;
|
|
116
|
+
const stoveD = 510;
|
|
117
|
+
|
|
118
|
+
// Stovetop surface (slightly raised)
|
|
119
|
+
const stoveSurface = box(stoveW, stoveD, 3, true)
|
|
120
|
+
.translate(stoveX, counterD / 2, counterH + 1.5);
|
|
121
|
+
|
|
122
|
+
// Burners — 4 circles
|
|
123
|
+
const burnerPositions = [
|
|
124
|
+
[-120, -100], [120, -100],
|
|
125
|
+
[-120, 100], [120, 100],
|
|
126
|
+
];
|
|
127
|
+
const burnerRings = [];
|
|
128
|
+
for (const [bx, by] of burnerPositions) {
|
|
129
|
+
const r = Math.abs(by) < 50 ? 90 : 70; // back burners larger
|
|
130
|
+
const ring = circle2d(r).subtract(circle2d(r - 8))
|
|
131
|
+
.extrude(2)
|
|
132
|
+
.translate(stoveX + bx, counterD / 2 + by, counterH + 3);
|
|
133
|
+
burnerRings.push(ring);
|
|
134
|
+
}
|
|
135
|
+
const burners = union(...burnerRings);
|
|
136
|
+
|
|
137
|
+
// Knobs (front of stove area)
|
|
138
|
+
const knobs = [];
|
|
139
|
+
for (let i = 0; i < 4; i++) {
|
|
140
|
+
knobs.push(
|
|
141
|
+
cylinder(8, 12)
|
|
142
|
+
.translate(stoveX - 180 + i * 120, counterD + 5, counterH - topT + cabinetH * 0.92)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const stoveKnobs = union(...knobs);
|
|
146
|
+
|
|
147
|
+
// ─── Range hood (above stove) ───
|
|
148
|
+
|
|
149
|
+
const hoodW = stoveW + 60;
|
|
150
|
+
const hoodD = counterD - 50;
|
|
151
|
+
const hoodH = 120;
|
|
152
|
+
const hoodZ = counterH + 650;
|
|
153
|
+
|
|
154
|
+
const hoodBody = box(hoodW, hoodD, hoodH, true)
|
|
155
|
+
.translate(stoveX, counterD / 2, hoodZ);
|
|
156
|
+
// Chimney
|
|
157
|
+
const chimneyW = hoodW * 0.4;
|
|
158
|
+
const chimneyH = 400;
|
|
159
|
+
const chimney = box(chimneyW, hoodD * 0.5, chimneyH, true)
|
|
160
|
+
.translate(stoveX, counterD / 2, hoodZ + hoodH / 2 + chimneyH / 2);
|
|
161
|
+
|
|
162
|
+
const rangeHood = union(hoodBody, chimney);
|
|
163
|
+
|
|
164
|
+
// ─── Upper cabinets (on back wall above counter) ───
|
|
165
|
+
|
|
166
|
+
const upperH = param("Upper Cab Height", 700, { min: 500, max: 900, unit: "mm" });
|
|
167
|
+
const upperD = 350;
|
|
168
|
+
const upperZ = counterH + 500;
|
|
169
|
+
const upperW = counterW * 0.35; // left section only (right has hood)
|
|
170
|
+
|
|
171
|
+
const upperCab = box(upperW, upperD, upperH)
|
|
172
|
+
.translate(0, 0, upperZ);
|
|
173
|
+
|
|
174
|
+
// Upper door lines
|
|
175
|
+
const upperDoorW = upperW / 2;
|
|
176
|
+
const upperDoorLine = box(doorGap, 3, upperH)
|
|
177
|
+
.translate(upperDoorW, upperD - 1, upperZ);
|
|
178
|
+
|
|
179
|
+
// Upper handles
|
|
180
|
+
const upperHandle1 = box(handleW, 8, handleH, true)
|
|
181
|
+
.translate(upperDoorW / 2, upperD + 4, upperZ + upperH * 0.2);
|
|
182
|
+
const upperHandle2 = box(handleW, 8, handleH, true)
|
|
183
|
+
.translate(upperDoorW + upperDoorW / 2, upperD + 4, upperZ + upperH * 0.2);
|
|
184
|
+
|
|
185
|
+
const upperSection = union(upperCab, upperDoorLine);
|
|
186
|
+
const upperHandles = union(upperHandle1, upperHandle2);
|
|
187
|
+
|
|
188
|
+
// ─── Refrigerator (at far left end) ───
|
|
189
|
+
|
|
190
|
+
const fridgeW = param("Fridge Width", 700, { min: 600, max: 900, unit: "mm" });
|
|
191
|
+
const fridgeD = 650;
|
|
192
|
+
const fridgeH = 1800;
|
|
193
|
+
const fridgeX = -fridgeW - 30; // left of counter
|
|
194
|
+
|
|
195
|
+
const fridgeBody = box(fridgeW, fridgeD, fridgeH)
|
|
196
|
+
.translate(fridgeX, 0, 0);
|
|
197
|
+
|
|
198
|
+
// Fridge door split (top 60%, bottom 40%)
|
|
199
|
+
const splitZ = fridgeH * 0.4;
|
|
200
|
+
const fridgeSplit = box(fridgeW + 2, 3, 3)
|
|
201
|
+
.translate(fridgeX - 1, fridgeD - 1, splitZ);
|
|
202
|
+
|
|
203
|
+
// Fridge handles
|
|
204
|
+
const fridgeHandleTop = box(8, 30, 250, true)
|
|
205
|
+
.translate(fridgeX + fridgeW - 30, fridgeD + 15, splitZ + (fridgeH - splitZ) / 2);
|
|
206
|
+
const fridgeHandleBot = box(8, 30, 150, true)
|
|
207
|
+
.translate(fridgeX + fridgeW - 30, fridgeD + 15, splitZ / 2);
|
|
208
|
+
|
|
209
|
+
const fridge = union(fridgeBody, fridgeSplit);
|
|
210
|
+
const fridgeHandles = union(fridgeHandleTop, fridgeHandleBot);
|
|
211
|
+
|
|
212
|
+
// ─── Assemble ───
|
|
213
|
+
|
|
214
|
+
const counterWithSink = countertop.subtract(sinkHole);
|
|
215
|
+
const cabsWithLines = cabinets.subtract(allDoorLines);
|
|
216
|
+
|
|
217
|
+
return [
|
|
218
|
+
{ name: "Cabinets", shape: cabsWithLines, color: "#f5f0e8" },
|
|
219
|
+
{ name: "Handles", shape: allHandles, color: "#888888" },
|
|
220
|
+
{ name: "Countertop", shape: counterWithSink, color: "#404040" },
|
|
221
|
+
{ name: "Sink", shape: sinkBowl.subtract(drain), color: "#c0c0c0" },
|
|
222
|
+
{ name: "Faucet", shape: faucet, color: "#a0a0a0" },
|
|
223
|
+
{ name: "Stove Surface", shape: stoveSurface, color: "#1a1a1a" },
|
|
224
|
+
{ name: "Burners", shape: burners, color: "#cc3300" },
|
|
225
|
+
{ name: "Stove Knobs", shape: stoveKnobs, color: "#333333" },
|
|
226
|
+
{ name: "Range Hood", shape: rangeHood, color: "#c0c0c0" },
|
|
227
|
+
{ name: "Upper Cabinets", shape: union(upperSection), color: "#f5f0e8" },
|
|
228
|
+
{ name: "Upper Handles", shape: upperHandles, color: "#888888" },
|
|
229
|
+
{ name: "Refrigerator", shape: fridge, color: "#d0d0d0" },
|
|
230
|
+
{ name: "Fridge Handles", shape: fridgeHandles, color: "#888888" },
|
|
231
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Lamp Shade profile — trapezoid cross-section for revolution
|
|
2
|
+
// Returns a Sketch (revolved by the parent .forge.js)
|
|
3
|
+
|
|
4
|
+
const topR = param("Top Radius", 15, { min: 8, max: 30, unit: "mm" });
|
|
5
|
+
const bottomR = param("Bottom Radius", 30, { min: 15, max: 50, unit: "mm" });
|
|
6
|
+
const shadeH = param("Shade Height", 35, { min: 20, max: 60, unit: "mm" });
|
|
7
|
+
const wall = param("Wall Thickness", 1.5, { min: 0.5, max: 4, unit: "mm" });
|
|
8
|
+
|
|
9
|
+
// Outer trapezoid profile (right half for revolution around Y axis)
|
|
10
|
+
const outer = polygon([
|
|
11
|
+
[topR, shadeH],
|
|
12
|
+
[bottomR, 0],
|
|
13
|
+
[bottomR, -wall], // bottom lip thickness
|
|
14
|
+
[topR - wall, shadeH], // inner top
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
return outer;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Laptop — parametric with opening hinge
|
|
2
|
+
// Demonstrates: joint(), multi-object, roundedRect, boolean ops
|
|
3
|
+
|
|
4
|
+
const w = param("Width", 320, { min: 250, max: 400, unit: "mm" });
|
|
5
|
+
const d = param("Depth", 220, { min: 170, max: 300, unit: "mm" });
|
|
6
|
+
const baseH = param("Base Height", 15, { min: 8, max: 25, unit: "mm" });
|
|
7
|
+
const lidH = param("Lid Height", 6, { min: 3, max: 12, unit: "mm" });
|
|
8
|
+
const cornerR = param("Corner Radius", 10, { min: 3, max: 25, unit: "mm" });
|
|
9
|
+
const screenInset = param("Screen Bezel", 8, { min: 3, max: 20, unit: "mm" });
|
|
10
|
+
const kbInsetX = param("KB Inset X", 25, { min: 10, max: 50, unit: "mm" });
|
|
11
|
+
const kbInsetY = param("KB Inset Y", 15, { min: 8, max: 40, unit: "mm" });
|
|
12
|
+
|
|
13
|
+
// ─── Base ───
|
|
14
|
+
const baseProfile = roundedRect(w, d, cornerR, true);
|
|
15
|
+
let base = baseProfile.extrude(baseH);
|
|
16
|
+
|
|
17
|
+
// Keyboard recess
|
|
18
|
+
const kbW = w - kbInsetX * 2;
|
|
19
|
+
const kbD = d * 0.55;
|
|
20
|
+
const kbDepth = 1.5;
|
|
21
|
+
const kbRecess = roundedRect(kbW, kbD, 4, true)
|
|
22
|
+
.extrude(kbDepth + 1)
|
|
23
|
+
.translate(0, d * 0.1, baseH - kbDepth);
|
|
24
|
+
base = base.subtract(kbRecess);
|
|
25
|
+
|
|
26
|
+
// Trackpad recess
|
|
27
|
+
const tpW = w * 0.3;
|
|
28
|
+
const tpD = d * 0.22;
|
|
29
|
+
const tpRecess = roundedRect(tpW, tpD, 3, true)
|
|
30
|
+
.extrude(kbDepth + 1)
|
|
31
|
+
.translate(0, -d * 0.28, baseH - kbDepth);
|
|
32
|
+
base = base.subtract(tpRecess);
|
|
33
|
+
|
|
34
|
+
// Key grid (simplified — rows of small boxes subtracted)
|
|
35
|
+
const keyW = 14;
|
|
36
|
+
const keyH = 14;
|
|
37
|
+
const keyGap = 2;
|
|
38
|
+
const keyStep = keyW + keyGap;
|
|
39
|
+
const keyCols = Math.floor(kbW / keyStep);
|
|
40
|
+
const keyRows = Math.floor(kbD / keyStep);
|
|
41
|
+
const keyStartX = -((keyCols - 1) * keyStep) / 2;
|
|
42
|
+
const keyStartY = d * 0.1 - ((keyRows - 1) * keyStep) / 2;
|
|
43
|
+
|
|
44
|
+
const keys = [];
|
|
45
|
+
for (let r = 0; r < keyRows; r++) {
|
|
46
|
+
for (let c = 0; c < keyCols; c++) {
|
|
47
|
+
keys.push(
|
|
48
|
+
roundedRect(keyW, keyH, 1, true)
|
|
49
|
+
.extrude(1)
|
|
50
|
+
.translate(keyStartX + c * keyStep, keyStartY + r * keyStep, baseH - kbDepth - 0.5)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const keyboard = union(...keys);
|
|
55
|
+
|
|
56
|
+
// Ports — left side
|
|
57
|
+
const usbC1 = roundedRect(9, 3.5, 1.5, true).extrude(8)
|
|
58
|
+
.rotate(0, 0, 90)
|
|
59
|
+
.translate(-w / 2, -d * 0.1, baseH * 0.4);
|
|
60
|
+
const usbC2 = roundedRect(9, 3.5, 1.5, true).extrude(8)
|
|
61
|
+
.rotate(0, 0, 90)
|
|
62
|
+
.translate(-w / 2, d * 0.05, baseH * 0.4);
|
|
63
|
+
base = base.subtract(usbC1).subtract(usbC2);
|
|
64
|
+
|
|
65
|
+
// Headphone jack — right side
|
|
66
|
+
const hpJack = cylinder(8, 2)
|
|
67
|
+
.rotate(0, 90, 0)
|
|
68
|
+
.translate(w / 2, -d * 0.1, baseH * 0.5);
|
|
69
|
+
base = base.subtract(hpJack);
|
|
70
|
+
|
|
71
|
+
// Vent slots on bottom
|
|
72
|
+
const ventSlots = [];
|
|
73
|
+
const ventCount = 12;
|
|
74
|
+
const ventW = w * 0.4;
|
|
75
|
+
const ventGap = 3;
|
|
76
|
+
for (let i = 0; i < ventCount; i++) {
|
|
77
|
+
ventSlots.push(
|
|
78
|
+
roundedRect(ventW, 1.5, 0.5, true)
|
|
79
|
+
.extrude(2)
|
|
80
|
+
.translate(0, -d * 0.1 + i * ventGap, -1)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
base = difference(base, ...ventSlots);
|
|
84
|
+
|
|
85
|
+
// ─── Lid ───
|
|
86
|
+
const lidProfile = roundedRect(w, d, cornerR, true);
|
|
87
|
+
let lid = lidProfile.extrude(lidH);
|
|
88
|
+
|
|
89
|
+
// Screen cutout (inset from inner face)
|
|
90
|
+
const screenW = w - screenInset * 2;
|
|
91
|
+
const screenD = d - screenInset * 2;
|
|
92
|
+
const screenCutDepth = 1;
|
|
93
|
+
const screenCut = roundedRect(screenW, screenD, cornerR - screenInset / 2, true)
|
|
94
|
+
.extrude(screenCutDepth + 1)
|
|
95
|
+
.translate(0, 0, -0.5);
|
|
96
|
+
lid = lid.subtract(screenCut);
|
|
97
|
+
|
|
98
|
+
// Camera dot (tiny cylinder on top bezel)
|
|
99
|
+
const cameraDot = cylinder(lidH + 2, 2)
|
|
100
|
+
.translate(0, d / 2 - screenInset / 2, -1);
|
|
101
|
+
lid = lid.subtract(cameraDot);
|
|
102
|
+
|
|
103
|
+
// Screen panel (thin slab filling the cutout)
|
|
104
|
+
const screenPanel = roundedRect(screenW - 1, screenD - 1, cornerR - screenInset / 2 - 1, true)
|
|
105
|
+
.extrude(screenCutDepth)
|
|
106
|
+
.translate(0, 0, 0);
|
|
107
|
+
|
|
108
|
+
// ─── Hinge: rotate lid open ───
|
|
109
|
+
// Lid pivots around the back edge of the base
|
|
110
|
+
const hingeY = d / 2;
|
|
111
|
+
const hingeZ = baseH;
|
|
112
|
+
|
|
113
|
+
// Position lid at hinge point, then use joint to rotate
|
|
114
|
+
const lidAtHinge = lid.translate(0, -hingeY, 0)
|
|
115
|
+
.rotate(180, 0, 0)
|
|
116
|
+
.translate(0, hingeY, hingeZ);
|
|
117
|
+
|
|
118
|
+
const screenAtHinge = screenPanel.translate(0, -hingeY, 0)
|
|
119
|
+
.rotate(180, 0, 0)
|
|
120
|
+
.translate(0, hingeY, hingeZ);
|
|
121
|
+
|
|
122
|
+
const lidAngle = param("Lid Angle", 110, { min: 0, max: 135, unit: "°" });
|
|
123
|
+
|
|
124
|
+
const openLid = lidAtHinge.rotateAround([1, 0, 0], lidAngle, [0, hingeY, hingeZ]);
|
|
125
|
+
const openScreen = screenAtHinge.rotateAround([1, 0, 0], lidAngle, [0, hingeY, hingeZ]);
|
|
126
|
+
|
|
127
|
+
// Hinge cylinders (cosmetic)
|
|
128
|
+
const hingeR = baseH * 0.35;
|
|
129
|
+
const hingeLen = 40;
|
|
130
|
+
const hingeL = cylinder(hingeLen, hingeR)
|
|
131
|
+
.pointAlong([-1, 0, 0])
|
|
132
|
+
.translate(-w * 0.25 + hingeLen / 2, hingeY, hingeZ);
|
|
133
|
+
const hingeR2 = cylinder(hingeLen, hingeR)
|
|
134
|
+
.pointAlong([1, 0, 0])
|
|
135
|
+
.translate(w * 0.25 - hingeLen / 2, hingeY, hingeZ);
|
|
136
|
+
|
|
137
|
+
return [
|
|
138
|
+
{ name: "Base", shape: base, color: "#2a2a2a" },
|
|
139
|
+
{ name: "Keyboard", shape: keyboard, color: "#1a1a1a" },
|
|
140
|
+
{ name: "Lid", shape: openLid, color: "#2a2a2a" },
|
|
141
|
+
{ name: "Screen", shape: openScreen, color: "#0a0a1a" },
|
|
142
|
+
{ name: "Hinge L", shape: hingeL, color: "#444444" },
|
|
143
|
+
{ name: "Hinge R", shape: hingeR2, color: "#444444" },
|
|
144
|
+
];
|