partforge 0.41.0 → 0.44.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 +31 -10
- package/bin/cli.js +100 -27
- package/docs/AUTHORING-PARTS.md +126 -14
- package/docs/ERROR-PATTERNS.md +6 -0
- package/package.json +48 -7
- package/skills/partforge/SKILL.md +17 -3
- package/src/app-embed-test.js +1 -1
- package/src/app-hinged-box.js +12 -0
- package/src/framework/animation-controls.js +243 -0
- package/src/framework/animation.js +217 -0
- package/src/framework/app.css +32 -0
- package/src/framework/assembly.js +1 -1
- package/src/framework/backend-select.js +25 -0
- package/src/framework/camera-tween.js +58 -0
- package/src/framework/chrome.css +16 -0
- package/src/framework/controls.js +13 -3
- package/src/framework/cutaway-gizmo-scene.js +244 -0
- package/src/framework/cutaway-gizmo.js +80 -243
- package/src/framework/default-view.js +46 -0
- package/src/framework/download.js +7 -2
- package/src/framework/export-controller.js +13 -2
- package/src/framework/geometry/probe.js +3 -22
- package/src/framework/jobs.js +9 -40
- package/src/framework/lint/finding.js +4 -0
- package/src/framework/lint/index.js +7 -3
- package/src/framework/lint/rules-animations.js +404 -0
- package/src/framework/lint/rules-place.js +76 -0
- package/src/framework/lint/rules-shape.js +12 -0
- package/src/framework/lint/rules-verify.js +2 -2
- package/src/framework/mount.js +93 -18
- package/src/{testing → framework/oracle}/build.js +1 -1
- package/src/{testing → framework/oracle}/bvh.js +1 -1
- package/src/{testing → framework/oracle}/measure.js +1 -1
- package/src/{testing → framework/oracle}/min-wall.js +1 -1
- package/src/{testing → framework/oracle}/verify.js +3 -3
- package/src/framework/param-deps.js +1 -1
- package/src/framework/part-model.js +48 -0
- package/src/framework/pick-request/client.js +11 -3
- package/src/framework/pick-request/endpoint.js +60 -0
- package/src/framework/pick-request/index.js +6 -0
- package/src/framework/pick-request/server.js +222 -34
- package/src/framework/pick-request/token-store.js +31 -0
- package/src/framework/pose-fast-path.js +12 -1
- package/src/framework/pose-probe-core.js +129 -0
- package/src/framework/pose-probe.js +7 -123
- package/src/framework/regen-loop.js +10 -3
- package/src/framework/safe-name.js +26 -0
- package/src/framework/verify-metrics.js +4 -4
- package/src/framework/view-state.js +25 -21
- package/src/framework/view-tabs.js +22 -7
- package/src/framework/viewer-controls.js +5 -26
- package/src/framework/viewer.js +58 -17
- package/src/hinged-box-worker.js +3 -0
- package/src/index.js +1 -1
- package/src/parts/hinged-box.js +94 -0
- package/src/testing/render.js +19 -8
- package/src/testing.js +15 -8
- package/types/derive.d.ts +14 -0
- package/types/geometry.d.ts +117 -0
- package/types/index.d.ts +240 -0
- package/types/kernel.d.ts +409 -0
- package/types/lint.d.ts +85 -0
- package/types/part.d.ts +381 -0
- package/types/testing.d.ts +362 -0
- package/types/worker.d.ts +21 -0
- /package/src/{testing → framework/oracle}/assert-dsl.js +0 -0
- /package/src/{testing → framework/oracle}/cases.js +0 -0
- /package/src/{testing → framework/oracle}/dfm-profiles.js +0 -0
- /package/src/{testing → framework/oracle}/gaps.js +0 -0
- /package/src/{testing → framework/oracle}/mesh.js +0 -0
package/src/testing/render.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { join, resolve, sep } from "node:path";
|
|
3
|
+
import { safeName } from "../framework/safe-name.js";
|
|
4
|
+
import { buildView } from "../framework/oracle/build.js";
|
|
5
|
+
import { bounds } from "../framework/oracle/mesh.js";
|
|
5
6
|
|
|
6
7
|
// Canonical view directions in MODEL space (Z-up). `dir` is the direction from
|
|
7
8
|
// the part centre toward the camera; `up` is the camera up vector.
|
|
@@ -22,7 +23,6 @@ export const RENDER_ANGLES = {
|
|
|
22
23
|
};
|
|
23
24
|
export const RENDER_VIEWS = Object.keys(RENDER_ANGLES);
|
|
24
25
|
|
|
25
|
-
const slug = (s) => String(s).toLowerCase().replace(/\s+/g, "-");
|
|
26
26
|
const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
27
27
|
const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
28
28
|
const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
@@ -33,7 +33,7 @@ const norm = (a) => { const l = Math.hypot(a[0], a[1], a[2]) || 1; return [a[0]
|
|
|
33
33
|
// overlays). No native module, no browser. Returns the written file paths.
|
|
34
34
|
// pngjs is lazy-imported so importing the testing barrel for measure never loads it.
|
|
35
35
|
export async function renderViews(kernel, part, view = Object.keys(part.views)[0], {
|
|
36
|
-
views = ["iso", "front", "top"], out = "render", size = [800, 600], edges = true, params = {},
|
|
36
|
+
views = ["iso", "front", "top"], out = "render", size = [800, 600], edges = true, params = {}, tag = "",
|
|
37
37
|
} = {}) {
|
|
38
38
|
const { PNG } = await import("pngjs");
|
|
39
39
|
const [W, H] = size;
|
|
@@ -54,8 +54,13 @@ export async function renderViews(kernel, part, view = Object.keys(part.views)[0
|
|
|
54
54
|
const ambient = 0.35, diffuse = 0.75;
|
|
55
55
|
const bias = radius * 0.02; // edge depth bias so visible edges win ties
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
// `out` is operator-supplied (a CLI flag) and stays verbatim; the part-derived
|
|
58
|
+
// title and view key are sanitized, since this is the one place a part's
|
|
59
|
+
// strings reach the filesystem.
|
|
60
|
+
const outDir = resolve(out);
|
|
61
|
+
mkdirSync(outDir, { recursive: true });
|
|
62
|
+
const name = safeName(part.meta?.title ?? view);
|
|
63
|
+
const viewName = safeName(view);
|
|
59
64
|
const written = [];
|
|
60
65
|
|
|
61
66
|
for (const angle of views) {
|
|
@@ -121,7 +126,13 @@ export async function renderViews(kernel, part, view = Object.keys(part.views)[0
|
|
|
121
126
|
for (let i = 0; i < W * H; i++) {
|
|
122
127
|
png.data[i * 4] = color[i * 3]; png.data[i * 4 + 1] = color[i * 3 + 1]; png.data[i * 4 + 2] = color[i * 3 + 2]; png.data[i * 4 + 3] = 255;
|
|
123
128
|
}
|
|
124
|
-
|
|
129
|
+
// The animation frame tag goes through safeName() as well — one rule for
|
|
130
|
+
// every string that reaches a filename here.
|
|
131
|
+
const file = join(out, `${name}-${viewName}-${angle}${tag ? `-${safeName(tag)}` : ""}.png`);
|
|
132
|
+
// Belt and braces over safeName(): assert the escape never happened rather
|
|
133
|
+
// than trusting the slug, because a miss here writes bytes to disk. (The
|
|
134
|
+
// returned paths stay relative to `out` — the CLI echoes them.)
|
|
135
|
+
if (!resolve(file).startsWith(outDir + sep)) throw new Error(`renderViews: refusing to write outside ${out}`);
|
|
125
136
|
writeFileSync(file, PNG.sync.write(png));
|
|
126
137
|
written.push(file);
|
|
127
138
|
}
|
package/src/testing.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
// partforge/testing — utilities for testing parts headlessly (Manifold kernel, the
|
|
2
2
|
// job loop, the assembly collision check, an OCCT kernel, and mesh measures).
|
|
3
3
|
// See docs/AUTHORING-PARTS.md "Testing a part".
|
|
4
|
+
//
|
|
5
|
+
// Most of what this re-exports is NOT test-only code: the oracle (measure/verify/
|
|
6
|
+
// buildView/gaps/BVH/min-wall) also runs inside the browser geometry worker, so it
|
|
7
|
+
// lives in src/framework/oracle/. Only the Node-bound harness — the two kernel
|
|
8
|
+
// booters, the PNG renderer — lives in src/testing/. This barrel is the published
|
|
9
|
+
// entry point and hides that split from downstream consumers.
|
|
4
10
|
export { createManifoldKernel } from "./framework/geometry/manifold-backend.js";
|
|
5
11
|
export { bootManifoldKernel } from "./testing/manifold.js";
|
|
6
|
-
export { handle
|
|
12
|
+
export { handle } from "./framework/jobs.js";
|
|
13
|
+
export { viewSubParts } from "./framework/part-model.js";
|
|
7
14
|
export { resolveDerived } from "./framework/derive.js";
|
|
8
15
|
export { relevantParamKeys, RELEVANT_ALL } from "./framework/param-deps.js";
|
|
9
16
|
export { assemblyOverlaps } from "./framework/assembly.js";
|
|
10
|
-
export { assemblyGaps, meshGaps } from "./
|
|
17
|
+
export { assemblyGaps, meshGaps } from "./framework/oracle/gaps.js";
|
|
11
18
|
export { bootOcctKernel } from "./testing/occt.js";
|
|
12
|
-
export { meshVolume, bboxSize } from "./
|
|
13
|
-
export { buildView } from "./
|
|
14
|
-
export { measure } from "./
|
|
19
|
+
export { meshVolume, bboxSize } from "./framework/oracle/mesh.js";
|
|
20
|
+
export { buildView } from "./framework/oracle/build.js";
|
|
21
|
+
export { measure } from "./framework/oracle/measure.js";
|
|
15
22
|
export { renderViews, RENDER_VIEWS } from "./testing/render.js";
|
|
16
|
-
export { verify } from "./
|
|
17
|
-
export { buildBVH } from "./
|
|
18
|
-
export { minWall } from "./
|
|
23
|
+
export { verify } from "./framework/oracle/verify.js";
|
|
24
|
+
export { buildBVH } from "./framework/oracle/bvh.js";
|
|
25
|
+
export { minWall } from "./framework/oracle/min-wall.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// partforge/derive — a lean, DOM-free entry so a part module (or a helper, or a
|
|
2
|
+
// test) can merge a grouped `derive` exactly the way the framework does.
|
|
3
|
+
|
|
4
|
+
import type { Derived, PartDefinition, ResolvedParams } from "./part.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a part's `derive` into the derived-values object `d` builds receive.
|
|
8
|
+
*
|
|
9
|
+
* Both authoring forms are handled: one function computed in a single pass, or
|
|
10
|
+
* named groups run in declaration order (each seeing the merged outputs of the
|
|
11
|
+
* groups before it). A group that reads a key no earlier group produced throws.
|
|
12
|
+
* Returns `{}` when the part declares no `derive`.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveDerived(part: Pick<PartDefinition, "derive">, p: ResolvedParams): Derived;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// partforge/geometry — pure 2-D profile helpers and solid patterns.
|
|
2
|
+
//
|
|
3
|
+
// DOM-free and kernel-free: this is the entry a part's build functions import
|
|
4
|
+
// (importing "partforge" inside a worker throws `document is not defined`).
|
|
5
|
+
|
|
6
|
+
import type { ArcContour, Point2, Point3, PointsContour, Region2D, Solid } from "./kernel.js";
|
|
7
|
+
|
|
8
|
+
export type { ArcContour, Point2, Point3, PointsContour, Region2D, Solid };
|
|
9
|
+
|
|
10
|
+
/** A pie/sector wedge with its tip at the origin. */
|
|
11
|
+
export function piePolygon(tipR: number, arcDeg: number, segs?: number): PointsContour;
|
|
12
|
+
|
|
13
|
+
/** A regular hexagon of circumradius `r`. */
|
|
14
|
+
export function hexPolygon(r: number): PointsContour;
|
|
15
|
+
|
|
16
|
+
/** A `w` × `h` rectangle centred at the origin with corner radius `r`. */
|
|
17
|
+
export function roundedRectPolygon(w: number, h: number, r: number, segs?: number): PointsContour;
|
|
18
|
+
|
|
19
|
+
/** A regular `n`-gon of circumradius `r`; `flat: true` seats a flat side down. */
|
|
20
|
+
export function regularPolygon(n: number, r: number, opts?: { flat?: boolean }): PointsContour;
|
|
21
|
+
|
|
22
|
+
export function ellipsePolygon(rx: number, ry: number, segs?: number): PointsContour;
|
|
23
|
+
|
|
24
|
+
/** A stadium/slot; overall length is `length + 2r`. */
|
|
25
|
+
export function slotPolygon(length: number, r: number, segs?: number): PointsContour;
|
|
26
|
+
|
|
27
|
+
export function starPolygon(points: number, outerR: number, innerR: number): PointsContour;
|
|
28
|
+
|
|
29
|
+
/** An annular sector. `arcDeg` must be < 360 — a full ring is a contour-with-hole. */
|
|
30
|
+
export function ringSectorPolygon(innerR: number, outerR: number, arcDeg: number, segs?: number): PointsContour;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A CCW circle of radius `r` centred at `center`, as a FACETED point list
|
|
34
|
+
* (`segs` segments). For curve-exact corners use `roundedProfile`/`pathProfile`.
|
|
35
|
+
*/
|
|
36
|
+
export function circleProfile(r: number, center?: Point2, segs?: number): PointsContour;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Per-corner rounding geometry shared by `filletPolygon` and `roundedProfile`:
|
|
40
|
+
* the incoming/outgoing tangent points, the arc centre, the clamped radius, the
|
|
41
|
+
* short sweep and its start angle — or `null` for a corner that stays sharp.
|
|
42
|
+
*/
|
|
43
|
+
export function cornerArc(
|
|
44
|
+
p0: Point2,
|
|
45
|
+
p1: Point2,
|
|
46
|
+
p2: Point2,
|
|
47
|
+
r: number,
|
|
48
|
+
): { a: number[]; b: number[]; c: number[]; rr: number; dA: number; a0: number } | null;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Round every corner of a CCW polygon, BAKING each arc into line facets — so
|
|
52
|
+
* STEP corners are faceted. Use `roundedProfile` for true circular edges.
|
|
53
|
+
*/
|
|
54
|
+
export function filletPolygon(points: PointsContour, r: number, opts?: { segs?: number }): PointsContour;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Round corners the same way as `filletPolygon` but keep them mathematically
|
|
58
|
+
* TRUE — the arc is carried symbolically, so STEP export gets real circular
|
|
59
|
+
* edges. A scalar `r` rounds every corner; a per-corner `r[]` (length = points)
|
|
60
|
+
* rounds selectively. Accepted by `prism`/`extrude`, not yet by `loft`.
|
|
61
|
+
*/
|
|
62
|
+
export function roundedProfile(points: PointsContour, r: number | number[]): ArcContour;
|
|
63
|
+
|
|
64
|
+
/** The fluent builder `pathProfile` returns. `close()` snapshots the contour. */
|
|
65
|
+
export interface PathProfileBuilder {
|
|
66
|
+
lineTo(to: Point2): PathProfileBuilder;
|
|
67
|
+
/** A circular arc to `to` passing through `via`. */
|
|
68
|
+
arcTo(to: Point2, via: Point2): PathProfileBuilder;
|
|
69
|
+
/** A cubic Bézier to `to` with control points `c1`/`c2`. */
|
|
70
|
+
cubicTo(to: Point2, c1: Point2, c2: Point2): PathProfileBuilder;
|
|
71
|
+
/** Close the contour and return it. Needs at least one segment. */
|
|
72
|
+
close(): ArcContour;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A fluent builder for a curve-native path contour. Cubic segments become exact
|
|
77
|
+
* B-rep spline edges on OCCT and facet at mesh LOD on Manifold.
|
|
78
|
+
*/
|
|
79
|
+
export function pathProfile(start: Point2): PathProfileBuilder;
|
|
80
|
+
|
|
81
|
+
/** Convex-corner style for `offsetPolygon`. */
|
|
82
|
+
export type OffsetCorners = "round" | "chamfer" | "sharp";
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Offset a point-list polygon or an `{ outer, holes }` region by `delta` mm —
|
|
86
|
+
* positive grows material, negative insets (regions offset material-wise).
|
|
87
|
+
* Simple polygon in, simple polygon out: an offset that would collapse or split
|
|
88
|
+
* the contour THROWS. Pure, so it works in `derive()` as well as `build()`.
|
|
89
|
+
*/
|
|
90
|
+
export function offsetPolygon(
|
|
91
|
+
profile: PointsContour,
|
|
92
|
+
delta: number,
|
|
93
|
+
opts?: { corners?: OffsetCorners; segs?: number },
|
|
94
|
+
): PointsContour;
|
|
95
|
+
export function offsetPolygon(
|
|
96
|
+
profile: Region2D,
|
|
97
|
+
delta: number,
|
|
98
|
+
opts?: { corners?: OffsetCorners; segs?: number },
|
|
99
|
+
): Region2D;
|
|
100
|
+
|
|
101
|
+
/** `count` copies of `solid` translated by `i * step`. Feed to `k.union` / `s.cutAll`. */
|
|
102
|
+
export function linearPattern(solid: Solid, count: number, step: Point3): Solid[];
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* `count` copies spaced `angle / count` degrees apart around `axis` through
|
|
106
|
+
* `center`. `rotateCopies: false` keeps each copy's original orientation.
|
|
107
|
+
*/
|
|
108
|
+
export function circularPattern(
|
|
109
|
+
solid: Solid,
|
|
110
|
+
count: number,
|
|
111
|
+
opts?: {
|
|
112
|
+
center?: Point3;
|
|
113
|
+
axis?: "X" | "Y" | "Z" | Point3;
|
|
114
|
+
angle?: number;
|
|
115
|
+
rotateCopies?: boolean;
|
|
116
|
+
},
|
|
117
|
+
): Solid[];
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// partforge — the app entry (DOM).
|
|
2
|
+
//
|
|
3
|
+
// This entry pulls in the three.js viewer and the control panel, so it must NOT
|
|
4
|
+
// be imported from a part's build functions; those run in a Web Worker and take
|
|
5
|
+
// their geometry helpers from "partforge/geometry".
|
|
6
|
+
|
|
7
|
+
import type { BackendName } from "./kernel.js";
|
|
8
|
+
import type { ParamValue, PartDefinition } from "./part.js";
|
|
9
|
+
|
|
10
|
+
export * from "./kernel.js";
|
|
11
|
+
export * from "./part.js";
|
|
12
|
+
|
|
13
|
+
/** A canonical capture angle. */
|
|
14
|
+
export type CanonicalView = "iso" | "front" | "back" | "left" | "right" | "top" | "bottom";
|
|
15
|
+
|
|
16
|
+
/** Which pane a narrow layout shows. `null` hands selection back to partforge. */
|
|
17
|
+
export type HostPane = "stage" | "rail" | null;
|
|
18
|
+
|
|
19
|
+
/** An export file format. STEP is routed to OCCT automatically. */
|
|
20
|
+
export type ExportFormat = "stl" | "step" | "3mf";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A semantic click result: which sub-part was hit, where (in the sub-part's own
|
|
24
|
+
* local frame, quantized to 0.01 mm), the snapped surface normal, and only the
|
|
25
|
+
* params that sub-part actually reads.
|
|
26
|
+
*/
|
|
27
|
+
export interface Selection {
|
|
28
|
+
subPart: string;
|
|
29
|
+
point: [number, number, number];
|
|
30
|
+
normal: [number, number, number];
|
|
31
|
+
params: Record<string, ParamValue>;
|
|
32
|
+
/** Present when the hit surface carries a `Solid.label()` name. */
|
|
33
|
+
feature?: { label: string };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PickEvent {
|
|
37
|
+
selection: Selection;
|
|
38
|
+
/** The feature label, the sub-part's label, or the sub-part name. */
|
|
39
|
+
label: string;
|
|
40
|
+
/** The selection formatted for an LLM prompt. */
|
|
41
|
+
prompt: string;
|
|
42
|
+
/** The selection formatted as a compact token. */
|
|
43
|
+
token: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Fired once per completed build. NOT fired for a pose-only edit. */
|
|
47
|
+
export type BuildEvent =
|
|
48
|
+
| { status: "success"; ms?: number }
|
|
49
|
+
| { status: "error"; error: string };
|
|
50
|
+
|
|
51
|
+
/** The bytes of a finished export, when the host supplies its own download sink. */
|
|
52
|
+
export interface DownloadPayload {
|
|
53
|
+
data: ArrayBuffer | Uint8Array;
|
|
54
|
+
filename: string;
|
|
55
|
+
mime: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Element references. Every entry falls back to the legacy global-ID lookup
|
|
60
|
+
* (`#app`, `#controls`, `#panel`, `#part`, `#download*`, `#status`/`#busy`/
|
|
61
|
+
* `#phase`, `#reframe`/`#theme`/`#cutaway`/`#rail-toggle`), resolved
|
|
62
|
+
* exactly once at mount.
|
|
63
|
+
*/
|
|
64
|
+
export interface MountElements {
|
|
65
|
+
/** The viewer canvas host (`#app`). */
|
|
66
|
+
viewer?: HTMLElement | null;
|
|
67
|
+
/** The control panel host (`#controls`). */
|
|
68
|
+
controls?: HTMLElement | null;
|
|
69
|
+
/** The full-height controls rail (`#panel`). */
|
|
70
|
+
rail?: HTMLElement | null;
|
|
71
|
+
/**
|
|
72
|
+
* The positioned `.pf-shell` ancestor. Only needed when the rail is not a
|
|
73
|
+
* direct child of it — the resize seam is positioned against
|
|
74
|
+
* `rail.parentElement` otherwise.
|
|
75
|
+
*/
|
|
76
|
+
shell?: HTMLElement | null;
|
|
77
|
+
status?: {
|
|
78
|
+
status?: HTMLElement | null;
|
|
79
|
+
busy?: HTMLElement | null;
|
|
80
|
+
phase?: HTMLElement | null;
|
|
81
|
+
};
|
|
82
|
+
/** The view-tab bar (`#part`); leave the element empty, `mount` fills it. */
|
|
83
|
+
tabs?: HTMLElement | null;
|
|
84
|
+
exports?: {
|
|
85
|
+
stl?: HTMLElement | null;
|
|
86
|
+
step?: HTMLElement | null;
|
|
87
|
+
threeMf?: HTMLElement | null;
|
|
88
|
+
};
|
|
89
|
+
chrome?: {
|
|
90
|
+
reframe?: HTMLElement | null;
|
|
91
|
+
theme?: HTMLElement | null;
|
|
92
|
+
cutaway?: HTMLElement | null;
|
|
93
|
+
railToggle?: HTMLElement | null;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface MountOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Spawns a geometry worker. Called once per backend with `name` as the
|
|
100
|
+
* Worker's `name` option. The `new Worker(new URL(...))` call must stay
|
|
101
|
+
* inline in the app module or Vite will not bundle the worker.
|
|
102
|
+
*/
|
|
103
|
+
createWorker: (name: BackendName) => Worker;
|
|
104
|
+
elements?: MountElements;
|
|
105
|
+
onBuild?: (event: BuildEvent) => void;
|
|
106
|
+
/**
|
|
107
|
+
* Programmatic click-to-select. Supplying this arms the picker permanently
|
|
108
|
+
* and takes precedence over the `?pick` and `?pickserver` URL modes.
|
|
109
|
+
*/
|
|
110
|
+
onPick?: (event: PickEvent) => void;
|
|
111
|
+
/** Receive exported bytes instead of partforge's own DOM download. */
|
|
112
|
+
onDownload?: (file: DownloadPayload) => void;
|
|
113
|
+
/** @deprecated alias for `elements.viewer`. */
|
|
114
|
+
container?: HTMLElement | null;
|
|
115
|
+
/** @deprecated alias for `elements.controls`. */
|
|
116
|
+
controls?: HTMLElement | null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface ExportPartsOptions {
|
|
120
|
+
/** Sub-part names, as `listExportableParts()` reports them. */
|
|
121
|
+
parts: string[];
|
|
122
|
+
format: ExportFormat;
|
|
123
|
+
/** Mesh quality for STL/3MF. Defaults to `"print"`. */
|
|
124
|
+
quality?: "preview" | "print";
|
|
125
|
+
onProgress?: (phase: string) => void;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface CaptureCurrentOptions {
|
|
129
|
+
/** Long-edge resolution in px, clamped into `[256, maxTextureSize]`. */
|
|
130
|
+
size?: number;
|
|
131
|
+
/** Keep the floor grid so the capture matches the on-screen look. */
|
|
132
|
+
hideGrid?: boolean;
|
|
133
|
+
/** JPEG quality, 0..1. */
|
|
134
|
+
quality?: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Where playback is: idle, swinging the camera to an intro cue, playing, or paused. */
|
|
138
|
+
export type AnimationStatus = "idle" | "intro" | "playing" | "paused";
|
|
139
|
+
|
|
140
|
+
export interface AnimationState {
|
|
141
|
+
/** The selected animation's key. */
|
|
142
|
+
animation: string;
|
|
143
|
+
status: AnimationStatus;
|
|
144
|
+
/** Position on the timeline, 0..1 over the animation's total duration. */
|
|
145
|
+
t: number;
|
|
146
|
+
/** Which step `t` falls in; 0 for a single-step animation. */
|
|
147
|
+
stepIndex: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Part-declared animation playback — the same engine the viewer's transport bar
|
|
152
|
+
* drives. Playback writes real params, so exporting while paused exports the
|
|
153
|
+
* posed state, and any user or host param edit pauses it.
|
|
154
|
+
*/
|
|
155
|
+
export interface AnimationRuntime {
|
|
156
|
+
/**
|
|
157
|
+
* Play, optionally switching to a named animation first. An unknown name
|
|
158
|
+
* warns and does nothing rather than playing whatever is selected.
|
|
159
|
+
*/
|
|
160
|
+
play(name?: string): void;
|
|
161
|
+
pause(): void;
|
|
162
|
+
/** Jump to a position on the timeline, 0..1. Never moves the camera. */
|
|
163
|
+
seek(t: number): void;
|
|
164
|
+
/** Stop and restore the param values the animation started from. */
|
|
165
|
+
stop(): void;
|
|
166
|
+
state(): AnimationState;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The runtime handle `mount()` returns. See `makeHandle` in src/framework/mount.js. */
|
|
170
|
+
export interface PartRuntime {
|
|
171
|
+
/** Resolves on the first successful build of the default view; rejects on a first-build error. */
|
|
172
|
+
ready: Promise<void>;
|
|
173
|
+
/** Full teardown of everything this mount created. Idempotent. */
|
|
174
|
+
dispose(): void;
|
|
175
|
+
/**
|
|
176
|
+
* Programmatic param entry point (the animation hook). Same change path as a
|
|
177
|
+
* slider edit: pose-only changes repair synchronously, geometry changes fall
|
|
178
|
+
* through to the regen loop.
|
|
179
|
+
*/
|
|
180
|
+
setParams(partial: Record<string, ParamValue>): void;
|
|
181
|
+
/**
|
|
182
|
+
* Canonical-angle captures (fixed poses, framed to the visible assembly,
|
|
183
|
+
* 1024², grid hidden) — sized for feeding a vision model. Defaults to
|
|
184
|
+
* `["iso", "front", "top"]`; unknown names are dropped.
|
|
185
|
+
*/
|
|
186
|
+
captureViews(viewNames?: CanonicalView[] | string[]): Array<{ view: string; dataUrl: string }>;
|
|
187
|
+
/**
|
|
188
|
+
* One offscreen render of the user's CURRENT framing at a chosen resolution —
|
|
189
|
+
* the showcase capture. Returns a `data:image/jpeg;base64,…` string, or `null`
|
|
190
|
+
* when disposed or nothing is built yet. Never throws.
|
|
191
|
+
*/
|
|
192
|
+
captureCurrent(opts?: CaptureCurrentOptions): string | null;
|
|
193
|
+
/**
|
|
194
|
+
* Park/unpark the viewer: stops the render loop and releases the drawing
|
|
195
|
+
* buffer and cached capture target. For a host that hides the canvas without
|
|
196
|
+
* unmounting it. Captures still work while parked. Safe after `dispose()`.
|
|
197
|
+
*/
|
|
198
|
+
setActive(active: boolean): void;
|
|
199
|
+
/**
|
|
200
|
+
* Subscribe to WebGL context loss — i.e. the GPU or the OS gave up — so a host
|
|
201
|
+
* can say so rather than showing a dead canvas. The listener takes no
|
|
202
|
+
* arguments (the underlying event is consumed and `preventDefault`ed).
|
|
203
|
+
* Returns an unsubscribe.
|
|
204
|
+
*/
|
|
205
|
+
onContextLost(listener: () => void): () => void;
|
|
206
|
+
/**
|
|
207
|
+
* Every exportable sub-part — excludes any `exportable: false` part, respects
|
|
208
|
+
* each part's `enabled(params)` — INDEPENDENT of the active view.
|
|
209
|
+
*/
|
|
210
|
+
listExportableParts(): Array<{ name: string; label: string }>;
|
|
211
|
+
/**
|
|
212
|
+
* Headless export of a chosen subset. Resolves once the file is written
|
|
213
|
+
* (handed to your `onDownload` sink, or downloaded directly); rejects on
|
|
214
|
+
* build/export failure or an empty selection.
|
|
215
|
+
*/
|
|
216
|
+
exportParts(opts: ExportPartsOptions): Promise<void>;
|
|
217
|
+
/**
|
|
218
|
+
* Narrow-layout pane selection, for a host that draws its own tab bar.
|
|
219
|
+
* `null` hands selection back to partforge's built-in bar.
|
|
220
|
+
*/
|
|
221
|
+
setHostPane(pane: HostPane): void;
|
|
222
|
+
/**
|
|
223
|
+
* Part-declared animation playback, or `null` when the part declares no
|
|
224
|
+
* `animations` block.
|
|
225
|
+
*/
|
|
226
|
+
animation: AnimationRuntime | null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Mount a full parametric-part app from a `PartDefinition`. */
|
|
230
|
+
export function mount(part: PartDefinition, options: MountOptions): PartRuntime;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The sub-parts a view shows: declared in the view and `enabled` for these
|
|
234
|
+
* params, in `Object.keys(part.parts)` order. Handy for app-side view logic.
|
|
235
|
+
*/
|
|
236
|
+
export function viewSubParts(
|
|
237
|
+
part: PartDefinition,
|
|
238
|
+
view: string,
|
|
239
|
+
params: Record<string, ParamValue>,
|
|
240
|
+
): string[];
|