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,126 @@
|
|
|
1
|
+
// Home AC Unit — Indoor and Outdoor on opposite sides of a wall
|
|
2
|
+
// Strategy: build each part at origin, then attachTo() its parent.
|
|
3
|
+
|
|
4
|
+
// === Parameters ===
|
|
5
|
+
const wallW = param("Wall Width", 400, { min: 200, max: 600, unit: "mm" });
|
|
6
|
+
const wallH = param("Wall Height", 300, { min: 200, max: 500, unit: "mm" });
|
|
7
|
+
const wallT = param("Wall Thickness", 20, { min: 10, max: 50, unit: "mm" });
|
|
8
|
+
|
|
9
|
+
const inW = param("Indoor Width", 200, { min: 100, max: 400, unit: "mm" });
|
|
10
|
+
const inH = param("Indoor Height", 80, { min: 40, max: 150, unit: "mm" });
|
|
11
|
+
const inD = param("Indoor Depth", 25, { min: 15, max: 60, unit: "mm" });
|
|
12
|
+
|
|
13
|
+
const outW = param("Outdoor Width", 180, { min: 100, max: 400, unit: "mm" });
|
|
14
|
+
const outH = param("Outdoor Height", 150, { min: 80, max: 300, unit: "mm" });
|
|
15
|
+
const outD = param("Outdoor Depth", 50, { min: 30, max: 100, unit: "mm" });
|
|
16
|
+
|
|
17
|
+
// === Colors ===
|
|
18
|
+
const C_WALL = '#D4C4A8';
|
|
19
|
+
const C_INDOOR = '#FFFFFF';
|
|
20
|
+
const C_ACCENT = '#E8E8E8';
|
|
21
|
+
const C_OUTDOOR = '#F5F5F5';
|
|
22
|
+
const C_GRILL = '#404040';
|
|
23
|
+
const C_PIPE = '#A0A0A0';
|
|
24
|
+
|
|
25
|
+
// === Wall (centered at origin) ===
|
|
26
|
+
const wall = box(wallW, wallT, wallH, true).color(C_WALL);
|
|
27
|
+
|
|
28
|
+
// === Indoor Unit ===
|
|
29
|
+
// Sits on the front face of the wall (-Y side), near the ceiling.
|
|
30
|
+
// Build at origin, then attach: indoor's back face → wall's front face, then offset up and out.
|
|
31
|
+
const indoorBody = box(inW, inD, inH, true).color(C_INDOOR)
|
|
32
|
+
.attachTo(wall, 'top-front', 'top-back', [0, -5, -20]);
|
|
33
|
+
// back of indoor touches front of wall, shifted 5mm away from wall, 20mm below ceiling
|
|
34
|
+
|
|
35
|
+
// Vent slats — protrude from the FRONT face of the indoor unit
|
|
36
|
+
const slatCount = 4;
|
|
37
|
+
const slats = [];
|
|
38
|
+
for (let i = 0; i < slatCount; i++) {
|
|
39
|
+
const slat = box(inW - 30, 8, 3, true).color(C_ACCENT)
|
|
40
|
+
.attachTo(indoorBody, 'bottom-front', 'bottom-back', [0, -1, 8 + i * 12]);
|
|
41
|
+
// back of slat on front of indoor, 1mm gap, stacked upward from bottom
|
|
42
|
+
slats.push(slat);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Control panel — sticks out from front-right area
|
|
46
|
+
const panel = box(35, 4, 18, true).color('#1A1A1A')
|
|
47
|
+
.attachTo(indoorBody, 'top-front-right', 'top-back-right', [5, -1, -15]);
|
|
48
|
+
|
|
49
|
+
// LED
|
|
50
|
+
const led = sphere(2.5).color('#00FF00')
|
|
51
|
+
.attachTo(panel, 'front', 'center', [5, -2, -5]);
|
|
52
|
+
|
|
53
|
+
// === Outdoor Unit ===
|
|
54
|
+
// Sits on the back face of the wall (+Y side), near the ground.
|
|
55
|
+
const outdoorBody = box(outW, outD, outH, true).color(C_OUTDOOR)
|
|
56
|
+
.attachTo(wall, 'bottom-back', 'bottom-front', [0, 10, 30]);
|
|
57
|
+
// front of outdoor touches back of wall, 10mm standoff, 30mm above ground
|
|
58
|
+
|
|
59
|
+
// Fan grille — on the back face (+Y) of outdoor unit, centered
|
|
60
|
+
const grilleR = Math.min(outW, outH) * 0.35;
|
|
61
|
+
const fanRim = cylinder(4, grilleR).color(C_GRILL)
|
|
62
|
+
.pointAlong([0, 1, 0]) // lay along +Y
|
|
63
|
+
.attachTo(outdoorBody, 'back', 'front', [0, 2, 0]);
|
|
64
|
+
|
|
65
|
+
const fanCenter = cylinder(2, grilleR * 0.4).color('#505050')
|
|
66
|
+
.pointAlong([0, 1, 0])
|
|
67
|
+
.attachTo(outdoorBody, 'back', 'front', [0, 3, 0]);
|
|
68
|
+
|
|
69
|
+
// Cooling fins — protrude from left and right sides
|
|
70
|
+
const finCount = 8;
|
|
71
|
+
const fins = [];
|
|
72
|
+
const outdoorBB = outdoorBody.boundingBox();
|
|
73
|
+
const finSpacing = (outH - 40) / finCount;
|
|
74
|
+
for (let i = 0; i < finCount; i++) {
|
|
75
|
+
const zOff = -outH / 2 + 20 + i * finSpacing;
|
|
76
|
+
// Left fin: right face of fin on left face of outdoor
|
|
77
|
+
const leftFin = box(5, 12, 3, true).color(C_GRILL)
|
|
78
|
+
.attachTo(outdoorBody, 'left', 'right', [0, -10, zOff]);
|
|
79
|
+
// Right fin: left face of fin on right face of outdoor
|
|
80
|
+
const rightFin = box(5, 12, 3, true).color(C_GRILL)
|
|
81
|
+
.attachTo(outdoorBody, 'right', 'left', [0, -10, zOff]);
|
|
82
|
+
fins.push(leftFin, rightFin);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// === Refrigerant Pipes ===
|
|
86
|
+
// Connect from back of indoor unit through wall to front of outdoor unit.
|
|
87
|
+
// Pipe runs along Y axis.
|
|
88
|
+
const indoorBB = indoorBody.boundingBox();
|
|
89
|
+
const pipeStartY = indoorBB.max[1]; // back of indoor (wall side)
|
|
90
|
+
const pipeEndY = outdoorBB.min[1]; // front of outdoor (wall side)
|
|
91
|
+
const pipeLen = pipeEndY - pipeStartY;
|
|
92
|
+
const pipeZ = indoorBB.min[2] + 15; // near bottom of indoor unit
|
|
93
|
+
const pipeX = 40;
|
|
94
|
+
|
|
95
|
+
// cylinder after pointAlong([0,1,0]) spans Y=0..pipeLen, so translate to pipeStartY
|
|
96
|
+
const liquidPipe = cylinder(pipeLen, 3.5).color(C_PIPE)
|
|
97
|
+
.pointAlong([0, 1, 0])
|
|
98
|
+
.translate(pipeX, pipeStartY, pipeZ);
|
|
99
|
+
|
|
100
|
+
const gasPipe = cylinder(pipeLen, 5).color(C_PIPE)
|
|
101
|
+
.pointAlong([0, 1, 0])
|
|
102
|
+
.translate(pipeX + 15, pipeStartY, pipeZ);
|
|
103
|
+
|
|
104
|
+
// Insulation at outdoor connection
|
|
105
|
+
const insLiquid = cylinder(12, 7).color('#333333')
|
|
106
|
+
.pointAlong([0, 1, 0])
|
|
107
|
+
.translate(pipeX, pipeEndY - 12, pipeZ);
|
|
108
|
+
|
|
109
|
+
const insGas = cylinder(12, 10).color('#333333')
|
|
110
|
+
.pointAlong([0, 1, 0])
|
|
111
|
+
.translate(pipeX + 15, pipeEndY - 12, pipeZ);
|
|
112
|
+
|
|
113
|
+
// === Return ===
|
|
114
|
+
return [
|
|
115
|
+
{ name: "Wall", shape: wall },
|
|
116
|
+
{ name: "Indoor Unit", shape: indoorBody },
|
|
117
|
+
{ name: "Vent Slats", shape: union(...slats) },
|
|
118
|
+
{ name: "Control Panel", shape: panel },
|
|
119
|
+
{ name: "LED", shape: led },
|
|
120
|
+
{ name: "Outdoor Unit", shape: outdoorBody },
|
|
121
|
+
{ name: "Fan Grille", shape: union(fanRim, fanCenter) },
|
|
122
|
+
{ name: "Cooling Fins", shape: union(...fins) },
|
|
123
|
+
{ name: "Liquid Pipe", shape: liquidPipe },
|
|
124
|
+
{ name: "Gas Pipe", shape: gasPipe },
|
|
125
|
+
{ name: "Pipe Insulation", shape: union(insLiquid, insGas) },
|
|
126
|
+
];
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Adjustable Height Table — manual crank mechanism
|
|
2
|
+
// Shows real construction details: telescoping legs, cross braces,
|
|
3
|
+
// crank shaft, and tabletop.
|
|
4
|
+
//
|
|
5
|
+
// BUILD NOTES (at bottom of file):
|
|
6
|
+
// Materials, dimensions, and assembly instructions for actually building this.
|
|
7
|
+
|
|
8
|
+
const topW = param("Top Width", 120, { min: 80, max: 200, unit: "cm" });
|
|
9
|
+
const topD = param("Top Depth", 60, { min: 40, max: 100, unit: "cm" });
|
|
10
|
+
const topThick = param("Top Thickness", 3, { min: 2, max: 5, unit: "cm" });
|
|
11
|
+
const minH = param("Min Height", 72, { min: 60, max: 80, unit: "cm" });
|
|
12
|
+
const maxH = param("Max Height", 120, { min: 100, max: 140, unit: "cm" });
|
|
13
|
+
const heightPct = param("Height %", 30, { min: 0, max: 100, unit: "%" });
|
|
14
|
+
const outerLeg = param("Outer Leg", 6, { min: 4, max: 10, unit: "cm" });
|
|
15
|
+
const innerLeg = param("Inner Leg", 4, { min: 3, max: 8, unit: "cm" });
|
|
16
|
+
const legWall = param("Leg Wall", 0.3, { min: 0.2, max: 0.5, unit: "cm" });
|
|
17
|
+
const inset = param("Leg Inset", 5, { min: 2, max: 15, unit: "cm" });
|
|
18
|
+
const braceW = param("Brace Width", 3, { min: 2, max: 5, unit: "cm" });
|
|
19
|
+
const braceH = param("Brace Height", 1.5, { min: 1, max: 3, unit: "cm" });
|
|
20
|
+
const crankR = param("Crank Radius", 8, { min: 5, max: 15, unit: "cm" });
|
|
21
|
+
|
|
22
|
+
// Current height based on slider
|
|
23
|
+
const currentH = minH + (maxH - minH) * heightPct / 100;
|
|
24
|
+
const legH = currentH - topThick;
|
|
25
|
+
const extensionH = legH - minH + topThick; // how much inner leg extends
|
|
26
|
+
|
|
27
|
+
// --- Tabletop ---
|
|
28
|
+
const top = box(topW, topD, topThick).translate(0, 0, legH);
|
|
29
|
+
|
|
30
|
+
// --- Outer legs (fixed, bolted to frame under tabletop) ---
|
|
31
|
+
// These are square tubes — outer shell minus inner hollow
|
|
32
|
+
const outerLegH = minH - topThick; // fixed portion height
|
|
33
|
+
|
|
34
|
+
const makeOuterLeg = (x, y) => {
|
|
35
|
+
const outer = box(outerLeg, outerLeg, outerLegH).translate(x, y, legH - outerLegH);
|
|
36
|
+
const inner = box(outerLeg - legWall * 2, outerLeg - legWall * 2, outerLegH + 1)
|
|
37
|
+
.translate(x + legWall, y + legWall, legH - outerLegH - 0.5);
|
|
38
|
+
return outer.subtract(inner);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// --- Inner legs (slide inside outer, extend downward) ---
|
|
42
|
+
const makeInnerLeg = (x, y) => {
|
|
43
|
+
const legOffset = (outerLeg - innerLeg) / 2;
|
|
44
|
+
const inner = box(innerLeg, innerLeg, legH)
|
|
45
|
+
.translate(x + legOffset, y + legOffset, 0);
|
|
46
|
+
const hollow = box(innerLeg - legWall * 2, innerLeg - legWall * 2, legH + 1)
|
|
47
|
+
.translate(x + legOffset + legWall, y + legOffset + legWall, -0.5);
|
|
48
|
+
return inner.subtract(hollow);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Leg positions
|
|
52
|
+
const legPositions = [
|
|
53
|
+
[inset, inset],
|
|
54
|
+
[topW - inset - outerLeg, inset],
|
|
55
|
+
[inset, topD - inset - outerLeg],
|
|
56
|
+
[topW - inset - outerLeg, topD - inset - outerLeg],
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const outerLegs = union(...legPositions.map(([x, y]) => makeOuterLeg(x, y)));
|
|
60
|
+
const innerLegs = union(...legPositions.map(([x, y]) => makeInnerLeg(x, y)));
|
|
61
|
+
|
|
62
|
+
// --- Locking pin holes (show where pins go through both tubes) ---
|
|
63
|
+
// Holes at multiple heights for discrete height adjustment
|
|
64
|
+
const pinHoleR = 0.4;
|
|
65
|
+
const pinSpacing = 5; // every 5cm
|
|
66
|
+
const pinHoles = [];
|
|
67
|
+
const numPins = Math.floor((maxH - minH) / pinSpacing);
|
|
68
|
+
|
|
69
|
+
for (const [lx, ly] of legPositions) {
|
|
70
|
+
const legCenterX = lx + outerLeg / 2;
|
|
71
|
+
const legCenterY = ly + outerLeg / 2;
|
|
72
|
+
for (let i = 0; i <= numPins; i++) {
|
|
73
|
+
const pz = legH - outerLegH + 5 + i * pinSpacing;
|
|
74
|
+
if (pz > 0 && pz < legH) {
|
|
75
|
+
// Through-hole in Y direction
|
|
76
|
+
pinHoles.push(
|
|
77
|
+
cylinder(outerLeg + 2, pinHoleR)
|
|
78
|
+
.rotate(90, 0, 0)
|
|
79
|
+
.translate(legCenterX, ly - 1, pz)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --- Cross braces (connect legs for rigidity) ---
|
|
86
|
+
// Front brace
|
|
87
|
+
const frontBraceLen = topW - 2 * inset - 2 * outerLeg;
|
|
88
|
+
const frontBrace = box(frontBraceLen, braceW, braceH)
|
|
89
|
+
.translate(inset + outerLeg, inset + outerLeg / 2 - braceW / 2, legH - outerLegH + 10);
|
|
90
|
+
|
|
91
|
+
// Back brace
|
|
92
|
+
const backBrace = box(frontBraceLen, braceW, braceH)
|
|
93
|
+
.translate(inset + outerLeg, topD - inset - outerLeg / 2 - braceW / 2, legH - outerLegH + 10);
|
|
94
|
+
|
|
95
|
+
// Side braces
|
|
96
|
+
const sideBraceLen = topD - 2 * inset - 2 * outerLeg;
|
|
97
|
+
const leftBrace = box(braceW, sideBraceLen, braceH)
|
|
98
|
+
.translate(inset + outerLeg / 2 - braceW / 2, inset + outerLeg, legH - outerLegH + 10);
|
|
99
|
+
const rightBrace = box(braceW, sideBraceLen, braceH)
|
|
100
|
+
.translate(topW - inset - outerLeg / 2 - braceW / 2, inset + outerLeg, legH - outerLegH + 10);
|
|
101
|
+
|
|
102
|
+
// --- Crank mechanism (side-mounted, visual representation) ---
|
|
103
|
+
// Crank shaft runs between front legs
|
|
104
|
+
const shaftY = inset + outerLeg / 2;
|
|
105
|
+
const shaftZ = legH - outerLegH + 25;
|
|
106
|
+
const shaftLen = topW - 2 * inset;
|
|
107
|
+
const shaft = cylinder(shaftLen, 0.8)
|
|
108
|
+
.rotate(0, 90, 0)
|
|
109
|
+
.translate(inset, shaftY, shaftZ);
|
|
110
|
+
|
|
111
|
+
// Crank handle (on the right side)
|
|
112
|
+
const handleX = topW - inset + 2;
|
|
113
|
+
const crankArm = cylinder(crankR, 0.5)
|
|
114
|
+
.translate(handleX, shaftY, shaftZ);
|
|
115
|
+
const crankKnob = sphere(1.2)
|
|
116
|
+
.translate(handleX, shaftY, shaftZ + crankR);
|
|
117
|
+
|
|
118
|
+
// --- Foot pads (rubber feet at bottom of inner legs) ---
|
|
119
|
+
const footPads = union(
|
|
120
|
+
...legPositions.map(([lx, ly]) => {
|
|
121
|
+
const cx = lx + outerLeg / 2;
|
|
122
|
+
const cy = ly + outerLeg / 2;
|
|
123
|
+
return cylinder(0.5, innerLeg / 2 + 0.5)
|
|
124
|
+
.translate(cx, cy, 0);
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// --- Assembly ---
|
|
129
|
+
let structure = union(outerLegs, innerLegs, frontBrace, backBrace, leftBrace, rightBrace);
|
|
130
|
+
if (pinHoles.length > 0) {
|
|
131
|
+
structure = structure.subtract(union(...pinHoles));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return [
|
|
135
|
+
{ name: "Tabletop", shape: top, color: "#8B7355" },
|
|
136
|
+
{ name: "Leg Structure", shape: structure, color: "#888888" },
|
|
137
|
+
{ name: "Crank Shaft", shape: shaft, color: "#666666" },
|
|
138
|
+
{ name: "Crank Arm", shape: crankArm, color: "#555555" },
|
|
139
|
+
{ name: "Crank Knob", shape: crankKnob, color: "#444444" },
|
|
140
|
+
{ name: "Foot Pads", shape: footPads, color: "#333333" },
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
// ============================================================
|
|
144
|
+
// BUILD NOTES — How to actually build this table
|
|
145
|
+
// ============================================================
|
|
146
|
+
//
|
|
147
|
+
// MATERIALS:
|
|
148
|
+
// - Tabletop: 18mm plywood or solid wood panel (120×60cm)
|
|
149
|
+
// - Outer legs: 60×60mm square steel tube, 2mm wall (×4, cut to ~69cm)
|
|
150
|
+
// - Inner legs: 40×40mm square steel tube, 2mm wall (×4, cut to ~120cm)
|
|
151
|
+
// - Cross braces: 30×15mm steel flat bar (×4)
|
|
152
|
+
// - Crank shaft: 16mm steel rod, threaded ends
|
|
153
|
+
// - Locking pins: 8mm spring pins or clevis pins
|
|
154
|
+
// - Foot pads: rubber furniture feet (40mm)
|
|
155
|
+
// - Hardware: M8 bolts for frame, M6 for braces
|
|
156
|
+
//
|
|
157
|
+
// ASSEMBLY:
|
|
158
|
+
// 1. Cut all steel tubes to length. Deburr edges.
|
|
159
|
+
// 2. Weld or bolt cross braces to outer leg tubes at 10cm from top.
|
|
160
|
+
// 3. Slide inner legs into outer legs from below.
|
|
161
|
+
// 4. Drill pin holes through both tubes at 5cm intervals.
|
|
162
|
+
// Use a drill press for alignment. Start from bottom.
|
|
163
|
+
// 5. Mount outer leg assembly to underside of tabletop with
|
|
164
|
+
// L-brackets and M8 bolts (4 per leg).
|
|
165
|
+
// 6. Thread crank shaft through front legs. Add bearing blocks
|
|
166
|
+
// or bushings at each leg pass-through.
|
|
167
|
+
// 7. Attach crank handle to shaft end.
|
|
168
|
+
// 8. For the crank-to-leg connection: weld a small gear or
|
|
169
|
+
// threaded collar to the shaft at each inner leg position.
|
|
170
|
+
// The inner leg needs a matching rack or threaded insert.
|
|
171
|
+
// SIMPLER ALTERNATIVE: Skip the crank, use spring pins only
|
|
172
|
+
// for discrete height positions (every 5cm).
|
|
173
|
+
// 9. Press-fit rubber feet onto inner leg bottoms.
|
|
174
|
+
// 10. Sand and paint/powder-coat all steel parts.
|
|
175
|
+
//
|
|
176
|
+
// TOOLS NEEDED:
|
|
177
|
+
// - Angle grinder or chop saw (cutting tubes)
|
|
178
|
+
// - Drill press (pin holes — alignment critical)
|
|
179
|
+
// - Welder (MIG preferred) OR bolt-together with brackets
|
|
180
|
+
// - Socket set, Allen keys
|
|
181
|
+
// - Level, tape measure, square
|
|
182
|
+
//
|
|
183
|
+
// COST ESTIMATE (EU prices, 2026):
|
|
184
|
+
// - Steel tubes: ~€40-60
|
|
185
|
+
// - Plywood top: ~€25-40
|
|
186
|
+
// - Hardware + feet: ~€15-20
|
|
187
|
+
// - Paint/finish: ~€10-15
|
|
188
|
+
// - Total: ~€90-135
|
|
189
|
+
//
|
|
190
|
+
// WEIGHT CAPACITY: ~80kg evenly distributed (depends on pin strength)
|
|
191
|
+
// Upgrade: use 2 pins per leg for more stability.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Assembly + gear coupling demo
|
|
2
|
+
// Uses addGearCoupling(...) so the driven joint follows pinion motion automatically.
|
|
3
|
+
|
|
4
|
+
const pinionDeg = param("Pinion Angle", 20, { min: -180, max: 180, step: 1, unit: "°" });
|
|
5
|
+
|
|
6
|
+
const pair = lib.gearPair({
|
|
7
|
+
pinion: { module: 1.25, teeth: 14, faceWidth: 8, boreDiameter: 5 },
|
|
8
|
+
gear: { module: 1.25, teeth: 42, faceWidth: 8, boreDiameter: 8 },
|
|
9
|
+
backlash: 0.05,
|
|
10
|
+
place: false,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const mech = assembly("Gear Coupling Demo")
|
|
14
|
+
.addFrame("Base")
|
|
15
|
+
.addPart("Pinion", pair.pinion.color("#d5a15f"))
|
|
16
|
+
.addPart("Driven", pair.gear.color("#9ab3ca"), {
|
|
17
|
+
transform: Transform.identity().translate(pair.centerDistance, 0, 0),
|
|
18
|
+
})
|
|
19
|
+
.addRevolute("Pinion", "Base", "Pinion", {
|
|
20
|
+
axis: [0, 0, 1],
|
|
21
|
+
min: -720,
|
|
22
|
+
max: 720,
|
|
23
|
+
})
|
|
24
|
+
.addRevolute("Driven", "Base", "Driven", {
|
|
25
|
+
axis: [0, 0, 1],
|
|
26
|
+
min: -720,
|
|
27
|
+
max: 720,
|
|
28
|
+
})
|
|
29
|
+
.addGearCoupling("Driven", "Pinion", { pair });
|
|
30
|
+
|
|
31
|
+
const solved = mech.solve({ Pinion: pinionDeg });
|
|
32
|
+
return solved.toScene();
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Assembly + mechanism demo
|
|
2
|
+
// Shows Transform composition, assembly joints, BOM metadata, and collision checks.
|
|
3
|
+
|
|
4
|
+
const baseYaw = param("Base Yaw", 20, { min: -170, max: 170, unit: "°" });
|
|
5
|
+
const shoulder = param("Shoulder", 30, { min: -30, max: 110, unit: "°" });
|
|
6
|
+
const elbow = param("Elbow", 45, { min: -20, max: 135, unit: "°" });
|
|
7
|
+
const open = param("Gripper Open", 28, { min: 0, max: 55, unit: "mm" });
|
|
8
|
+
|
|
9
|
+
const upperLen = 180;
|
|
10
|
+
const foreLen = 160;
|
|
11
|
+
|
|
12
|
+
const basePlate = box(180, 140, 10, true).translate(0, 0, 5);
|
|
13
|
+
const tower = cylinder(20, 36).translate(0, 0, 10);
|
|
14
|
+
|
|
15
|
+
const m4 = lib.fastenerHole({ size: "M4", fit: "normal", depth: 14, counterbore: { depth: 4 } });
|
|
16
|
+
const mountHoles = [
|
|
17
|
+
m4.translate(55, 40, 7),
|
|
18
|
+
m4.translate(-55, 40, 7),
|
|
19
|
+
m4.translate(55, -40, 7),
|
|
20
|
+
m4.translate(-55, -40, 7),
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const base = difference(union(basePlate, tower), ...mountHoles).color("#6e7b88");
|
|
24
|
+
|
|
25
|
+
const upperArm = box(upperLen, 28, 28)
|
|
26
|
+
.translate(0, -14, -14)
|
|
27
|
+
.subtract(cylinder(32, 8).pointAlong([0, 1, 0]).translate(0, 0, 0))
|
|
28
|
+
.color("#5f87c6");
|
|
29
|
+
|
|
30
|
+
const forearm = box(foreLen, 24, 24)
|
|
31
|
+
.translate(0, -12, -12)
|
|
32
|
+
.subtract(cylinder(28, 7).pointAlong([0, 1, 0]).translate(0, 0, 0))
|
|
33
|
+
.color("#6fa2d6");
|
|
34
|
+
|
|
35
|
+
const wristHub = cylinder(26, 10).pointAlong([1, 0, 0]).translate(0, 0, 0);
|
|
36
|
+
const palm = box(34, 44, 16, true).translate(16, 0, 0);
|
|
37
|
+
const toolBody = union(wristHub, palm).color("#b8c5d3");
|
|
38
|
+
|
|
39
|
+
const fingerLen = 50;
|
|
40
|
+
const finger = box(fingerLen, 8, 10).translate(8, -4, -5).color("#414952");
|
|
41
|
+
const fingerLeft = finger.translate(18, 8 + open * 0.5, 0);
|
|
42
|
+
const fingerRight = finger.translate(18, -8 - open * 0.5, 0);
|
|
43
|
+
const gripper = group(
|
|
44
|
+
{ name: "Tool Body", shape: toolBody },
|
|
45
|
+
{ name: "Left Finger", shape: fingerLeft },
|
|
46
|
+
{ name: "Right Finger", shape: fingerRight },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const mech = assembly("Robot Arm Demo")
|
|
50
|
+
.addPart("Base", base, {
|
|
51
|
+
metadata: { material: "PETG", process: "FDM", tolerance: "+/-0.2mm", qty: 1 },
|
|
52
|
+
})
|
|
53
|
+
.addPart("Upper Arm", upperArm, {
|
|
54
|
+
metadata: { material: "PETG-CF", process: "FDM", qty: 1 },
|
|
55
|
+
})
|
|
56
|
+
.addPart("Forearm", forearm, {
|
|
57
|
+
metadata: { material: "PETG-CF", process: "FDM", qty: 1 },
|
|
58
|
+
})
|
|
59
|
+
.addPart("Gripper", gripper, {
|
|
60
|
+
metadata: { material: "PETG", process: "FDM", notes: "Print fingers in TPU for compliance", qty: 1 },
|
|
61
|
+
})
|
|
62
|
+
.addJoint("baseYaw", "revolute", "Base", "Upper Arm", {
|
|
63
|
+
axis: [0, 0, 1],
|
|
64
|
+
min: -170,
|
|
65
|
+
max: 170,
|
|
66
|
+
frame: Transform.identity().translate(0, 0, 46),
|
|
67
|
+
})
|
|
68
|
+
.addJoint("shoulder", "revolute", "Upper Arm", "Forearm", {
|
|
69
|
+
axis: [0, -1, 0],
|
|
70
|
+
min: -30,
|
|
71
|
+
max: 110,
|
|
72
|
+
frame: Transform.identity().translate(upperLen + 8, 0, 0),
|
|
73
|
+
})
|
|
74
|
+
.addJoint("elbow", "revolute", "Forearm", "Gripper", {
|
|
75
|
+
axis: [0, -1, 0],
|
|
76
|
+
min: -20,
|
|
77
|
+
max: 135,
|
|
78
|
+
frame: Transform.identity().translate(foreLen + 12, 0, 0),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const solved = mech.solve({
|
|
82
|
+
baseYaw,
|
|
83
|
+
shoulder,
|
|
84
|
+
elbow,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const collisions = solved.collisionReport({
|
|
88
|
+
minOverlapVolume: 0.5,
|
|
89
|
+
ignorePairs: [
|
|
90
|
+
["Upper Arm", "Forearm"],
|
|
91
|
+
["Forearm", "Gripper"],
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (collisions.length > 0) {
|
|
96
|
+
console.warn("Assembly collisions:", collisions);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const elbowSweep = mech.sweepJoint("elbow", -20, 135, 16, {
|
|
100
|
+
baseYaw,
|
|
101
|
+
shoulder,
|
|
102
|
+
});
|
|
103
|
+
const sweptCollisions = elbowSweep.filter(step => step.collisions.length > 0).length;
|
|
104
|
+
if (sweptCollisions > 0) {
|
|
105
|
+
console.info(`Elbow sweep has collisions in ${sweptCollisions}/${elbowSweep.length} steps`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log("BOM", solved.bom());
|
|
109
|
+
console.log("BOM CSV\n" + solved.bomCsv());
|
|
110
|
+
|
|
111
|
+
return solved.toScene();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// attachTo() — the primary way to position parts relative to each other.
|
|
2
|
+
//
|
|
3
|
+
// Mental model: child.attachTo(parent, parentAnchor, selfAnchor, offset)
|
|
4
|
+
// "Put my [selfAnchor] at the parent's [parentAnchor], then shift by [offset]"
|
|
5
|
+
//
|
|
6
|
+
// Anchor names:
|
|
7
|
+
// 1 word = face center: 'top', 'bottom', 'front', 'back', 'left', 'right'
|
|
8
|
+
// 2 words = edge midpoint: 'top-front', 'back-left', etc.
|
|
9
|
+
// 3 words = corner: 'top-front-left', 'bottom-back-right', etc.
|
|
10
|
+
|
|
11
|
+
const baseW = param("Base Width", 100, { min: 50, max: 200, unit: "mm" });
|
|
12
|
+
const baseD = param("Base Depth", 80, { min: 40, max: 150, unit: "mm" });
|
|
13
|
+
const baseH = param("Base Height", 10, { min: 5, max: 30, unit: "mm" });
|
|
14
|
+
|
|
15
|
+
const base = box(baseW, baseD, baseH, true).color('#888888');
|
|
16
|
+
|
|
17
|
+
// Stack on top: column's bottom face meets base's top face
|
|
18
|
+
const column = cylinder(40, 8).color('#4488cc')
|
|
19
|
+
.attachTo(base, 'top', 'bottom');
|
|
20
|
+
|
|
21
|
+
// Protrude from front: button's back face meets base's front face
|
|
22
|
+
const button = box(20, 6, 10, true).color('#cc4444')
|
|
23
|
+
.attachTo(base, 'front', 'back');
|
|
24
|
+
|
|
25
|
+
// Hang below: bracket's top face meets base's bottom face
|
|
26
|
+
const bracket = box(30, 30, 5, true).color('#44cc44')
|
|
27
|
+
.attachTo(base, 'bottom', 'top');
|
|
28
|
+
|
|
29
|
+
// Attach to side with offset: panel's left face meets base's right face,
|
|
30
|
+
// then shift 0mm on X, 0mm on Y, 10mm up on Z
|
|
31
|
+
const sidePanel = box(4, 40, 25, true).color('#cc8844')
|
|
32
|
+
.attachTo(base, 'right', 'left', [0, 0, 10]);
|
|
33
|
+
|
|
34
|
+
// Corner alignment: small cube at top-front-right corner of base
|
|
35
|
+
const corner = box(8, 8, 8, true).color('#8844cc')
|
|
36
|
+
.attachTo(base, 'top-front-right', 'bottom-back-left');
|
|
37
|
+
|
|
38
|
+
return [
|
|
39
|
+
{ name: "Base", shape: base },
|
|
40
|
+
{ name: "Column (top→bottom)", shape: column },
|
|
41
|
+
{ name: "Button (front→back)", shape: button },
|
|
42
|
+
{ name: "Bracket (bottom→top)", shape: bracket },
|
|
43
|
+
{ name: "Side Panel (right→left, +10Z)", shape: sidePanel },
|
|
44
|
+
{ name: "Corner Cube", shape: corner },
|
|
45
|
+
];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Benchy-style hull concept using reusable curve/surface APIs.
|
|
2
|
+
// Not an exact #3DBenchy clone; this shows the modeling workflow:
|
|
3
|
+
// sections -> loft hull, sweep rails/chimney, simple superstructure.
|
|
4
|
+
|
|
5
|
+
const length = param("Length", 92, { min: 60, max: 150, unit: "mm" });
|
|
6
|
+
const beam = param("Beam", 42, { min: 24, max: 70, unit: "mm" });
|
|
7
|
+
const hullH = param("Hull Height", 34, { min: 18, max: 60, unit: "mm" });
|
|
8
|
+
const deckDrop = param("Deck Drop", 6, { min: 2, max: 12, unit: "mm" });
|
|
9
|
+
|
|
10
|
+
const mkSection = (w, h, keel = 0, chine = 0) => spline2d([
|
|
11
|
+
[w * 0.5, 0],
|
|
12
|
+
[w * 0.45, h * 0.28 + chine],
|
|
13
|
+
[w * 0.25, h * 0.5 + chine],
|
|
14
|
+
[0, h * 0.58 + keel],
|
|
15
|
+
[-w * 0.25, h * 0.5 + chine],
|
|
16
|
+
[-w * 0.45, h * 0.28 + chine],
|
|
17
|
+
[-w * 0.5, 0],
|
|
18
|
+
[-w * 0.45, -h * 0.18],
|
|
19
|
+
[-w * 0.23, -h * 0.32],
|
|
20
|
+
[0, -h * 0.36 - deckDrop],
|
|
21
|
+
[w * 0.23, -h * 0.32],
|
|
22
|
+
[w * 0.45, -h * 0.18],
|
|
23
|
+
], {
|
|
24
|
+
closed: true,
|
|
25
|
+
samplesPerSegment: 10,
|
|
26
|
+
tension: 0.45,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const z0 = 0;
|
|
30
|
+
const z1 = length * 0.22;
|
|
31
|
+
const z2 = length * 0.56;
|
|
32
|
+
const z3 = length * 0.88;
|
|
33
|
+
const z4 = length;
|
|
34
|
+
|
|
35
|
+
let hull = loft(
|
|
36
|
+
[
|
|
37
|
+
mkSection(beam * 0.52, hullH * 0.72, 2, 1), // stern
|
|
38
|
+
mkSection(beam * 0.94, hullH * 0.95, 3, 1.5),
|
|
39
|
+
mkSection(beam, hullH, 3.5, 1.2), // max beam
|
|
40
|
+
mkSection(beam * 0.58, hullH * 0.82, 1.5, 0.5),
|
|
41
|
+
mkSection(beam * 0.18, hullH * 0.35, 0, 0), // bow tip
|
|
42
|
+
],
|
|
43
|
+
[z0, z1, z2, z3, z4],
|
|
44
|
+
{ edgeLength: 0.95 },
|
|
45
|
+
);
|
|
46
|
+
hull = hull.smoothOut(72, 0.28).refine(2);
|
|
47
|
+
|
|
48
|
+
// Orient hull so length goes along X, beam along Y, height along Z.
|
|
49
|
+
hull = hull
|
|
50
|
+
.rotate(0, 90, 0) // Z (loft stations) -> X
|
|
51
|
+
.rotate(90, 0, 0) // Y (section height) -> Z
|
|
52
|
+
.translate(-length * 0.5, 0, hullH * 0.58);
|
|
53
|
+
|
|
54
|
+
// Deckhouse and cabin
|
|
55
|
+
const houseW = beam * 0.48;
|
|
56
|
+
const houseD = length * 0.26;
|
|
57
|
+
const houseH = hullH * 0.62;
|
|
58
|
+
const house = roundedRect(houseW, houseD, 4, true).extrude(houseH)
|
|
59
|
+
.translate(length * 0.04, 0, hullH * 0.82);
|
|
60
|
+
|
|
61
|
+
const cabinCut = roundedRect(houseW * 0.68, houseD * 0.56, 2.2, true).extrude(houseH * 0.7)
|
|
62
|
+
.translate(length * 0.04, 0, hullH * 1.08);
|
|
63
|
+
|
|
64
|
+
// Chimney via sweep
|
|
65
|
+
const stackPath = spline3d(
|
|
66
|
+
[
|
|
67
|
+
[length * 0.02, 0, hullH * 1.45],
|
|
68
|
+
[length * 0.02, 0, hullH * 1.72],
|
|
69
|
+
[length * 0.08, 0, hullH * 1.84],
|
|
70
|
+
],
|
|
71
|
+
{ tension: 0.5 },
|
|
72
|
+
);
|
|
73
|
+
const stack = sweep(circle2d(3.8), stackPath, {
|
|
74
|
+
samples: 28,
|
|
75
|
+
edgeLength: 0.55,
|
|
76
|
+
});
|
|
77
|
+
const stackInner = sweep(circle2d(2.2), stackPath, {
|
|
78
|
+
samples: 28,
|
|
79
|
+
edgeLength: 0.55,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const cabin = house.subtract(cabinCut);
|
|
83
|
+
const chimney = stack.subtract(stackInner);
|
|
84
|
+
|
|
85
|
+
return [
|
|
86
|
+
{ name: "Hull", shape: hull.color('#ce6f4e') },
|
|
87
|
+
{ name: "Cabin", shape: cabin.color('#f0eee9') },
|
|
88
|
+
{ name: "Chimney", shape: chimney.color('#3d4854') },
|
|
89
|
+
];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// API demo: script-declared bill of materials that gets auto-summed in report export
|
|
2
|
+
|
|
3
|
+
const frameWidth = param('Frame Width', 900, { min: 300, max: 1800, unit: 'mm' });
|
|
4
|
+
const frameDepth = param('Frame Depth', 500, { min: 200, max: 1200, unit: 'mm' });
|
|
5
|
+
const legHeight = param('Leg Height', 720, { min: 300, max: 1200, unit: 'mm' });
|
|
6
|
+
const tubeW = param('Tube Width', 30, { min: 15, max: 80, unit: 'mm' });
|
|
7
|
+
const tubeH = param('Tube Height', 20, { min: 10, max: 80, unit: 'mm' });
|
|
8
|
+
|
|
9
|
+
const frontBolts = param('Front Bolts', 8, { min: 0, max: 64, integer: true });
|
|
10
|
+
const rearBolts = param('Rear Bolts', 8, { min: 0, max: 64, integer: true });
|
|
11
|
+
const boltLength = param('Bolt Length', 16, { min: 6, max: 60, unit: 'mm' });
|
|
12
|
+
|
|
13
|
+
const wall = 2;
|
|
14
|
+
const longTubeMm = frameWidth * 2;
|
|
15
|
+
const shortTubeMm = frameDepth * 2;
|
|
16
|
+
const legTubeMm = legHeight * 4;
|
|
17
|
+
const totalTubeMm = longTubeMm + shortTubeMm + legTubeMm;
|
|
18
|
+
|
|
19
|
+
// Physical materials are authored by code, not inferred from mesh primitives.
|
|
20
|
+
bom(totalTubeMm, `iron tube with dimensions ${tubeW} x ${tubeH}`, { unit: 'mm' });
|
|
21
|
+
|
|
22
|
+
// These two lines intentionally share the same descriptor so report export sums them.
|
|
23
|
+
bom(frontBolts, `M4 bolt of ${boltLength} mm length`, { unit: 'pieces' });
|
|
24
|
+
bom(rearBolts, `M4 bolt of ${boltLength} mm length`, { unit: 'pieces' });
|
|
25
|
+
|
|
26
|
+
const railFront = box(frameWidth, tubeW, tubeH).color('#778da9');
|
|
27
|
+
const railBack = box(frameWidth, tubeW, tubeH).translate(0, frameDepth - tubeW, 0).color('#778da9');
|
|
28
|
+
const railLeft = box(tubeW, frameDepth, tubeH).color('#778da9');
|
|
29
|
+
const railRight = box(tubeW, frameDepth, tubeH).translate(frameWidth - tubeW, 0, 0).color('#778da9');
|
|
30
|
+
|
|
31
|
+
const legSize = Math.min(tubeW, tubeH);
|
|
32
|
+
const legA = box(legSize, legSize, legHeight).translate(0, 0, tubeH).color('#415a77');
|
|
33
|
+
const legB = box(legSize, legSize, legHeight).translate(frameWidth - legSize, 0, tubeH).color('#415a77');
|
|
34
|
+
const legC = box(legSize, legSize, legHeight).translate(0, frameDepth - legSize, tubeH).color('#415a77');
|
|
35
|
+
const legD = box(legSize, legSize, legHeight).translate(frameWidth - legSize, frameDepth - legSize, tubeH).color('#415a77');
|
|
36
|
+
|
|
37
|
+
return [
|
|
38
|
+
{ name: 'Front Rail', shape: railFront },
|
|
39
|
+
{ name: 'Back Rail', shape: railBack },
|
|
40
|
+
{ name: 'Left Rail', shape: railLeft },
|
|
41
|
+
{ name: 'Right Rail', shape: railRight },
|
|
42
|
+
{ name: 'Leg A', shape: legA },
|
|
43
|
+
{ name: 'Leg B', shape: legB },
|
|
44
|
+
{ name: 'Leg C', shape: legC },
|
|
45
|
+
{ name: 'Leg D', shape: legD },
|
|
46
|
+
];
|