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,88 @@
|
|
|
1
|
+
# Compiler Regression Corpus
|
|
2
|
+
|
|
3
|
+
These parts are the curated multi-feature corpus behind `forgecad check compiler`,
|
|
4
|
+
the focused `forgecad check query-propagation` snapshots, and `forgecad check brep`.
|
|
5
|
+
They are intentionally ordinary mechanical parts instead of isolated geometry
|
|
6
|
+
tricks, so lowerer regressions show up in workflows that look like real
|
|
7
|
+
product-design code.
|
|
8
|
+
|
|
9
|
+
Each file is deterministic: no randomness, no params, fixed named solid result(s).
|
|
10
|
+
|
|
11
|
+
## Coverage Map
|
|
12
|
+
|
|
13
|
+
| Part | Main workflow families |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `enclosure-shell-cuts.forge.js` | `shell()`, face-driven cuts, mirrored feet, boolean chain |
|
|
16
|
+
| `motor-mount-plate.forge.js` | circular pattern, mirrored ears, deterministic boolean pockets |
|
|
17
|
+
| `sensor-bracket.forge.js` | mirrored ribs, upright `onFace()` cuts, repeated detail holes |
|
|
18
|
+
| `edge-finished-mount.forge.js` | tracked-edge fillet/chamfer, downstream hole/cut edits, boolean chain |
|
|
19
|
+
| `fastener-plate-variants.forge.js` | counterbores, countersinks, planar `upToFace`, created-face propagation |
|
|
20
|
+
| `folded-service-panel-cover.forge.js` | compiler-owned sheet metal, named panel/flange/bend descendants, folded + flat outputs |
|
|
21
|
+
| `projection-relay-cover.forge.js` | `projectToPlane()` replay from repeated union descendants |
|
|
22
|
+
| `service-panel-cover.forge.js` | repeated bosses plus richer hole/cut details plus projection replay |
|
|
23
|
+
| `trimmed-access-cover.forge.js` | `trimByPlane()`, plane-cap ownership, later union edits |
|
|
24
|
+
|
|
25
|
+
## Parts
|
|
26
|
+
|
|
27
|
+
### `enclosure-shell-cuts.forge.js`
|
|
28
|
+
|
|
29
|
+
Guards:
|
|
30
|
+
- shell lowering inside an enclosure-style boolean workflow
|
|
31
|
+
- `onFace()`-driven subtractive features that need semantic workplane placement in the compiler graph
|
|
32
|
+
- mirrored mounting feet that stay exact-exportable after later subtracts
|
|
33
|
+
|
|
34
|
+
### `motor-mount-plate.forge.js`
|
|
35
|
+
|
|
36
|
+
Guards:
|
|
37
|
+
- `circularPattern()` around a real bolt circle instead of a toy transform demo
|
|
38
|
+
- stacked analytic bore cutters inside an exact-exportable boolean workflow, not compiler-owned counterbore features yet
|
|
39
|
+
- mirrored tabs and center pocket booleans that must remain deterministic
|
|
40
|
+
|
|
41
|
+
### `sensor-bracket.forge.js`
|
|
42
|
+
|
|
43
|
+
Guards:
|
|
44
|
+
- mirrored reinforcement ribs inside a larger bracket union
|
|
45
|
+
- front/side face cuts placed with `onFace()` on an upright wall
|
|
46
|
+
- repeated indicator-hole cutters plus raw mounting bores in the same boolean tree
|
|
47
|
+
|
|
48
|
+
### `edge-finished-mount.forge.js`
|
|
49
|
+
|
|
50
|
+
Guards:
|
|
51
|
+
- tracked-edge `filletEdge()` plus later propagated-edge `chamferEdge()` lowering on the defended vertical-edge subset
|
|
52
|
+
- mirrored additive features staying exact-exportable while the selected preserved edge keeps one unique propagated lineage
|
|
53
|
+
- downstream `hole()` / `cutout()` edits that still target the original tracked body owner after the broadened edge-finish flow
|
|
54
|
+
|
|
55
|
+
### `fastener-plate-variants.forge.js`
|
|
56
|
+
|
|
57
|
+
Guards:
|
|
58
|
+
- compiler-owned counterbore and countersink holes inside a normal fastener-plate workflow
|
|
59
|
+
- `upToFace` hole/cut extents lowering through both Manifold and CadQuery/OCCT from the shared feature family
|
|
60
|
+
- defended created-face/query semantics staying inspectable after multiple richer hole rewrites
|
|
61
|
+
|
|
62
|
+
### `folded-service-panel-cover.forge.js`
|
|
63
|
+
|
|
64
|
+
Guards:
|
|
65
|
+
- compiler-owned `sheetMetal()` intent lowering to both the folded cover and the flat pattern from one semantic model
|
|
66
|
+
- named `panel`, `flange-*`, and `bend-*` descendants staying inspectable after panel/flange cutouts split those regions
|
|
67
|
+
- exact export and query-propagation coverage for a manufacturable-looking sheet-metal cover instead of an isolated toy bend
|
|
68
|
+
|
|
69
|
+
### `projection-relay-cover.forge.js`
|
|
70
|
+
|
|
71
|
+
Guards:
|
|
72
|
+
- `projectToPlane()` replay after a repeated top-edge boss chain has already been merged through a supported union
|
|
73
|
+
- projection-driven downstream lips staying exact-exportable instead of collapsing back to runtime-only geometry
|
|
74
|
+
- downstream placement still using defended face-query lineage from the base plate instead of anonymous heuristics
|
|
75
|
+
|
|
76
|
+
### `service-panel-cover.forge.js`
|
|
77
|
+
|
|
78
|
+
Guards:
|
|
79
|
+
- repeated top-side bosses staying compiler-owned before later richer hole/cut rewrites hit the same part
|
|
80
|
+
- counterbores, countersinks, and a face-driven display pocket remaining exact-exportable in one ordinary service-cover workflow
|
|
81
|
+
- projection-driven gasket geometry replaying from a hole/cut/union source instead of dropping back to runtime-only silhouette logic
|
|
82
|
+
|
|
83
|
+
### `trimmed-access-cover.forge.js`
|
|
84
|
+
|
|
85
|
+
Guards:
|
|
86
|
+
- `trimByPlane()` exposing the defended `plane-cap` created-face query inside a normal cover-style workflow
|
|
87
|
+
- earlier `hole()` / `cutout()` rewrites still surfacing explicit split-face ambiguity and unsupported created-edge diagnostics
|
|
88
|
+
- later union edits staying reviewable after the trim-created face lands
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const base = box(84, 54, 24, true);
|
|
2
|
+
|
|
3
|
+
const pocket = roundedRect(20, 10, 2, true)
|
|
4
|
+
.onFace(base, 'top', { u: 12, v: -8, selfAnchor: 'center' });
|
|
5
|
+
|
|
6
|
+
const bossSeed = roundedRect(14, 8, 1.5, true)
|
|
7
|
+
.onFace(base, 'top', { u: -18, v: 8, protrude: 0.25, selfAnchor: 'center' })
|
|
8
|
+
.extrude(8);
|
|
9
|
+
|
|
10
|
+
const once = filletEdge(base.toShape(), base.edge('vert-br'), 6, [-1, -1]);
|
|
11
|
+
const bosses = mirrorCopy(bossSeed, [1, 0, 0]);
|
|
12
|
+
|
|
13
|
+
const body = chamferEdge(once.add(bosses), base.edge('vert-bl'), 4, [1, -1])
|
|
14
|
+
.hole(base.face('top'), { diameter: 6, u: -18, v: 10, depth: 9 })
|
|
15
|
+
.cutout(pocket, { depth: 5 })
|
|
16
|
+
.subtract(cylinder(28, 4, undefined, undefined, true).translate(0, -20, 0));
|
|
17
|
+
|
|
18
|
+
return [{ name: 'Edge Finished Mount', shape: body }];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Guard part: shell + face-driven cuts + mirrored feet in one ordinary enclosure workflow.
|
|
2
|
+
|
|
3
|
+
const base = roundedRect(120, 80, 10, true).extrude(36);
|
|
4
|
+
const shell = base.shell(3, { openFaces: ['top'] });
|
|
5
|
+
|
|
6
|
+
const displayCut = roundedRect(34, 18, 3, true)
|
|
7
|
+
.onFace(base, 'front', { u: 0, v: 8, protrude: 0.25, selfAnchor: 'center' })
|
|
8
|
+
.extrude(10);
|
|
9
|
+
|
|
10
|
+
const cableCut = circle2d(7)
|
|
11
|
+
.onFace(base, 'right', { u: -12, v: -8, protrude: 0.25, selfAnchor: 'center' })
|
|
12
|
+
.extrude(10);
|
|
13
|
+
|
|
14
|
+
const foot = roundedRect(18, 18, 4, true)
|
|
15
|
+
.onFace(base, 'bottom', { u: 36, v: 20, protrude: 0, selfAnchor: 'center' })
|
|
16
|
+
.extrude(6);
|
|
17
|
+
|
|
18
|
+
const feet = mirrorCopy(mirrorCopy(foot, [1, 0, 0]), [0, 1, 0]);
|
|
19
|
+
|
|
20
|
+
const enclosure = union(shell, feet)
|
|
21
|
+
.subtract(displayCut)
|
|
22
|
+
.subtract(cableCut);
|
|
23
|
+
|
|
24
|
+
return [{ name: 'Enclosure Shell Cuts', shape: enclosure }];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Guard part: counterbore/countersink hole variants plus an up-to-face service pocket in one ordinary plate workflow.
|
|
2
|
+
|
|
3
|
+
const plate = roundedRect(118, 76, 8, true).extrude(18);
|
|
4
|
+
const entryFace = plate.face('top');
|
|
5
|
+
const exitFace = plate.face('bottom');
|
|
6
|
+
const pocketExitFace = plate.toShape().face('side-top');
|
|
7
|
+
|
|
8
|
+
const servicePocket = roundedRect(34, 18, 4, true)
|
|
9
|
+
.onFace(plate, 'front', { u: 0, v: -2, selfAnchor: 'center' });
|
|
10
|
+
|
|
11
|
+
const fastenerPlate = plate
|
|
12
|
+
.hole(entryFace, {
|
|
13
|
+
diameter: 6.2,
|
|
14
|
+
u: -34,
|
|
15
|
+
v: 20,
|
|
16
|
+
upToFace: exitFace,
|
|
17
|
+
counterbore: { diameter: 11.5, depth: 4 },
|
|
18
|
+
})
|
|
19
|
+
.hole(entryFace, {
|
|
20
|
+
diameter: 6.2,
|
|
21
|
+
u: 34,
|
|
22
|
+
v: 20,
|
|
23
|
+
upToFace: exitFace,
|
|
24
|
+
counterbore: { diameter: 11.5, depth: 4 },
|
|
25
|
+
})
|
|
26
|
+
.hole(entryFace, {
|
|
27
|
+
diameter: 4.3,
|
|
28
|
+
u: -34,
|
|
29
|
+
v: -20,
|
|
30
|
+
upToFace: exitFace,
|
|
31
|
+
countersink: { diameter: 9, angleDeg: 90 },
|
|
32
|
+
})
|
|
33
|
+
.hole(entryFace, {
|
|
34
|
+
diameter: 4.3,
|
|
35
|
+
u: 34,
|
|
36
|
+
v: -20,
|
|
37
|
+
upToFace: exitFace,
|
|
38
|
+
countersink: { diameter: 9, angleDeg: 90 },
|
|
39
|
+
})
|
|
40
|
+
.cutout(servicePocket, { upToFace: pocketExitFace });
|
|
41
|
+
|
|
42
|
+
return [{ name: 'Fastener Plate Variants', shape: fastenerPlate }];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Guard part: patterned fastener holes, mirrored ears, and deterministic boolean pockets.
|
|
2
|
+
|
|
3
|
+
const plate = roundedRect(132, 96, 12, true).extrude(10);
|
|
4
|
+
const ear = roundedRect(28, 42, 8, true)
|
|
5
|
+
.extrude(10)
|
|
6
|
+
.translate(0, 62, 0);
|
|
7
|
+
const body = union(plate, mirrorCopy(ear, [0, 1, 0]));
|
|
8
|
+
|
|
9
|
+
const counterboredHole = union(
|
|
10
|
+
cylinder(14, 2.25, undefined, undefined, true),
|
|
11
|
+
cylinder(4, 4.25, undefined, undefined, true).translate(0, 0, 5),
|
|
12
|
+
);
|
|
13
|
+
const centerBore = cylinder(14, 24, undefined, undefined, true);
|
|
14
|
+
const bolt = counterboredHole.translate(32, 0, 0);
|
|
15
|
+
const boltCircle = circularPattern(bolt, 4);
|
|
16
|
+
|
|
17
|
+
const earSlot = roundedRect(16, 8, 3, true)
|
|
18
|
+
.extrude(14, { center: true })
|
|
19
|
+
.translate(0, 62, 5);
|
|
20
|
+
const earSlots = mirrorCopy(earSlot, [0, 1, 0]);
|
|
21
|
+
|
|
22
|
+
const lighteningPocket = roundedRect(54, 22, 5, true)
|
|
23
|
+
.extrude(12, { center: true })
|
|
24
|
+
.translate(0, 0, 5);
|
|
25
|
+
|
|
26
|
+
const motorMount = body
|
|
27
|
+
.subtract(centerBore)
|
|
28
|
+
.subtract(boltCircle)
|
|
29
|
+
.subtract(earSlots)
|
|
30
|
+
.subtract(lighteningPocket);
|
|
31
|
+
|
|
32
|
+
return [{ name: 'Motor Mount Plate', shape: motorMount }];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const plate = roundedRect(96, 56, 6, true).extrude(12);
|
|
2
|
+
|
|
3
|
+
const bossSeed = roundedRect(18, 12, 2.5, true)
|
|
4
|
+
.onFace(plate, 'top', { u: -28, v: 24, protrude: 0.5, selfAnchor: 'center' })
|
|
5
|
+
.extrude(7);
|
|
6
|
+
|
|
7
|
+
const bosses = linearPattern(bossSeed, 3, 28, 0, 0);
|
|
8
|
+
const cover = union(plate, bosses);
|
|
9
|
+
|
|
10
|
+
const projected = projectToPlane(cover, { plane: 'XY' });
|
|
11
|
+
const lip = projected
|
|
12
|
+
.offset(2)
|
|
13
|
+
.onFace(plate.face('top'), { protrude: 0.5, selfAnchor: 'center' })
|
|
14
|
+
.extrude(1.2);
|
|
15
|
+
|
|
16
|
+
return [{ name: 'Projection Relay Cover', shape: union(cover, lip) }];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Guard part: mirrored ribs, face-mounted cuts, and repeated wall details in one bracket.
|
|
2
|
+
|
|
3
|
+
const base = roundedRect(100, 54, 4, true).extrude(10);
|
|
4
|
+
const upright = roundedRect(100, 10, 4, true)
|
|
5
|
+
.extrude(56)
|
|
6
|
+
.translate(0, 22, 10);
|
|
7
|
+
const rib = box(10, 24, 36, true).translate(36, 10, 28);
|
|
8
|
+
const ribs = mirrorCopy(rib, [1, 0, 0]);
|
|
9
|
+
|
|
10
|
+
const mountHole = union(
|
|
11
|
+
cylinder(14, 2.25, undefined, undefined, true),
|
|
12
|
+
cylinder(3.5, 4.25, undefined, undefined, true).translate(0, 0, 5.25),
|
|
13
|
+
).translate(28, 0, 5);
|
|
14
|
+
const mountHoles = mirrorCopy(mountHole, [1, 0, 0]);
|
|
15
|
+
|
|
16
|
+
const sensorWindow = roundedRect(36, 16, 3, true)
|
|
17
|
+
.onFace(upright, 'front', { u: 0, v: 6, protrude: 0.25, selfAnchor: 'center' })
|
|
18
|
+
.extrude(12);
|
|
19
|
+
|
|
20
|
+
const cablePort = roundedRect(18, 8, 2, true)
|
|
21
|
+
.onFace(upright, 'right', { u: 10, v: -8, protrude: 0.25, selfAnchor: 'center' })
|
|
22
|
+
.extrude(12);
|
|
23
|
+
|
|
24
|
+
const indicatorHole = circle2d(2.5)
|
|
25
|
+
.onFace(upright, 'front', { u: -16, v: -12, protrude: 0.25, selfAnchor: 'center' })
|
|
26
|
+
.extrude(12);
|
|
27
|
+
const indicatorArray = linearPattern(indicatorHole, 3, 16, 0);
|
|
28
|
+
|
|
29
|
+
const bracket = union(base, upright, ribs)
|
|
30
|
+
.subtract(mountHoles)
|
|
31
|
+
.subtract(sensorWindow)
|
|
32
|
+
.subtract(cablePort)
|
|
33
|
+
.subtract(indicatorArray);
|
|
34
|
+
|
|
35
|
+
return [{ name: 'Sensor Bracket', shape: bracket }];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Guard part: repeated bosses, richer hole variants, projection replay, and a face-driven cut in one service-cover workflow.
|
|
2
|
+
|
|
3
|
+
const plate = roundedRect(124, 78, 8, true).extrude(12);
|
|
4
|
+
|
|
5
|
+
const bossSeed = roundedRect(18, 10, 2, true)
|
|
6
|
+
.onFace(plate, 'top', { u: -30, v: 22, protrude: 0.5, selfAnchor: 'center' })
|
|
7
|
+
.extrude(7);
|
|
8
|
+
const bosses = linearPattern(bossSeed, 3, 30, 0, 0);
|
|
9
|
+
|
|
10
|
+
const displayPocket = roundedRect(30, 14, 3, true)
|
|
11
|
+
.onFace(plate, 'top', { u: 0, v: 4, protrude: 0.25, selfAnchor: 'center' });
|
|
12
|
+
|
|
13
|
+
const topFace = plate.face('top');
|
|
14
|
+
const bottomFace = plate.face('bottom');
|
|
15
|
+
|
|
16
|
+
const cover = union(plate, bosses)
|
|
17
|
+
.hole(topFace, {
|
|
18
|
+
diameter: 5.2,
|
|
19
|
+
u: -46,
|
|
20
|
+
v: -22,
|
|
21
|
+
upToFace: bottomFace,
|
|
22
|
+
countersink: { diameter: 10.5, angleDeg: 90 },
|
|
23
|
+
})
|
|
24
|
+
.hole(topFace, {
|
|
25
|
+
diameter: 5.2,
|
|
26
|
+
u: 46,
|
|
27
|
+
v: -22,
|
|
28
|
+
upToFace: bottomFace,
|
|
29
|
+
countersink: { diameter: 10.5, angleDeg: 90 },
|
|
30
|
+
})
|
|
31
|
+
.hole(topFace, {
|
|
32
|
+
diameter: 4.5,
|
|
33
|
+
u: -30,
|
|
34
|
+
v: 22,
|
|
35
|
+
upToFace: bottomFace,
|
|
36
|
+
counterbore: { diameter: 9, depth: 3 },
|
|
37
|
+
})
|
|
38
|
+
.hole(topFace, {
|
|
39
|
+
diameter: 4.5,
|
|
40
|
+
u: 30,
|
|
41
|
+
v: 22,
|
|
42
|
+
upToFace: bottomFace,
|
|
43
|
+
counterbore: { diameter: 9, depth: 3 },
|
|
44
|
+
})
|
|
45
|
+
.cutout(displayPocket, { depth: 4 });
|
|
46
|
+
|
|
47
|
+
const projected = projectToPlane(cover, { plane: 'XY' });
|
|
48
|
+
const gasket = projected
|
|
49
|
+
.offset(1.8)
|
|
50
|
+
.onFace(topFace, { protrude: 0.5, selfAnchor: 'center' })
|
|
51
|
+
.extrude(1.2);
|
|
52
|
+
|
|
53
|
+
return [{ name: 'Service Panel Cover', shape: union(cover, gasket) }];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Guard part: trim-created plane cap plus upstream hole/cut rewrites in one ordinary cover workflow.
|
|
2
|
+
|
|
3
|
+
const base = roundedRect(104, 68, 8, true).extrude(18);
|
|
4
|
+
|
|
5
|
+
const gusset = roundedRect(20, 8, 2, true)
|
|
6
|
+
.onFace(base, 'front', { u: -22, v: 4, protrude: 0.25, selfAnchor: 'center' })
|
|
7
|
+
.extrude(10);
|
|
8
|
+
|
|
9
|
+
const merged = union(base, gusset);
|
|
10
|
+
|
|
11
|
+
const servicePocket = roundedRect(22, 12, 2, true)
|
|
12
|
+
.onFace(base, 'top', { u: 18, v: -10, protrude: 0.25, selfAnchor: 'center' });
|
|
13
|
+
|
|
14
|
+
const preTrim = merged
|
|
15
|
+
.hole(base.face('top'), { diameter: 6, u: -24, v: 12, depth: 9 })
|
|
16
|
+
.cutout(servicePocket, { depth: 6 });
|
|
17
|
+
|
|
18
|
+
const trimmed = preTrim.trimByPlane([0, 1, 0], 6);
|
|
19
|
+
|
|
20
|
+
const latch = roundedRect(16, 10, 2, true)
|
|
21
|
+
.extrude(8, { center: true })
|
|
22
|
+
.translate(0, 28, 8);
|
|
23
|
+
|
|
24
|
+
const cover = union(trimmed, latch);
|
|
25
|
+
|
|
26
|
+
return [{ name: 'Trimmed Access Cover', shape: cover }];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Cup with larger top than bottom
|
|
2
|
+
|
|
3
|
+
// Parameters
|
|
4
|
+
const height = param("Height", 80, { min: 40, max: 150, unit: "mm" });
|
|
5
|
+
const topRadius = param("Top Radius", 35, { min: 20, max: 60, unit: "mm" });
|
|
6
|
+
const bottomRadius = param("Bottom Radius", 25, { min: 15, max: 50, unit: "mm" });
|
|
7
|
+
const wallThickness = param("Wall Thickness", 3, { min: 1, max: 8, unit: "mm" });
|
|
8
|
+
const baseThickness = param("Base Thickness", 5, { min: 2, max: 10, unit: "mm" });
|
|
9
|
+
|
|
10
|
+
// Outer cup shape - tapered cylinder (truncated cone)
|
|
11
|
+
const outerCup = cylinder(height, bottomRadius, topRadius);
|
|
12
|
+
|
|
13
|
+
// Inner hollow shape - slightly smaller, shorter to leave a base
|
|
14
|
+
const innerHeight = height - baseThickness;
|
|
15
|
+
const innerTopRadius = topRadius - wallThickness;
|
|
16
|
+
const innerBottomRadius = bottomRadius - wallThickness;
|
|
17
|
+
|
|
18
|
+
// Create hollow interior by subtracting inner cone
|
|
19
|
+
const innerCup = cylinder(innerHeight, innerBottomRadius, innerTopRadius)
|
|
20
|
+
.translate(0, 0, baseThickness);
|
|
21
|
+
|
|
22
|
+
// Final cup with hollow interior
|
|
23
|
+
const cup = outerCup.subtract(innerCup);
|
|
24
|
+
|
|
25
|
+
return cup;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Cut Plane Demo — toggle section views in the View Panel
|
|
2
|
+
//
|
|
3
|
+
// cutPlane(name, normal, offsetOrOptions?, options?)
|
|
4
|
+
// normal: direction pointing toward the side that gets removed
|
|
5
|
+
// offset: distance from origin along the normal where the cut happens
|
|
6
|
+
// exclude: object name(s) to keep uncut for this plane
|
|
7
|
+
|
|
8
|
+
const size = param("Size", 60, { min: 20, max: 120, unit: "mm" });
|
|
9
|
+
const holeR = param("Hole Radius", 12, { min: 4, max: 25, unit: "mm" });
|
|
10
|
+
const cutZ = param("Cut Z", 10, { min: -60, max: 60, unit: "mm" });
|
|
11
|
+
const cutY = param("Cut Y", 0, { min: -60, max: 60, unit: "mm" });
|
|
12
|
+
|
|
13
|
+
// Define cut planes — they appear as toggles in the View Panel
|
|
14
|
+
cutPlane("Horizontal Section", [0, 0, 1], cutZ, { exclude: "Probe" }); // removes top half except Probe
|
|
15
|
+
cutPlane("Front Section", [0, -1, 0], cutY, { exclude: "Probe" }); // removes front half except Probe
|
|
16
|
+
|
|
17
|
+
// Build a box with a cylindrical hole
|
|
18
|
+
const body = box(size, size, size, true);
|
|
19
|
+
const hole = cylinder(size + 2, holeR, holeR, 32, true);
|
|
20
|
+
const part = body.subtract(hole).color("#8aa7c8");
|
|
21
|
+
const probe = cylinder(size + 14, Math.max(2, holeR * 0.22), undefined, 32, true)
|
|
22
|
+
.translate(size * 0.3, size * 0.18, 0)
|
|
23
|
+
.color("#efaa6d");
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
{ name: "Body", shape: part },
|
|
27
|
+
{ name: "Probe", shape: probe },
|
|
28
|
+
];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Door with hinges — the door pivots around the hinge axis using joint().
|
|
2
|
+
// Drag the "Door Angle" slider to open/close the door.
|
|
3
|
+
|
|
4
|
+
const doorW = param("Door Width", 80, { min: 40, max: 120, unit: "mm" });
|
|
5
|
+
const doorH = param("Door Height", 200, { min: 100, max: 300, unit: "mm" });
|
|
6
|
+
const doorT = param("Door Thickness", 4, { min: 2, max: 10, unit: "mm" });
|
|
7
|
+
|
|
8
|
+
const frameW = param("Frame Width", 8, { min: 4, max: 15, unit: "mm" });
|
|
9
|
+
const frameD = param("Frame Depth", 6, { min: 3, max: 12, unit: "mm" });
|
|
10
|
+
|
|
11
|
+
const hingeR = param("Hinge Radius", 3, { min: 1.5, max: 6, unit: "mm" });
|
|
12
|
+
const hingeH = param("Hinge Height", 16, { min: 8, max: 30, unit: "mm" });
|
|
13
|
+
|
|
14
|
+
// ── Door frame (stationary U-shape) ──
|
|
15
|
+
const frame = union(
|
|
16
|
+
box(frameW, frameD, doorH), // left
|
|
17
|
+
box(frameW, frameD, doorH).translate(doorW + frameW, 0, 0), // right
|
|
18
|
+
box(doorW + 2 * frameW, frameD, frameW).translate(0, 0, doorH), // top
|
|
19
|
+
).color("#8B7355");
|
|
20
|
+
|
|
21
|
+
// ── Hinge barrels ──
|
|
22
|
+
const hingePivot = [frameW, 0, 0]; // pivot axis runs along Z at this XY
|
|
23
|
+
const hingeZ1 = doorH * 0.15;
|
|
24
|
+
const hingeZ2 = doorH * 0.85;
|
|
25
|
+
const hinge1 = cylinder(hingeH, hingeR).translate(frameW, 0, hingeZ1 - hingeH / 2).color("#888888");
|
|
26
|
+
const hinge2 = cylinder(hingeH, hingeR).translate(frameW, 0, hingeZ2 - hingeH / 2).color("#888888");
|
|
27
|
+
|
|
28
|
+
// ── Door panel ──
|
|
29
|
+
// Position at frame edge, then use joint() to rotate around hinge axis
|
|
30
|
+
const doorPanel = box(doorW, doorT, doorH)
|
|
31
|
+
.translate(frameW, -doorT / 2, 0)
|
|
32
|
+
.color("#C4A46C");
|
|
33
|
+
|
|
34
|
+
// joint() auto-creates the "Door Angle" slider and rotates around the pivot
|
|
35
|
+
const rotatedDoor = joint("Door Angle", doorPanel, [frameW, 0, 0], {
|
|
36
|
+
axis: [0, 0, 1],
|
|
37
|
+
min: -170,
|
|
38
|
+
max: 0,
|
|
39
|
+
default: -45,
|
|
40
|
+
reverse: true
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ── Phase 1 demo: diagonal intersection to find door center ──
|
|
44
|
+
const doorRect = rectangle(0, 0, doorW, doorH);
|
|
45
|
+
const [d1, d2] = doorRect.diagonals();
|
|
46
|
+
const doorCenter = d1.intersect(d2);
|
|
47
|
+
console.log("Door center (from diagonals):", doorCenter.x.toFixed(1), doorCenter.y.toFixed(1));
|
|
48
|
+
|
|
49
|
+
return [
|
|
50
|
+
{ name: "Frame", shape: frame },
|
|
51
|
+
{ name: "Door", shape: rotatedDoor },
|
|
52
|
+
{ name: "Hinge Bottom", shape: hinge1 },
|
|
53
|
+
{ name: "Hinge Top", shape: hinge2 },
|
|
54
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Headphone Hanger — stroke approach
|
|
2
|
+
// Draw centerline, thicken it. Done.
|
|
3
|
+
|
|
4
|
+
const plateW = param("Plate Width", 50, { min: 30, max: 80, unit: "mm" });
|
|
5
|
+
const t = param("Thickness", 4, { min: 2, max: 8, unit: "mm" });
|
|
6
|
+
const armLen = param("Arm Length", 70, { min: 40, max: 120, unit: "mm" });
|
|
7
|
+
const hookLen = param("Hook Length", 20, { min: 10, max: 40, unit: "mm" });
|
|
8
|
+
const hookAngle = param("Hook Angle", 47, { min: 15, max: 60, unit: "°" });
|
|
9
|
+
const hookP2Length = param("Hook p2 length", 20, { min: 10, max: 40, unit: "mm" });
|
|
10
|
+
|
|
11
|
+
return path()
|
|
12
|
+
.moveTo(0, 0)
|
|
13
|
+
.lineH(plateW)
|
|
14
|
+
.lineV(-armLen)
|
|
15
|
+
.lineAngled(hookLen, 270 - hookAngle)
|
|
16
|
+
.lineH(-hookP2Length)
|
|
17
|
+
.lineAngled(hookLen, 90+hookAngle)
|
|
18
|
+
.stroke(t);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Headphone Hanger — under-desk clamp mount
|
|
2
|
+
// Clamps to the bottom edge of a desk, hook hangs below.
|
|
3
|
+
// Designed for actual 3D printing / building.
|
|
4
|
+
|
|
5
|
+
const deskThick = param("Desk Thickness", 25, { min: 15, max: 50, unit: "mm" });
|
|
6
|
+
const clampDepth = param("Clamp Depth", 40, { min: 25, max: 70, unit: "mm" });
|
|
7
|
+
const width = param("Width", 50, { min: 30, max: 80, unit: "mm" });
|
|
8
|
+
const thick = param("Material Thick", 5, { min: 3, max: 10, unit: "mm" });
|
|
9
|
+
const hookDrop = param("Hook Drop", 60, { min: 30, max: 100, unit: "mm" });
|
|
10
|
+
const hookLen = param("Hook Length", 35, { min: 15, max: 60, unit: "mm" });
|
|
11
|
+
const hookCurveR = param("Hook Curve R", 15, { min: 5, max: 30, unit: "mm" });
|
|
12
|
+
const padThick = param("Pad Thickness", 2, { min: 1, max: 5, unit: "mm" });
|
|
13
|
+
const boltHoleD = param("Bolt Hole", 5, { min: 0, max: 8, unit: "mm" });
|
|
14
|
+
|
|
15
|
+
// The clamp is a C-shape that grips the desk edge.
|
|
16
|
+
// Top jaw sits on top of desk, bottom jaw presses from below.
|
|
17
|
+
// A vertical arm drops down, then curves into the hook.
|
|
18
|
+
|
|
19
|
+
// --- Top jaw (sits on desk surface) ---
|
|
20
|
+
const topJaw = box(width, clampDepth, thick);
|
|
21
|
+
|
|
22
|
+
// --- Vertical back (connects top to bottom jaw) ---
|
|
23
|
+
const backWall = box(width, thick, deskThick + thick * 2)
|
|
24
|
+
.translate(0, 0, -deskThick - thick);
|
|
25
|
+
|
|
26
|
+
// --- Bottom jaw (presses up against desk bottom) ---
|
|
27
|
+
const bottomJaw = box(width, clampDepth * 0.6, thick)
|
|
28
|
+
.translate(0, 0, -deskThick - thick);
|
|
29
|
+
|
|
30
|
+
// --- Pad on bottom jaw (rubber contact, slightly inset) ---
|
|
31
|
+
const pad = box(width - 10, clampDepth * 0.5, padThick)
|
|
32
|
+
.translate(5, 5, -deskThick);
|
|
33
|
+
|
|
34
|
+
// --- Vertical arm dropping down from bottom jaw ---
|
|
35
|
+
const armTop = -deskThick - thick;
|
|
36
|
+
const arm = box(width, thick, hookDrop)
|
|
37
|
+
.translate(0, clampDepth * 0.6 - thick, armTop - hookDrop);
|
|
38
|
+
|
|
39
|
+
// --- Hook (curved part at bottom) ---
|
|
40
|
+
// Use revolve to make a clean quarter-torus for the curve
|
|
41
|
+
const hookZ = armTop - hookDrop;
|
|
42
|
+
const armY = clampDepth * 0.6 - thick;
|
|
43
|
+
|
|
44
|
+
// Quarter-torus: revolve a rect(width x thick) around an axis at hookCurveR distance
|
|
45
|
+
// The profile sits at X = hookCurveR (distance from revolution axis = Y)
|
|
46
|
+
// revolve() goes around Y axis, so profile X = radial, profile Y = height
|
|
47
|
+
const curveProfile = rect(thick, width).translate(hookCurveR, 0);
|
|
48
|
+
const curvePiece = curveProfile.revolve(90)
|
|
49
|
+
// revolve produces shape around Y axis; rotate to align:
|
|
50
|
+
// we need the arc to go from -Z (down) to +Y (forward)
|
|
51
|
+
.rotate(90, 0, 90) // align width along X
|
|
52
|
+
.rotate(180, 0, 0) // flip so arc opens downward-to-forward
|
|
53
|
+
.translate(0, clampDepth * 0.6 + hookCurveR, hookZ);
|
|
54
|
+
|
|
55
|
+
// Straight hook tip extending forward
|
|
56
|
+
const tipY = armY + thick + hookCurveR;
|
|
57
|
+
const tipZ = hookZ - hookCurveR - thick;
|
|
58
|
+
const hookTip = box(width, hookLen - hookCurveR, thick)
|
|
59
|
+
.translate(0, tipY, tipZ);
|
|
60
|
+
|
|
61
|
+
// Small upward lip at the end to prevent headphones from sliding off
|
|
62
|
+
const lipHeight = 8;
|
|
63
|
+
const hookLip = box(width, thick, lipHeight)
|
|
64
|
+
.translate(0, tipY + hookLen - hookCurveR - thick, tipZ + thick);
|
|
65
|
+
|
|
66
|
+
// --- Bolt holes for tightening clamp (optional) ---
|
|
67
|
+
const boltHoles = [];
|
|
68
|
+
if (boltHoleD > 0) {
|
|
69
|
+
// Two holes through the top jaw
|
|
70
|
+
const holeSpacing = width * 0.6;
|
|
71
|
+
for (let i = -1; i <= 1; i += 2) {
|
|
72
|
+
boltHoles.push(
|
|
73
|
+
cylinder(thick + 2, boltHoleD / 2)
|
|
74
|
+
.translate(width / 2 + i * holeSpacing / 2, clampDepth * 0.3, -1)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// --- Assembly ---
|
|
80
|
+
let clamp = union(topJaw, backWall, bottomJaw, arm, curvePiece, hookTip, hookLip);
|
|
81
|
+
if (boltHoles.length > 0) {
|
|
82
|
+
clamp = clamp.subtract(union(...boltHoles));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [
|
|
86
|
+
{ name: "Clamp + Hook", shape: clamp, color: "#445566" },
|
|
87
|
+
{ name: "Desk Pad", shape: pad, color: "#338855" },
|
|
88
|
+
];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Foldable iPhone Stand — two-piece with hinge
|
|
2
|
+
// A base plate and a back support that folds flat for travel.
|
|
3
|
+
|
|
4
|
+
const standW = param("Stand Width", 85, { min: 70, max: 120, unit: "mm" });
|
|
5
|
+
const thick = param("Thickness", 4, { min: 2, max: 8, unit: "mm" });
|
|
6
|
+
const baseLen = param("Base Length", 80, { min: 50, max: 120, unit: "mm" });
|
|
7
|
+
const backH = param("Back Height", 70, { min: 40, max: 120, unit: "mm" });
|
|
8
|
+
const lipH = param("Lip Height", 12, { min: 5, max: 25, unit: "mm" });
|
|
9
|
+
const lipAngle = param("Lip Angle", 80, { min: 60, max: 90, unit: "°" });
|
|
10
|
+
const foldAngle = param("Fold Angle", 65, { min: 0, max: 90, unit: "°" });
|
|
11
|
+
const hingeR = param("Hinge Radius", 3, { min: 2, max: 6, unit: "mm" });
|
|
12
|
+
const cableHoleD = param("Cable Hole", 12, { min: 0, max: 20, unit: "mm" });
|
|
13
|
+
const gripSlots = param("Grip Slots", 3, { min: 0, max: 5 });
|
|
14
|
+
|
|
15
|
+
// --- Base plate (centered on XY, bottom at Z=0) ---
|
|
16
|
+
const base = roundedRect(standW, baseLen, 3, true).extrude(thick);
|
|
17
|
+
|
|
18
|
+
// --- Front lip (holds the phone) ---
|
|
19
|
+
// Build at origin: sits on XZ plane, thick in Y, lipH tall in Z
|
|
20
|
+
// Then tilt backward, then move to front edge
|
|
21
|
+
const lipRaw = box(standW - 10, thick, lipH, true)
|
|
22
|
+
.translate(0, 0, lipH / 2); // lift so bottom edge is at Z=0
|
|
23
|
+
const lipTilted = lipRaw.rotate(-(90 - lipAngle), 0, 0); // tilt back
|
|
24
|
+
const lip = lipTilted.translate(0, -baseLen / 2, thick); // move to front edge, on top of base
|
|
25
|
+
|
|
26
|
+
// --- Grip slots (cross-wise along X to prevent phone sliding forward) ---
|
|
27
|
+
const slotParts = [];
|
|
28
|
+
if (gripSlots > 0) {
|
|
29
|
+
const slotSpacing = (baseLen * 0.5) / (gripSlots + 1);
|
|
30
|
+
for (let i = 0; i < gripSlots; i++) {
|
|
31
|
+
const sy = -baseLen / 4 + slotSpacing * (i + 1);
|
|
32
|
+
slotParts.push(
|
|
33
|
+
box(standW * 0.6, 2, thick + 2, true)
|
|
34
|
+
.translate(0, sy, thick / 2)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// --- Cable hole (near front lip where phone bottom rests) ---
|
|
40
|
+
let baseFinal = union(base, lip);
|
|
41
|
+
if (slotParts.length > 0) {
|
|
42
|
+
baseFinal = baseFinal.subtract(union(...slotParts));
|
|
43
|
+
}
|
|
44
|
+
if (cableHoleD > 0) {
|
|
45
|
+
const cableHole = cylinder(thick + 2, cableHoleD / 2)
|
|
46
|
+
.translate(0, -baseLen / 4, -1);
|
|
47
|
+
baseFinal = baseFinal.subtract(cableHole);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Back support (foldable) ---
|
|
51
|
+
// Build panel at origin standing up in Z, then rotate around its bottom edge, then translate to hinge
|
|
52
|
+
const backPanel = box(standW - 6, thick, backH, true)
|
|
53
|
+
.translate(0, 0, backH / 2); // bottom edge at Z=0
|
|
54
|
+
const backRotated = backPanel
|
|
55
|
+
.rotate(-foldAngle, 0, 0) // fold: 0°=vertical, 90°=flat
|
|
56
|
+
.translate(0, baseLen / 2, thick); // move to back edge of base
|
|
57
|
+
|
|
58
|
+
// --- Hinge cylinders (decorative pivots along X axis) ---
|
|
59
|
+
const hingeLen = 10;
|
|
60
|
+
const hingeLeft = cylinder(hingeLen, hingeR, hingeR)
|
|
61
|
+
.rotate(0, 90, 0)
|
|
62
|
+
.translate(-standW / 2 + 8, baseLen / 2, thick);
|
|
63
|
+
const hingeRight = cylinder(hingeLen, hingeR, hingeR)
|
|
64
|
+
.rotate(0, 90, 0)
|
|
65
|
+
.translate(standW / 2 - 8 - hingeLen, baseLen / 2, thick);
|
|
66
|
+
|
|
67
|
+
return [
|
|
68
|
+
{ name: "Base", shape: baseFinal, color: "#556677" },
|
|
69
|
+
{ name: "Back Support", shape: backRotated, color: "#778899" },
|
|
70
|
+
{ name: "Hinge L", shape: hingeLeft, color: "#aabbcc" },
|
|
71
|
+
{ name: "Hinge R", shape: hingeRight, color: "#aabbcc" },
|
|
72
|
+
];
|