partforge 0.121.0 → 0.122.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/docs/AUTHORING-PARTS.md +164 -3
- package/package.json +3 -1
- package/src/app-materials.js +23 -0
- package/src/framework/app.css +16 -1
- package/src/framework/cutaway.js +12 -0
- package/src/framework/geometry/arc-math.js +27 -0
- package/src/framework/geometry/contour-corners.js +130 -0
- package/src/framework/geometry/contour-ops.js +11 -125
- package/src/framework/geometry/paper-bridge.js +4 -21
- package/src/framework/geometry/threemf.js +32 -3
- package/src/framework/jobs.js +2 -1
- package/src/framework/lint/index.js +2 -1
- package/src/framework/lint/rules-materials.js +56 -0
- package/src/framework/materials/assets/SOURCES.md +90 -0
- package/src/framework/materials/assets/env-outdoor.jpg +0 -0
- package/src/framework/materials/assets/env-print-bed.jpg +0 -0
- package/src/framework/materials/assets/env-studio.jpg +0 -0
- package/src/framework/materials/assets/env-workshop.jpg +0 -0
- package/src/framework/materials/assets/ground-concrete-normal.jpg +0 -0
- package/src/framework/materials/assets/ground-concrete-rough.jpg +0 -0
- package/src/framework/materials/assets/ground-concrete.jpg +0 -0
- package/src/framework/materials/assets/ground-maple-color.jpg +0 -0
- package/src/framework/materials/assets/ground-maple-normal.jpg +0 -0
- package/src/framework/materials/assets/ground-paper-normal.jpg +0 -0
- package/src/framework/materials/assets/ground-paper-rough.jpg +0 -0
- package/src/framework/materials/assets/ground-paper.jpg +0 -0
- package/src/framework/materials/assets/ground-pei-rough.jpg +0 -0
- package/src/framework/materials/assets/ground-pei.jpg +0 -0
- package/src/framework/materials/assets/pattern-carbon.jpg +0 -0
- package/src/framework/materials/assets/pattern-oak-color.jpg +0 -0
- package/src/framework/materials/assets/pattern-oak-normal.jpg +0 -0
- package/src/framework/materials/assets/pattern-oak-rough.jpg +0 -0
- package/src/framework/materials/assets/pattern-walnut-color.jpg +0 -0
- package/src/framework/materials/assets/pattern-walnut-normal.jpg +0 -0
- package/src/framework/materials/assets/pattern-walnut-rough.jpg +0 -0
- package/src/framework/materials/assets.js +39 -0
- package/src/framework/materials/contact-shadow.js +155 -0
- package/src/framework/materials/environment.js +157 -0
- package/src/framework/materials/environments.js +82 -0
- package/src/framework/materials/patterns.js +195 -0
- package/src/framework/materials/physical.js +82 -0
- package/src/framework/materials/presets.js +89 -0
- package/src/framework/materials/print-bed.js +169 -0
- package/src/framework/materials/print-frame.js +18 -0
- package/src/framework/materials/resolve.js +117 -0
- package/src/framework/materials/tonemap-readback.js +36 -0
- package/src/framework/materials/uv.js +23 -0
- package/src/framework/mount.js +121 -4
- package/src/framework/oracle/shape-probe.js +2 -2
- package/src/framework/realistic-controls.js +78 -0
- package/src/framework/selection/feature-highlight.js +3 -0
- package/src/framework/view-state.js +27 -1
- package/src/framework/viewer.js +623 -34
- package/src/materials-worker.js +3 -0
- package/src/parts/bracket.js +2 -1
- package/src/parts/hinged-box.js +3 -1
- package/src/parts/material-swatches.js +40 -0
- package/src/parts/planter.js +2 -1
- package/src/parts/propeller.js +2 -1
- package/src/parts/scott-label.js +3 -2
- package/src/parts/screw.js +2 -1
- package/types/index.d.ts +48 -3
- package/types/part.d.ts +15 -2
package/src/parts/bracket.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { roundedRectPolygon, circleProfile } from "partforge/geometry";
|
|
10
10
|
|
|
11
11
|
export default {
|
|
12
|
-
meta: { title: "Cross bracket", units: "mm", background: 0x15181d },
|
|
12
|
+
meta: { title: "Cross bracket", units: "mm", background: 0x15181d, environment: "workshop" },
|
|
13
13
|
parameters: [
|
|
14
14
|
{
|
|
15
15
|
id: "size",
|
|
@@ -58,6 +58,7 @@ export default {
|
|
|
58
58
|
label: "Cross bracket",
|
|
59
59
|
views: ["bracket"],
|
|
60
60
|
export: { name: "cross-bracket" },
|
|
61
|
+
display: { material: "powder-coat", color: 0x2b2f36 },
|
|
61
62
|
build: (k, p) => {
|
|
62
63
|
const barH = k.shape2d(roundedRectPolygon(p.span, p.bar, p.corner));
|
|
63
64
|
const barV = k.shape2d(roundedRectPolygon(p.bar, p.span, p.corner));
|
package/src/parts/hinged-box.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// before any motion. Also shown: an intro camera + markdown description on
|
|
7
7
|
// `open`, a looping autoplay `cycle`, and per-step cameras on `assemble`.
|
|
8
8
|
export default {
|
|
9
|
-
meta: { title: "Hinged Box", units: "mm" },
|
|
9
|
+
meta: { title: "Hinged Box", units: "mm", environment: "workshop" },
|
|
10
10
|
parameters: [
|
|
11
11
|
{
|
|
12
12
|
id: "box",
|
|
@@ -41,6 +41,7 @@ export default {
|
|
|
41
41
|
label: "Base",
|
|
42
42
|
views: ["box"],
|
|
43
43
|
export: { name: "base" },
|
|
44
|
+
display: { material: "walnut" },
|
|
44
45
|
build: (k, p) =>
|
|
45
46
|
k.box({ min: [0, 0, 0], max: [p.width, p.depth, p.height] })
|
|
46
47
|
.cut(k.box({ min: [p.wall, p.wall, p.wall], max: [p.width - p.wall, p.depth - p.wall, p.height + 1] })),
|
|
@@ -49,6 +50,7 @@ export default {
|
|
|
49
50
|
label: "Lid",
|
|
50
51
|
views: ["box"],
|
|
51
52
|
export: { name: "lid" },
|
|
53
|
+
display: { material: "walnut" },
|
|
52
54
|
build: (k, p) => k.box({ min: [0, 0, p.height], max: [p.width, p.depth, p.height + p.wall] }),
|
|
53
55
|
// Display: swing about the hinge line (rear top edge, axis +X through
|
|
54
56
|
// [0, depth, height]; negative angle opens upward), then the assembly
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Dev-only contact sheet for the material library: one 30 mm sample per preset,
|
|
2
|
+
// laid out in a grid, so a change to a preset or the pattern shader can be
|
|
3
|
+
// judged by eye in every environment (materials.html). Not a reference part.
|
|
4
|
+
import { PRESETS } from "../framework/materials/presets.js";
|
|
5
|
+
|
|
6
|
+
const ids = Object.keys(PRESETS).filter((id) => id !== "default");
|
|
7
|
+
const COLS = 6, PITCH = 40;
|
|
8
|
+
|
|
9
|
+
const withHole = (k) =>
|
|
10
|
+
k.box({ size: [30, 30, 12] }).fillet({ r: 3 }).cut(k.cylinder({ r: 5, h: 20 }).translate([0, 0, -4]));
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
meta: { title: "Material swatches", units: "mm", environment: "studio" },
|
|
14
|
+
defaults: {},
|
|
15
|
+
views: { all: { label: "All presets", default: true }, print: { label: "Layer lines" } },
|
|
16
|
+
parts: {
|
|
17
|
+
...Object.fromEntries(ids.map((id, i) => [id.replaceAll("-", "_"), {
|
|
18
|
+
label: PRESETS[id].label,
|
|
19
|
+
views: ["all"],
|
|
20
|
+
build: (k) => withHole(k).translate([(i % COLS) * PITCH, -Math.floor(i / COLS) * PITCH, 0]),
|
|
21
|
+
display: { material: id },
|
|
22
|
+
}])),
|
|
23
|
+
upright_pla: {
|
|
24
|
+
label: "PLA, displayed upright, printed flat",
|
|
25
|
+
views: ["print"],
|
|
26
|
+
build: (k) => k.box({ size: [30, 12, 40] }),
|
|
27
|
+
// Displayed standing on its Z-tall face; the export pose lies it flat on
|
|
28
|
+
// its printed layer plane, which is what the layer-line orientation check
|
|
29
|
+
// in the "Layer lines" view is for.
|
|
30
|
+
place: (s, { purpose }) => (purpose === "export" ? s.rotateX(90) : s),
|
|
31
|
+
display: { material: "pla-print", color: 0xe0592a },
|
|
32
|
+
},
|
|
33
|
+
flat_pla: {
|
|
34
|
+
label: "PLA, same pose both ways",
|
|
35
|
+
views: ["print"],
|
|
36
|
+
build: (k) => k.box({ size: [30, 12, 40] }).translate([50, 0, 0]),
|
|
37
|
+
display: { material: "pla-print", color: 0xe0592a },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
package/src/parts/planter.js
CHANGED
|
@@ -24,7 +24,7 @@ const ngon = (R, n) => {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export default {
|
|
27
|
-
meta: { title: "Faceted Planter", units: "mm", background: 0x15181d },
|
|
27
|
+
meta: { title: "Faceted Planter", units: "mm", background: 0x15181d, environment: "print-bed" },
|
|
28
28
|
parameters: [
|
|
29
29
|
{
|
|
30
30
|
id: "body",
|
|
@@ -100,6 +100,7 @@ export default {
|
|
|
100
100
|
label: "Planter",
|
|
101
101
|
views: ["planter"],
|
|
102
102
|
export: { name: "planter" },
|
|
103
|
+
display: { material: "pla-print", color: 0xc4622d },
|
|
103
104
|
build: (k, p, d) => {
|
|
104
105
|
const body = k.prism({ points: d.outerPts, h: p.height, scaleTop: p.taper, twist: p.twist }).label("Faceted wall");
|
|
105
106
|
// Hollow it. The cavity is built from z=0 sharing the body's exact twist RATE and
|
package/src/parts/propeller.js
CHANGED
|
@@ -113,7 +113,7 @@ export const bladeSections = (p) =>
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
export default {
|
|
116
|
-
meta: { title: "Propeller", units: "mm", background: 0x15181d },
|
|
116
|
+
meta: { title: "Propeller", units: "mm", background: 0x15181d, environment: "studio" },
|
|
117
117
|
parameters: [
|
|
118
118
|
{
|
|
119
119
|
id: "prop",
|
|
@@ -173,6 +173,7 @@ export default {
|
|
|
173
173
|
parts: {
|
|
174
174
|
propeller: {
|
|
175
175
|
label: "Propeller", views: ["propeller"], export: { name: "propeller" },
|
|
176
|
+
display: { material: "brushed-aluminum" },
|
|
176
177
|
build: (k, p) => {
|
|
177
178
|
const sections = bladeSections(p);
|
|
178
179
|
const bladeUp = p.smooth
|
package/src/parts/scott-label.js
CHANGED
|
@@ -71,6 +71,7 @@ export default {
|
|
|
71
71
|
title: "Editable Layered Label",
|
|
72
72
|
units: "mm",
|
|
73
73
|
background: 0x171a20,
|
|
74
|
+
environment: "print-bed",
|
|
74
75
|
},
|
|
75
76
|
|
|
76
77
|
parameters: [
|
|
@@ -185,7 +186,7 @@ export default {
|
|
|
185
186
|
backing: {
|
|
186
187
|
label: "Lower outer layer",
|
|
187
188
|
views: ["backing", "assembly"],
|
|
188
|
-
display: { color: 0x1e88e5 },
|
|
189
|
+
display: { material: "pla-print", color: 0x1e88e5 },
|
|
189
190
|
build: (k, p, d) => {
|
|
190
191
|
const rawTextProfile = k.text2d(d.textValue, {
|
|
191
192
|
size: p.textSize,
|
|
@@ -209,7 +210,7 @@ export default {
|
|
|
209
210
|
lettering: {
|
|
210
211
|
label: "Editable lettering",
|
|
211
212
|
views: ["lettering", "assembly"],
|
|
212
|
-
display: { color: 0xfdd835 },
|
|
213
|
+
display: { material: "pla-print", color: 0xfdd835 },
|
|
213
214
|
build: (k, p, d) => {
|
|
214
215
|
const rawLetteringProfile = k.text2d(d.textValue, {
|
|
215
216
|
size: p.textSize,
|
package/src/parts/screw.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// one screwSweep call yields the whole threaded shank with no boolean against a
|
|
4
4
|
// core. See docs/AUTHORING-PARTS.md "Helical & threaded features".
|
|
5
5
|
export default {
|
|
6
|
-
meta: { title: "Screw", units: "mm", background: 0x15181d },
|
|
6
|
+
meta: { title: "Screw", units: "mm", background: 0x15181d, environment: "studio" },
|
|
7
7
|
parameters: [
|
|
8
8
|
{
|
|
9
9
|
id: "thread",
|
|
@@ -52,6 +52,7 @@ export default {
|
|
|
52
52
|
label: "Screw",
|
|
53
53
|
views: ["screw"],
|
|
54
54
|
export: { name: "screw" },
|
|
55
|
+
display: { material: "brushed-stainless" },
|
|
55
56
|
build: (k, p, d) => {
|
|
56
57
|
// Periodic profile: exactly one pitch tall, first radius == last radius.
|
|
57
58
|
const shank = k.screwSweep({
|
package/types/index.d.ts
CHANGED
|
@@ -110,6 +110,10 @@ export interface MountElements {
|
|
|
110
110
|
measure?: HTMLElement | null;
|
|
111
111
|
annotate?: HTMLElement | null;
|
|
112
112
|
railToggle?: HTMLElement | null;
|
|
113
|
+
/** Realistic-mode toggle (default id `#realistic`). */
|
|
114
|
+
realistic?: HTMLButtonElement | null;
|
|
115
|
+
/** Environment picker, an empty `<select>` the mount fills (default id `#environment`). Shown only while realistic. */
|
|
116
|
+
environment?: HTMLSelectElement | null;
|
|
113
117
|
};
|
|
114
118
|
}
|
|
115
119
|
|
|
@@ -265,6 +269,20 @@ export interface ViewerState {
|
|
|
265
269
|
camera: { pos: [number, number, number]; target: [number, number, number] } | null;
|
|
266
270
|
projection: "perspective" | "orthographic";
|
|
267
271
|
cutaway: CutawayState | null;
|
|
272
|
+
/** Always reported by `getViewerState()` — the mode being headed for, so a realistic switch still loading reads `"realistic"` (a failed one settles to `"cad"`). Optional on the way back in (a state saved by an older partforge has none). */
|
|
273
|
+
renderMode?: RenderMode;
|
|
274
|
+
/** The realistic environment id, present only when one was CHOSEN (`environment.set`, the picker) — a merely seeded default is left out so the part's own `meta.environment` still applies on remount. An unknown id falls back to `"studio"`. */
|
|
275
|
+
environment?: string;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** The viewer's appearance: the drafting view, or physical materials under an environment. */
|
|
279
|
+
export type RenderMode = "cad" | "realistic";
|
|
280
|
+
|
|
281
|
+
/** What `renderMode.onChange` listeners receive. `busy` while realistic's assets load; `error` when they failed. */
|
|
282
|
+
export interface RenderModeEvent {
|
|
283
|
+
mode: RenderMode;
|
|
284
|
+
busy: boolean;
|
|
285
|
+
error: string | null;
|
|
268
286
|
}
|
|
269
287
|
|
|
270
288
|
/** Custom controls' transient state, keyed by the param each control owns. Plain JSON. */
|
|
@@ -342,6 +360,14 @@ export interface CaptureCurrentOptions {
|
|
|
342
360
|
* Default false.
|
|
343
361
|
*/
|
|
344
362
|
recenter?: boolean;
|
|
363
|
+
/**
|
|
364
|
+
* The look to capture in. Omitted: whatever the live view shows. `"cad"`
|
|
365
|
+
* always renders CAD. `"realistic"` from a CAD view renders realistic only
|
|
366
|
+
* once the current environment's assets have loaded (this call is
|
|
367
|
+
* synchronous and cannot wait); before that it falls back to the live look —
|
|
368
|
+
* `renderViews` is the async path that guarantees realistic.
|
|
369
|
+
*/
|
|
370
|
+
renderMode?: RenderMode;
|
|
345
371
|
}
|
|
346
372
|
|
|
347
373
|
export interface CaptureViewOptions {
|
|
@@ -500,13 +526,15 @@ export interface PartRuntime {
|
|
|
500
526
|
/**
|
|
501
527
|
* Canonical-angle captures (fixed poses, framed to the visible assembly,
|
|
502
528
|
* 1024², grid hidden) — sized for feeding a vision model. Defaults to
|
|
503
|
-
* `["iso", "front", "top"]`; unknown names are dropped.
|
|
529
|
+
* `["iso", "front", "top"]`; unknown names are dropped. Always the CAD look,
|
|
530
|
+
* whatever the live render mode — `renderViews` asks for realistic.
|
|
504
531
|
*/
|
|
505
532
|
captureViews(viewNames?: CanonicalView[] | string[]): Array<{ view: string; dataUrl: string }>;
|
|
506
533
|
/**
|
|
507
534
|
* One offscreen render of the user's CURRENT framing at a chosen resolution —
|
|
508
535
|
* the showcase capture. Returns a `data:image/jpeg;base64,…` string, or `null`
|
|
509
|
-
* when disposed or nothing is built yet. Never throws.
|
|
536
|
+
* when disposed or nothing is built yet. Never throws. Follows the live
|
|
537
|
+
* render mode unless `opts.renderMode` pins one.
|
|
510
538
|
*/
|
|
511
539
|
captureCurrent(opts?: CaptureCurrentOptions): string | null;
|
|
512
540
|
/** The active view (tab) name. Never null once mounted. */
|
|
@@ -527,7 +555,7 @@ export interface PartRuntime {
|
|
|
527
555
|
*/
|
|
528
556
|
setActive(active: boolean): void;
|
|
529
557
|
/**
|
|
530
|
-
* Snapshot the camera, projection and
|
|
558
|
+
* Snapshot the camera, projection, cutaway, render mode and environment so a REMOUNT can resume them —
|
|
531
559
|
* pass the result as the next `mount()`'s `viewerState`. Read at teardown
|
|
532
560
|
* time, so it is the live pose, unlike the camera the viewer persists for a
|
|
533
561
|
* page reload (which only records the end of an orbit drag, and so misses a
|
|
@@ -592,6 +620,23 @@ export interface PartRuntime {
|
|
|
592
620
|
annotate: AnnotateRuntime;
|
|
593
621
|
/** The pick marker's lifetime — hold it on screen and follow it across the canvas. Always present (a no-op stand-in outside `makeHandle` tests). */
|
|
594
622
|
pickMarker: PickMarkerRuntime;
|
|
623
|
+
/** Realistic render mode (docs "Materials and appearance"). set() resolves to the mode actually in effect — "cad" if assets failed to load. */
|
|
624
|
+
renderMode: {
|
|
625
|
+
get(): RenderMode;
|
|
626
|
+
set(mode: RenderMode): Promise<RenderMode>;
|
|
627
|
+
onChange(cb: (e: RenderModeEvent) => void): () => void;
|
|
628
|
+
};
|
|
629
|
+
/** The realistic environment. set() resolves to the id in effect — a failed switch while realistic reverts, and onChange hears the revert. */
|
|
630
|
+
environment: {
|
|
631
|
+
get(): string;
|
|
632
|
+
set(id: string): Promise<string>;
|
|
633
|
+
onChange(cb: (id: string) => void): () => void;
|
|
634
|
+
list(): Array<{ id: string; label: string }>;
|
|
635
|
+
};
|
|
636
|
+
/** True when any sub-part names a display.material. */
|
|
637
|
+
declaresMaterials: boolean;
|
|
638
|
+
/** Canonical-view renders in a chosen appearance, without switching the live view; "cad" (the default) is exactly captureViews(), CAD in either live mode. */
|
|
639
|
+
renderViews(viewNames?: CanonicalView[] | string[], opts?: { renderMode?: RenderMode }): Promise<Array<{ view: string; dataUrl: string }>>;
|
|
595
640
|
}
|
|
596
641
|
|
|
597
642
|
/** Mount a full parametric-part app from a `PartDefinition`. */
|
package/types/part.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface PartMeta {
|
|
|
48
48
|
units?: string;
|
|
49
49
|
/** Scene background as `0xRRGGBB`. */
|
|
50
50
|
background?: number;
|
|
51
|
+
/** Realistic-mode lighting/ground preset. See docs/AUTHORING-PARTS.md "Materials and appearance". */
|
|
52
|
+
environment?: "studio" | "workshop" | "print-bed" | "outdoor" | string;
|
|
51
53
|
/** Pin a backend instead of letting the probe route the part. */
|
|
52
54
|
backend?: BackendName;
|
|
53
55
|
}
|
|
@@ -383,8 +385,19 @@ export interface SubPartDefinition<P = ResolvedParams, D = Derived> {
|
|
|
383
385
|
* / `refBboxDelta` gate metrics.
|
|
384
386
|
*/
|
|
385
387
|
reference?: string;
|
|
386
|
-
/** Viewer-only
|
|
387
|
-
display?: {
|
|
388
|
+
/** Viewer-only appearance. See docs/AUTHORING-PARTS.md "Materials and appearance". */
|
|
389
|
+
display?: {
|
|
390
|
+
color?: number;
|
|
391
|
+
opacity?: number;
|
|
392
|
+
/** A preset id from docs/AUTHORING-PARTS.md "Materials and appearance". */
|
|
393
|
+
material?: string;
|
|
394
|
+
roughness?: number;
|
|
395
|
+
metalness?: number;
|
|
396
|
+
clearcoat?: number;
|
|
397
|
+
clearcoatRoughness?: number;
|
|
398
|
+
anisotropy?: number;
|
|
399
|
+
textureScale?: number;
|
|
400
|
+
};
|
|
388
401
|
/** Filename / object name on export; defaults to the key. */
|
|
389
402
|
export?: { name: string };
|
|
390
403
|
}
|