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.
Files changed (70) hide show
  1. package/README.md +31 -10
  2. package/bin/cli.js +100 -27
  3. package/docs/AUTHORING-PARTS.md +126 -14
  4. package/docs/ERROR-PATTERNS.md +6 -0
  5. package/package.json +48 -7
  6. package/skills/partforge/SKILL.md +17 -3
  7. package/src/app-embed-test.js +1 -1
  8. package/src/app-hinged-box.js +12 -0
  9. package/src/framework/animation-controls.js +243 -0
  10. package/src/framework/animation.js +217 -0
  11. package/src/framework/app.css +32 -0
  12. package/src/framework/assembly.js +1 -1
  13. package/src/framework/backend-select.js +25 -0
  14. package/src/framework/camera-tween.js +58 -0
  15. package/src/framework/chrome.css +16 -0
  16. package/src/framework/controls.js +13 -3
  17. package/src/framework/cutaway-gizmo-scene.js +244 -0
  18. package/src/framework/cutaway-gizmo.js +80 -243
  19. package/src/framework/default-view.js +46 -0
  20. package/src/framework/download.js +7 -2
  21. package/src/framework/export-controller.js +13 -2
  22. package/src/framework/geometry/probe.js +3 -22
  23. package/src/framework/jobs.js +9 -40
  24. package/src/framework/lint/finding.js +4 -0
  25. package/src/framework/lint/index.js +7 -3
  26. package/src/framework/lint/rules-animations.js +404 -0
  27. package/src/framework/lint/rules-place.js +76 -0
  28. package/src/framework/lint/rules-shape.js +12 -0
  29. package/src/framework/lint/rules-verify.js +2 -2
  30. package/src/framework/mount.js +93 -18
  31. package/src/{testing → framework/oracle}/build.js +1 -1
  32. package/src/{testing → framework/oracle}/bvh.js +1 -1
  33. package/src/{testing → framework/oracle}/measure.js +1 -1
  34. package/src/{testing → framework/oracle}/min-wall.js +1 -1
  35. package/src/{testing → framework/oracle}/verify.js +3 -3
  36. package/src/framework/param-deps.js +1 -1
  37. package/src/framework/part-model.js +48 -0
  38. package/src/framework/pick-request/client.js +11 -3
  39. package/src/framework/pick-request/endpoint.js +60 -0
  40. package/src/framework/pick-request/index.js +6 -0
  41. package/src/framework/pick-request/server.js +222 -34
  42. package/src/framework/pick-request/token-store.js +31 -0
  43. package/src/framework/pose-fast-path.js +12 -1
  44. package/src/framework/pose-probe-core.js +129 -0
  45. package/src/framework/pose-probe.js +7 -123
  46. package/src/framework/regen-loop.js +10 -3
  47. package/src/framework/safe-name.js +26 -0
  48. package/src/framework/verify-metrics.js +4 -4
  49. package/src/framework/view-state.js +25 -21
  50. package/src/framework/view-tabs.js +22 -7
  51. package/src/framework/viewer-controls.js +5 -26
  52. package/src/framework/viewer.js +58 -17
  53. package/src/hinged-box-worker.js +3 -0
  54. package/src/index.js +1 -1
  55. package/src/parts/hinged-box.js +94 -0
  56. package/src/testing/render.js +19 -8
  57. package/src/testing.js +15 -8
  58. package/types/derive.d.ts +14 -0
  59. package/types/geometry.d.ts +117 -0
  60. package/types/index.d.ts +240 -0
  61. package/types/kernel.d.ts +409 -0
  62. package/types/lint.d.ts +85 -0
  63. package/types/part.d.ts +381 -0
  64. package/types/testing.d.ts +362 -0
  65. package/types/worker.d.ts +21 -0
  66. /package/src/{testing → framework/oracle}/assert-dsl.js +0 -0
  67. /package/src/{testing → framework/oracle}/cases.js +0 -0
  68. /package/src/{testing → framework/oracle}/dfm-profiles.js +0 -0
  69. /package/src/{testing → framework/oracle}/gaps.js +0 -0
  70. /package/src/{testing → framework/oracle}/mesh.js +0 -0
@@ -1,7 +1,8 @@
1
1
  import { writeFileSync, mkdirSync } from "node:fs";
2
- import { join } from "node:path";
3
- import { buildView } from "./build.js";
4
- import { bounds } from "./mesh.js";
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
- mkdirSync(out, { recursive: true });
58
- const name = slug(part.meta?.title ?? view);
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
- const file = join(out, `${name}-${view}-${angle}.png`);
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, viewSubParts } from "./framework/jobs.js";
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 "./testing/gaps.js";
17
+ export { assemblyGaps, meshGaps } from "./framework/oracle/gaps.js";
11
18
  export { bootOcctKernel } from "./testing/occt.js";
12
- export { meshVolume, bboxSize } from "./testing/mesh.js";
13
- export { buildView } from "./testing/build.js";
14
- export { measure } from "./testing/measure.js";
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 "./testing/verify.js";
17
- export { buildBVH } from "./testing/bvh.js";
18
- export { minWall } from "./testing/min-wall.js";
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[];
@@ -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[];