partforge 0.141.0 → 0.143.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 +1 -1
- package/docs/AUTHORING-PARTS.md +107 -63
- package/docs/ERROR-PATTERNS.md +25 -7
- package/docs/KERNEL-CONTRACT.md +1 -1
- package/package.json +1 -1
- package/src/framework/default-view.js +2 -1
- package/src/framework/export/bed-layout.js +62 -0
- package/src/framework/jobs.js +15 -5
- package/src/framework/lint/rules-animations.js +4 -3
- package/src/framework/lint/rules-place.js +91 -35
- package/src/framework/lint/rules-shape.js +10 -7
- package/src/framework/materials/print-frame.js +20 -1
- package/src/framework/materials/sheet-look.js +2 -0
- package/src/framework/mount.js +2 -0
- package/src/framework/oracle/measure.js +28 -8
- package/src/framework/oracle/verify.js +1 -1
- package/src/framework/param-deps.js +5 -3
- package/src/framework/part-model.js +5 -4
- package/src/framework/pose-probe-core.js +5 -3
- package/src/framework/sheet/part.js +5 -1
- package/src/framework/sub-part-views.js +56 -0
- package/src/parts/hinged-box.js +12 -13
- package/src/parts/laser-box.js +8 -9
- package/src/parts/lattice-box.js +110 -0
- package/src/parts/mixed-smoke.js +2 -3
- package/types/geometry.d.ts +1 -1
- package/types/part.d.ts +10 -3
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Worked example for the views map (docs/AUTHORING-PARTS.md "The PartDefinition
|
|
2
|
+
// contract"): a storage box with a hinged lid, a lattice insert in the lid window, a
|
|
3
|
+
// nameplate on the front and a filament-offcut hinge pin. Every piece is ONE sub-part,
|
|
4
|
+
// built as it prints; each view entry only moves it — `assembly` puts it together and
|
|
5
|
+
// swings the lid, the piece tabs show it as built, `print` spreads the printed pieces
|
|
6
|
+
// on the bed. lidAngle is read only in view entries, so the Open lid animation never
|
|
7
|
+
// rebuilds geometry, and layer lines follow each piece's print orientation.
|
|
8
|
+
|
|
9
|
+
// The one hinge every piece that rides on the lid swings about.
|
|
10
|
+
const swing = (s, p, d) => s.rotate(-p.lidAngle, d.hinge, [1, 0, 0]);
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
meta: { title: "Lattice Box", units: "mm" },
|
|
14
|
+
parameters: [
|
|
15
|
+
{
|
|
16
|
+
id: "box", title: "Box",
|
|
17
|
+
advanced: [
|
|
18
|
+
{ key: "w", label: "Width", unit: "mm", min: 50, max: 140, step: 1 },
|
|
19
|
+
{ key: "d", label: "Depth", unit: "mm", min: 40, max: 120, step: 1 },
|
|
20
|
+
{ key: "h", label: "Height", unit: "mm", min: 20, max: 80, step: 1 },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "pose", title: "Pose",
|
|
25
|
+
advanced: [{ key: "lidAngle", label: "Lid angle", unit: "°", min: 0, max: 110, step: 1 }],
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
defaults: { w: 80, d: 60, h: 40, wall: 2, t: 2.4, lidAngle: 0 },
|
|
29
|
+
derive: (p) => ({
|
|
30
|
+
hinge: [0, p.d + 1, p.h], // just behind the rear top edge, along X
|
|
31
|
+
win: { x: 12, y: 12, w: p.w - 24, d: p.d - 24 }, // the lid's window
|
|
32
|
+
fit: 0.2,
|
|
33
|
+
gap: 10, // spacing in the print view
|
|
34
|
+
}),
|
|
35
|
+
parts: {
|
|
36
|
+
body: {
|
|
37
|
+
label: "Body",
|
|
38
|
+
display: { color: 0x1e88e5 },
|
|
39
|
+
// Open-top box; prints open side up, as built.
|
|
40
|
+
build: (k, p) => k.box({ min: [0, 0, 0], max: [p.w, p.d, p.h] })
|
|
41
|
+
.cut(k.box({ min: [p.wall, p.wall, p.wall], max: [p.w - p.wall, p.d - p.wall, p.h + 1] })),
|
|
42
|
+
views: { assembly: true, body: true, print: true },
|
|
43
|
+
},
|
|
44
|
+
lid: {
|
|
45
|
+
label: "Lid",
|
|
46
|
+
display: { color: 0xe53935 },
|
|
47
|
+
// A flat plate with the lattice window, printed flat.
|
|
48
|
+
build: (k, p, d) => k.box({ min: [0, 0, 0], max: [p.w, p.d, p.t] })
|
|
49
|
+
.cut(k.box({ min: [d.win.x, d.win.y, -1], max: [d.win.x + d.win.w, d.win.y + d.win.d, p.t + 1] })),
|
|
50
|
+
views: {
|
|
51
|
+
assembly: (s, p, d) => swing(s.translate([0, 0, p.h]), p, d),
|
|
52
|
+
lid: true,
|
|
53
|
+
print: (s, p, d) => s.translate([p.w + d.gap, 0, 0]),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
mesh: {
|
|
57
|
+
label: "Lattice insert",
|
|
58
|
+
display: { color: 0x222222 },
|
|
59
|
+
// Fills the window less the fit, with a staggered grid of round holes; printed flat.
|
|
60
|
+
build: (k, p, d) => {
|
|
61
|
+
const W = d.win.w - 2 * d.fit, D = d.win.d - 2 * d.fit;
|
|
62
|
+
let plate = k.box({ min: [0, 0, 0], max: [W, D, p.t] });
|
|
63
|
+
let row = 0;
|
|
64
|
+
for (let y = 4; y <= D - 4; y += 6, row++) {
|
|
65
|
+
for (let x = 4 + (row % 2) * 3; x <= W - 4; x += 6) {
|
|
66
|
+
plate = plate.cut(k.cylinder({ r: 2.2, h: p.t + 2 }).translate([x, y, -1]));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return plate;
|
|
70
|
+
},
|
|
71
|
+
views: {
|
|
72
|
+
assembly: (s, p, d) => swing(s.translate([d.win.x + d.fit, d.win.y + d.fit, p.h]), p, d),
|
|
73
|
+
mesh: true,
|
|
74
|
+
print: (s, p, d) => s.translate([p.w + d.gap, p.d + d.gap, 0]),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
plate: {
|
|
78
|
+
label: "Nameplate",
|
|
79
|
+
display: { color: 0xffd400 },
|
|
80
|
+
// A thin plate, printed flat; stands just off the front of the box (0.2 mm clear) in the assembly.
|
|
81
|
+
build: (k) => k.box({ min: [0, 0, 0], max: [40, 12, 1.6] }),
|
|
82
|
+
views: {
|
|
83
|
+
assembly: (s, p) => s.rotateX(90).translate([(p.w - 40) / 2, -0.2, p.h / 2 - 6]),
|
|
84
|
+
plate: true,
|
|
85
|
+
print: (s, p, d) => s.translate([0, p.d + d.gap, 0]),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
pin: {
|
|
89
|
+
label: "Hinge pin (1.75 mm filament)",
|
|
90
|
+
exportable: false,
|
|
91
|
+
display: { color: 0x9e9e9e },
|
|
92
|
+
build: (k, p) => k.cylinder({ r: 0.875, h: p.w }).rotate(90, [0, 0, 0], [0, 1, 0]),
|
|
93
|
+
views: { assembly: (s, p, d) => s.translate(d.hinge) },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
views: {
|
|
97
|
+
assembly: {
|
|
98
|
+
label: "Assembly",
|
|
99
|
+
animations: {
|
|
100
|
+
open: { label: "Open lid", duration: 1.2, tracks: { lidAngle: [[0, 0], [1, 110]] } },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
body: { label: "Body" },
|
|
104
|
+
lid: { label: "Lid" },
|
|
105
|
+
mesh: { label: "Lattice insert" },
|
|
106
|
+
plate: { label: "Nameplate" },
|
|
107
|
+
print: { label: "Print bed" },
|
|
108
|
+
},
|
|
109
|
+
verify: { process: "fdm-pla", expect: { _view: { overlaps: 0 } } },
|
|
110
|
+
};
|
package/src/parts/mixed-smoke.js
CHANGED
|
@@ -21,7 +21,7 @@ export default {
|
|
|
21
21
|
parts: {
|
|
22
22
|
body: {
|
|
23
23
|
label: "Body",
|
|
24
|
-
views:
|
|
24
|
+
views: { assembly: true },
|
|
25
25
|
export: { name: "body" },
|
|
26
26
|
// The t > 0 branch is what drives the routing: the probe re-runs with live
|
|
27
27
|
// params, so the shell call is only seen (and OCCT only engaged) when the
|
|
@@ -33,10 +33,9 @@ export default {
|
|
|
33
33
|
},
|
|
34
34
|
lid: {
|
|
35
35
|
label: "Lid",
|
|
36
|
-
views: [
|
|
36
|
+
views: { assembly: (s) => s.at([0, 0, 12]) },
|
|
37
37
|
export: { name: "lid" },
|
|
38
38
|
build: (k, p) => k.box({ min: [0, 0, 0], max: [p.w, p.w, 2] }),
|
|
39
|
-
place: (s) => s.at([0, 0, 12]),
|
|
40
39
|
},
|
|
41
40
|
},
|
|
42
41
|
views: { assembly: { label: "Assembly" } },
|
package/types/geometry.d.ts
CHANGED
|
@@ -317,7 +317,7 @@ export interface SheetPartSpec<P = ResolvedParams, D = Derived> {
|
|
|
317
317
|
pose?: SheetPose | ((p: P, d: D) => SheetPose | null) | null;
|
|
318
318
|
process?: "laser";
|
|
319
319
|
label?: string;
|
|
320
|
-
views: string[]
|
|
320
|
+
views: string[] | Record<string, true | ((s: Solid, p: P, d: D) => Solid)>;
|
|
321
321
|
display?: SubPartDefinition<P, D>["display"];
|
|
322
322
|
export?: { name: string };
|
|
323
323
|
enabled?: (p: P) => unknown;
|
package/types/part.d.ts
CHANGED
|
@@ -350,6 +350,7 @@ export type DeriveSpec<P = ResolvedParams, D = Derived> =
|
|
|
350
350
|
|
|
351
351
|
// --- sub-parts --------------------------------------------------------------
|
|
352
352
|
|
|
353
|
+
/** The argument to the LEGACY `place` (views-array form); the `views` map form needs none. */
|
|
353
354
|
export interface PlaceContext<P = ResolvedParams, D = Derived> {
|
|
354
355
|
/** The active view. Display placement must NOT depend on it. */
|
|
355
356
|
view: string;
|
|
@@ -367,12 +368,18 @@ export interface SubPartDefinition<P = ResolvedParams, D = Derived> {
|
|
|
367
368
|
*/
|
|
368
369
|
build: (k: GeometryKernel, p: P, d: D, onProgress?: (phase: string) => void) => Solid;
|
|
369
370
|
/**
|
|
370
|
-
*
|
|
371
|
+
* LEGACY form (with a `views` array); with a `views` map put each pose in its
|
|
372
|
+
* entry instead. Optional reposition (default identity), for a part whose display pose
|
|
371
373
|
* differs from its export pose. Any such difference must be a RIGID motion.
|
|
372
374
|
*/
|
|
373
375
|
place?: (solid: Solid, ctx: PlaceContext<P, D>) => Solid;
|
|
374
|
-
/**
|
|
375
|
-
|
|
376
|
+
/**
|
|
377
|
+
* Each view the sub-part appears in: a map of view name to `true` (shown as
|
|
378
|
+
* built) or a rigid pose `(s, p, d) => s.translate(…)`. Every name must exist in
|
|
379
|
+
* the part's `views`. The legacy form, an array of names (placed by `place`),
|
|
380
|
+
* still works.
|
|
381
|
+
*/
|
|
382
|
+
views: string[] | Record<string, true | ((s: Solid, p: P, d: D) => Solid)>;
|
|
376
383
|
/** Gate a conditional sub-part. Coerced with `!!`. */
|
|
377
384
|
enabled?: (p: P) => unknown;
|
|
378
385
|
/** `false` = reference/preview-only: shown in the viewer, never exported. */
|