forgecad 0.10.0 → 0.10.1
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/dist/assets/{AdminPage-DwYHz72L.js → AdminPage-DcCnj0qo.js} +1 -1
- package/dist/assets/{BenchmarkPage-a9_f-1US.js → BenchmarkPage-BVEpJSVk.js} +1 -1
- package/dist/assets/{BlogPage-DodHpvmf.js → BlogPage-DHaGP50_.js} +1 -1
- package/dist/assets/{DocsPage-B5LePEuj.js → DocsPage-CDoxHkz8.js} +33 -2
- package/dist/assets/{EditorApp-QXsAISLR.js → EditorApp-BJ0Dloyh.js} +174 -35
- package/dist/assets/{EmbedViewer-DdEHGUMU.js → EmbedViewer-CRKZbY0y.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-yhhOodbf.js → LandingPageProofDriven-BxHkYRE7.js} +1 -1
- package/dist/assets/{LegalPage-5RbKRGYK.js → LegalPage-B-u6FrVv.js} +1 -1
- package/dist/assets/{PricingPage-E3Rma7aV.js → PricingPage-CzpZ6-Ce.js} +1 -1
- package/dist/assets/{SettingsPage-BJZcM97j.js → SettingsPage-CIZSSAd0.js} +1 -1
- package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
- package/dist/assets/{app-DSYrDg0V.js → app-DaTMg3nH.js} +612 -120
- package/dist/assets/cli/{render-ZMHR9HkV.js → render-DPf4AYJK.js} +38 -16
- package/dist/assets/{evalWorker-DbNs7Dkp.js → evalWorker-CjZZWRWW.js} +1428 -1038
- package/dist/assets/{jointPose-DO6mnXn_.js → jointPose-DzQOViQH.js} +1 -1
- package/dist/assets/{manifold-BGlQBBH9.js → manifold-BYlzU521.js} +1 -1
- package/dist/assets/{manifold-fy2MV7K1.js → manifold-DgXo0T5P.js} +2 -2
- package/dist/assets/{manifold-BU-tJwQh.js → manifold-K1SkarlQ.js} +1 -1
- package/dist/assets/{reportWorker-DO6hcQbh.js → reportWorker-B9nWwSrB.js} +1402 -1012
- package/dist/assets/{scalar-sampling-budget-o90NSNmF.js → scalar-sampling-budget-prBw_s8t.js} +2139 -1749
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/CLI.md +18 -3
- package/dist/docs-raw/generated/assembly.md +70 -5
- package/dist/docs-raw/generated/concepts.md +16 -2
- package/dist/docs-raw/generated/core.md +9 -2
- package/dist/docs-raw/generated/lib.md +1 -1
- package/dist/docs-raw/generated/output.md +14 -43
- package/dist/docs-raw/generated/runtime-names.md +4 -4
- package/dist/docs-raw/guides/simready-quickstart.md +171 -0
- package/dist/docs-raw/simulation-workflow.md +273 -0
- package/dist/index.html +2 -2
- package/dist/sitemap.xml +25 -13
- package/dist-cli/{check-compiler-JTVBITCR.js → check-compiler-II7NLPAB.js} +1 -1
- package/dist-cli/{check-query-propagation-3FFLSMVN.js → check-query-propagation-7462TR3R.js} +1 -1
- package/dist-cli/{chunk-OAN5T4XD.js → chunk-UWTJCGXF.js} +1455 -722
- package/dist-cli/forgecad.js +2994 -529
- package/dist-skill/CONTEXT.md +94 -55
- package/dist-skill/docs/API/core/concepts.md +1 -1
- package/dist-skill/docs/CLI.md +18 -3
- package/dist-skill/docs/generated/assembly.md +66 -5
- package/dist-skill/docs/generated/core.md +9 -2
- package/dist-skill/docs/generated/lib.md +1 -1
- package/dist-skill/docs/generated/output.md +14 -43
- package/dist-skill/docs/generated/runtime-names.md +4 -4
- package/examples/robotics/README.md +46 -0
- package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
- package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
- package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
- package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
- package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
- package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
- package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
- package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
- package/examples/robotics/simready-asset-crate.forge.js +79 -0
- package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
- package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
- package/package.json +1 -1
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// Chassis of the scout cam rover: printed tub + service door + motor pods,
|
|
2
|
+
// purchased N20 gearmotors, driver/IMU/buck boards, and a rocker switch.
|
|
3
|
+
// Tub-local frame: centered on XY, floor at z=0, axle along X at z=tub.axleZl.
|
|
4
|
+
// Connector recipe (probed): child parts are built +Z from a `z=0, axis [0,0,-1]`
|
|
5
|
+
// connector; the parent connector axis is the desired world direction of the child.
|
|
6
|
+
|
|
7
|
+
const wallP = param('wall_thickness', 2.4, { min: 2.0, max: 3.0, step: 0.1, unit: 'mm' });
|
|
8
|
+
const wheelODP = param('wheel_od', 110, { min: 96, max: 130, step: 1, unit: 'mm' });
|
|
9
|
+
|
|
10
|
+
const dimsMod = require('../lib/dims.js');
|
|
11
|
+
|
|
12
|
+
const COL_TEAL = '#2fb3b8';
|
|
13
|
+
const COL_BLACK = '#1f2023';
|
|
14
|
+
const COL_STEEL = '#b9bdc4';
|
|
15
|
+
const COL_SCREW = '#3a3d42';
|
|
16
|
+
|
|
17
|
+
function tealMat(s) { return s.color(COL_TEAL).material({ metalness: 0.05, roughness: 0.45 }); }
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------- tub --------
|
|
20
|
+
function buildTub(D) {
|
|
21
|
+
const T = D.tub, M = D.motor, dr = D.door, P = D.pod, B = D.boards, SW = D.sw;
|
|
22
|
+
const hw = T.w / 2, hd = T.d / 2;
|
|
23
|
+
const ihw = hw - T.wall, ihd = hd - T.wall;
|
|
24
|
+
const az = T.axleZl;
|
|
25
|
+
|
|
26
|
+
let tub = roundedRect(T.w, T.d, T.cornerR).extrude(T.h);
|
|
27
|
+
tub = tub.subtract(
|
|
28
|
+
roundedRect(2 * ihw, 2 * ihd, T.cornerR - 2).extrude(T.h - T.floorT + 2).translate(0, 0, T.floorT)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// corner posts that receive the body-shell plugs (welded 0.3mm into the walls).
|
|
32
|
+
// The outward diagonal corner is chamfered flat so the web to the rounded
|
|
33
|
+
// outer wall corner stays >= 1.4mm (a square corner leaves a 0.4mm sliver).
|
|
34
|
+
const pcx = hw - T.wall - T.postInset, pcy = hd - T.wall - T.postInset;
|
|
35
|
+
const ocx = hw - T.cornerR, ocy = hd - T.cornerR; // outer corner arc centers
|
|
36
|
+
for (const sx of [-1, 1]) for (const sy of [-1, 1]) {
|
|
37
|
+
let post = box(T.postSq + 0.6, T.postSq + 0.6, T.h - T.floorT)
|
|
38
|
+
.translate(sx * pcx, sy * pcy, T.floorT);
|
|
39
|
+
const diag = Math.SQRT1_2;
|
|
40
|
+
const cut = box(20, 20, T.h + 4).rotate([0, 0, 1], 45)
|
|
41
|
+
.translate(sx * (ocx + (T.cornerR - 1.4 + 10) * diag), sy * (ocy + (T.cornerR - 1.4 + 10) * diag), T.floorT - 2);
|
|
42
|
+
post = post.subtract(cut);
|
|
43
|
+
post = post.subtract(
|
|
44
|
+
box(D.body.socketSq, D.body.socketSq, D.body.socketDepth + 1)
|
|
45
|
+
.translate(sx * pcx, sy * pcy, T.h - D.body.socketDepth)
|
|
46
|
+
);
|
|
47
|
+
tub = tub.add(post);
|
|
48
|
+
}
|
|
49
|
+
// horizontal shell-screw clearance holes through the side walls into the sockets
|
|
50
|
+
for (const sx of [-1, 1]) for (const sy of [-1, 1]) {
|
|
51
|
+
tub = tub.subtract(
|
|
52
|
+
cylinder(8, 1.0).pointAlong([sx, 0, 0]).translate(sx * (hw - 7), sy * pcy, T.sideScrewZ)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// raised border frame around the service door (reference look: inset panel in a frame)
|
|
57
|
+
const frameSk = roundedRect(dr.panelW + 12, dr.panelH + 12, 4)
|
|
58
|
+
.subtract(roundedRect(dr.panelW + 2 * dr.recessSlop + 4, dr.panelH + 2 * dr.recessSlop + 4, 3));
|
|
59
|
+
// welded 1.7 deep: the front face recedes up to 1.5mm where the frame spans
|
|
60
|
+
// the rounded wall corners, and a shallow weld leaves knife-edge slivers
|
|
61
|
+
tub = tub.add(
|
|
62
|
+
frameSk.extrude(1.4 + 1.7).rotate([1, 0, 0], 90).translate(0, -hd + 1.7, dr.zc)
|
|
63
|
+
.trimByPlane([0, 0, 1], 0.8) // keep the border inside the tub footprint
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// service-door recess + opening + screw bosses (front wall, -Y)
|
|
67
|
+
tub = tub.subtract(
|
|
68
|
+
box(dr.panelW + 2 * dr.recessSlop, dr.recess + 0.05, dr.panelH + 2 * dr.recessSlop)
|
|
69
|
+
.translate(0, -hd + (dr.recess + 0.05) / 2 - 0.05, dr.zc - (dr.panelH + 2 * dr.recessSlop) / 2)
|
|
70
|
+
);
|
|
71
|
+
tub = tub.subtract(
|
|
72
|
+
box(dr.openW, T.wall + dr.recess + 1, dr.openH)
|
|
73
|
+
.translate(0, -hd + (T.wall + dr.recess) / 2, dr.zc - dr.openH / 2)
|
|
74
|
+
);
|
|
75
|
+
for (const sx of [-1, 1]) for (const sz of [-1, 1]) {
|
|
76
|
+
const bz = dr.zc + sz * dr.screwDZ;
|
|
77
|
+
tub = tub.add(
|
|
78
|
+
cylinder(dr.bossLen + 0.3, dr.bossD / 2).pointAlong([0, 1, 0])
|
|
79
|
+
.translate(sx * dr.screwDX, -ihd - 0.3, bz)
|
|
80
|
+
);
|
|
81
|
+
tub = tub.subtract(
|
|
82
|
+
cylinder(7.2, dr.pilotD / 2).pointAlong([0, 1, 0])
|
|
83
|
+
.translate(sx * dr.screwDX, -hd + dr.recess - 0.1, bz)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// motor wall slots (gearbox nose passes through, flush with outer face),
|
|
88
|
+
// plus the pod screw clearance holes — one batched subtract per side
|
|
89
|
+
for (const sx of [-1, 1]) {
|
|
90
|
+
const cuts = [
|
|
91
|
+
box(T.wall + 3, M.w + 0.6, M.h + 0.6).translate(sx * (hw - (T.wall + 3) / 2 + 1), 0, az - (M.h + 0.6) / 2),
|
|
92
|
+
];
|
|
93
|
+
for (const pt of circularLayout(P.screwN, P.screwR, { startDeg: 90 })) {
|
|
94
|
+
cuts.push(cylinder(6, P.wallClearD / 2).pointAlong([sx, 0, 0]).translate(sx * (hw - 5), pt.x, az + pt.y));
|
|
95
|
+
}
|
|
96
|
+
tub = tub.subtract(...cuts);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// motor saddles: base + side cheeks + rear stop rib with terminal notch
|
|
100
|
+
const sadY = M.w / 2 + 0.15; // cheek inner face
|
|
101
|
+
const motorBackX = hw - M.len;
|
|
102
|
+
for (const sx of [-1, 1]) {
|
|
103
|
+
const x0 = motorBackX - 0.2, x1 = ihw + 0.4; // welded into the wall
|
|
104
|
+
const sadLen = x1 - x0;
|
|
105
|
+
tub = tub.add(box(sadLen, 2 * sadY + 4.8, az - M.h / 2 - T.floorT + 0.2)
|
|
106
|
+
.translate(sx * (x0 + sadLen / 2), 0, T.floorT - 0.2));
|
|
107
|
+
for (const sy of [-1, 1]) {
|
|
108
|
+
tub = tub.add(box(sadLen, 2.4, M.h - 3)
|
|
109
|
+
.translate(sx * (x0 + sadLen / 2), sy * (sadY + 1.2), az - M.h / 2 - 0.2));
|
|
110
|
+
}
|
|
111
|
+
let rib = box(2.4, 2 * sadY + 4.8, az + M.h / 2 + 2.8 - T.floorT)
|
|
112
|
+
.translate(sx * (motorBackX - 1.2), 0, T.floorT - 0.2);
|
|
113
|
+
rib = rib.subtract(box(3, 9, M.h - 1).translate(sx * (motorBackX - 1.2), 0, az - M.h / 2 + 0.5));
|
|
114
|
+
tub = tub.add(rib);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// board standoff posts (4 per module, corner-inset 2.5mm)
|
|
118
|
+
for (const bd of [B.drv, B.buck, B.mpu]) {
|
|
119
|
+
for (const sx of [-1, 1]) for (const sy of [-1, 1]) {
|
|
120
|
+
tub = tub.add(
|
|
121
|
+
cylinder(B.standoffH + 0.3, 2).translate(bd.x + sx * (bd.w / 2 - 2.5), bd.y + sy * (bd.d / 2 - 2.5), T.floorT - 0.3)
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// rocker-switch opening (rear wall, +Y)
|
|
127
|
+
tub = tub.subtract(
|
|
128
|
+
box(SW.w + 0.4, T.wall + 1, SW.h + 0.4).translate(0, hd - (T.wall + 1) / 2 + 0.4, SW.zc - (SW.h + 0.4) / 2)
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// connector map, incl. one bearing-plane connector per screw
|
|
132
|
+
const conns = {
|
|
133
|
+
deck: connector('mount', { origin: [0, 0, T.h], axis: [0, 0, 1], up: [0, 1, 0], kind: 'fixed' }),
|
|
134
|
+
door_frame: connector('mount', { origin: [0, -hd + dr.recess, dr.zc], axis: [0, -1, 0], up: [0, 0, 1], kind: 'fixed' }),
|
|
135
|
+
pod_seat_l: connector('mount', { origin: [-hw, 0, az], axis: [-1, 0, 0], up: [0, 0, 1], kind: 'fixed' }),
|
|
136
|
+
pod_seat_r: connector('mount', { origin: [hw, 0, az], axis: [1, 0, 0], up: [0, 0, 1], kind: 'fixed' }),
|
|
137
|
+
motor_seat_l: connector('mount', { origin: [-hw, 0, az], axis: [-1, 0, 0], up: [0, 0, 1], kind: 'fixed' }),
|
|
138
|
+
motor_seat_r: connector('mount', { origin: [hw, 0, az], axis: [1, 0, 0], up: [0, 0, 1], kind: 'fixed' }),
|
|
139
|
+
boards_drv: connector('mount', { origin: [0, 0, T.floorT], axis: [0, 0, 1], up: [0, 1, 0], kind: 'fixed' }),
|
|
140
|
+
boards_buck: connector('mount', { origin: [0, 0, T.floorT], axis: [0, 0, 1], up: [0, 1, 0], kind: 'fixed' }),
|
|
141
|
+
boards_mpu: connector('mount', { origin: [0, 0, T.floorT], axis: [0, 0, 1], up: [0, 1, 0], kind: 'fixed' }),
|
|
142
|
+
switch_seat: connector('mount', { origin: [0, hd, SW.zc], axis: [0, 1, 0], up: [0, 0, 1], kind: 'fixed' }),
|
|
143
|
+
};
|
|
144
|
+
// door screws bear on the panel face
|
|
145
|
+
let di = 0;
|
|
146
|
+
for (const sx of [-1, 1]) for (const sz of [-1, 1]) {
|
|
147
|
+
conns[`door_screw_${di++}`] = connector('mount', {
|
|
148
|
+
origin: [sx * dr.screwDX, -hd + dr.recess - dr.panelT, dr.zc + sz * dr.screwDZ],
|
|
149
|
+
axis: [0, -1, 0], up: [0, 0, 1], kind: 'fixed',
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// pod screws bear on the inner wall face (heads inside the tub)
|
|
153
|
+
for (const side of [-1, 1]) {
|
|
154
|
+
let pi = 0;
|
|
155
|
+
for (const pt of circularLayout(P.screwN, P.screwR, { startDeg: 90 })) {
|
|
156
|
+
conns[`pod_screw_${side < 0 ? 'l' : 'r'}_${pi++}`] = connector('mount', {
|
|
157
|
+
origin: [side * ihw, pt.x, az + pt.y], axis: [-side, 0, 0], up: [0, 0, 1], kind: 'fixed',
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// shell screws bear on the outer side walls
|
|
162
|
+
let si = 0;
|
|
163
|
+
for (const sx of [-1, 1]) for (const sy of [-1, 1]) {
|
|
164
|
+
conns[`shell_screw_${si++}`] = connector('mount', {
|
|
165
|
+
origin: [sx * hw, sy * pcy, T.sideScrewZ], axis: [sx, 0, 0], up: [0, 0, 1], kind: 'fixed',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return tealMat(tub).withConnectors(conns);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ------------------------------------------------------------- door ----------
|
|
172
|
+
function buildDoor(D) {
|
|
173
|
+
const dr = D.door;
|
|
174
|
+
let panel = roundedRect(dr.panelW, dr.panelH, 3).extrude(dr.panelT);
|
|
175
|
+
const plug = roundedRect(dr.openW - 2 * dr.slop, dr.openH - 2 * dr.slop, 2)
|
|
176
|
+
.extrude(dr.plugT).translate(0, 0, -dr.plugT);
|
|
177
|
+
let door = panel.add(plug);
|
|
178
|
+
for (const sx of [-1, 1]) for (const sy of [-1, 1]) {
|
|
179
|
+
door = door.subtract(cylinder(dr.plugT + dr.panelT + 1, 1.0)
|
|
180
|
+
.translate(sx * dr.screwDX, sy * dr.screwDZ, -dr.plugT - 0.5));
|
|
181
|
+
}
|
|
182
|
+
return tealMat(door).withConnectors({
|
|
183
|
+
back: connector('mount', { origin: [0, 0, 0], axis: [0, 0, -1], up: [0, 1, 0], kind: 'fixed' }),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ------------------------------------------------------------- pods ----------
|
|
188
|
+
function buildPod(D) {
|
|
189
|
+
const P = D.pod;
|
|
190
|
+
let pod = cylinder(P.len, P.od / 2).subtract(cylinder(P.len + 1, P.boreD / 2).translate(0, 0, -0.5));
|
|
191
|
+
for (const pt of circularLayout(P.screwN, P.screwR, { startDeg: 90 })) {
|
|
192
|
+
pod = pod.subtract(cylinder(P.pilotDepth + 0.1, P.pilotD / 2).translate(pt.x, pt.y, -0.1));
|
|
193
|
+
}
|
|
194
|
+
// cosmetic face groove
|
|
195
|
+
pod = pod.subtract(
|
|
196
|
+
cylinder(1.0, P.od / 2 - 2).subtract(cylinder(1.4, P.od / 2 - 3.4).translate(0, 0, -0.2))
|
|
197
|
+
.translate(0, 0, P.len - 1)
|
|
198
|
+
);
|
|
199
|
+
return pod.color(COL_BLACK).material({ metalness: 0.1, roughness: 0.6 }).withConnectors({
|
|
200
|
+
flange: connector('mount', { origin: [0, 0, 0], axis: [0, 0, -1], up: [0, 1, 0], kind: 'fixed' }),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ------------------------------------------------------------ motor ----------
|
|
205
|
+
function buildMotor(D) {
|
|
206
|
+
const M = D.motor;
|
|
207
|
+
// single Shape (not a group) so collision/clearance verifies stay exact
|
|
208
|
+
const body = box(M.w, M.h, M.len).translate(0, 0, -M.len);
|
|
209
|
+
const nose = cylinder(M.noseBossL, M.noseBossD / 2);
|
|
210
|
+
// only a stub of the D-shaft: the exposed shaft is modeled on the wheel part,
|
|
211
|
+
// where it turns with the wheel (a static D-shaft in a turning D-bore would
|
|
212
|
+
// interpenetrate at every non-zero joint angle)
|
|
213
|
+
const stub = cylinder(1.3, M.shaftD / 2);
|
|
214
|
+
const terms = box(1.2, 3, 1.9).translate(3, 0, -M.len - 1.5)
|
|
215
|
+
.add(box(1.2, 3, 1.9).translate(-3, 0, -M.len - 1.5));
|
|
216
|
+
return body.add(nose, stub, terms)
|
|
217
|
+
.color(COL_STEEL).material({ metalness: 0.85, roughness: 0.35 })
|
|
218
|
+
.withConnectors({
|
|
219
|
+
nose: connector('mount', { origin: [0, 0, 0], axis: [0, 0, -1], up: [0, 1, 0], kind: 'fixed' }),
|
|
220
|
+
shaft_tip: connector('axle', { origin: [0, 0, 1.5], axis: [0, 0, 1], up: [0, 1, 0], kind: 'revolute' }),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ----------------------------------------------------- electronics -----------
|
|
225
|
+
function buildBoards(D) {
|
|
226
|
+
const B = D.boards;
|
|
227
|
+
const mk = (bd, colPcb, chipW, chipD) => {
|
|
228
|
+
const pcb = box(bd.w, bd.d, bd.t).translate(bd.x, bd.y, B.standoffH);
|
|
229
|
+
const chip = box(chipW, chipD, 1.2).translate(bd.x, bd.y, B.standoffH + bd.t);
|
|
230
|
+
return pcb.add(chip).color(colPcb).material({ metalness: 0.1, roughness: 0.5 })
|
|
231
|
+
.withConnectors({
|
|
232
|
+
mount: connector('mount', { origin: [0, 0, 0], axis: [0, 0, -1], up: [0, 1, 0], kind: 'fixed' }),
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
// three separate purchased modules, each its own Shape (exact collision checks)
|
|
236
|
+
return {
|
|
237
|
+
drv: mk(B.drv, '#6b21a8', 6, 6),
|
|
238
|
+
buck: mk(B.buck, '#1e6fb8', 8, 7),
|
|
239
|
+
mpu: mk(B.mpu, '#1e40af', 5, 5),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function buildSwitch(D) {
|
|
244
|
+
const SW = D.sw;
|
|
245
|
+
// local frame: +Z = outward, mating plane (plate back on wall outer face) at z=0
|
|
246
|
+
const plateL = box(SW.plateW, SW.plateH, SW.plateT);
|
|
247
|
+
const bodyL = box(SW.w, SW.h, SW.t).translate(0, 0, -SW.t);
|
|
248
|
+
const rocker = box(8, 5, 1.6).translate(0, 0, SW.plateT);
|
|
249
|
+
let sw = plateL.add(bodyL).add(rocker);
|
|
250
|
+
return sw.color('#15161a').material({ metalness: 0.15, roughness: 0.45 }).withConnectors({
|
|
251
|
+
mount: connector('mount', { origin: [0, 0, 0], axis: [0, 0, -1], up: [0, 1, 0], kind: 'fixed' }),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ------------------------------------------------------------------------------
|
|
256
|
+
const D = dimsMod.compute({ wall: wallP, wheelOD: wheelODP });
|
|
257
|
+
|
|
258
|
+
const make = { buildTub, buildDoor, buildPod, buildMotor, buildBoards, buildSwitch };
|
|
259
|
+
|
|
260
|
+
if (require.main === module) {
|
|
261
|
+
const hw = require('./hardware.forge.js');
|
|
262
|
+
const tub = buildTub(D);
|
|
263
|
+
const door = buildDoor(D).matchTo(tub, 'back', 'door_frame');
|
|
264
|
+
const screws = [];
|
|
265
|
+
for (let i = 0; i < 4; i++) screws.push({ name: `Door Screw ${i + 1}`, shape: hw.make.screwM2(8).matchTo(tub, 'seat', `door_screw_${i}`) });
|
|
266
|
+
for (const sd of ['l', 'r']) for (let i = 0; i < 3; i++) {
|
|
267
|
+
screws.push({ name: `Pod Screw ${sd.toUpperCase()} ${i + 1}`, shape: hw.make.screwM2(8).matchTo(tub, 'seat', `pod_screw_${sd}_${i}`) });
|
|
268
|
+
}
|
|
269
|
+
for (let i = 0; i < 4; i++) screws.push({ name: `Shell Screw ${i + 1}`, shape: hw.make.screwM2(8).matchTo(tub, 'seat', `shell_screw_${i}`) });
|
|
270
|
+
const podL = buildPod(D).matchTo(tub, 'flange', 'pod_seat_l');
|
|
271
|
+
const podR = buildPod(D).matchTo(tub, 'flange', 'pod_seat_r');
|
|
272
|
+
const motorL = buildMotor(D).matchTo(tub, 'nose', 'motor_seat_l');
|
|
273
|
+
const motorR = buildMotor(D).matchTo(tub, 'nose', 'motor_seat_r');
|
|
274
|
+
const mods = buildBoards(D);
|
|
275
|
+
const drv = mods.drv.matchTo(tub, 'mount', 'boards_drv');
|
|
276
|
+
const buck = mods.buck.matchTo(tub, 'mount', 'boards_buck');
|
|
277
|
+
const mpu = mods.mpu.matchTo(tub, 'mount', 'boards_mpu');
|
|
278
|
+
const rocker = buildSwitch(D).matchTo(tub, 'mount', 'switch_seat');
|
|
279
|
+
|
|
280
|
+
verify.notColliding('motor L clear of tub', motorL, tub);
|
|
281
|
+
verify.notColliding('motor R clear of tub', motorR, tub);
|
|
282
|
+
verify.notColliding('DRV8833 clear of tub', drv, tub);
|
|
283
|
+
verify.notColliding('buck clear of tub', buck, tub);
|
|
284
|
+
verify.notColliding('IMU clear of tub', mpu, tub);
|
|
285
|
+
verify.notColliding('door clear of tub', door, tub);
|
|
286
|
+
verify.notColliding('pod L clear of motor L', podL, motorL);
|
|
287
|
+
verify.notColliding('switch clear of tub', rocker, tub);
|
|
288
|
+
verify.clearanceBetween('door seats in recess', door, tub, -0.01, 0.1);
|
|
289
|
+
verify.clearanceBetween('motor L seats on saddle', motorL, tub, -0.01, 0.1);
|
|
290
|
+
verify.clearanceBetween('pod L seats on wall', podL, tub, -0.01, 0.1);
|
|
291
|
+
verify.clearanceBetween('IMU seats on standoffs', mpu, tub, -0.01, 0.1);
|
|
292
|
+
|
|
293
|
+
scene({
|
|
294
|
+
camera: { position: [180, -190, 150], target: [0, 0, 26], fov: 40 },
|
|
295
|
+
environment: { preset: 'studio', intensity: 0.25, background: false },
|
|
296
|
+
lights: [
|
|
297
|
+
{ type: 'ambient', color: '#efe7dc', intensity: 0.18 },
|
|
298
|
+
{ type: 'directional', position: [140, -160, 220], color: '#ffe2bf', intensity: 2.6, castShadow: true },
|
|
299
|
+
{ type: 'directional', position: [-130, 110, 110], color: '#d4e6fb', intensity: 0.8 },
|
|
300
|
+
],
|
|
301
|
+
});
|
|
302
|
+
return {
|
|
303
|
+
preview: [
|
|
304
|
+
{ name: 'Tub', shape: tub },
|
|
305
|
+
{ name: 'Door', shape: door },
|
|
306
|
+
...screws,
|
|
307
|
+
{ name: 'Pod L', shape: podL },
|
|
308
|
+
{ name: 'Pod R', shape: podR },
|
|
309
|
+
{ name: 'Motor L', shape: motorL },
|
|
310
|
+
{ name: 'Motor R', shape: motorR },
|
|
311
|
+
{ name: 'DRV8833', shape: drv },
|
|
312
|
+
{ name: 'MP1584 Buck', shape: buck },
|
|
313
|
+
{ name: 'MPU6050 IMU', shape: mpu },
|
|
314
|
+
{ name: 'Switch', shape: rocker },
|
|
315
|
+
],
|
|
316
|
+
make,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { make };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Shared purchased hardware builders for the scout cam rover.
|
|
2
|
+
// screwM2(len): pan-head M2 self-tapping screw, modeled at thread minor diameter
|
|
3
|
+
// (1.6) so clearance holes (1.9-2.0) and pilots (1.7) never interpenetrate.
|
|
4
|
+
// Local frame: bearing plane (under-head) at z=0, head +Z, shank -Z.
|
|
5
|
+
// Connector 'seat': recipe frame — parent connector axis = world direction of the head side.
|
|
6
|
+
|
|
7
|
+
function screwM2(len) {
|
|
8
|
+
const head = cylinder(1.5, 1.9);
|
|
9
|
+
const shank = cylinder(len, 0.8).translate(0, 0, -len);
|
|
10
|
+
return head.add(shank)
|
|
11
|
+
.color('#3a3d42').material({ metalness: 0.8, roughness: 0.4 })
|
|
12
|
+
.withConnectors({
|
|
13
|
+
seat: connector('mount', { origin: [0, 0, 0], axis: [0, 0, -1], up: [0, 1, 0], kind: 'fixed' }),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (require.main === module) {
|
|
18
|
+
return { preview: [{ name: 'M2x8', shape: screwM2(8) }], make: { screwM2 } };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { make: { screwM2 } };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Pan turret of the scout cam rover (printed in black PLA).
|
|
2
|
+
// Rides the SG90 round horn through the neck bore: the central column reaches
|
|
3
|
+
// down through the neck, its socket ceiling rests on the horn top (M2 screw up
|
|
4
|
+
// through the horn into the spline retains it — purchased hardware, see BOM).
|
|
5
|
+
// Turret-local frame: base underside at z=0, +Z up, spline axis = Z.
|
|
6
|
+
|
|
7
|
+
const wallP = param('wall_thickness', 2.4, { min: 2.0, max: 3.0, step: 0.1, unit: 'mm' });
|
|
8
|
+
const wheelODP = param('wheel_od', 110, { min: 96, max: 130, step: 1, unit: 'mm' });
|
|
9
|
+
|
|
10
|
+
const dimsMod = require('../lib/dims.js');
|
|
11
|
+
|
|
12
|
+
const COL_BLACK = '#1f2023';
|
|
13
|
+
|
|
14
|
+
function buildTurret(D) {
|
|
15
|
+
const T = D.turret;
|
|
16
|
+
|
|
17
|
+
// base disc with 4 cosmetic lugs
|
|
18
|
+
let tr = cylinder(T.baseH, T.baseD / 2);
|
|
19
|
+
for (const pt of circularLayout(T.lugN, T.baseD / 2 + T.lugT / 2 - 0.3, { startDeg: 45 })) {
|
|
20
|
+
tr = tr.add(
|
|
21
|
+
box(T.lugW, T.lugT, T.lugH).translate(pt.x, pt.y, 0)
|
|
22
|
+
.rotate([0, 0, 1], (Math.atan2(pt.y, pt.x) * 180) / Math.PI, { pivot: [pt.x, pt.y, 0] })
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// barrel + cap
|
|
27
|
+
tr = tr.add(cylinder(T.barrelH, T.barrelD / 2).translate(0, 0, T.baseH));
|
|
28
|
+
tr = tr.add(cylinder(T.capH, T.capD / 2).translate(0, 0, T.baseH + T.barrelH));
|
|
29
|
+
|
|
30
|
+
// emblem on top: two engraved rings + a small dome
|
|
31
|
+
const capTop = T.baseH + T.barrelH + T.capH;
|
|
32
|
+
const ringCut = (rOut, rIn) =>
|
|
33
|
+
cylinder(1.0, rOut).subtract(cylinder(1.4, rIn).translate(0, 0, -0.2)).translate(0, 0, capTop - 0.8);
|
|
34
|
+
tr = tr.subtract(ringCut(10, 8), ringCut(5, 3.4));
|
|
35
|
+
tr = tr.add(sphere(2.2).scale([1, 1, 0.55]).translate(0, 0, capTop - 0.4));
|
|
36
|
+
|
|
37
|
+
// central column down through the neck to the servo horn
|
|
38
|
+
const colTop = 0.5; // weld into the base disc
|
|
39
|
+
let col = cylinder(T.colLen + colTop, T.colD / 2).translate(0, 0, -T.colLen);
|
|
40
|
+
col = col.add(cylinder(T.colHubH, T.colHubD / 2).translate(0, 0, -T.colLen));
|
|
41
|
+
// horn socket: open downward, ceiling rests on the horn top face
|
|
42
|
+
col = col.subtract(cylinder(T.socketDepth + 0.1, T.socketD / 2).translate(0, 0, -T.colLen - 0.1));
|
|
43
|
+
// clearance bore above the socket for the M2 retention screw head
|
|
44
|
+
col = col.subtract(cylinder(6, 2.6).translate(0, 0, -T.colLen + T.socketDepth - 0.1));
|
|
45
|
+
tr = tr.add(col);
|
|
46
|
+
|
|
47
|
+
return tr.color(COL_BLACK).material({ metalness: 0.15, roughness: 0.5, clearcoat: 0.3 })
|
|
48
|
+
.withConnectors({
|
|
49
|
+
hub: connector('axle', {
|
|
50
|
+
origin: [0, 0, -T.colLen + T.socketDepth], axis: [0, 0, -1], up: [0, 1, 0], kind: 'revolute',
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const D = dimsMod.compute({ wall: wallP, wheelOD: wheelODP });
|
|
56
|
+
|
|
57
|
+
if (require.main === module) {
|
|
58
|
+
scene({
|
|
59
|
+
camera: { position: [90, -100, 80], target: [0, 0, 8], fov: 40 },
|
|
60
|
+
environment: { preset: 'studio', intensity: 0.3, background: false },
|
|
61
|
+
lights: [
|
|
62
|
+
{ type: 'ambient', color: '#efe7dc', intensity: 0.25 },
|
|
63
|
+
{ type: 'directional', position: [80, -90, 130], color: '#ffe2bf', intensity: 2.4, castShadow: true },
|
|
64
|
+
{ type: 'directional', position: [-70, 60, 60], color: '#d4e6fb', intensity: 0.9 },
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
return { preview: [{ name: 'Turret', shape: buildTurret(D) }], make: { buildTurret } };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { make: { buildTurret } };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Wheel for the scout cam rover: printed rim (PLA) + printed tire (TPU).
|
|
2
|
+
// Wheel-local frame: z=0 is the hub bore mouth (faces the robot), +Z points outboard.
|
|
3
|
+
// The hub boss runs inside the motor-pod bore and grips the N20 D-shaft (press fit).
|
|
4
|
+
|
|
5
|
+
const wallP = param('wall_thickness', 2.4, { min: 2.0, max: 3.0, step: 0.1, unit: 'mm' });
|
|
6
|
+
const wheelODP = param('wheel_od', 110, { min: 96, max: 130, step: 1, unit: 'mm' });
|
|
7
|
+
|
|
8
|
+
const dimsMod = require('../lib/dims.js');
|
|
9
|
+
|
|
10
|
+
const COL_TEAL = '#2fb3b8';
|
|
11
|
+
const COL_TIRE = '#cfc8bc';
|
|
12
|
+
|
|
13
|
+
function buildRim(D) {
|
|
14
|
+
const hub = D.hub, web = D.web;
|
|
15
|
+
const rimOR = D.rimOD / 2, rimIR = D.rimID / 2;
|
|
16
|
+
|
|
17
|
+
// hub boss with D-bore for the N20 shaft
|
|
18
|
+
let boss = cylinder(hub.bossL, hub.bossD / 2);
|
|
19
|
+
const flatY = hub.boreFlat - hub.boreD / 2; // flat plane offset from center
|
|
20
|
+
const dProfile = circle2d(hub.boreD / 2)
|
|
21
|
+
.intersect(rect(hub.boreD + 0.4, hub.boreFlat).translate(0, (hub.boreFlat / 2) - hub.boreD / 2));
|
|
22
|
+
const dBore = dProfile.extrude(hub.boreDepth + 0.1).translate(0, 0, -0.1);
|
|
23
|
+
boss = boss.subtract(dBore);
|
|
24
|
+
verify.greaterThan('D-bore flat offset is positive', flatY + hub.boreD / 2, 0);
|
|
25
|
+
|
|
26
|
+
// spoke web: hub disc + crosshair spokes + mid ring, welded 1mm into the rim band
|
|
27
|
+
const webReach = rimIR + 1;
|
|
28
|
+
const webSk = circle2d(11)
|
|
29
|
+
.add(rect(webReach * 2, 8))
|
|
30
|
+
.add(rect(8, webReach * 2))
|
|
31
|
+
.add(circle2d(28).subtract(circle2d(24)));
|
|
32
|
+
let webSolid = webSk.extrude(web.t).translate(0, 0, web.z0);
|
|
33
|
+
|
|
34
|
+
// rim band
|
|
35
|
+
const rimBand = cylinder(D.rimW, rimOR).subtract(cylinder(D.rimW + 2, rimIR).translate(0, 0, -1))
|
|
36
|
+
.translate(0, 0, D.rim.z0);
|
|
37
|
+
|
|
38
|
+
let rimPart = union(boss, webSolid, rimBand);
|
|
39
|
+
|
|
40
|
+
// cosmetic hub-face dimples (visible on the outboard web face)
|
|
41
|
+
for (const pt of circularLayout(hub.faceHoleN, hub.faceHoleR, { startDeg: 90 })) {
|
|
42
|
+
rimPart = rimPart.subtract(
|
|
43
|
+
cylinder(1.6, hub.faceHoleD / 2).translate(pt.x, pt.y, web.z0 + web.t - 1.5)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return rimPart.color(COL_TEAL).material({ metalness: 0.05, roughness: 0.45 });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildTire(D) {
|
|
50
|
+
const rimOR = D.rimOD / 2, tireOR = D.tireOD / 2;
|
|
51
|
+
let prof = rect(tireOR - rimOR, D.tireW).translate(rimOR + (tireOR - rimOR) / 2, D.tireW / 2);
|
|
52
|
+
prof = prof.filletCorner([tireOR, 0], 2.5).filletCorner([tireOR, D.tireW], 2.5);
|
|
53
|
+
return prof.revolve()
|
|
54
|
+
.translate(0, 0, D.rim.z0)
|
|
55
|
+
.color(COL_TIRE).material({ metalness: 0.0, roughness: 0.85 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildShaft(D) {
|
|
59
|
+
// The N20 D-shaft rides in the wheel's D-bore and turns WITH the wheel, so it
|
|
60
|
+
// is modeled as a child of the wheel part (the motor keeps a short stub).
|
|
61
|
+
const M = D.motor;
|
|
62
|
+
const prof = circle2d(M.shaftD / 2)
|
|
63
|
+
.intersect(rect(M.shaftD + 0.4, M.shaftFlat).translate(0, M.shaftFlat / 2 - M.shaftD / 2));
|
|
64
|
+
return prof.extrude(8.5).translate(0, 0, -0.1)
|
|
65
|
+
.color('#d8dbe0').material({ metalness: 0.9, roughness: 0.25 });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Returns the wheel as three separate single-body parts that share the same
|
|
69
|
+
// local frame: the rim carries the joint bore plus identity seats for the tire
|
|
70
|
+
// (stretch fit on the rim band) and the N20 D-shaft (turns with the wheel).
|
|
71
|
+
function buildWheel(D) {
|
|
72
|
+
const idSeat = () => connector('mount', { origin: [0, 0, 0], axis: [0, 0, 1], up: [0, 1, 0], kind: 'fixed' });
|
|
73
|
+
const idChild = () => connector('mount', { origin: [0, 0, 0], axis: [0, 0, -1], up: [0, 1, 0], kind: 'fixed' });
|
|
74
|
+
const rimPart = buildRim(D).withConnectors({
|
|
75
|
+
bore: connector('axle', { origin: [0, 0, 0], axis: [0, 0, -1], up: [0, 1, 0], kind: 'revolute' }),
|
|
76
|
+
tire_seat: idSeat(),
|
|
77
|
+
shaft_seat: idSeat(),
|
|
78
|
+
});
|
|
79
|
+
const tirePart = buildTire(D).withConnectors({
|
|
80
|
+
seat: idChild(),
|
|
81
|
+
tread: connector('contact', {
|
|
82
|
+
origin: [0, -D.tireOD / 2, D.rim.z0 + D.tireW / 2],
|
|
83
|
+
axis: [0, -1, 0],
|
|
84
|
+
up: [0, 0, 1],
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
const shaftPart = buildShaft(D).withConnectors({ seat: idChild() });
|
|
88
|
+
verify.clearanceBetween('tire seats on rim band', tirePart, rimPart, -0.01, 0.05);
|
|
89
|
+
verify.clearanceBetween('D-shaft rides in the D-bore', shaftPart, rimPart, 0.02, 0.15);
|
|
90
|
+
return { rim: rimPart, tire: tirePart, shaft: shaftPart };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const D = dimsMod.compute({ wall: wallP, wheelOD: wheelODP });
|
|
94
|
+
|
|
95
|
+
if (require.main === module) {
|
|
96
|
+
scene({
|
|
97
|
+
camera: { position: [160, -150, 120], target: [0, 0, 17], fov: 40 },
|
|
98
|
+
environment: { preset: 'studio', intensity: 0.25, background: false },
|
|
99
|
+
lights: [
|
|
100
|
+
{ type: 'ambient', color: '#efe7dc', intensity: 0.18 },
|
|
101
|
+
{ type: 'directional', position: [140, -160, 220], color: '#ffe2bf', intensity: 2.6, castShadow: true },
|
|
102
|
+
{ type: 'directional', position: [-130, 110, 110], color: '#d4e6fb', intensity: 0.8 },
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
const wp = buildWheel(D);
|
|
106
|
+
return {
|
|
107
|
+
preview: [
|
|
108
|
+
{ name: 'Rim', shape: wp.rim },
|
|
109
|
+
{ name: 'Tire', shape: wp.tire },
|
|
110
|
+
{ name: 'Drive Shaft', shape: wp.shaft },
|
|
111
|
+
],
|
|
112
|
+
make: { buildWheel },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { make: { buildWheel } };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const pine = Sim.material("pine crate", {
|
|
2
|
+
densityKgM3: 520,
|
|
3
|
+
staticFriction: 0.55,
|
|
4
|
+
dynamicFriction: 0.42,
|
|
5
|
+
restitution: 0.08,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const woodMaterial = { roughness: 0.78, metalness: 0.02 };
|
|
9
|
+
const darkEndgrain = { roughness: 0.86, metalness: 0.01 };
|
|
10
|
+
|
|
11
|
+
const rim = group(
|
|
12
|
+
{ name: "Front Top Rail", shape: box(132, 8, 8).translate(0, -50, 37).color("#6f4a25").material(darkEndgrain) },
|
|
13
|
+
{ name: "Back Top Rail", shape: box(132, 8, 8).translate(0, 50, 37).color("#6f4a25").material(darkEndgrain) },
|
|
14
|
+
{ name: "Left Top Rail", shape: box(8, 92, 8).translate(-66, 0, 37).color("#6f4a25").material(darkEndgrain) },
|
|
15
|
+
{ name: "Right Top Rail", shape: box(8, 92, 8).translate(66, 0, 37).color("#6f4a25").material(darkEndgrain) },
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const cornerPosts = group(
|
|
19
|
+
{ name: "Front Left Post", shape: box(10, 10, 78).translate(-64, -48, 0).color("#714d27").material(darkEndgrain) },
|
|
20
|
+
{ name: "Front Right Post", shape: box(10, 10, 78).translate(64, -48, 0).color("#714d27").material(darkEndgrain) },
|
|
21
|
+
{ name: "Back Left Post", shape: box(10, 10, 78).translate(-64, 48, 0).color("#714d27").material(darkEndgrain) },
|
|
22
|
+
{ name: "Back Right Post", shape: box(10, 10, 78).translate(64, 48, 0).color("#714d27").material(darkEndgrain) },
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const sideSlats = group(
|
|
26
|
+
{ name: "Front Upper Slat", shape: box(118, 7, 12).translate(0, -52, 18).color("#81582c").material(woodMaterial) },
|
|
27
|
+
{ name: "Front Lower Slat", shape: box(118, 7, 12).translate(0, -52, -12).color("#9a6a38").material(woodMaterial) },
|
|
28
|
+
{ name: "Back Upper Slat", shape: box(118, 7, 12).translate(0, 52, 18).color("#81582c").material(woodMaterial) },
|
|
29
|
+
{ name: "Back Lower Slat", shape: box(118, 7, 12).translate(0, 52, -12).color("#9a6a38").material(woodMaterial) },
|
|
30
|
+
{ name: "Left Upper Slat", shape: box(7, 78, 12).translate(-70, 0, 18).color("#81582c").material(woodMaterial) },
|
|
31
|
+
{ name: "Left Lower Slat", shape: box(7, 78, 12).translate(-70, 0, -12).color("#9a6a38").material(woodMaterial) },
|
|
32
|
+
{ name: "Right Upper Slat", shape: box(7, 78, 12).translate(70, 0, 18).color("#81582c").material(woodMaterial) },
|
|
33
|
+
{ name: "Right Lower Slat", shape: box(7, 78, 12).translate(70, 0, -12).color("#9a6a38").material(woodMaterial) },
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const floorPlanks = group(
|
|
37
|
+
{ name: "Floor Left", shape: box(36, 86, 8).translate(-40, 0, -36).color("#a8733d").material(woodMaterial) },
|
|
38
|
+
{ name: "Floor Center", shape: box(36, 86, 8).translate(0, 0, -36).color("#b47b42").material(woodMaterial) },
|
|
39
|
+
{ name: "Floor Right", shape: box(36, 86, 8).translate(40, 0, -36).color("#a8733d").material(woodMaterial) },
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const forkliftSkids = group(
|
|
43
|
+
{ name: "Left Fork Pocket", shape: box(112, 10, 8).translate(0, -28, -50).color("#6f4a25").material(darkEndgrain) },
|
|
44
|
+
{ name: "Right Fork Pocket", shape: box(112, 10, 8).translate(0, 28, -50).color("#6f4a25").material(darkEndgrain) },
|
|
45
|
+
{ name: "Left Spacer", shape: box(14, 10, 18).translate(-46, -28, -42).color("#714d27").material(darkEndgrain) },
|
|
46
|
+
{ name: "Right Spacer", shape: box(14, 10, 18).translate(46, 28, -42).color("#714d27").material(darkEndgrain) },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const handles = group(
|
|
50
|
+
{ name: "Front Handhold", shape: box(48, 5, 14).translate(0, -56, 3).color("#3f2a18").material({ roughness: 0.9 }) },
|
|
51
|
+
{ name: "Back Handhold", shape: box(48, 5, 14).translate(0, 56, 3).color("#3f2a18").material({ roughness: 0.9 }) },
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const crate = group(
|
|
55
|
+
{ name: "Rim", group: rim },
|
|
56
|
+
{ name: "Posts", group: cornerPosts },
|
|
57
|
+
{ name: "Side Slats", group: sideSlats },
|
|
58
|
+
{ name: "Floor Planks", group: floorPlanks },
|
|
59
|
+
{ name: "Forklift Skids", group: forkliftSkids },
|
|
60
|
+
{ name: "Handles", group: handles },
|
|
61
|
+
).withConnectors({
|
|
62
|
+
grip: connector({ origin: [0, -56, 3], axis: [0, -1, 0], up: [0, 0, 1] }),
|
|
63
|
+
floor_contact: connector({ origin: [0, 0, -54], axis: [0, 0, -1], up: [1, 0, 0] }),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return assembly("SimReady Asset Crate")
|
|
67
|
+
.addPart("Body", crate, {
|
|
68
|
+
sim: Sim.body({
|
|
69
|
+
massKg: 2.4,
|
|
70
|
+
material: pine,
|
|
71
|
+
collider: Sim.collider.convexHull(),
|
|
72
|
+
contacts: {
|
|
73
|
+
grip: Sim.contact.gripperSurface("grip"),
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
})
|
|
77
|
+
.withSimulation({
|
|
78
|
+
profile: Sim.profile.roboticsAssetPhysx(),
|
|
79
|
+
});
|