forgecad 0.6.3 → 0.7.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/README.md +2 -11
- package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-DAu1C1ST.js} +250 -151
- package/dist/assets/{BlogPage-P_AJP0v9.js → BlogPage-CJEXL_zJ.js} +94 -70
- package/dist/assets/{DocsPage-CKRV2iq2.js → DocsPage-Gc_BCdqC.js} +269 -143
- package/dist/assets/EditorApp-D9bJvtf7.js +11338 -0
- package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-DG1-oUSV.css} +459 -87
- package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-CEO8XbV8.js} +2 -4
- package/dist/assets/LandingPage-CdCuEOdC.js +451 -0
- package/dist/assets/PricingPage-BSrxu6d7.js +232 -0
- package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-FUCSIRq6.js} +129 -5
- package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-KoR0SNKq.js} +6770 -2914
- package/dist/assets/{index-2hfs_ub0.css → index-CyVd1D4D.css} +227 -53
- package/dist/assets/{Viewport-CoB46f5R.js → index-wTEK39at.js} +31385 -6439
- package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
- package/dist/assets/{manifold-CqNMHHKO.js → manifold-B1sGWdYk.js} +4 -3
- package/dist/assets/{manifold-Cce9wRFz.js → manifold-D7o0N50J.js} +1 -1
- package/dist/assets/{manifold-D6BeHIOo.js → manifold-G5sBaXzi.js} +1 -1
- package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-DYcRHhv9.js} +6798 -3341
- package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
- package/dist/docs-raw/generated/assembly.md +691 -112
- package/dist/docs-raw/generated/concepts.md +1225 -1400
- package/dist/docs-raw/generated/core.md +464 -1412
- package/dist/docs-raw/generated/curves.md +593 -117
- package/dist/docs-raw/generated/lib.md +38 -748
- package/dist/docs-raw/generated/output.md +139 -245
- package/dist/docs-raw/generated/sheet-metal.md +473 -21
- package/dist/docs-raw/generated/sketch.md +553 -349
- package/dist/docs-raw/generated/viewport.md +345 -303
- package/dist/docs-raw/generated/wood.md +104 -0
- package/dist/index.html +2 -2
- package/dist/sitemap.xml +6 -6
- package/dist-cli/chunk-PZ5AY32C.js +10 -0
- package/dist-cli/chunk-PZ5AY32C.js.map +1 -0
- package/dist-cli/forgecad.js +9435 -5407
- package/dist-cli/forgecad.js.map +1 -0
- package/dist-cli/solver-FV7TJZGI.js +365 -0
- package/dist-cli/solver-FV7TJZGI.js.map +1 -0
- package/dist-skill/CONTEXT.md +3186 -7145
- package/dist-skill/SKILL-dev.md +21 -63
- package/dist-skill/SKILL.md +12 -56
- package/dist-skill/docs/API/core/concepts.md +16 -98
- package/dist-skill/docs/CLI/export.md +91 -0
- package/dist-skill/docs/CLI/projects.md +107 -0
- package/dist-skill/docs/CLI/studio_publishing.md +52 -0
- package/dist-skill/docs/CLI/validation.md +66 -0
- package/dist-skill/docs/generated/assembly.md +691 -112
- package/dist-skill/docs/generated/core.md +464 -1412
- package/dist-skill/docs/generated/curves.md +593 -117
- package/dist-skill/docs/generated/lib.md +38 -748
- package/dist-skill/docs/generated/output.md +139 -245
- package/dist-skill/docs/generated/sheet-metal.md +473 -21
- package/dist-skill/docs/generated/sketch.md +553 -349
- package/dist-skill/docs/generated/viewport.md +345 -303
- package/dist-skill/docs/generated/wood.md +104 -0
- package/dist-skill/docs/guides/coordinate-system.md +11 -17
- package/dist-skill/docs/guides/geometry-conventions.md +13 -70
- package/dist-skill/docs/guides/modeling-recipes.md +22 -195
- package/dist-skill/docs/guides/positioning.md +88 -147
- package/dist-skill/docs-dev/API/core/concepts.md +51 -0
- package/dist-skill/docs-dev/API/core/sdf-advanced.md +92 -0
- package/dist-skill/docs-dev/API/core/sdf-primitives.md +58 -0
- package/dist-skill/docs-dev/API/core/sdf-workflow.md +42 -0
- package/dist-skill/docs-dev/CLI/export.md +91 -0
- package/dist-skill/docs-dev/CLI/projects.md +107 -0
- package/dist-skill/docs-dev/CLI/studio_publishing.md +52 -0
- package/dist-skill/docs-dev/CLI/validation.md +66 -0
- package/dist-skill/{docs → docs-dev}/blueprint-first.md +5 -0
- package/dist-skill/{docs → docs-dev}/coding-best-practices.md +6 -8
- package/dist-skill/{docs → docs-dev}/coding.md +1 -3
- package/dist-skill/docs-dev/generated/assembly.md +771 -0
- package/dist-skill/docs-dev/generated/core.md +775 -0
- package/dist-skill/docs-dev/generated/curves.md +688 -0
- package/dist-skill/docs-dev/generated/lib.md +50 -0
- package/dist-skill/docs-dev/generated/output.md +234 -0
- package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
- package/dist-skill/docs-dev/generated/sketch.md +801 -0
- package/dist-skill/docs-dev/generated/viewport.md +486 -0
- package/dist-skill/docs-dev/generated/wood.md +104 -0
- package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
- package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
- package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
- package/dist-skill/docs-dev/guides/positioning.md +151 -0
- package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
- package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
- package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
- package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
- package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
- package/examples/api/attachTo-basics.forge.js +5 -5
- package/examples/api/boolean-operations.forge.js +3 -3
- package/examples/api/bounding-box-visualizer.forge.js +2 -2
- package/examples/api/clone-duplicate.forge.js +1 -1
- package/examples/api/colors-union-vs-array.forge.js +6 -6
- package/examples/api/connector-assembly.forge.js +4 -4
- package/examples/api/connector-basics.forge.js +2 -2
- package/examples/api/extrude-options.forge.js +4 -10
- package/examples/api/feature-created-faces.forge.js +6 -10
- package/examples/api/fillet-showcase.forge.js +1 -1
- package/examples/api/folded-service-panel-cover.forge.js +2 -2
- package/examples/api/group-test.forge.js +1 -1
- package/examples/api/group-vs-union.forge.js +1 -1
- package/examples/api/highlight-debug.forge.js +4 -0
- package/examples/api/js-module-pillars.js +1 -1
- package/examples/api/js-module-scene.js +2 -2
- package/examples/api/mesh-import-slats.forge.js +1 -1
- package/examples/api/pointAlong-orientation.forge.js +1 -1
- package/examples/api/profile-2020-b-slot6.forge.js +0 -1
- package/examples/api/route-perimeter-flange.forge.js +1 -1
- package/examples/api/sdf-rover-demo.forge.js +10 -10
- package/examples/api/sketch-on-face-demo.forge.js +2 -2
- package/examples/api/sketch-regions.forge.js +4 -4
- package/examples/api/transition-curves.forge.js +1 -1
- package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
- package/examples/api/variable-sweep-test.forge.js +2 -2
- package/examples/api/wood-joinery.forge.js +60 -0
- package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
- package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
- package/examples/experiments/drone-arm.forge.js +53 -0
- package/examples/furniture/adjustable-table.forge.js +2 -2
- package/examples/furniture/bathroom.forge.js +11 -11
- package/examples/furniture/chair.forge.js +1 -1
- package/examples/generative/crystal-growth.forge.js +2 -2
- package/examples/generative/frost-spires.forge.js +3 -3
- package/examples/generative/golden-spiral-tower.forge.js +3 -3
- package/examples/mechanical/3d-printer.forge.js +28 -28
- package/examples/mechanical/5-finger-robot-hand.forge.js +15 -15
- package/examples/mechanical/airplane-propeller.forge.js +2 -2
- package/examples/mechanical/fillet-enclosure.forge.js +1 -1
- package/examples/mechanical/headphone-hanger-v2.forge.js +2 -2
- package/examples/mechanical/robot_hand.forge.js +15 -15
- package/examples/mechanical/robot_hand_2.forge.js +9 -9
- package/examples/products/bottle.forge.js +1 -1
- package/examples/products/chess-set.forge.js +19 -19
- package/examples/products/classical-piano.forge.js +11 -11
- package/examples/products/clock.forge.js +12 -12
- package/examples/products/iphone.forge.js +8 -8
- package/examples/products/laptop.forge.js +15 -15
- package/examples/products/liquid-soap-dispenser.forge.js +18 -18
- package/examples/products/origami-fish.forge.js +8 -6
- package/examples/products/spiderman-cake.forge.js +4 -4
- package/examples/toolbox/bolted-joint.forge.js +2 -2
- package/package.json +7 -4
- package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
- package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
- package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
- package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
- package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
- package/dist/assets/index-1CYp3zUp.js +0 -1455
- package/dist/docs-raw/CLI.md +0 -865
- package/dist-skill/docs/API/API.md +0 -1666
- package/dist-skill/docs/API/README.md +0 -37
- package/dist-skill/docs/API/assembly/assembly.md +0 -617
- package/dist-skill/docs/API/core/edge-queries.md +0 -130
- package/dist-skill/docs/API/core/parameters.md +0 -122
- package/dist-skill/docs/API/core/reserved-terms.md +0 -137
- package/dist-skill/docs/API/core/sdf.md +0 -326
- package/dist-skill/docs/API/core/skill-cli.md +0 -194
- package/dist-skill/docs/API/core/skill-guide.md +0 -205
- package/dist-skill/docs/API/core/specs.md +0 -186
- package/dist-skill/docs/API/core/topology.md +0 -372
- package/dist-skill/docs/API/entities.md +0 -268
- package/dist-skill/docs/API/output/bom.md +0 -58
- package/dist-skill/docs/API/output/brep-export.md +0 -87
- package/dist-skill/docs/API/output/dimensions.md +0 -67
- package/dist-skill/docs/API/output/export.md +0 -110
- package/dist-skill/docs/API/output/gcode.md +0 -195
- package/dist-skill/docs/API/runtime/viewport.md +0 -420
- package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
- package/dist-skill/docs/API/sketch/anchor.md +0 -37
- package/dist-skill/docs/API/sketch/booleans.md +0 -91
- package/dist-skill/docs/API/sketch/core.md +0 -73
- package/dist-skill/docs/API/sketch/extrude.md +0 -62
- package/dist-skill/docs/API/sketch/on-face.md +0 -104
- package/dist-skill/docs/API/sketch/operations.md +0 -78
- package/dist-skill/docs/API/sketch/path.md +0 -75
- package/dist-skill/docs/API/sketch/primitives.md +0 -146
- package/dist-skill/docs/API/sketch/regions.md +0 -80
- package/dist-skill/docs/API/sketch/text.md +0 -108
- package/dist-skill/docs/API/sketch/transforms.md +0 -65
- package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
- package/dist-skill/docs/CLI.md +0 -865
- package/dist-skill/docs/INDEX.md +0 -94
- package/dist-skill/docs/RELEASING.md +0 -55
- package/dist-skill/docs/cli-monetization.md +0 -111
- package/dist-skill/docs/deployment.md +0 -281
- package/dist-skill/docs/generated/concepts.md +0 -2112
- package/dist-skill/docs/internals/shape-from-slices.md +0 -152
- package/dist-skill/docs/platform/admin.md +0 -45
- package/dist-skill/docs/platform/architecture.md +0 -79
- package/dist-skill/docs/platform/auth.md +0 -110
- package/dist-skill/docs/platform/email.md +0 -67
- package/dist-skill/docs/platform/projects.md +0 -111
- package/dist-skill/docs/platform/sharing.md +0 -90
- package/dist-skill/docs/runbook.md +0 -345
|
@@ -29,4 +29,8 @@ highlight(b.edge('top-right'), { color: 'blue', label: 'top-right edge' });
|
|
|
29
29
|
// Shape highlight — transparent colored overlay on the entire shape
|
|
30
30
|
highlight(b, { color: '#ff00ff' });
|
|
31
31
|
|
|
32
|
+
// Intermediary shape highlight — snapshot a temporary shape that is not returned
|
|
33
|
+
const temporary = b.translate(40, 0, 0);
|
|
34
|
+
highlight(temporary, { color: '#00ffff', label: 'temporary snapshot' });
|
|
35
|
+
|
|
32
36
|
return b;
|
|
@@ -16,7 +16,7 @@ export default class PillarPair {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
build() {
|
|
19
|
-
const pillar = box(6, 6, this.height
|
|
19
|
+
const pillar = box(6, 6, this.height);
|
|
20
20
|
return union(
|
|
21
21
|
pillar.translate(-this.spacing / 2, 0, this.height / 2),
|
|
22
22
|
pillar.translate(this.spacing / 2, 0, this.height / 2),
|
|
@@ -2,8 +2,8 @@ import { box, union } from "forgecad";
|
|
|
2
2
|
import PillarPair, { capHeight } from "./js-module-pillars.js";
|
|
3
3
|
|
|
4
4
|
export function buildAssembly() {
|
|
5
|
-
const base = box(40, 18, 4
|
|
5
|
+
const base = box(40, 18, 4);
|
|
6
6
|
const pillars = new PillarPair(24, 12).build().translate(0, 0, 2);
|
|
7
|
-
const cap = box(14, 18, capHeight
|
|
7
|
+
const cap = box(14, 18, capHeight).translate(0, 0, 17);
|
|
8
8
|
return union(base, pillars, cap).color("#d6a86a");
|
|
9
9
|
}
|
|
@@ -19,7 +19,7 @@ const slats = [];
|
|
|
19
19
|
for (let i = 0; i < slatCount; i++) {
|
|
20
20
|
const y = center[1] - (slatCount - 1) * pitch / 2 + i * pitch;
|
|
21
21
|
slats.push(
|
|
22
|
-
box(size[0] + 10, slatThickness, size[2] + 10
|
|
22
|
+
box(size[0] + 10, slatThickness, size[2] + 10)
|
|
23
23
|
.translate(center[0], y, center[2])
|
|
24
24
|
);
|
|
25
25
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// pointAlong() — orient a cylinder's axis without thinking about Euler angles.
|
|
2
2
|
//
|
|
3
3
|
// Cylinders default to Z-up. To lay one along X or Y:
|
|
4
|
-
// ❌ cylinder(80, 5).
|
|
4
|
+
// ❌ cylinder(80, 5).rotateX(90) — works, but less readable
|
|
5
5
|
// ✅ cylinder(80, 5).pointAlong([0, 1, 0]) — "point along Y"
|
|
6
6
|
//
|
|
7
7
|
// After pointAlong, the cylinder starts at origin and extends in that direction.
|
|
@@ -29,7 +29,7 @@ let body = constrainedSketch()
|
|
|
29
29
|
// Subtract center bore
|
|
30
30
|
body = body.subtract(circle2d(R_CORE_IN))
|
|
31
31
|
|
|
32
|
-
const solidCutRectangle = rect(200, 8
|
|
32
|
+
const solidCutRectangle = rect(200, 8);
|
|
33
33
|
const solidRing = circle2d(R_CORE)
|
|
34
34
|
.subtract(circle2d(R_CORE_IN))
|
|
35
35
|
.subtract(solidCutRectangle)
|
|
@@ -26,33 +26,33 @@ const wheelbase = 250;
|
|
|
26
26
|
const groundClearance = 26;
|
|
27
27
|
const bodyZ = wheelRadius + groundClearance + chassisHeight * 0.5;
|
|
28
28
|
|
|
29
|
-
const baseDeck = box(chassisLength, chassisWidth, chassisHeight
|
|
29
|
+
const baseDeck = box(chassisLength, chassisWidth, chassisHeight)
|
|
30
30
|
.translate(0, 0, bodyZ);
|
|
31
31
|
|
|
32
|
-
const roofPod = box(roofLength, roofWidth, roofHeight
|
|
32
|
+
const roofPod = box(roofLength, roofWidth, roofHeight)
|
|
33
33
|
.translate(20, 0, bodyZ + 40);
|
|
34
34
|
|
|
35
35
|
const bumper = union(
|
|
36
|
-
box(54, bumperWidth, bumperDepth
|
|
37
|
-
box(bumperLength, bumperWidth - 42, bumperDepth * 0.7
|
|
36
|
+
box(54, bumperWidth, bumperDepth).translate(chassisLength * 0.5 - 18, 0, wheelRadius + 6),
|
|
37
|
+
box(bumperLength, bumperWidth - 42, bumperDepth * 0.7).translate(chassisLength * 0.5 + 46, 0, wheelRadius - 10),
|
|
38
38
|
).color('#c8742b');
|
|
39
39
|
|
|
40
40
|
const sensorMast = union(
|
|
41
|
-
cylinder(92, 10, undefined, 40
|
|
42
|
-
box(78, 34, 26
|
|
41
|
+
cylinder(92, 10, undefined, 40).translate(58, 0, bodyZ + 78),
|
|
42
|
+
box(78, 34, 26).translate(88, 0, bodyZ + 126),
|
|
43
43
|
).color('#d7dee8');
|
|
44
44
|
|
|
45
45
|
const chassis = union(baseDeck, roofPod)
|
|
46
46
|
.color('#60707d');
|
|
47
47
|
|
|
48
48
|
const wheelTire = difference(
|
|
49
|
-
cylinder(wheelWidth, wheelRadius, undefined, 64
|
|
50
|
-
cylinder(wheelWidth + 2, wheelRadius * 0.56, undefined, 48
|
|
49
|
+
cylinder(wheelWidth, wheelRadius, undefined, 64).pointAlong([0, 1, 0]),
|
|
50
|
+
cylinder(wheelWidth + 2, wheelRadius * 0.56, undefined, 48).pointAlong([0, 1, 0]),
|
|
51
51
|
).color('#1d2329');
|
|
52
52
|
|
|
53
53
|
const wheelRim = union(
|
|
54
|
-
cylinder(wheelWidth * 0.86, wheelRadius * 0.52, undefined, 40
|
|
55
|
-
cylinder(wheelWidth * 1.02, wheelRadius * 0.16, undefined, 28
|
|
54
|
+
cylinder(wheelWidth * 0.86, wheelRadius * 0.52, undefined, 40).pointAlong([0, 1, 0]),
|
|
55
|
+
cylinder(wheelWidth * 1.02, wheelRadius * 0.16, undefined, 28).pointAlong([0, 1, 0]),
|
|
56
56
|
).color('#b8c5d3');
|
|
57
57
|
|
|
58
58
|
const wheel = group(
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
const W = 80, D = 50, H = 30;
|
|
9
9
|
|
|
10
10
|
// ── Base enclosure ────────────────────────────────────────────────────────────
|
|
11
|
-
const body = roundedRect(W, D, 4
|
|
11
|
+
const body = roundedRect(W, D, 4)
|
|
12
12
|
.extrude(H)
|
|
13
13
|
.color('#c8cdd6');
|
|
14
14
|
|
|
@@ -41,7 +41,7 @@ const vents = ribSketch
|
|
|
41
41
|
.color('#1a1a2e');
|
|
42
42
|
|
|
43
43
|
// ── Badge recess on the front face ────────────────────────────────────────────
|
|
44
|
-
const badge = roundedRect(36, 12, 2
|
|
44
|
+
const badge = roundedRect(36, 12, 2)
|
|
45
45
|
.onFace(body, 'front', { v: 4, protrude: 0.05 })
|
|
46
46
|
.extrude(1.5)
|
|
47
47
|
.color('#2563eb');
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
// A "T" cross-section: wide base + narrow stem. The boolean creates one
|
|
23
23
|
// connected region; we pick it with the regions() API.
|
|
24
24
|
|
|
25
|
-
const base = rect(120, 20
|
|
26
|
-
const stem = rect(30, 50
|
|
25
|
+
const base = rect(120, 20); // centered at origin
|
|
26
|
+
const stem = rect(30, 50).translate(0, 35); // stem centered above base
|
|
27
27
|
const tShape = base.add(stem);
|
|
28
28
|
const [tRegion] = tShape.regions(); // one connected T region
|
|
29
29
|
const tPart = tRegion.extrude(6); // extrude the T profile
|
|
@@ -32,8 +32,8 @@ const tPart = tRegion.extrude(6); // extrude the T profile
|
|
|
32
32
|
// A 100×40 plate with a 60×20 hole punched in the center, leaving a frame.
|
|
33
33
|
// Pick the frame ring as one region using a corner seed.
|
|
34
34
|
|
|
35
|
-
const plate = rect(100, 40
|
|
36
|
-
const hole = rect(60, 20
|
|
35
|
+
const plate = rect(100, 40); // centered at origin
|
|
36
|
+
const hole = rect(60, 20); // centered cutout
|
|
37
37
|
const frame = plate.subtract(hole);
|
|
38
38
|
|
|
39
39
|
// The seed [45, 15] is in the top-right corner — inside the frame wall, outside the hole.
|
|
@@ -9,7 +9,7 @@ const radius = param("Tube Radius", 1.5, { min: 0.3, max: 5, unit: "mm" });
|
|
|
9
9
|
// Small anchor block at a point (visual reference for where the tube connects)
|
|
10
10
|
function anchor(pos) {
|
|
11
11
|
const s = 3;
|
|
12
|
-
return box(s, s, s
|
|
12
|
+
return box(s, s, s).translate(pos[0], pos[1], pos[2]);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// ── 1. L-bend: 90-degree pipe turn ──────────────────────────────
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Pure-SDF reference for the variableSweep taper test.
|
|
2
|
+
// If this renders with clean sidewalls while variable-sweep-test.forge.js does not,
|
|
3
|
+
// the artifact is in variableSweep()'s spine field construction rather than the
|
|
4
|
+
// shared SDF meshing pipeline.
|
|
5
|
+
|
|
6
|
+
const controlPoints = [
|
|
7
|
+
[0, 0, 0],
|
|
8
|
+
[20, 0, 10],
|
|
9
|
+
[40, 10, 20],
|
|
10
|
+
[60, 10, 30],
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const spine = spline3d(controlPoints, { tension: 0.4 });
|
|
14
|
+
const pathPoints = spine.sample(160);
|
|
15
|
+
|
|
16
|
+
const cumulativeLengths = [0];
|
|
17
|
+
for (let index = 1; index < pathPoints.length; index += 1) {
|
|
18
|
+
const [ax, ay, az] = pathPoints[index - 1];
|
|
19
|
+
const [bx, by, bz] = pathPoints[index];
|
|
20
|
+
cumulativeLengths.push(
|
|
21
|
+
cumulativeLengths[cumulativeLengths.length - 1] + Math.hypot(bx - ax, by - ay, bz - az),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const totalLength = cumulativeLengths[cumulativeLengths.length - 1];
|
|
25
|
+
|
|
26
|
+
const smallRadius = 3;
|
|
27
|
+
const largeRadius = 8;
|
|
28
|
+
const maxRadius = largeRadius;
|
|
29
|
+
const pad = maxRadius + 4;
|
|
30
|
+
|
|
31
|
+
let minX = Infinity;
|
|
32
|
+
let minY = Infinity;
|
|
33
|
+
let minZ = Infinity;
|
|
34
|
+
let maxX = -Infinity;
|
|
35
|
+
let maxY = -Infinity;
|
|
36
|
+
let maxZ = -Infinity;
|
|
37
|
+
for (const [x, y, z] of pathPoints) {
|
|
38
|
+
minX = Math.min(minX, x);
|
|
39
|
+
minY = Math.min(minY, y);
|
|
40
|
+
minZ = Math.min(minZ, z);
|
|
41
|
+
maxX = Math.max(maxX, x);
|
|
42
|
+
maxY = Math.max(maxY, y);
|
|
43
|
+
maxZ = Math.max(maxZ, z);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const embeddedPath = JSON.stringify(pathPoints);
|
|
47
|
+
const embeddedArc = JSON.stringify(cumulativeLengths);
|
|
48
|
+
|
|
49
|
+
const pureSweepSdf = new Function(`
|
|
50
|
+
return (x, y, z) => (() => {
|
|
51
|
+
const pts = ${embeddedPath};
|
|
52
|
+
const arc = ${embeddedArc};
|
|
53
|
+
const totalLength = ${totalLength};
|
|
54
|
+
const smallRadius = ${smallRadius};
|
|
55
|
+
const largeRadius = ${largeRadius};
|
|
56
|
+
|
|
57
|
+
function clamp(v, lo, hi) {
|
|
58
|
+
return Math.max(lo, Math.min(hi, v));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function catmullScalar(p0, p1, p2, p3, t0, t1, t2, t3, t) {
|
|
62
|
+
const dt = Math.max(t2 - t1, 1e-9);
|
|
63
|
+
const local = clamp((t - t1) / dt, 0, 1);
|
|
64
|
+
const tt = local * local;
|
|
65
|
+
const ttt = tt * local;
|
|
66
|
+
const m1 = (p2 - p0) / Math.max(t2 - t0, 1e-9);
|
|
67
|
+
const m2 = (p3 - p1) / Math.max(t3 - t1, 1e-9);
|
|
68
|
+
const h00 = 2 * ttt - 3 * tt + 1;
|
|
69
|
+
const h10 = ttt - 2 * tt + local;
|
|
70
|
+
const h01 = -2 * ttt + 3 * tt;
|
|
71
|
+
const h11 = ttt - tt;
|
|
72
|
+
return h00 * p1 + h10 * dt * m1 + h01 * p2 + h11 * dt * m2;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function radiusAt(t) {
|
|
76
|
+
const tc = clamp(t, 0, 1);
|
|
77
|
+
if (tc <= 0) return smallRadius;
|
|
78
|
+
if (tc >= 1) return smallRadius;
|
|
79
|
+
if (tc <= 0.5) return catmullScalar(smallRadius, smallRadius, largeRadius, smallRadius, -0.5, 0, 0.5, 1, tc);
|
|
80
|
+
return catmullScalar(smallRadius, largeRadius, smallRadius, smallRadius, 0, 0.5, 1, 1.5, tc);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let bestDist2 = Infinity;
|
|
84
|
+
let bestIndex = 0;
|
|
85
|
+
let bestSegmentT = 0;
|
|
86
|
+
let bestPoint = pts[0];
|
|
87
|
+
|
|
88
|
+
for (let index = 0; index < pts.length - 1; index += 1) {
|
|
89
|
+
const a = pts[index];
|
|
90
|
+
const b = pts[index + 1];
|
|
91
|
+
const dx = b[0] - a[0];
|
|
92
|
+
const dy = b[1] - a[1];
|
|
93
|
+
const dz = b[2] - a[2];
|
|
94
|
+
const len2 = dx * dx + dy * dy + dz * dz;
|
|
95
|
+
if (len2 < 1e-12) continue;
|
|
96
|
+
|
|
97
|
+
const px = x - a[0];
|
|
98
|
+
const py = y - a[1];
|
|
99
|
+
const pz = z - a[2];
|
|
100
|
+
const segT = clamp((px * dx + py * dy + pz * dz) / len2, 0, 1);
|
|
101
|
+
const qx = a[0] + dx * segT;
|
|
102
|
+
const qy = a[1] + dy * segT;
|
|
103
|
+
const qz = a[2] + dz * segT;
|
|
104
|
+
const rx = x - qx;
|
|
105
|
+
const ry = y - qy;
|
|
106
|
+
const rz = z - qz;
|
|
107
|
+
const dist2 = rx * rx + ry * ry + rz * rz;
|
|
108
|
+
|
|
109
|
+
if (dist2 < bestDist2) {
|
|
110
|
+
bestDist2 = dist2;
|
|
111
|
+
bestIndex = index;
|
|
112
|
+
bestSegmentT = segT;
|
|
113
|
+
bestPoint = [qx, qy, qz];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const radiusT = (arc[bestIndex] + (arc[bestIndex + 1] - arc[bestIndex]) * bestSegmentT) / Math.max(totalLength, 1e-9);
|
|
118
|
+
let sdfValue = Math.sqrt(bestDist2) - radiusAt(radiusT);
|
|
119
|
+
|
|
120
|
+
if (bestIndex === 0 && bestSegmentT <= 1e-6) {
|
|
121
|
+
const a = pts[0];
|
|
122
|
+
const b = pts[1];
|
|
123
|
+
const dx = b[0] - a[0];
|
|
124
|
+
const dy = b[1] - a[1];
|
|
125
|
+
const dz = b[2] - a[2];
|
|
126
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
127
|
+
if (len > 1e-9) {
|
|
128
|
+
const tx = dx / len;
|
|
129
|
+
const ty = dy / len;
|
|
130
|
+
const tz = dz / len;
|
|
131
|
+
const startCap = -((x - a[0]) * tx + (y - a[1]) * ty + (z - a[2]) * tz);
|
|
132
|
+
sdfValue = Math.max(sdfValue, startCap);
|
|
133
|
+
}
|
|
134
|
+
} else if (bestIndex === pts.length - 2 && bestSegmentT >= 1 - 1e-6) {
|
|
135
|
+
const a = pts[pts.length - 2];
|
|
136
|
+
const b = pts[pts.length - 1];
|
|
137
|
+
const dx = b[0] - a[0];
|
|
138
|
+
const dy = b[1] - a[1];
|
|
139
|
+
const dz = b[2] - a[2];
|
|
140
|
+
const len = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
141
|
+
if (len > 1e-9) {
|
|
142
|
+
const tx = dx / len;
|
|
143
|
+
const ty = dy / len;
|
|
144
|
+
const tz = dz / len;
|
|
145
|
+
const endCap = (x - b[0]) * tx + (y - b[1]) * ty + (z - b[2]) * tz;
|
|
146
|
+
sdfValue = Math.max(sdfValue, endCap);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return sdfValue;
|
|
151
|
+
})();
|
|
152
|
+
`)();
|
|
153
|
+
|
|
154
|
+
const tapered = sdf.fromFunction(
|
|
155
|
+
pureSweepSdf,
|
|
156
|
+
{
|
|
157
|
+
min: [minX - pad, minY - pad, minZ - pad],
|
|
158
|
+
max: [maxX + pad, maxY + pad, maxZ + pad],
|
|
159
|
+
},
|
|
160
|
+
).toShape({ edgeLength: 0.8 });
|
|
161
|
+
|
|
162
|
+
return tapered.color('#8899aa');
|
|
@@ -8,8 +8,8 @@ const spine = spline3d([
|
|
|
8
8
|
[60, 10, 30],
|
|
9
9
|
], { tension: 0.4 });
|
|
10
10
|
|
|
11
|
-
const smallCircle = circle2d(3
|
|
12
|
-
const largeCircle = circle2d(8
|
|
11
|
+
const smallCircle = circle2d(3);
|
|
12
|
+
const largeCircle = circle2d(8);
|
|
13
13
|
|
|
14
14
|
const tapered = variableSweep(spine, [
|
|
15
15
|
{ t: 0.0, profile: smallCircle },
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Wood Joinery — dado, rabbet, mortise & tenon
|
|
2
|
+
// Demonstrates joint cuts and assembled positioning.
|
|
3
|
+
|
|
4
|
+
const t = 18; // stock thickness
|
|
5
|
+
|
|
6
|
+
// ─── BOOKCASE CORNER ───────────────────────────────────────────
|
|
7
|
+
// Side panel lies flat: width=250 (X, depth), height=400 (Y, vertical), thickness=18 (Z, face)
|
|
8
|
+
const side = Wood.board(250, 400, t, { species: 'birch' });
|
|
9
|
+
const shelf = Wood.board(250, 300, t, { species: 'birch' });
|
|
10
|
+
|
|
11
|
+
// Dado: channel across the face of side for shelf to sit in
|
|
12
|
+
Wood.dado(side, shelf, { fromBottom: 150 });
|
|
13
|
+
|
|
14
|
+
// Rabbet: step on the back face for a back panel
|
|
15
|
+
Wood.rabbet(side, { edge: 'back', width: 6, depth: 9 });
|
|
16
|
+
|
|
17
|
+
// Assemble the bookcase corner:
|
|
18
|
+
// Side panel stands upright — rotate so Y (400mm) becomes vertical (Z)
|
|
19
|
+
const sideAssembled = side.shape.rotateX(90).color('#c4956a');
|
|
20
|
+
|
|
21
|
+
// Shelf sits in the dado channel
|
|
22
|
+
// After side.rotateX(90): side is in XZ plane, dado channel is at Z = -200 + 150 + 9 = -41
|
|
23
|
+
// Shelf needs its 18mm thickness edge in the dado
|
|
24
|
+
const shelfAssembled = shelf.shape
|
|
25
|
+
.rotateX(90) // stand upright
|
|
26
|
+
.rotateZ(90) // perpendicular to side
|
|
27
|
+
.translate(0, t, -200 + 150 + t / 2)
|
|
28
|
+
.color('#b8864e');
|
|
29
|
+
|
|
30
|
+
// ─── TABLE JOINT: MORTISE & TENON ──────────────────────────────
|
|
31
|
+
// Leg: width=45 (X), height=400 (Y, vertical), thickness=45 (Z, face for mortise)
|
|
32
|
+
// The mortise cuts into Z (45mm face), positioned along Y (400mm height)
|
|
33
|
+
const leg = Wood.board(45, 400, 45, { species: 'oak' });
|
|
34
|
+
const apron = Wood.board(300, 80, 20, { species: 'oak' });
|
|
35
|
+
|
|
36
|
+
Wood.mortiseAndTenon(leg, apron, {
|
|
37
|
+
style: 'blind',
|
|
38
|
+
position: { fromTop: 25 },
|
|
39
|
+
cornerRadius: 4,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Tenon defaults: thickness = 20/3 ≈ 6.7mm, width = min(48, 320) = 48mm, length = 45*2/3 = 30mm
|
|
43
|
+
|
|
44
|
+
// Assemble: leg stands on its Y-axis (already vertical), apron connects to face
|
|
45
|
+
const legAssembled = leg.shape
|
|
46
|
+
.translate(500, 0, 0)
|
|
47
|
+
.color('#a07040');
|
|
48
|
+
|
|
49
|
+
// Apron: tenon protrudes from +X end. Rotate so it points into the leg's Z-face.
|
|
50
|
+
const apronAssembled = apron.shape
|
|
51
|
+
.rotateY(90) // tenon now points +Z (into leg face)
|
|
52
|
+
.translate(500, 200 - 25 - 24, 45) // align with mortise position on leg
|
|
53
|
+
.color('#8b6030');
|
|
54
|
+
|
|
55
|
+
return [
|
|
56
|
+
{ name: 'Side (dado + rabbet)', shape: sideAssembled },
|
|
57
|
+
{ name: 'Shelf', shape: shelfAssembled },
|
|
58
|
+
{ name: 'Leg (mortise)', shape: legAssembled },
|
|
59
|
+
{ name: 'Apron (tenon)', shape: apronAssembled },
|
|
60
|
+
];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Guard part: shell + face-driven cuts + mirrored feet in one ordinary enclosure workflow.
|
|
2
2
|
|
|
3
|
-
const base = roundedRect(120, 80, 10
|
|
3
|
+
const base = roundedRect(120, 80, 10).extrude(36);
|
|
4
4
|
const shell = base.shell(3, { openFaces: ['top'] });
|
|
5
5
|
|
|
6
|
-
const displayCut = roundedRect(34, 18, 3
|
|
6
|
+
const displayCut = roundedRect(34, 18, 3)
|
|
7
7
|
.onFace(base, 'front', { u: 0, v: 8, protrude: 0.25, selfAnchor: 'center' })
|
|
8
8
|
.extrude(10);
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ const cableCut = circle2d(7)
|
|
|
11
11
|
.onFace(base, 'right', { u: -12, v: -8, protrude: 0.25, selfAnchor: 'center' })
|
|
12
12
|
.extrude(10);
|
|
13
13
|
|
|
14
|
-
const foot = roundedRect(18, 18, 4
|
|
14
|
+
const foot = roundedRect(18, 18, 4)
|
|
15
15
|
.onFace(base, 'bottom', { u: 36, v: 20, protrude: 0, selfAnchor: 'center' })
|
|
16
16
|
.extrude(6);
|
|
17
17
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Guard part: counterbore/countersink hole variants plus an up-to-face service pocket in one ordinary plate workflow.
|
|
2
2
|
|
|
3
|
-
const plate = roundedRect(118, 76, 8
|
|
3
|
+
const plate = roundedRect(118, 76, 8).extrude(18);
|
|
4
4
|
const entryFace = plate.face('top');
|
|
5
5
|
const exitFace = plate.face('bottom');
|
|
6
6
|
const pocketExitFace = plate.face('side-top');
|
|
7
7
|
|
|
8
|
-
const servicePocket = roundedRect(34, 18, 4
|
|
8
|
+
const servicePocket = roundedRect(34, 18, 4)
|
|
9
9
|
.onFace(plate, 'front', { u: 0, v: -2, selfAnchor: 'center' });
|
|
10
10
|
|
|
11
11
|
const fastenerPlate = plate
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Smooth drone arm: flat rectangular mount → organic taper → circular motor mount
|
|
2
|
+
// Tests: cross-section morphing, S-curve sweep, shell, G2 transitions
|
|
3
|
+
|
|
4
|
+
// --- Spine: gentle S-curve from origin to motor mount ---
|
|
5
|
+
const armLength = 120;
|
|
6
|
+
const spine = spline3d([
|
|
7
|
+
[0, 0, 0],
|
|
8
|
+
[armLength * 0.3, 0, 8],
|
|
9
|
+
[armLength * 0.7, 0, -5],
|
|
10
|
+
[armLength, 0, 3],
|
|
11
|
+
], { tension: 0.3 });
|
|
12
|
+
|
|
13
|
+
// --- Cross-sections ---
|
|
14
|
+
|
|
15
|
+
// Mount end: flat rectangle with rounded corners
|
|
16
|
+
const mountProfile = roundedRect(20, 6, 1.5);
|
|
17
|
+
|
|
18
|
+
// Mid-section: transitional rounded rectangle
|
|
19
|
+
const midProfile = roundedRect(14, 10, 4);
|
|
20
|
+
|
|
21
|
+
// Motor end: circle
|
|
22
|
+
const motorProfile = circle2d(8);
|
|
23
|
+
|
|
24
|
+
// --- Variable sweep: morph between profiles along spine ---
|
|
25
|
+
const solidArm = variableSweep(spine, [
|
|
26
|
+
{ t: 0, profile: mountProfile },
|
|
27
|
+
{ t: 0.4, profile: midProfile },
|
|
28
|
+
{ t: 1, profile: motorProfile },
|
|
29
|
+
], { edgeLength: 1.5 });
|
|
30
|
+
|
|
31
|
+
// --- Hollow it out ---
|
|
32
|
+
const arm = solidArm.shell(1.5);
|
|
33
|
+
|
|
34
|
+
// --- Mounting holes at the flat end ---
|
|
35
|
+
const mountHole = cylinder(10, 2).translate(0, 0, -5);
|
|
36
|
+
const mountHoles = union(
|
|
37
|
+
mountHole.translate(-6, 0, 0),
|
|
38
|
+
mountHole.translate(6, 0, 0),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// --- Motor mount ring at the far end ---
|
|
42
|
+
// Place at the end of the spine
|
|
43
|
+
const motorRing = difference(
|
|
44
|
+
cylinder(4, 10),
|
|
45
|
+
cylinder(4, 7),
|
|
46
|
+
).translate(armLength, 0, 3);
|
|
47
|
+
|
|
48
|
+
const result = difference(
|
|
49
|
+
union(arm, motorRing),
|
|
50
|
+
mountHoles,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return result;
|
|
@@ -75,7 +75,7 @@ for (const [lx, ly] of legPositions) {
|
|
|
75
75
|
// Through-hole in Y direction
|
|
76
76
|
pinHoles.push(
|
|
77
77
|
cylinder(outerLeg + 2, pinHoleR)
|
|
78
|
-
.
|
|
78
|
+
.rotateX(90)
|
|
79
79
|
.translate(legCenterX, ly - 1, pz)
|
|
80
80
|
);
|
|
81
81
|
}
|
|
@@ -105,7 +105,7 @@ const shaftY = inset + outerLeg / 2;
|
|
|
105
105
|
const shaftZ = legH - outerLegH + 25;
|
|
106
106
|
const shaftLen = topW - 2 * inset;
|
|
107
107
|
const shaft = cylinder(shaftLen, 0.8)
|
|
108
|
-
.
|
|
108
|
+
.rotateY(90)
|
|
109
109
|
.translate(inset, shaftY, shaftZ);
|
|
110
110
|
|
|
111
111
|
// Crank handle (on the right side)
|
|
@@ -51,7 +51,7 @@ const armLen = 250;
|
|
|
51
51
|
const showerArmX = tubX + tubL * 0.6;
|
|
52
52
|
const showerArmZ = wallT + glassH - 150;
|
|
53
53
|
const showerArm = cylinder(armLen, 12)
|
|
54
|
-
.
|
|
54
|
+
.rotateX(90)
|
|
55
55
|
.translate(showerArmX, roomD - wallT, showerArmZ);
|
|
56
56
|
|
|
57
57
|
// Shower head — disc at end of arm
|
|
@@ -83,11 +83,11 @@ const bowl = bowlOuter.subtract(bowlInner)
|
|
|
83
83
|
const tankW = toiletW * 0.85;
|
|
84
84
|
const tankD = 150;
|
|
85
85
|
const tankH = 320;
|
|
86
|
-
const tank = box(tankW, tankD, tankH
|
|
86
|
+
const tank = box(tankW, tankD, tankH)
|
|
87
87
|
.translate(toiletCX, toiletY + toiletD - tankD / 2 - 10, wallT + toiletH / 2 + tankH / 2 - 80);
|
|
88
88
|
|
|
89
89
|
// Lid — flat slab on top of tank
|
|
90
|
-
const lid = box(tankW + 10, tankD + 10, 15
|
|
90
|
+
const lid = box(tankW + 10, tankD + 10, 15)
|
|
91
91
|
.translate(toiletCX, toiletY + toiletD - tankD / 2 - 10, wallT + toiletH / 2 + tankH - 80 + 7);
|
|
92
92
|
|
|
93
93
|
// Seat — flat ring on top of bowl
|
|
@@ -109,14 +109,14 @@ const sinkZ = 850;
|
|
|
109
109
|
const sinkCX = roomW - wallT - sinkD / 2 - 30;
|
|
110
110
|
const sinkCY = roomD * 0.35;
|
|
111
111
|
|
|
112
|
-
const basinOuter = box(sinkD, sinkW, sinkH
|
|
113
|
-
const basinInner = box(sinkD - 50, sinkW - 50, sinkH - 25
|
|
112
|
+
const basinOuter = box(sinkD, sinkW, sinkH);
|
|
113
|
+
const basinInner = box(sinkD - 50, sinkW - 50, sinkH - 25).translate(0, 0, 12);
|
|
114
114
|
const drain = cylinder(27, 18).translate(0, 0, -sinkH / 2);
|
|
115
115
|
const basin = basinOuter.subtract(basinInner).subtract(drain)
|
|
116
116
|
.translate(sinkCX, sinkCY, wallT + sinkZ + sinkH / 2);
|
|
117
117
|
|
|
118
118
|
// Pedestal
|
|
119
|
-
const pedestal = box(80, 80, sinkZ
|
|
119
|
+
const pedestal = box(80, 80, sinkZ)
|
|
120
120
|
.translate(sinkCX, sinkCY, wallT + sinkZ / 2);
|
|
121
121
|
|
|
122
122
|
// Faucet — vertical stem + curved spout
|
|
@@ -124,12 +124,12 @@ const faucetX = sinkCX + sinkD / 2 - 40;
|
|
|
124
124
|
const faucetZ = wallT + sinkZ + sinkH;
|
|
125
125
|
const faucetStem = cylinder(100, 10)
|
|
126
126
|
.translate(faucetX, sinkCY, faucetZ);
|
|
127
|
-
const faucetSpout = box(80, 16, 16
|
|
127
|
+
const faucetSpout = box(80, 16, 16)
|
|
128
128
|
.translate(faucetX - 40, sinkCY, faucetZ + 100);
|
|
129
129
|
// Handles
|
|
130
|
-
const handleL = box(30, 8, 8
|
|
130
|
+
const handleL = box(30, 8, 8)
|
|
131
131
|
.translate(faucetX, sinkCY - 30, faucetZ + 50);
|
|
132
|
-
const handleR = box(30, 8, 8
|
|
132
|
+
const handleR = box(30, 8, 8)
|
|
133
133
|
.translate(faucetX, sinkCY + 30, faucetZ + 50);
|
|
134
134
|
|
|
135
135
|
const sink = union(basin, pedestal, faucetStem, faucetSpout, handleL, handleR);
|
|
@@ -167,7 +167,7 @@ const bracketL = box(70, bracketSize, bracketSize)
|
|
|
167
167
|
const bracketR = box(70, bracketSize, bracketSize)
|
|
168
168
|
.translate(wallT, rackY + rackLen / 2 - bracketSize, wallT + rackZ);
|
|
169
169
|
const towelBar = cylinder(rackLen - bracketSize * 2, barR)
|
|
170
|
-
.
|
|
170
|
+
.rotateX(-90)
|
|
171
171
|
.translate(wallT + 50, rackY - rackLen / 2 + bracketSize, wallT + rackZ + bracketSize / 2);
|
|
172
172
|
|
|
173
173
|
const towelRack = union(bracketL, bracketR, towelBar);
|
|
@@ -177,7 +177,7 @@ const towelRack = union(bracketL, bracketR, towelBar);
|
|
|
177
177
|
const matW = 700;
|
|
178
178
|
const matD = 450;
|
|
179
179
|
const matH = 8;
|
|
180
|
-
const bathMat = box(matW, matD, matH
|
|
180
|
+
const bathMat = box(matW, matD, matH)
|
|
181
181
|
.translate(tubX + tubL / 2, tubY - matD / 2 - 30, wallT + matH / 2);
|
|
182
182
|
|
|
183
183
|
// ─── Assemble ───
|
|
@@ -37,7 +37,7 @@ const legs = union(
|
|
|
37
37
|
// Tilted slightly backward — rotate around X then position at back edge
|
|
38
38
|
const backPanel = box(seatW, backT, backH)
|
|
39
39
|
.translate(-seatW / 2, -backT / 2, 0) // center for rotation
|
|
40
|
-
.
|
|
40
|
+
.rotateX(-backTilt)
|
|
41
41
|
.translate(seatW / 2, seatD - legInset - legW / 2, seatH + seatT);
|
|
42
42
|
|
|
43
43
|
// --- Stretchers (side rails between legs) ---
|
|
@@ -103,7 +103,7 @@ const mainW = rand(14, 20);
|
|
|
103
103
|
crystals.push({
|
|
104
104
|
name: 'Main Crystal',
|
|
105
105
|
shape: crystal(mainH, mainW)
|
|
106
|
-
.
|
|
106
|
+
.rotateZ(rand(0, 360))
|
|
107
107
|
.translate(rand(-5, 5), rand(-5, 5), 0),
|
|
108
108
|
color: '#a855f7',
|
|
109
109
|
});
|
|
@@ -118,7 +118,7 @@ for (let i = 0; i < crystalCount - 1; i++) {
|
|
|
118
118
|
crystals.push({
|
|
119
119
|
name: `Crystal ${i + 2}`,
|
|
120
120
|
shape: crystal(h, w)
|
|
121
|
-
.
|
|
121
|
+
.rotateX(rand(-8, 8)).rotateY(rand(-12, 12)).rotateZ(rand(0, 360))
|
|
122
122
|
.translate(Math.cos(angle) * dist, Math.sin(angle) * dist, 0),
|
|
123
123
|
color: pickColor(),
|
|
124
124
|
});
|
|
@@ -127,7 +127,7 @@ for (let i = 0; i < spireCount; i++) {
|
|
|
127
127
|
shape: ngon(6, r)
|
|
128
128
|
.extrude(h)
|
|
129
129
|
.translate(px, py, 0)
|
|
130
|
-
.
|
|
130
|
+
.rotateZ(rand(0, 60))
|
|
131
131
|
.material({
|
|
132
132
|
opacity: rand(0.4, 0.75),
|
|
133
133
|
metalness: 0.0,
|
|
@@ -145,7 +145,7 @@ for (let i = 0; i < spireCount; i++) {
|
|
|
145
145
|
shape: ngon(6, r * 0.4)
|
|
146
146
|
.extrude(h * 0.7)
|
|
147
147
|
.translate(px, py, h * 0.1)
|
|
148
|
-
.
|
|
148
|
+
.rotateZ(rand(0, 60))
|
|
149
149
|
.material({
|
|
150
150
|
emissive: pick(glowColors),
|
|
151
151
|
emissiveIntensity: rand(1, 3),
|
|
@@ -183,7 +183,7 @@ for (let i = 0; i < 10; i++) {
|
|
|
183
183
|
shape: ngon(6, size)
|
|
184
184
|
.extrude(size * rand(0.3, 0.8))
|
|
185
185
|
.translate(Math.cos(angle) * dist, Math.sin(angle) * dist, 0)
|
|
186
|
-
.
|
|
186
|
+
.rotateX(rand(-15, 15)).rotateY(rand(-15, 15)).rotateZ(rand(0, 60))
|
|
187
187
|
.material({
|
|
188
188
|
opacity: rand(0.4, 0.7),
|
|
189
189
|
metalness: 0.0,
|
|
@@ -91,15 +91,15 @@ for (let i = 0; i < count; i++) {
|
|
|
91
91
|
const lum = 45 + t * 15; // slightly brighter at top
|
|
92
92
|
const r255 = hslToHex(hue, sat, lum);
|
|
93
93
|
|
|
94
|
-
const element = box(s, s * 0.6, s * 1.2
|
|
95
|
-
.
|
|
94
|
+
const element = box(s, s * 0.6, s * 1.2)
|
|
95
|
+
.rotateZ(facingDeg)
|
|
96
96
|
.translate(Math.cos(angle) * r, Math.sin(angle) * r, z);
|
|
97
97
|
|
|
98
98
|
shapes.push({ name: `Petal ${i + 1}`, shape: element, color: r255 });
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
// Central spine
|
|
102
|
-
const spine = box(6, 6, towerHeight * 0.9
|
|
102
|
+
const spine = box(6, 6, towerHeight * 0.9)
|
|
103
103
|
.translate(0, 0, towerHeight * 0.45);
|
|
104
104
|
shapes.push({ name: 'Spine', shape: spine, color: '#1a1a2e' });
|
|
105
105
|
|