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,232 @@
|
|
|
1
|
+
const squareSize = param("Square Size", 36, { min: 24, max: 54, unit: "mm" });
|
|
2
|
+
const boardThickness = param("Board Thickness", 14, { min: 8, max: 24, unit: "mm" });
|
|
3
|
+
const borderWidth = param("Border Width", 10, { min: 4, max: 18, unit: "mm" });
|
|
4
|
+
const tileHeight = param("Tile Height", 1.8, { min: 0.8, max: 4, step: 0.1, unit: "mm" });
|
|
5
|
+
const pieceScale = param("Piece Scale", 1, { min: 0.8, max: 1.25, step: 0.01 });
|
|
6
|
+
const pieceLift = param("Piece Lift", 0, { min: 0, max: 16, unit: "mm" });
|
|
7
|
+
|
|
8
|
+
const boardSize = squareSize * 8;
|
|
9
|
+
const frameSize = boardSize + borderWidth * 2;
|
|
10
|
+
const boardTop = boardThickness;
|
|
11
|
+
const pieceZ = boardTop + tileHeight + 0.25 + pieceLift;
|
|
12
|
+
|
|
13
|
+
const woodDark = "#4b2f1f";
|
|
14
|
+
const woodMid = "#7b5a3b";
|
|
15
|
+
const woodLight = "#d7be96";
|
|
16
|
+
const squareDark = "#7f5a3e";
|
|
17
|
+
const whitePiece = "#f1eadf";
|
|
18
|
+
const blackPiece = "#2b2a29";
|
|
19
|
+
const accentMetal = "#c7a86a";
|
|
20
|
+
|
|
21
|
+
function ring(height, outerRadius, innerRadius) {
|
|
22
|
+
return cylinder(height, outerRadius).subtract(
|
|
23
|
+
cylinder(height + 2, innerRadius).translate(0, 0, -1)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pieceBase(baseRadius, footHeight) {
|
|
28
|
+
return union(
|
|
29
|
+
cylinder(footHeight, baseRadius),
|
|
30
|
+
cylinder(footHeight * 0.45, baseRadius * 0.8).translate(0, 0, footHeight),
|
|
31
|
+
ring(footHeight * 0.28, baseRadius * 0.78, baseRadius * 0.55).translate(0, 0, footHeight * 1.45)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function pawnShape() {
|
|
36
|
+
const base = pieceBase(squareSize * 0.29, squareSize * 0.11);
|
|
37
|
+
const stem = cylinder(squareSize * 0.36, squareSize * 0.11, squareSize * 0.09)
|
|
38
|
+
.translate(0, 0, squareSize * 0.18);
|
|
39
|
+
const collar = ring(squareSize * 0.05, squareSize * 0.16, squareSize * 0.11)
|
|
40
|
+
.translate(0, 0, squareSize * 0.48);
|
|
41
|
+
const body = sphere(squareSize * 0.18).translate(0, 0, squareSize * 0.58);
|
|
42
|
+
const neck = cylinder(squareSize * 0.08, squareSize * 0.09).translate(0, 0, squareSize * 0.7);
|
|
43
|
+
const head = sphere(squareSize * 0.14).translate(0, 0, squareSize * 0.88);
|
|
44
|
+
return union(base, stem, collar, body, neck, head);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function rookShape() {
|
|
48
|
+
const base = pieceBase(squareSize * 0.31, squareSize * 0.12);
|
|
49
|
+
const lowerBody = cylinder(squareSize * 0.15, squareSize * 0.2, squareSize * 0.17)
|
|
50
|
+
.translate(0, 0, squareSize * 0.19);
|
|
51
|
+
const tower = cylinder(squareSize * 0.45, squareSize * 0.16)
|
|
52
|
+
.translate(0, 0, squareSize * 0.34);
|
|
53
|
+
const crown = cylinder(squareSize * 0.17, squareSize * 0.23)
|
|
54
|
+
.translate(0, 0, squareSize * 0.79);
|
|
55
|
+
const slot = box(squareSize * 0.08, squareSize * 0.18, squareSize * 0.14, true)
|
|
56
|
+
.translate(0, squareSize * 0.18, squareSize * 0.88);
|
|
57
|
+
const battlements = crown.subtract(circularPattern(slot, 4));
|
|
58
|
+
return union(base, lowerBody, tower, battlements);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function bishopShape() {
|
|
62
|
+
const base = pieceBase(squareSize * 0.31, squareSize * 0.12);
|
|
63
|
+
const lowerBody = cylinder(squareSize * 0.16, squareSize * 0.21, squareSize * 0.16)
|
|
64
|
+
.translate(0, 0, squareSize * 0.18);
|
|
65
|
+
const body = cylinder(squareSize * 0.5, squareSize * 0.15, squareSize * 0.08)
|
|
66
|
+
.translate(0, 0, squareSize * 0.34);
|
|
67
|
+
const head = sphere(squareSize * 0.15).translate(0, 0, squareSize * 0.93);
|
|
68
|
+
const crown = ring(squareSize * 0.05, squareSize * 0.16, squareSize * 0.11)
|
|
69
|
+
.translate(0, 0, squareSize * 0.74);
|
|
70
|
+
const piece = union(base, lowerBody, body, crown, head);
|
|
71
|
+
const slit = box(squareSize * 0.06, squareSize * 0.28, squareSize * 0.42, true)
|
|
72
|
+
.rotate(0, 0, 32)
|
|
73
|
+
.translate(0, 0, squareSize * 0.86);
|
|
74
|
+
return piece.subtract(slit);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function queenShape() {
|
|
78
|
+
const base = pieceBase(squareSize * 0.33, squareSize * 0.12);
|
|
79
|
+
const lowerBody = cylinder(squareSize * 0.18, squareSize * 0.22, squareSize * 0.17)
|
|
80
|
+
.translate(0, 0, squareSize * 0.19);
|
|
81
|
+
const body = cylinder(squareSize * 0.56, squareSize * 0.17, squareSize * 0.08)
|
|
82
|
+
.translate(0, 0, squareSize * 0.37);
|
|
83
|
+
const shoulder = sphere(squareSize * 0.11).translate(0, 0, squareSize * 0.92);
|
|
84
|
+
const crownBand = ring(squareSize * 0.045, squareSize * 0.19, squareSize * 0.12)
|
|
85
|
+
.translate(0, 0, squareSize * 1.02);
|
|
86
|
+
const orb = sphere(squareSize * 0.09).translate(0, 0, squareSize * 1.12);
|
|
87
|
+
|
|
88
|
+
const crownRadius = squareSize * 0.17;
|
|
89
|
+
const crownBall = sphere(squareSize * 0.05).translate(crownRadius, 0, squareSize * 1.08);
|
|
90
|
+
const crownBalls = circularPattern(crownBall, 6);
|
|
91
|
+
|
|
92
|
+
return union(base, lowerBody, body, shoulder, crownBand, crownBalls, orb);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function kingShape() {
|
|
96
|
+
const base = pieceBase(squareSize * 0.34, squareSize * 0.12);
|
|
97
|
+
const lowerBody = cylinder(squareSize * 0.18, squareSize * 0.22, squareSize * 0.18)
|
|
98
|
+
.translate(0, 0, squareSize * 0.19);
|
|
99
|
+
const body = cylinder(squareSize * 0.58, squareSize * 0.17, squareSize * 0.09)
|
|
100
|
+
.translate(0, 0, squareSize * 0.37);
|
|
101
|
+
const shoulder = sphere(squareSize * 0.12).translate(0, 0, squareSize * 0.94);
|
|
102
|
+
const neck = cylinder(squareSize * 0.08, squareSize * 0.08).translate(0, 0, squareSize * 1.04);
|
|
103
|
+
const crossStem = box(squareSize * 0.055, squareSize * 0.055, squareSize * 0.22, true)
|
|
104
|
+
.translate(0, 0, squareSize * 1.2);
|
|
105
|
+
const crossArm = box(squareSize * 0.18, squareSize * 0.05, squareSize * 0.05, true)
|
|
106
|
+
.translate(0, 0, squareSize * 1.23);
|
|
107
|
+
return union(base, lowerBody, body, shoulder, neck, crossStem, crossArm);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function knightShape() {
|
|
111
|
+
const base = pieceBase(squareSize * 0.32, squareSize * 0.12);
|
|
112
|
+
const chest = sphere(squareSize * 0.18).translate(0, 0, squareSize * 0.45);
|
|
113
|
+
const neck = hull3d(
|
|
114
|
+
sphere(squareSize * 0.14).translate(0, 0, squareSize * 0.62),
|
|
115
|
+
sphere(squareSize * 0.12).translate(0, squareSize * 0.08, squareSize * 0.82),
|
|
116
|
+
sphere(squareSize * 0.1).translate(0, squareSize * 0.12, squareSize * 1.02)
|
|
117
|
+
);
|
|
118
|
+
const head = hull3d(
|
|
119
|
+
sphere(squareSize * 0.11).translate(0, squareSize * 0.12, squareSize * 1.02),
|
|
120
|
+
sphere(squareSize * 0.08).translate(0, squareSize * 0.22, squareSize * 0.98),
|
|
121
|
+
sphere(squareSize * 0.07).translate(0, squareSize * 0.18, squareSize * 1.14)
|
|
122
|
+
);
|
|
123
|
+
const muzzle = hull3d(
|
|
124
|
+
sphere(squareSize * 0.055).translate(0, squareSize * 0.24, squareSize * 0.98),
|
|
125
|
+
sphere(squareSize * 0.045).translate(0, squareSize * 0.31, squareSize * 0.94)
|
|
126
|
+
);
|
|
127
|
+
const mane = box(squareSize * 0.08, squareSize * 0.12, squareSize * 0.36, true)
|
|
128
|
+
.rotate(-12, 0, 0)
|
|
129
|
+
.translate(0, squareSize * 0.03, squareSize * 0.94);
|
|
130
|
+
const earL = box(squareSize * 0.035, squareSize * 0.08, squareSize * 0.12, true)
|
|
131
|
+
.rotate(-18, 0, 14)
|
|
132
|
+
.translate(-squareSize * 0.04, squareSize * 0.18, squareSize * 1.2);
|
|
133
|
+
const earR = box(squareSize * 0.035, squareSize * 0.08, squareSize * 0.12, true)
|
|
134
|
+
.rotate(-18, 0, -14)
|
|
135
|
+
.translate(squareSize * 0.04, squareSize * 0.18, squareSize * 1.2);
|
|
136
|
+
const chinCut = box(squareSize * 0.5, squareSize * 0.28, squareSize * 0.34, true)
|
|
137
|
+
.rotate(58, 0, 0)
|
|
138
|
+
.translate(0, -squareSize * 0.04, squareSize * 0.93);
|
|
139
|
+
return union(base, chest, neck, head, muzzle, mane, earL, earR).subtract(chinCut);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function makePiece(kind) {
|
|
143
|
+
if (kind === "pawn") return pawnShape();
|
|
144
|
+
if (kind === "rook") return rookShape();
|
|
145
|
+
if (kind === "knight") return knightShape();
|
|
146
|
+
if (kind === "bishop") return bishopShape();
|
|
147
|
+
if (kind === "queen") return queenShape();
|
|
148
|
+
return kingShape();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function squareCenter(file, rank) {
|
|
152
|
+
return [
|
|
153
|
+
(file - 3.5) * squareSize,
|
|
154
|
+
(rank - 3.5) * squareSize,
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function placePiece(kind, file, rank, color, facingDeg, name) {
|
|
159
|
+
const [x, y] = squareCenter(file, rank);
|
|
160
|
+
const shape = makePiece(kind)
|
|
161
|
+
.scale(pieceScale)
|
|
162
|
+
.rotate(0, 0, facingDeg)
|
|
163
|
+
.translate(x, y, pieceZ)
|
|
164
|
+
.color(color);
|
|
165
|
+
return { name, shape };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const frame = difference(
|
|
169
|
+
box(frameSize, frameSize, boardThickness, true).translate(0, 0, boardThickness * 0.5),
|
|
170
|
+
box(boardSize + 1, boardSize + 1, boardThickness * 0.32, true)
|
|
171
|
+
.translate(0, 0, boardThickness - boardThickness * 0.16)
|
|
172
|
+
)
|
|
173
|
+
.color(woodDark);
|
|
174
|
+
|
|
175
|
+
const lightSquares = [];
|
|
176
|
+
const darkSquares = [];
|
|
177
|
+
for (let file = 0; file < 8; file += 1) {
|
|
178
|
+
for (let rank = 0; rank < 8; rank += 1) {
|
|
179
|
+
const [x, y] = squareCenter(file, rank);
|
|
180
|
+
const tile = box(squareSize, squareSize, tileHeight, true)
|
|
181
|
+
.translate(x, y, boardTop + tileHeight * 0.5);
|
|
182
|
+
if ((file + rank) % 2 === 0) lightSquares.push(tile);
|
|
183
|
+
else darkSquares.push(tile);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const lightSquareField = union(...lightSquares).color(woodLight);
|
|
188
|
+
const darkSquareField = union(...darkSquares).color(squareDark);
|
|
189
|
+
|
|
190
|
+
const trim = difference(
|
|
191
|
+
box(frameSize - borderWidth * 0.45, frameSize - borderWidth * 0.45, tileHeight * 0.9, true)
|
|
192
|
+
.translate(0, 0, boardTop + tileHeight * 0.45),
|
|
193
|
+
box(boardSize + borderWidth * 0.2, boardSize + borderWidth * 0.2, tileHeight * 2, true)
|
|
194
|
+
.translate(0, 0, boardTop + tileHeight * 0.45)
|
|
195
|
+
).color(woodMid);
|
|
196
|
+
|
|
197
|
+
const cornerCap = sphere(borderWidth * 0.26)
|
|
198
|
+
.translate(boardSize * 0.5 + borderWidth * 0.72, boardSize * 0.5 + borderWidth * 0.72, boardThickness + borderWidth * 0.38);
|
|
199
|
+
const cornerCaps = union(
|
|
200
|
+
cornerCap,
|
|
201
|
+
cornerCap.mirror([1, 0, 0]),
|
|
202
|
+
cornerCap.mirror([0, 1, 0]),
|
|
203
|
+
cornerCap.mirror([1, 0, 0]).mirror([0, 1, 0])
|
|
204
|
+
).color(accentMetal);
|
|
205
|
+
|
|
206
|
+
const whiteBackRank = ["rook", "knight", "bishop", "queen", "king", "bishop", "knight", "rook"];
|
|
207
|
+
const blackBackRank = ["rook", "knight", "bishop", "queen", "king", "bishop", "knight", "rook"];
|
|
208
|
+
|
|
209
|
+
const whitePieces = [];
|
|
210
|
+
const blackPieces = [];
|
|
211
|
+
|
|
212
|
+
for (let file = 0; file < 8; file += 1) {
|
|
213
|
+
whitePieces.push(placePiece("pawn", file, 1, whitePiece, 0, `White Pawn ${file + 1}`));
|
|
214
|
+
blackPieces.push(placePiece("pawn", file, 6, blackPiece, 180, `Black Pawn ${file + 1}`));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (let file = 0; file < 8; file += 1) {
|
|
218
|
+
const whiteKind = whiteBackRank[file];
|
|
219
|
+
const blackKind = blackBackRank[file];
|
|
220
|
+
whitePieces.push(placePiece(whiteKind, file, 0, whitePiece, 0, `White ${whiteKind[0].toUpperCase()}${whiteKind.slice(1)} ${file + 1}`));
|
|
221
|
+
blackPieces.push(placePiece(blackKind, file, 7, blackPiece, 180, `Black ${blackKind[0].toUpperCase()}${blackKind.slice(1)} ${file + 1}`));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return [
|
|
225
|
+
{ name: "Frame", shape: frame },
|
|
226
|
+
{ name: "Light Squares", shape: lightSquareField },
|
|
227
|
+
{ name: "Dark Squares", shape: darkSquareField },
|
|
228
|
+
{ name: "Trim Ring", shape: trim },
|
|
229
|
+
{ name: "Corner Caps", shape: cornerCaps },
|
|
230
|
+
{ name: "White Pieces", group: whitePieces },
|
|
231
|
+
{ name: "Black Pieces", group: blackPieces },
|
|
232
|
+
];
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Classical Grand Piano — detailed example assembly.
|
|
3
|
+
|
|
4
|
+
Major parts:
|
|
5
|
+
- Curved grand body with hollow rim
|
|
6
|
+
- Soundboard
|
|
7
|
+
- Opening lid
|
|
8
|
+
- Keybed with white + black keys
|
|
9
|
+
- Music stand
|
|
10
|
+
- Three legs with casters
|
|
11
|
+
- Pedal rail and pedals
|
|
12
|
+
- Bench
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const bodyWidth = param("Body Width", 180, { min: 140, max: 240, unit: "mm" });
|
|
16
|
+
const bodyLength = param("Body Length", 280, { min: 220, max: 360, unit: "mm" });
|
|
17
|
+
const rimHeight = param("Rim Height", 28, { min: 20, max: 40, unit: "mm" });
|
|
18
|
+
const lidThickness = param("Lid Thickness", 6, { min: 4, max: 10, unit: "mm" });
|
|
19
|
+
const lidOpen = param("Lid Angle", 25, { min: 0, max: 50, unit: "°" });
|
|
20
|
+
const legHeight = param("Leg Height", 60, { min: 45, max: 90, unit: "mm" });
|
|
21
|
+
const legRadius = param("Leg Radius", 8, { min: 5, max: 12, unit: "mm" });
|
|
22
|
+
const keyboardDepth = param("Keyboard Depth", 60, { min: 45, max: 80, unit: "mm" });
|
|
23
|
+
const keyHeight = param("Key Height", 5, { min: 3, max: 8, unit: "mm" });
|
|
24
|
+
|
|
25
|
+
const rimThickness = Math.min(12, bodyWidth * 0.07);
|
|
26
|
+
const keyboardWidth = bodyWidth * 0.9;
|
|
27
|
+
const keybedHeight = 8;
|
|
28
|
+
|
|
29
|
+
const whiteKeyCount = 52;
|
|
30
|
+
const whiteKeyWidth = keyboardWidth / whiteKeyCount;
|
|
31
|
+
const whiteKeyDepth = keyboardDepth * 0.9;
|
|
32
|
+
|
|
33
|
+
const blackKeyWidth = whiteKeyWidth * 0.6;
|
|
34
|
+
const blackKeyDepth = whiteKeyDepth * 0.6;
|
|
35
|
+
const blackKeyHeight = keyHeight * 1.4;
|
|
36
|
+
|
|
37
|
+
// --- Body outline (grand-style curve) ---
|
|
38
|
+
const rectW = bodyWidth * 0.72;
|
|
39
|
+
const rectL = bodyLength;
|
|
40
|
+
const rectX = -bodyWidth / 2 + rectW / 2;
|
|
41
|
+
|
|
42
|
+
const mainRect = rect(rectW, rectL, true).translate(rectX, 0);
|
|
43
|
+
const tailRadius = bodyWidth * 0.42;
|
|
44
|
+
const frontRadius = bodyWidth * 0.28;
|
|
45
|
+
|
|
46
|
+
const tailCircle = circle2d(tailRadius).translate(bodyWidth * 0.15, bodyLength * 0.2);
|
|
47
|
+
const frontCircle = circle2d(frontRadius).translate(bodyWidth * 0.18, -bodyLength * 0.35);
|
|
48
|
+
|
|
49
|
+
const outerSketch = union2d(mainRect, tailCircle, frontCircle);
|
|
50
|
+
const innerSketch = outerSketch.offset(-rimThickness);
|
|
51
|
+
|
|
52
|
+
// --- Rim body (hollowed) ---
|
|
53
|
+
const rimOuter = outerSketch.extrude(rimHeight);
|
|
54
|
+
const rimInner = innerSketch.extrude(rimHeight - 4).translate(0, 0, 2);
|
|
55
|
+
|
|
56
|
+
const body = rimOuter
|
|
57
|
+
.subtract(rimInner)
|
|
58
|
+
.translate(0, 0, legHeight)
|
|
59
|
+
.color('#111111');
|
|
60
|
+
|
|
61
|
+
const bodyBB = body.boundingBox();
|
|
62
|
+
const [leftX, frontY] = bodyBB.min;
|
|
63
|
+
const [rightX, backY, topZ] = bodyBB.max;
|
|
64
|
+
const bodyWidthActual = rightX - leftX;
|
|
65
|
+
const bodyLengthActual = backY - frontY;
|
|
66
|
+
|
|
67
|
+
// --- Soundboard ---
|
|
68
|
+
const soundboard = innerSketch
|
|
69
|
+
.extrude(2)
|
|
70
|
+
.attachTo(body, 'top', 'top', [0, 0, -2])
|
|
71
|
+
.color('#c8a96a');
|
|
72
|
+
|
|
73
|
+
// --- Lid ---
|
|
74
|
+
const lidSketch = outerSketch.offset(-rimThickness * 0.2);
|
|
75
|
+
const lidRaw = lidSketch.extrude(lidThickness);
|
|
76
|
+
|
|
77
|
+
const lid = lidRaw
|
|
78
|
+
.attachTo(body, 'top-left', 'bottom-left')
|
|
79
|
+
.translate(-leftX, 0, -topZ)
|
|
80
|
+
.rotate(0, -lidOpen, 0)
|
|
81
|
+
.translate(leftX, 0, topZ)
|
|
82
|
+
.color('#111111');
|
|
83
|
+
|
|
84
|
+
// --- Keybed ---
|
|
85
|
+
const keybed = box(keyboardWidth, keyboardDepth, keybedHeight, true)
|
|
86
|
+
.attachTo(body, 'top-front', 'top-front', [0, 0, -2])
|
|
87
|
+
.color('#222222');
|
|
88
|
+
|
|
89
|
+
// --- White keys ---
|
|
90
|
+
const whiteKey = box(whiteKeyWidth * 0.98, whiteKeyDepth, keyHeight, true).color('#f5f5f5');
|
|
91
|
+
const keyboardLeft = -keyboardWidth / 2 + whiteKeyWidth / 2;
|
|
92
|
+
|
|
93
|
+
const whiteKeyRow = linearPattern(whiteKey, whiteKeyCount, whiteKeyWidth, 0, 0)
|
|
94
|
+
.translate(keyboardLeft, 0, 0);
|
|
95
|
+
|
|
96
|
+
const whiteKeys = whiteKeyRow
|
|
97
|
+
.attachTo(keybed, 'top-front', 'bottom-front', [0, -2, 0.1]);
|
|
98
|
+
|
|
99
|
+
// --- Black keys ---
|
|
100
|
+
const blackKey = box(blackKeyWidth, blackKeyDepth, blackKeyHeight, true).color('#111111');
|
|
101
|
+
const blackPattern = [0, 1, 3, 4, 5];
|
|
102
|
+
const blackKeysList = [];
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < whiteKeyCount - 1; i++) {
|
|
105
|
+
if (blackPattern.includes(i % 7)) {
|
|
106
|
+
const x = -keyboardWidth / 2 + (i + 1) * whiteKeyWidth - whiteKeyWidth / 2;
|
|
107
|
+
blackKeysList.push(blackKey.translate(x, 0, 0));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const blackKeysRow = union(...blackKeysList);
|
|
112
|
+
const blackKeys = blackKeysRow
|
|
113
|
+
.attachTo(keybed, 'top-front', 'bottom-front', [0, whiteKeyDepth * 0.2, 0.1]);
|
|
114
|
+
|
|
115
|
+
// --- Key slip (front rail) ---
|
|
116
|
+
const keySlip = box(keyboardWidth, 12, 6, true)
|
|
117
|
+
.attachTo(keybed, 'bottom-front', 'top-front', [0, -4, -2])
|
|
118
|
+
.color('#1b1b1b');
|
|
119
|
+
|
|
120
|
+
// --- Music stand ---
|
|
121
|
+
const standWidth = keyboardWidth * 0.8;
|
|
122
|
+
const standHeight = 50;
|
|
123
|
+
const standThickness = 4;
|
|
124
|
+
|
|
125
|
+
const standBase = box(standWidth, standThickness, standHeight, true)
|
|
126
|
+
.translate(0, 0, standHeight / 2)
|
|
127
|
+
.rotate(-15, 0, 0);
|
|
128
|
+
|
|
129
|
+
const musicStand = standBase
|
|
130
|
+
.attachTo(body, 'top-front', 'bottom-front', [0, keyboardDepth * 0.6, 0])
|
|
131
|
+
.color('#111111');
|
|
132
|
+
|
|
133
|
+
// --- Legs ---
|
|
134
|
+
const leg = cylinder(legHeight, legRadius).color('#222222');
|
|
135
|
+
const legInsetX = bodyWidthActual * 0.08;
|
|
136
|
+
const legInsetY = bodyLengthActual * 0.08;
|
|
137
|
+
|
|
138
|
+
const frontLeftLeg = leg.attachTo(body, 'bottom-front-left', 'top', [legInsetX, legInsetY, 0]);
|
|
139
|
+
const frontRightLeg = leg.attachTo(body, 'bottom-front-right', 'top', [-legInsetX, legInsetY, 0]);
|
|
140
|
+
const backLeg = leg.attachTo(body, 'bottom-back-right', 'top', [-legInsetX, -legInsetY, 0]);
|
|
141
|
+
|
|
142
|
+
// --- Casters ---
|
|
143
|
+
const caster = sphere(legRadius * 0.6).color('#555555');
|
|
144
|
+
const casterFL = caster.attachTo(frontLeftLeg, 'bottom', 'top');
|
|
145
|
+
const casterFR = caster.attachTo(frontRightLeg, 'bottom', 'top');
|
|
146
|
+
const casterBack = caster.attachTo(backLeg, 'bottom', 'top');
|
|
147
|
+
|
|
148
|
+
// --- Pedal rail + pedals ---
|
|
149
|
+
const pedalRail = box(50, 14, 6, true)
|
|
150
|
+
.translate(0, frontY + keyboardDepth * 0.5, legHeight * 0.35)
|
|
151
|
+
.color('#bfa14a');
|
|
152
|
+
|
|
153
|
+
const pedalBlade = box(4, 18, 2, true).rotate(10, 0, 0).color('#d8b45a');
|
|
154
|
+
const pedalSpacing = 12;
|
|
155
|
+
|
|
156
|
+
const pedalL = pedalBlade.attachTo(pedalRail, 'top', 'bottom', [-pedalSpacing, 0, 0]);
|
|
157
|
+
const pedalM = pedalBlade.attachTo(pedalRail, 'top', 'bottom', [0, 0, 0]);
|
|
158
|
+
const pedalR = pedalBlade.attachTo(pedalRail, 'top', 'bottom', [pedalSpacing, 0, 0]);
|
|
159
|
+
|
|
160
|
+
// --- Bench ---
|
|
161
|
+
const benchWidth = keyboardWidth * 0.6;
|
|
162
|
+
const benchDepth = keyboardDepth * 0.6;
|
|
163
|
+
const benchSeatThickness = 6;
|
|
164
|
+
const benchHeight = legHeight * 0.7;
|
|
165
|
+
|
|
166
|
+
const benchSeat = box(benchWidth, benchDepth, benchSeatThickness, true)
|
|
167
|
+
.translate(0, frontY - keyboardDepth * 0.9, benchHeight + benchSeatThickness / 2)
|
|
168
|
+
.color('#3b2a1a');
|
|
169
|
+
|
|
170
|
+
const benchLeg = cylinder(benchHeight, 4).color('#2b1f14');
|
|
171
|
+
const benchInsetX = benchWidth / 2 - 8;
|
|
172
|
+
const benchInsetY = benchDepth / 2 - 6;
|
|
173
|
+
|
|
174
|
+
const benchLegFL = benchLeg.attachTo(benchSeat, 'bottom-front-left', 'top', [benchInsetX, benchInsetY, 0]);
|
|
175
|
+
const benchLegFR = benchLeg.attachTo(benchSeat, 'bottom-front-right', 'top', [-benchInsetX, benchInsetY, 0]);
|
|
176
|
+
const benchLegBL = benchLeg.attachTo(benchSeat, 'bottom-back-left', 'top', [benchInsetX, -benchInsetY, 0]);
|
|
177
|
+
const benchLegBR = benchLeg.attachTo(benchSeat, 'bottom-back-right', 'top', [-benchInsetX, -benchInsetY, 0]);
|
|
178
|
+
|
|
179
|
+
return [
|
|
180
|
+
{ name: "Piano Body", shape: body },
|
|
181
|
+
{ name: "Soundboard", shape: soundboard },
|
|
182
|
+
{ name: "Lid", shape: lid },
|
|
183
|
+
{ name: "Keybed", shape: keybed },
|
|
184
|
+
{ name: "White Keys", shape: whiteKeys },
|
|
185
|
+
{ name: "Black Keys", shape: blackKeys },
|
|
186
|
+
{ name: "Key Slip", shape: keySlip },
|
|
187
|
+
{ name: "Music Stand", shape: musicStand },
|
|
188
|
+
{ name: "Front Left Leg", shape: frontLeftLeg },
|
|
189
|
+
{ name: "Front Right Leg", shape: frontRightLeg },
|
|
190
|
+
{ name: "Back Leg", shape: backLeg },
|
|
191
|
+
{ name: "Caster Front Left", shape: casterFL },
|
|
192
|
+
{ name: "Caster Front Right", shape: casterFR },
|
|
193
|
+
{ name: "Caster Back", shape: casterBack },
|
|
194
|
+
{ name: "Pedal Rail", shape: pedalRail },
|
|
195
|
+
{ name: "Pedal Left", shape: pedalL },
|
|
196
|
+
{ name: "Pedal Middle", shape: pedalM },
|
|
197
|
+
{ name: "Pedal Right", shape: pedalR },
|
|
198
|
+
{ name: "Bench Seat", shape: benchSeat },
|
|
199
|
+
{ name: "Bench Leg FL", shape: benchLegFL },
|
|
200
|
+
{ name: "Bench Leg FR", shape: benchLegFR },
|
|
201
|
+
{ name: "Bench Leg BL", shape: benchLegBL },
|
|
202
|
+
{ name: "Bench Leg BR", shape: benchLegBR },
|
|
203
|
+
];
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Parametric Wall Clock — Apple Style
|
|
2
|
+
// Clean, minimalist analog clock with full color customization
|
|
3
|
+
// Returns named objects to preserve colors and enable individual visibility control
|
|
4
|
+
|
|
5
|
+
// === Dimensions ===
|
|
6
|
+
const diameter = param("Diameter", 220, { min: 80, max: 400, unit: "mm" });
|
|
7
|
+
const depth = param("Depth", 18, { min: 8, max: 50, unit: "mm" });
|
|
8
|
+
const rimWidth = param("Rim Width", 8, { min: 3, max: 30, unit: "mm" });
|
|
9
|
+
|
|
10
|
+
// === Time Settings — Default 10:10:30 (Apple display time) ===
|
|
11
|
+
const hour = param("Hour", 10, { min: 1, max: 12, integer: true });
|
|
12
|
+
const minute = param("Minute", 10, { min: 0, max: 59, integer: true });
|
|
13
|
+
const second = param("Second", 30, { min: 0, max: 59, integer: true });
|
|
14
|
+
const showSecondHand = param("Show Seconds", 1, { min: 0, max: 1, integer: true });
|
|
15
|
+
|
|
16
|
+
// === Color Customization ===
|
|
17
|
+
const rimColor = param("Rim Color", 0, { min: 0, max: 3, integer: true }); // 0=silver, 1=gold, 2=black, 3=rose
|
|
18
|
+
const faceColor = param("Face Color", 0, { min: 0, max: 2, integer: true }); // 0=white, 1=black, 2=cream
|
|
19
|
+
const markerColor = param("Marker Color", 0, { min: 0, max: 2, integer: true }); // 0=black, 1=white, 2=gold
|
|
20
|
+
const hourHandColor = param("Hour Hand Color", 0, { min: 0, max: 3, integer: true }); // 0=black, 1=white, 2=gold, 3=red
|
|
21
|
+
const minuteHandColor = param("Minute Hand Color", 1, { min: 0, max: 3, integer: true }); // 0=black, 1=white, 2=gold, 3=red
|
|
22
|
+
const secondHandColor = param("Second Hand Color", 3, { min: 0, max: 3, integer: true }); // 0=black, 1=white, 2=gold, 3=red
|
|
23
|
+
|
|
24
|
+
// Color palette
|
|
25
|
+
const colors = {
|
|
26
|
+
silver: "#C0C0C0",
|
|
27
|
+
gold: "#D4AF37",
|
|
28
|
+
black: "#1A1A1A",
|
|
29
|
+
rose: "#B76E79",
|
|
30
|
+
white: "#FFFFFF",
|
|
31
|
+
cream: "#F5F5DC",
|
|
32
|
+
red: "#FF3B30",
|
|
33
|
+
darkGray: "#2F2F2F"
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Resolve colors
|
|
37
|
+
const rimHex = [colors.silver, colors.gold, colors.black, colors.rose][rimColor];
|
|
38
|
+
const faceHex = [colors.white, colors.black, colors.cream][faceColor];
|
|
39
|
+
const markerHex = [colors.black, colors.white, colors.gold][markerColor];
|
|
40
|
+
const hourHandHex = [colors.black, colors.white, colors.gold, colors.red][hourHandColor];
|
|
41
|
+
const minuteHandHex = [colors.black, colors.white, colors.gold, colors.red][minuteHandColor];
|
|
42
|
+
const secondHandHex = [colors.black, colors.white, colors.gold, colors.red][secondHandColor];
|
|
43
|
+
|
|
44
|
+
// === Calculated values ===
|
|
45
|
+
const radius = diameter / 2;
|
|
46
|
+
const innerRadius = radius - rimWidth;
|
|
47
|
+
const handBaseZ = depth / 2 - 2;
|
|
48
|
+
|
|
49
|
+
// === Clock Rim ===
|
|
50
|
+
const outer = cylinder(depth, radius, undefined, undefined, true);
|
|
51
|
+
const inner = cylinder(depth + 2, innerRadius, undefined, undefined, true);
|
|
52
|
+
const rimBody = outer.subtract(inner);
|
|
53
|
+
|
|
54
|
+
// Add subtle bevel to rim edge
|
|
55
|
+
const bevel = cylinder(depth - 5, radius - 1, radius, undefined, true);
|
|
56
|
+
const rim = union(rimBody, bevel).color(rimHex);
|
|
57
|
+
|
|
58
|
+
// === Clock Face ===
|
|
59
|
+
const face = cylinder(2, innerRadius - 1, undefined, undefined, true)
|
|
60
|
+
.translate(0, 0, depth / 2 - 3)
|
|
61
|
+
.color(faceHex);
|
|
62
|
+
|
|
63
|
+
// === Hour Markers ===
|
|
64
|
+
const markerLength = param("Marker Length", 10, { min: 5, max: 25, unit: "mm" });
|
|
65
|
+
const markerWidth = param("Marker Width", 2.5, { min: 1, max: 6, unit: "mm" });
|
|
66
|
+
|
|
67
|
+
const markerShapes = [];
|
|
68
|
+
for (let i = 0; i < 12; i++) {
|
|
69
|
+
const angle = i * 30;
|
|
70
|
+
const isCardinal = i % 3 === 0;
|
|
71
|
+
const length = isCardinal ? markerLength * 1.5 : markerLength;
|
|
72
|
+
const width = isCardinal ? markerWidth * 1.4 : markerWidth;
|
|
73
|
+
|
|
74
|
+
const r = innerRadius - length / 2 - 6;
|
|
75
|
+
const rad = (angle - 90) * Math.PI / 180;
|
|
76
|
+
const x = Math.cos(rad) * r;
|
|
77
|
+
const y = Math.sin(rad) * r;
|
|
78
|
+
|
|
79
|
+
const tick = roundedRect(width, length, width / 2, true)
|
|
80
|
+
.rotate(angle)
|
|
81
|
+
.extrude(1)
|
|
82
|
+
.translate(x, y, depth / 2 - 2);
|
|
83
|
+
|
|
84
|
+
markerShapes.push(tick);
|
|
85
|
+
}
|
|
86
|
+
const markers = union(...markerShapes).color(markerHex);
|
|
87
|
+
|
|
88
|
+
// === Clock Hands ===
|
|
89
|
+
const hourHandLength = param("Hour Hand Length", 55, { min: 25, max: 110, unit: "mm" });
|
|
90
|
+
const hourHandWidth = param("Hour Hand Width", 10, { min: 5, max: 20, unit: "mm" });
|
|
91
|
+
const minuteHandLength = param("Minute Hand Length", 80, { min: 40, max: 160, unit: "mm" });
|
|
92
|
+
const minuteHandWidth = param("Minute Hand Width", 7, { min: 3, max: 14, unit: "mm" });
|
|
93
|
+
const secondHandLength = param("Second Hand Length", 85, { min: 50, max: 170, unit: "mm" });
|
|
94
|
+
const secondHandWidth = param("Second Hand Width", 2, { min: 1, max: 5, unit: "mm" });
|
|
95
|
+
|
|
96
|
+
// Calculate hand angles (0 degrees = 12 o'clock, clockwise)
|
|
97
|
+
const hourAngle = (hour % 12) * 30 + (minute / 60) * 30;
|
|
98
|
+
const minuteAngle = minute * 6 + (second / 60) * 6;
|
|
99
|
+
const secondAngle = second * 6;
|
|
100
|
+
|
|
101
|
+
// Create Apple-style hand with rounded ends
|
|
102
|
+
function createHand(length, width, angle, zOffset, thickness) {
|
|
103
|
+
return roundedRect(width, length, width / 2, true)
|
|
104
|
+
.translate(0, length / 2)
|
|
105
|
+
.extrude(thickness)
|
|
106
|
+
.rotate(0, 0, angle - 90)
|
|
107
|
+
.translate(0, 0, zOffset);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Hour hand
|
|
111
|
+
const hourHand = createHand(hourHandLength, hourHandWidth, hourAngle, handBaseZ + 2, 4)
|
|
112
|
+
.color(hourHandHex);
|
|
113
|
+
|
|
114
|
+
// Minute hand
|
|
115
|
+
const minuteHand = createHand(minuteHandLength, minuteHandWidth, minuteAngle, handBaseZ + 6, 3)
|
|
116
|
+
.color(minuteHandHex);
|
|
117
|
+
|
|
118
|
+
// Second hand with counter-balance
|
|
119
|
+
let secondHand = null;
|
|
120
|
+
if (showSecondHand > 0) {
|
|
121
|
+
const counterLength = 25;
|
|
122
|
+
const counterWidth = 4;
|
|
123
|
+
|
|
124
|
+
const sweep = createHand(secondHandLength, secondHandWidth, secondAngle, handBaseZ + 10, 2);
|
|
125
|
+
const counter = roundedRect(counterWidth, counterLength, counterWidth / 2, true)
|
|
126
|
+
.translate(0, -counterLength / 2)
|
|
127
|
+
.extrude(2)
|
|
128
|
+
.rotate(0, 0, secondAngle - 90)
|
|
129
|
+
.translate(0, 0, handBaseZ + 10);
|
|
130
|
+
|
|
131
|
+
secondHand = union(sweep, counter).color(secondHandHex);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// === Center Cap ===
|
|
135
|
+
const centerRadius = 6;
|
|
136
|
+
const centerRing = cylinder(3, centerRadius + 2, undefined, undefined, true)
|
|
137
|
+
.translate(0, 0, handBaseZ + 13)
|
|
138
|
+
.color(rimHex);
|
|
139
|
+
|
|
140
|
+
const centerDot = cylinder(4, centerRadius, undefined, undefined, true)
|
|
141
|
+
.translate(0, 0, handBaseZ + 13)
|
|
142
|
+
.color(showSecondHand > 0 ? secondHandHex : "#444444");
|
|
143
|
+
|
|
144
|
+
// === Wall Mount ===
|
|
145
|
+
const mountWidth = 25;
|
|
146
|
+
const mountHeight = 12;
|
|
147
|
+
const mountDepth = 6;
|
|
148
|
+
const mount = roundedRect(mountWidth, mountHeight, 3, true)
|
|
149
|
+
.extrude(mountDepth)
|
|
150
|
+
.translate(0, -radius + 8, -depth / 2 - mountDepth / 2)
|
|
151
|
+
.color(rimHex);
|
|
152
|
+
|
|
153
|
+
// === Return Named Objects (colors preserved!) ===
|
|
154
|
+
const objects = [
|
|
155
|
+
{ name: "Rim", shape: rim },
|
|
156
|
+
{ name: "Face", shape: face },
|
|
157
|
+
{ name: "Markers", shape: markers },
|
|
158
|
+
{ name: "Hour Hand", shape: hourHand },
|
|
159
|
+
{ name: "Minute Hand", shape: minuteHand },
|
|
160
|
+
{ name: "Center Ring", shape: centerRing },
|
|
161
|
+
{ name: "Center Dot", shape: centerDot },
|
|
162
|
+
{ name: "Wall Mount", shape: mount }
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
if (secondHand) {
|
|
166
|
+
objects.push({ name: "Second Hand", shape: secondHand });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return objects;
|