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,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
circle2d,
|
|
3
|
+
rect,
|
|
4
|
+
roundedRect,
|
|
5
|
+
sheetMetal,
|
|
6
|
+
} from 'forgecad';
|
|
7
|
+
|
|
8
|
+
export const FOLDED_SERVICE_PANEL_COVER_SPEC = {
|
|
9
|
+
panel: { width: 180, height: 110 },
|
|
10
|
+
thickness: 1.5,
|
|
11
|
+
bendRadius: 2.0,
|
|
12
|
+
bendAllowance: { kFactor: 0.42 },
|
|
13
|
+
cornerRelief: { size: 4.0 },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const FOLDED_SERVICE_PANEL_REGION_NAMES = [
|
|
17
|
+
'panel',
|
|
18
|
+
'bend-top',
|
|
19
|
+
'flange-top',
|
|
20
|
+
'bend-right',
|
|
21
|
+
'flange-right',
|
|
22
|
+
'bend-bottom',
|
|
23
|
+
'flange-bottom',
|
|
24
|
+
'bend-left',
|
|
25
|
+
'flange-left',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function mountingHole(radius, u, v) {
|
|
29
|
+
return {
|
|
30
|
+
sketch: circle2d(radius),
|
|
31
|
+
u,
|
|
32
|
+
v,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assertIncludesAll(actual, expected, label) {
|
|
37
|
+
for (const name of expected) {
|
|
38
|
+
if (!actual.includes(name)) {
|
|
39
|
+
throw new Error(`${label} is missing "${name}". Got: ${actual.join(', ')}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function buildFoldedServicePanelCoverPart() {
|
|
45
|
+
let part = sheetMetal(FOLDED_SERVICE_PANEL_COVER_SPEC)
|
|
46
|
+
.flange('top', { length: 18, angleDeg: 90 })
|
|
47
|
+
.flange('right', { length: 18, angleDeg: 90 })
|
|
48
|
+
.flange('bottom', { length: 18, angleDeg: 90 })
|
|
49
|
+
.flange('left', { length: 18, angleDeg: 90 })
|
|
50
|
+
.cutout('panel', rect(72, 36, true), { selfAnchor: 'center' })
|
|
51
|
+
.cutout('flange-right', roundedRect(26, 10, 5, true), { selfAnchor: 'center' });
|
|
52
|
+
|
|
53
|
+
const holes = [
|
|
54
|
+
mountingHole(2.2, -68, -37),
|
|
55
|
+
mountingHole(2.2, 68, -37),
|
|
56
|
+
mountingHole(2.2, -68, 37),
|
|
57
|
+
mountingHole(2.2, 68, 37),
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
for (const hole of holes) {
|
|
61
|
+
part = part.cutout('panel', hole.sketch, {
|
|
62
|
+
u: hole.u,
|
|
63
|
+
v: hole.v,
|
|
64
|
+
selfAnchor: 'center',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return part;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function assertDescendant(face, label, semantic, minMembers = 1) {
|
|
72
|
+
if (!face.descendant) {
|
|
73
|
+
throw new Error(`${label} did not expose descendant metadata.`);
|
|
74
|
+
}
|
|
75
|
+
if (face.descendant.semantic !== semantic) {
|
|
76
|
+
throw new Error(`${label} expected descendant semantic "${semantic}", got "${face.descendant.semantic}".`);
|
|
77
|
+
}
|
|
78
|
+
if (face.descendant.memberCount < minMembers) {
|
|
79
|
+
throw new Error(`${label} expected at least ${minMembers} member(s), got ${face.descendant.memberCount}.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function buildFoldedServicePanelCoverOutputs() {
|
|
84
|
+
const part = buildFoldedServicePanelCoverPart();
|
|
85
|
+
const regionNames = part.regionNames();
|
|
86
|
+
assertIncludesAll(regionNames, FOLDED_SERVICE_PANEL_REGION_NAMES, 'Sheet-metal regionNames()');
|
|
87
|
+
if (regionNames.length !== FOLDED_SERVICE_PANEL_REGION_NAMES.length) {
|
|
88
|
+
throw new Error(`Sheet-metal regionNames() should expose ${FOLDED_SERVICE_PANEL_REGION_NAMES.length} entries, got ${regionNames.length}.`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const folded = part.folded();
|
|
92
|
+
const flat = part.flatPattern();
|
|
93
|
+
|
|
94
|
+
assertIncludesAll(folded.faceNames(), FOLDED_SERVICE_PANEL_REGION_NAMES, 'Folded faceNames()');
|
|
95
|
+
assertIncludesAll(flat.faceNames(), FOLDED_SERVICE_PANEL_REGION_NAMES, 'Flat faceNames()');
|
|
96
|
+
|
|
97
|
+
assertDescendant(folded.face('panel'), 'Folded panel', 'region');
|
|
98
|
+
assertDescendant(folded.face('flange-right'), 'Folded flange-right', 'region');
|
|
99
|
+
assertDescendant(folded.face('bend-right'), 'Folded bend-right', 'set', 2);
|
|
100
|
+
assertDescendant(flat.face('panel'), 'Flat panel', 'region');
|
|
101
|
+
assertDescendant(flat.face('flange-right'), 'Flat flange-right', 'region');
|
|
102
|
+
assertDescendant(flat.face('bend-right'), 'Flat bend-right', 'face');
|
|
103
|
+
|
|
104
|
+
return { part, folded, flat };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function buildFoldedServicePanelCoverScene(options = {}) {
|
|
108
|
+
const { folded, flat } = buildFoldedServicePanelCoverOutputs();
|
|
109
|
+
const flatOffsetX = options.flatOffsetX ?? 280;
|
|
110
|
+
const foldedColor = options.foldedColor ?? '#a2b0b7';
|
|
111
|
+
const flatColor = options.flatColor ?? '#d7bf95';
|
|
112
|
+
|
|
113
|
+
return [
|
|
114
|
+
{ name: 'Folded Service Panel Cover', shape: folded, color: foldedColor },
|
|
115
|
+
{ name: 'Flat Service Panel Cover', shape: flat.translate(flatOffsetX, 0, 0), color: flatColor },
|
|
116
|
+
];
|
|
117
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Bevel + face gear demo with runtime joint couplings.
|
|
2
|
+
// Use the "Joints" section in the View Panel to drive both stages.
|
|
3
|
+
|
|
4
|
+
const moduleSize = param("Module", 1.4, { min: 0.8, max: 3.0, step: 0.05 });
|
|
5
|
+
const bevelInput = param("Bevel Driver", 30, { min: -360, max: 360, step: 1, unit: "°" });
|
|
6
|
+
const faceInput = param("Face Driver", 20, { min: -360, max: 360, step: 1, unit: "°" });
|
|
7
|
+
const shaftAngle = param("Bevel Shaft", 90, { min: 60, max: 120, step: 1, unit: "°" });
|
|
8
|
+
const stageBacklash = moduleSize * 0.05;
|
|
9
|
+
|
|
10
|
+
const bevelStage = lib.bevelGearPair({
|
|
11
|
+
pinion: {
|
|
12
|
+
module: moduleSize,
|
|
13
|
+
teeth: 16,
|
|
14
|
+
faceWidth: 10,
|
|
15
|
+
boreDiameter: 5,
|
|
16
|
+
},
|
|
17
|
+
gear: {
|
|
18
|
+
module: moduleSize,
|
|
19
|
+
teeth: 32,
|
|
20
|
+
faceWidth: 9,
|
|
21
|
+
boreDiameter: 8,
|
|
22
|
+
},
|
|
23
|
+
shaftAngleDeg: shaftAngle,
|
|
24
|
+
backlash: stageBacklash,
|
|
25
|
+
place: true,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const faceStage = lib.faceGearPair({
|
|
29
|
+
face: {
|
|
30
|
+
module: moduleSize,
|
|
31
|
+
teeth: 44,
|
|
32
|
+
faceWidth: 7,
|
|
33
|
+
toothHeight: moduleSize * 0.9,
|
|
34
|
+
side: "top",
|
|
35
|
+
boreDiameter: 10,
|
|
36
|
+
},
|
|
37
|
+
vertical: {
|
|
38
|
+
module: moduleSize,
|
|
39
|
+
teeth: 14,
|
|
40
|
+
faceWidth: 8,
|
|
41
|
+
boreDiameter: 5,
|
|
42
|
+
},
|
|
43
|
+
backlash: stageBacklash,
|
|
44
|
+
place: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
for (const d of [...bevelStage.diagnostics, ...faceStage.diagnostics]) {
|
|
48
|
+
const tag = `[${d.level}] ${d.code}`;
|
|
49
|
+
if (d.level === "error") console.error(tag, d.message);
|
|
50
|
+
else if (d.level === "warn") console.warn(tag, d.message);
|
|
51
|
+
else console.info(tag, d.message);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const addOffset = (point, offset) => [
|
|
55
|
+
point[0] + offset[0],
|
|
56
|
+
point[1] + offset[1],
|
|
57
|
+
point[2] + offset[2],
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const bevelOffset = [-110, 0, 0];
|
|
61
|
+
const faceOffset = [110, 0, 0];
|
|
62
|
+
|
|
63
|
+
const bevelPinionPivot = addOffset(bevelStage.pinionCenter, bevelOffset);
|
|
64
|
+
const bevelGearPivot = addOffset(bevelStage.gearCenter, bevelOffset);
|
|
65
|
+
const faceGearPivot = addOffset([0, 0, 0], faceOffset);
|
|
66
|
+
const faceVerticalPivot = addOffset([faceStage.centerDistance, 0, faceStage.meshPlaneZ], faceOffset);
|
|
67
|
+
|
|
68
|
+
jointsView({
|
|
69
|
+
joints: [
|
|
70
|
+
{
|
|
71
|
+
name: "Bevel Driver",
|
|
72
|
+
child: "Bevel Pinion",
|
|
73
|
+
type: "revolute",
|
|
74
|
+
axis: bevelStage.pinionAxis,
|
|
75
|
+
pivot: bevelPinionPivot,
|
|
76
|
+
min: -1080,
|
|
77
|
+
max: 1080,
|
|
78
|
+
default: bevelInput,
|
|
79
|
+
unit: "°",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "Bevel Driven",
|
|
83
|
+
child: "Bevel Gear",
|
|
84
|
+
type: "revolute",
|
|
85
|
+
axis: bevelStage.gearAxis,
|
|
86
|
+
pivot: bevelGearPivot,
|
|
87
|
+
min: -1080,
|
|
88
|
+
max: 1080,
|
|
89
|
+
default: 0,
|
|
90
|
+
unit: "°",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "Face Driver",
|
|
94
|
+
child: "Face Gear",
|
|
95
|
+
type: "revolute",
|
|
96
|
+
axis: [0, 0, 1],
|
|
97
|
+
pivot: faceGearPivot,
|
|
98
|
+
min: -1080,
|
|
99
|
+
max: 1080,
|
|
100
|
+
default: faceInput,
|
|
101
|
+
unit: "°",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "Face Driven",
|
|
105
|
+
child: "Face Vertical",
|
|
106
|
+
type: "revolute",
|
|
107
|
+
axis: [0, 1, 0],
|
|
108
|
+
pivot: faceVerticalPivot,
|
|
109
|
+
min: -1080,
|
|
110
|
+
max: 1080,
|
|
111
|
+
default: 0,
|
|
112
|
+
unit: "°",
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
couplings: [
|
|
116
|
+
{
|
|
117
|
+
joint: "Bevel Driven",
|
|
118
|
+
terms: [{ joint: "Bevel Driver", ratio: bevelStage.jointRatio }],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
joint: "Face Driven",
|
|
122
|
+
terms: [{ joint: "Face Driver", ratio: faceStage.jointRatio }],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
animations: [
|
|
126
|
+
{
|
|
127
|
+
name: "Dual Spin",
|
|
128
|
+
duration: 2.4,
|
|
129
|
+
loop: true,
|
|
130
|
+
keyframes: [
|
|
131
|
+
{ at: 0.0, values: { "Bevel Driver": 0, "Face Driver": 0 } },
|
|
132
|
+
{ at: 0.5, values: { "Bevel Driver": 180, "Face Driver": 120 } },
|
|
133
|
+
{ at: 1.0, values: { "Bevel Driver": 360, "Face Driver": 240 } },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
defaultAnimation: "Dual Spin",
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return [
|
|
141
|
+
{
|
|
142
|
+
name: "Bevel Pinion",
|
|
143
|
+
shape: bevelStage.pinion.translate(bevelOffset[0], bevelOffset[1], bevelOffset[2]).color("#d7a25e"),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "Bevel Gear",
|
|
147
|
+
shape: bevelStage.gear.translate(bevelOffset[0], bevelOffset[1], bevelOffset[2]).color("#8ea8be"),
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "Face Gear",
|
|
151
|
+
shape: faceStage.face.translate(faceOffset[0], faceOffset[1], faceOffset[2]).color("#6f8795"),
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "Face Vertical",
|
|
155
|
+
shape: faceStage.vertical.translate(faceOffset[0], faceOffset[1], faceOffset[2]).color("#c98f5a"),
|
|
156
|
+
},
|
|
157
|
+
];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Tier 1 gears demo: spur pair + ring gear + rack gear
|
|
2
|
+
|
|
3
|
+
const moduleSize = param("Module", 1.25, { min: 0.6, max: 3.0, step: 0.05 });
|
|
4
|
+
const pinionTeeth = param("Pinion Teeth", 14, { min: 8, max: 28, integer: true });
|
|
5
|
+
const drivenTeeth = param("Driven Teeth", 42, { min: 16, max: 90, integer: true });
|
|
6
|
+
const backlash = param("Backlash", 0.05, { min: 0, max: 0.2, step: 0.01, unit: "mm" });
|
|
7
|
+
const faceWidth = param("Face Width", 10, { min: 4, max: 18, unit: "mm" });
|
|
8
|
+
|
|
9
|
+
const pair = lib.gearPair({
|
|
10
|
+
pinion: {
|
|
11
|
+
module: moduleSize,
|
|
12
|
+
teeth: pinionTeeth,
|
|
13
|
+
pressureAngleDeg: 20,
|
|
14
|
+
faceWidth,
|
|
15
|
+
boreDiameter: 5,
|
|
16
|
+
},
|
|
17
|
+
gear: {
|
|
18
|
+
module: moduleSize,
|
|
19
|
+
teeth: drivenTeeth,
|
|
20
|
+
pressureAngleDeg: 20,
|
|
21
|
+
faceWidth,
|
|
22
|
+
boreDiameter: 8,
|
|
23
|
+
},
|
|
24
|
+
backlash,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
for (const d of pair.diagnostics) {
|
|
28
|
+
const tag = `[${d.level}] ${d.code}`;
|
|
29
|
+
if (d.level === "error") console.error(tag, d.message);
|
|
30
|
+
else if (d.level === "warn") console.warn(tag, d.message);
|
|
31
|
+
else console.info(tag, d.message);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ring = lib.ringGear({
|
|
35
|
+
module: moduleSize,
|
|
36
|
+
teeth: Math.max(30, drivenTeeth + pinionTeeth + 4),
|
|
37
|
+
pressureAngleDeg: 20,
|
|
38
|
+
faceWidth,
|
|
39
|
+
backlash,
|
|
40
|
+
rimWidth: moduleSize * 3,
|
|
41
|
+
}).translate(0, 95, 0);
|
|
42
|
+
|
|
43
|
+
const rack = lib.rackGear({
|
|
44
|
+
module: moduleSize,
|
|
45
|
+
teeth: 22,
|
|
46
|
+
pressureAngleDeg: 20,
|
|
47
|
+
faceWidth,
|
|
48
|
+
backlash,
|
|
49
|
+
baseHeight: moduleSize * 2,
|
|
50
|
+
}).translate(0, -95, 0);
|
|
51
|
+
|
|
52
|
+
return [
|
|
53
|
+
{ name: "Spur Pinion", shape: pair.pinion.color("#d5a15f") },
|
|
54
|
+
{ name: "Spur Gear", shape: pair.gear.color("#9ab3ca") },
|
|
55
|
+
{ name: "Ring Gear", shape: ring.color("#71808d") },
|
|
56
|
+
{ name: "Rack Gear", shape: rack.color("#6f9272") },
|
|
57
|
+
];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Geometry provenance inspection.
|
|
2
|
+
// Run with: forgecad run examples/api/geometry-info.forge.js
|
|
3
|
+
// The CLI now prints backend/representation/fidelity/topology for each object.
|
|
4
|
+
|
|
5
|
+
const base = rectangle(-35, -20, 70, 40).extrude(18).color('#5f7c8a');
|
|
6
|
+
|
|
7
|
+
const cutter = circle2d(11, 36).extrude(26).translate(0, 0, -4);
|
|
8
|
+
const machined = base
|
|
9
|
+
.toShape()
|
|
10
|
+
.subtract(cutter)
|
|
11
|
+
.color('#9eb4bf')
|
|
12
|
+
.translate(0, 72, 0);
|
|
13
|
+
|
|
14
|
+
const station = (w, d) => spline2d([
|
|
15
|
+
[w * 0.5, 0],
|
|
16
|
+
[w * 0.32, d * 0.46],
|
|
17
|
+
[0, d * 0.55],
|
|
18
|
+
[-w * 0.32, d * 0.46],
|
|
19
|
+
[-w * 0.5, 0],
|
|
20
|
+
[-w * 0.32, -d * 0.46],
|
|
21
|
+
[0, -d * 0.55],
|
|
22
|
+
[w * 0.32, -d * 0.46],
|
|
23
|
+
], {
|
|
24
|
+
closed: true,
|
|
25
|
+
samplesPerSegment: 9,
|
|
26
|
+
tension: 0.35,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const lofted = loft(
|
|
30
|
+
[
|
|
31
|
+
station(26, 18),
|
|
32
|
+
station(48, 28),
|
|
33
|
+
station(34, 22),
|
|
34
|
+
],
|
|
35
|
+
[0, 20, 46],
|
|
36
|
+
{ edgeLength: 0.85 },
|
|
37
|
+
)
|
|
38
|
+
.translate(110, 18, 0)
|
|
39
|
+
.color('#d8b36a');
|
|
40
|
+
|
|
41
|
+
console.info('Tracked extrude', base.geometryInfo());
|
|
42
|
+
console.info('Boolean cut', machined.geometryInfo());
|
|
43
|
+
console.info('Lofted body', lofted.geometryInfo());
|
|
44
|
+
|
|
45
|
+
return [
|
|
46
|
+
{ name: 'Tracked Extrude', shape: base },
|
|
47
|
+
{ name: 'Boolean Cut', shape: machined },
|
|
48
|
+
{ name: 'Lofted Body', shape: lofted },
|
|
49
|
+
];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Test assembly grouping — nested group format
|
|
2
|
+
const baseW = param("Base Width", 100, { min: 60, max: 200, unit: "mm" });
|
|
3
|
+
const baseD = param("Base Depth", 80, { min: 40, max: 150, unit: "mm" });
|
|
4
|
+
|
|
5
|
+
// Bed assembly
|
|
6
|
+
const bedPlate = box(baseW, baseD, 5).color('#666666');
|
|
7
|
+
const glass = box(baseW - 10, baseD - 10, 3).translate(5, 5, 5).color('#aaddff');
|
|
8
|
+
const heater = box(baseW - 20, baseD - 20, 1).translate(10, 10, -1).color('#cc4444');
|
|
9
|
+
|
|
10
|
+
// Gantry
|
|
11
|
+
const leftRail = box(5, baseD, 60).translate(-10, 0, 8).color('#888888');
|
|
12
|
+
const rightRail = box(5, baseD, 60).translate(baseW + 5, 0, 8).color('#888888');
|
|
13
|
+
const crossBar = box(baseW + 20, 5, 5).translate(-10, baseD / 2, 63).color('#aaaaaa');
|
|
14
|
+
|
|
15
|
+
// Extruder (intentionally overlaps crossbar — intra-group collision)
|
|
16
|
+
const nozzle = cylinder(15, 4).translate(baseW / 2, baseD / 2, 48).color('#ff8800');
|
|
17
|
+
const heatsink = box(20, 20, 10, true).translate(baseW / 2, baseD / 2, 60).color('#cccccc');
|
|
18
|
+
|
|
19
|
+
return [
|
|
20
|
+
{ name: "Bed Assembly", group: [
|
|
21
|
+
{ name: "Bed Plate", shape: bedPlate },
|
|
22
|
+
{ name: "Glass Bed", shape: glass },
|
|
23
|
+
{ name: "Heater", shape: heater },
|
|
24
|
+
]},
|
|
25
|
+
{ name: "Gantry", group: [
|
|
26
|
+
{ name: "Left Rail", shape: leftRail },
|
|
27
|
+
{ name: "Right Rail", shape: rightRail },
|
|
28
|
+
{ name: "Cross Bar", shape: crossBar },
|
|
29
|
+
]},
|
|
30
|
+
{ name: "Extruder", group: [
|
|
31
|
+
{ name: "Nozzle", shape: nozzle },
|
|
32
|
+
{ name: "Heatsink", shape: heatsink },
|
|
33
|
+
]},
|
|
34
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// group() vs union() — when to use which.
|
|
2
|
+
//
|
|
3
|
+
// union(a, b) → merges into ONE mesh. Colors lost. Good for boolean operand.
|
|
4
|
+
// group(a, b) → keeps separate. Colors preserved. Transforms together.
|
|
5
|
+
//
|
|
6
|
+
// Use union when you need a single solid (e.g., to subtract from something).
|
|
7
|
+
// Use group when you want parts to move together but stay visually distinct.
|
|
8
|
+
|
|
9
|
+
const base = box(60, 60, 5, true).color('#888888');
|
|
10
|
+
const col = cylinder(30, 5).color('#cc4444')
|
|
11
|
+
.attachTo(base, 'top', 'bottom');
|
|
12
|
+
|
|
13
|
+
// --- group: colors preserved, transforms together (with named children) ---
|
|
14
|
+
const grouped = group(
|
|
15
|
+
{ name: "Base", shape: base },
|
|
16
|
+
{ name: "Column", shape: col }
|
|
17
|
+
).translate(-50, 0, 0);
|
|
18
|
+
|
|
19
|
+
// --- union: one solid, one color ---
|
|
20
|
+
const unioned = union(base, col).translate(50, 0, 0).color('#4488cc');
|
|
21
|
+
|
|
22
|
+
return [
|
|
23
|
+
grouped, // each child becomes a separate viewport object with names
|
|
24
|
+
{ name: "Union (single solid)", shape: unioned },
|
|
25
|
+
];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const left = importPart("api/import-args-unit.forge.js", {
|
|
2
|
+
"Width": 24,
|
|
3
|
+
"Height": 24,
|
|
4
|
+
"Depth": 8,
|
|
5
|
+
}).translate(-20, 0, 0);
|
|
6
|
+
|
|
7
|
+
const right = importPart("api/import-args-unit.forge.js", {
|
|
8
|
+
"Width": 52,
|
|
9
|
+
"Height": 16,
|
|
10
|
+
"Depth": 12,
|
|
11
|
+
}).translate(20, 0, 0);
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
{ name: "Left", shape: left, color: "#5c88da" },
|
|
15
|
+
{ name: "Right", shape: right, color: "#d97c45" },
|
|
16
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const left = importPart("api/dimensioned-bracket.forge.js", {
|
|
2
|
+
"Width": 55,
|
|
3
|
+
"Height": 45,
|
|
4
|
+
"Depth": 28,
|
|
5
|
+
"Thickness": 4,
|
|
6
|
+
}).translate(-80, 0, 0);
|
|
7
|
+
|
|
8
|
+
const right = importPart("api/dimensioned-bracket.forge.js", {
|
|
9
|
+
"Width": 55,
|
|
10
|
+
"Height": 45,
|
|
11
|
+
"Depth": 28,
|
|
12
|
+
"Thickness": 4,
|
|
13
|
+
}).translate(80, 0, 0).rotate(0, 0, 180);
|
|
14
|
+
|
|
15
|
+
return [
|
|
16
|
+
{ name: "Left Bracket", shape: left, color: "#6a7bd1" },
|
|
17
|
+
{ name: "Right Bracket", shape: right, color: "#d18a5a" },
|
|
18
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Placement references let imported parts define semantic attachment points.
|
|
2
|
+
|
|
3
|
+
const left = importPart("api/import-placement-widget-source.forge.js")
|
|
4
|
+
.placeReference("mount", [-90, 0, 0]);
|
|
5
|
+
|
|
6
|
+
const right = importPart("api/import-placement-widget-source.forge.js", {
|
|
7
|
+
"Post Height": 40,
|
|
8
|
+
}).attachTo(left, "objects.post.top", "mount", [90, 0, 0]);
|
|
9
|
+
|
|
10
|
+
const cap = box(18, 18, 8, true)
|
|
11
|
+
.attachTo(right, "objects.post.top", "bottom")
|
|
12
|
+
.color("#384b5f");
|
|
13
|
+
|
|
14
|
+
return [
|
|
15
|
+
{ name: "Left", shape: left, color: "#5b7c8d" },
|
|
16
|
+
{ name: "Right", shape: right, color: "#d38b4d" },
|
|
17
|
+
{ name: "Cap", shape: cap },
|
|
18
|
+
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const postHeight = param("Post Height", 26, { min: 12, max: 60, unit: "mm" });
|
|
2
|
+
|
|
3
|
+
const base = box(48, 32, 8, true);
|
|
4
|
+
const post = cylinder(postHeight, 5, undefined, undefined, true)
|
|
5
|
+
.translate(12, 0, 4 + postHeight / 2);
|
|
6
|
+
|
|
7
|
+
return union(base, post)
|
|
8
|
+
.withReferences({
|
|
9
|
+
points: {
|
|
10
|
+
mount: [0, -16, -4],
|
|
11
|
+
postCenter: [12, 0, 4 + postHeight / 2],
|
|
12
|
+
},
|
|
13
|
+
edges: {
|
|
14
|
+
postAxis: {
|
|
15
|
+
start: [12, 0, 4],
|
|
16
|
+
end: [12, 0, 4 + postHeight],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
surfaces: {
|
|
20
|
+
mountingFace: {
|
|
21
|
+
center: [0, -16, 0],
|
|
22
|
+
normal: [0, -1, 0],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
objects: {
|
|
26
|
+
base,
|
|
27
|
+
post,
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
.color("#5b7c8d");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Relative import paths: ./ resolves from this file's folder.
|
|
2
|
+
|
|
3
|
+
const left = importPart("./import-args-unit.forge.js", {
|
|
4
|
+
"Width": 26,
|
|
5
|
+
"Height": 22,
|
|
6
|
+
"Depth": 9,
|
|
7
|
+
}).translate(-24, 0, 0);
|
|
8
|
+
|
|
9
|
+
const right = importPart("./import-args-unit.forge.js", {
|
|
10
|
+
"Width": 46,
|
|
11
|
+
"Height": 18,
|
|
12
|
+
"Depth": 12,
|
|
13
|
+
}).translate(24, 0, 0);
|
|
14
|
+
|
|
15
|
+
return [
|
|
16
|
+
{ name: "Left (./)", shape: left, color: "#5f87c6" },
|
|
17
|
+
{ name: "Right (./)", shape: right, color: "#d18a5a" },
|
|
18
|
+
];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 80">
|
|
2
|
+
<path
|
|
3
|
+
fill="#111111"
|
|
4
|
+
fill-rule="evenodd"
|
|
5
|
+
d="M 8 8 H 72 V 72 H 8 Z M 28 28 H 52 V 52 H 28 Z"
|
|
6
|
+
/>
|
|
7
|
+
<circle cx="100" cy="16" r="7" fill="#111111" />
|
|
8
|
+
<polyline
|
|
9
|
+
points="88,46 100,60 112,46"
|
|
10
|
+
fill="none"
|
|
11
|
+
stroke="#111111"
|
|
12
|
+
stroke-width="5"
|
|
13
|
+
stroke-linejoin="round"
|
|
14
|
+
/>
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SVG import demo:
|
|
2
|
+
// - Filled regions (all vs largest only)
|
|
3
|
+
// - Stroke-only import
|
|
4
|
+
// - importSketch(...) overload for .svg
|
|
5
|
+
|
|
6
|
+
const allFill = importSvgSketch("api/import-svg-sketch-shape.svg", {
|
|
7
|
+
include: "fill",
|
|
8
|
+
regionSelection: "all",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const largestFill = importSketch("api/import-svg-sketch-shape.svg", {
|
|
12
|
+
include: "fill",
|
|
13
|
+
regionSelection: "largest",
|
|
14
|
+
maxWidth: 35,
|
|
15
|
+
maxHeight: 35,
|
|
16
|
+
centerOnOrigin: true,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const strokeOnly = importSvgSketch("api/import-svg-sketch-shape.svg", {
|
|
20
|
+
include: "stroke",
|
|
21
|
+
flattenTolerance: 0.2,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return [
|
|
25
|
+
{ name: "Fill (all regions)", shape: allFill.extrude(4).translate(-55, 0, 0).color("#5f87c6") },
|
|
26
|
+
{ name: "Fill (largest region)", shape: largestFill.extrude(4).color("#d08f5b") },
|
|
27
|
+
{ name: "Stroke geometry", shape: strokeOnly.extrude(4).translate(55, 0, 0).color("#66b38d") },
|
|
28
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { box, union } from "forgecad";
|
|
2
|
+
|
|
3
|
+
let moduleLoadCount = 0;
|
|
4
|
+
moduleLoadCount += 1;
|
|
5
|
+
|
|
6
|
+
export const capHeight = 6;
|
|
7
|
+
|
|
8
|
+
export function loadCount() {
|
|
9
|
+
return moduleLoadCount;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default class PillarPair {
|
|
13
|
+
constructor(spacing, height) {
|
|
14
|
+
this.spacing = spacing;
|
|
15
|
+
this.height = height;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
build() {
|
|
19
|
+
const pillar = box(6, 6, this.height, true);
|
|
20
|
+
return union(
|
|
21
|
+
pillar.translate(-this.spacing / 2, 0, this.height / 2),
|
|
22
|
+
pillar.translate(this.spacing / 2, 0, this.height / 2),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { box, union } from "forgecad";
|
|
2
|
+
import PillarPair, { capHeight } from "./js-module-pillars.js";
|
|
3
|
+
|
|
4
|
+
export function buildAssembly() {
|
|
5
|
+
const base = box(40, 18, 4, true);
|
|
6
|
+
const pillars = new PillarPair(24, 12).build().translate(0, 0, 2);
|
|
7
|
+
const cap = box(14, 18, capHeight, true).translate(0, 0, 17);
|
|
8
|
+
return union(base, pillars, cap).color("#d6a86a");
|
|
9
|
+
}
|