partforge 0.41.0 → 0.45.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 +138 -27
- package/docs/AUTHORING-PARTS.md +164 -17
- 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 +254 -0
- package/src/framework/animation.js +271 -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/capture-build.js +59 -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 +30 -40
- package/src/framework/lint/finding.js +4 -0
- package/src/framework/lint/index.js +7 -3
- package/src/framework/lint/rules-animations.js +441 -0
- package/src/framework/lint/rules-place.js +76 -0
- package/src/framework/lint/rules-schema.js +22 -0
- package/src/framework/lint/rules-shape.js +12 -0
- package/src/framework/lint/rules-verify.js +2 -2
- package/src/framework/mount.js +147 -20
- 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 +35 -7
- package/src/framework/viewer-controls.js +5 -26
- package/src/framework/viewer-lighting.js +8 -1
- package/src/framework/viewer.js +139 -20
- package/src/framework/worker.js +5 -1
- 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 +259 -0
- package/types/kernel.d.ts +409 -0
- package/types/lint.d.ts +85 -0
- package/types/part.d.ts +409 -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/types/index.d.ts
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
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 { CanonicalView, ParamValue, PartDefinition } from "./part.js";
|
|
9
|
+
|
|
10
|
+
export * from "./kernel.js";
|
|
11
|
+
export * from "./part.js";
|
|
12
|
+
|
|
13
|
+
/** Which pane a narrow layout shows. `null` hands selection back to partforge. */
|
|
14
|
+
export type HostPane = "stage" | "rail" | null;
|
|
15
|
+
|
|
16
|
+
/** An export file format. STEP is routed to OCCT automatically. */
|
|
17
|
+
export type ExportFormat = "stl" | "step" | "3mf";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A semantic click result: which sub-part was hit, where (in the sub-part's own
|
|
21
|
+
* local frame, quantized to 0.01 mm), the snapped surface normal, and only the
|
|
22
|
+
* params that sub-part actually reads.
|
|
23
|
+
*/
|
|
24
|
+
export interface Selection {
|
|
25
|
+
subPart: string;
|
|
26
|
+
point: [number, number, number];
|
|
27
|
+
normal: [number, number, number];
|
|
28
|
+
params: Record<string, ParamValue>;
|
|
29
|
+
/** Present when the hit surface carries a `Solid.label()` name. */
|
|
30
|
+
feature?: { label: string };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface PickEvent {
|
|
34
|
+
selection: Selection;
|
|
35
|
+
/** The feature label, the sub-part's label, or the sub-part name. */
|
|
36
|
+
label: string;
|
|
37
|
+
/** The selection formatted for an LLM prompt. */
|
|
38
|
+
prompt: string;
|
|
39
|
+
/** The selection formatted as a compact token. */
|
|
40
|
+
token: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Fired once per completed build. NOT fired for a pose-only edit. */
|
|
44
|
+
export type BuildEvent =
|
|
45
|
+
| { status: "success"; ms?: number }
|
|
46
|
+
| { status: "error"; error: string };
|
|
47
|
+
|
|
48
|
+
/** The bytes of a finished export, when the host supplies its own download sink. */
|
|
49
|
+
export interface DownloadPayload {
|
|
50
|
+
data: ArrayBuffer | Uint8Array;
|
|
51
|
+
filename: string;
|
|
52
|
+
mime: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Element references. Every entry falls back to the legacy global-ID lookup
|
|
57
|
+
* (`#app`, `#controls`, `#panel`, `#part`, `#download*`, `#status`/`#busy`/
|
|
58
|
+
* `#phase`, `#reframe`/`#theme`/`#cutaway`/`#rail-toggle`), resolved
|
|
59
|
+
* exactly once at mount.
|
|
60
|
+
*/
|
|
61
|
+
export interface MountElements {
|
|
62
|
+
/** The viewer canvas host (`#app`). */
|
|
63
|
+
viewer?: HTMLElement | null;
|
|
64
|
+
/** The control panel host (`#controls`). */
|
|
65
|
+
controls?: HTMLElement | null;
|
|
66
|
+
/** The full-height controls rail (`#panel`). */
|
|
67
|
+
rail?: HTMLElement | null;
|
|
68
|
+
/**
|
|
69
|
+
* The positioned `.pf-shell` ancestor. Only needed when the rail is not a
|
|
70
|
+
* direct child of it — the resize seam is positioned against
|
|
71
|
+
* `rail.parentElement` otherwise.
|
|
72
|
+
*/
|
|
73
|
+
shell?: HTMLElement | null;
|
|
74
|
+
status?: {
|
|
75
|
+
status?: HTMLElement | null;
|
|
76
|
+
busy?: HTMLElement | null;
|
|
77
|
+
phase?: HTMLElement | null;
|
|
78
|
+
};
|
|
79
|
+
/** The view-tab bar (`#part`); leave the element empty, `mount` fills it. */
|
|
80
|
+
tabs?: HTMLElement | null;
|
|
81
|
+
exports?: {
|
|
82
|
+
stl?: HTMLElement | null;
|
|
83
|
+
step?: HTMLElement | null;
|
|
84
|
+
threeMf?: HTMLElement | null;
|
|
85
|
+
};
|
|
86
|
+
chrome?: {
|
|
87
|
+
reframe?: HTMLElement | null;
|
|
88
|
+
theme?: HTMLElement | null;
|
|
89
|
+
cutaway?: HTMLElement | null;
|
|
90
|
+
railToggle?: HTMLElement | null;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface MountOptions {
|
|
95
|
+
/**
|
|
96
|
+
* Spawns a geometry worker. Called once per backend with `name` as the
|
|
97
|
+
* Worker's `name` option. The `new Worker(new URL(...))` call must stay
|
|
98
|
+
* inline in the app module or Vite will not bundle the worker.
|
|
99
|
+
*/
|
|
100
|
+
createWorker: (name: BackendName) => Worker;
|
|
101
|
+
elements?: MountElements;
|
|
102
|
+
onBuild?: (event: BuildEvent) => void;
|
|
103
|
+
/**
|
|
104
|
+
* Programmatic click-to-select. Supplying this arms the picker permanently
|
|
105
|
+
* and takes precedence over the `?pick` and `?pickserver` URL modes.
|
|
106
|
+
*/
|
|
107
|
+
onPick?: (event: PickEvent) => void;
|
|
108
|
+
/** Receive exported bytes instead of partforge's own DOM download. */
|
|
109
|
+
onDownload?: (file: DownloadPayload) => void;
|
|
110
|
+
/** The active view (tab) name — emitted once on mount, then on every change. */
|
|
111
|
+
onViewChange?: (view: string) => void;
|
|
112
|
+
/** @deprecated alias for `elements.viewer`. */
|
|
113
|
+
container?: HTMLElement | null;
|
|
114
|
+
/** @deprecated alias for `elements.controls`. */
|
|
115
|
+
controls?: HTMLElement | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface ExportPartsOptions {
|
|
119
|
+
/** Sub-part names, as `listExportableParts()` reports them. */
|
|
120
|
+
parts: string[];
|
|
121
|
+
format: ExportFormat;
|
|
122
|
+
/** Mesh quality for STL/3MF. Defaults to `"print"`. */
|
|
123
|
+
quality?: "preview" | "print";
|
|
124
|
+
onProgress?: (phase: string) => void;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface CaptureCurrentOptions {
|
|
128
|
+
/** Long-edge resolution in px, clamped into `[256, maxTextureSize]`. */
|
|
129
|
+
size?: number;
|
|
130
|
+
/** Keep the floor grid so the capture matches the on-screen look. */
|
|
131
|
+
hideGrid?: boolean;
|
|
132
|
+
/** JPEG quality, 0..1. */
|
|
133
|
+
quality?: number;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface CaptureViewOptions {
|
|
137
|
+
/** Square render resolution in px. Default 640. */
|
|
138
|
+
size?: number;
|
|
139
|
+
/** JPEG quality, 0..1. Default 0.8. */
|
|
140
|
+
quality?: number;
|
|
141
|
+
/** Canonical angle to render from. Default `"iso"`. */
|
|
142
|
+
angle?: CanonicalView | string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Where playback is: idle, swinging the camera to an intro cue, playing, or paused. */
|
|
146
|
+
export type AnimationStatus = "idle" | "intro" | "playing" | "paused";
|
|
147
|
+
|
|
148
|
+
export interface AnimationState {
|
|
149
|
+
/** The selected animation's key. */
|
|
150
|
+
animation: string;
|
|
151
|
+
status: AnimationStatus;
|
|
152
|
+
/** Position on the timeline, 0..1 over the animation's total duration. */
|
|
153
|
+
t: number;
|
|
154
|
+
/** Which step `t` falls in; 0 for a single-step animation. */
|
|
155
|
+
stepIndex: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Part-declared animation playback — the same engine the viewer's transport bar
|
|
160
|
+
* drives. Playback writes real params, so exporting while paused exports the
|
|
161
|
+
* posed state, and any user or host param edit pauses it.
|
|
162
|
+
*/
|
|
163
|
+
export interface AnimationRuntime {
|
|
164
|
+
/**
|
|
165
|
+
* Play, optionally switching to a named animation first. An unknown name
|
|
166
|
+
* warns and does nothing rather than playing whatever is selected.
|
|
167
|
+
*/
|
|
168
|
+
play(name?: string): void;
|
|
169
|
+
pause(): void;
|
|
170
|
+
/** Jump to a position on the timeline, 0..1. Never moves the camera. */
|
|
171
|
+
seek(t: number): void;
|
|
172
|
+
/** Stop and restore the param values the animation started from. */
|
|
173
|
+
stop(): void;
|
|
174
|
+
state(): AnimationState;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** The runtime handle `mount()` returns. See `makeHandle` in src/framework/mount.js. */
|
|
178
|
+
export interface PartRuntime {
|
|
179
|
+
/** Resolves on the first successful build of the default view; rejects on a first-build error. */
|
|
180
|
+
ready: Promise<void>;
|
|
181
|
+
/** Full teardown of everything this mount created. Idempotent. */
|
|
182
|
+
dispose(): void;
|
|
183
|
+
/**
|
|
184
|
+
* Programmatic param entry point (the animation hook). Same change path as a
|
|
185
|
+
* slider edit: pose-only changes repair synchronously, geometry changes fall
|
|
186
|
+
* through to the regen loop.
|
|
187
|
+
*/
|
|
188
|
+
setParams(partial: Record<string, ParamValue>): void;
|
|
189
|
+
/**
|
|
190
|
+
* Canonical-angle captures (fixed poses, framed to the visible assembly,
|
|
191
|
+
* 1024², grid hidden) — sized for feeding a vision model. Defaults to
|
|
192
|
+
* `["iso", "front", "top"]`; unknown names are dropped.
|
|
193
|
+
*/
|
|
194
|
+
captureViews(viewNames?: CanonicalView[] | string[]): Array<{ view: string; dataUrl: string }>;
|
|
195
|
+
/**
|
|
196
|
+
* One offscreen render of the user's CURRENT framing at a chosen resolution —
|
|
197
|
+
* the showcase capture. Returns a `data:image/jpeg;base64,…` string, or `null`
|
|
198
|
+
* when disposed or nothing is built yet. Never throws.
|
|
199
|
+
*/
|
|
200
|
+
captureCurrent(opts?: CaptureCurrentOptions): string | null;
|
|
201
|
+
/** The active view (tab) name. Never null once mounted. */
|
|
202
|
+
getView(): string;
|
|
203
|
+
/** Switch the active view; `false` if the part declares no such view. Persists per part for the session. */
|
|
204
|
+
setView(name: string): boolean;
|
|
205
|
+
/**
|
|
206
|
+
* Render a named view OFFSCREEN → a `data:image/jpeg;base64,…` string, or `null` on
|
|
207
|
+
* failure (a build error, a view with no sub-parts, or a disposed runtime). Omit
|
|
208
|
+
* `viewName` — or pass an unknown one — to render the part's DEFAULT view. Never
|
|
209
|
+
* disturbs the active tab, the live camera, or the on-screen scene.
|
|
210
|
+
*/
|
|
211
|
+
captureView(viewName?: string, opts?: CaptureViewOptions): Promise<string | null>;
|
|
212
|
+
/**
|
|
213
|
+
* Park/unpark the viewer: stops the render loop and releases the drawing
|
|
214
|
+
* buffer and cached capture target. For a host that hides the canvas without
|
|
215
|
+
* unmounting it. Captures still work while parked. Safe after `dispose()`.
|
|
216
|
+
*/
|
|
217
|
+
setActive(active: boolean): void;
|
|
218
|
+
/**
|
|
219
|
+
* Subscribe to WebGL context loss — i.e. the GPU or the OS gave up — so a host
|
|
220
|
+
* can say so rather than showing a dead canvas. The listener takes no
|
|
221
|
+
* arguments (the underlying event is consumed and `preventDefault`ed).
|
|
222
|
+
* Returns an unsubscribe.
|
|
223
|
+
*/
|
|
224
|
+
onContextLost(listener: () => void): () => void;
|
|
225
|
+
/**
|
|
226
|
+
* Every exportable sub-part — excludes any `exportable: false` part, respects
|
|
227
|
+
* each part's `enabled(params)` — INDEPENDENT of the active view.
|
|
228
|
+
*/
|
|
229
|
+
listExportableParts(): Array<{ name: string; label: string }>;
|
|
230
|
+
/**
|
|
231
|
+
* Headless export of a chosen subset. Resolves once the file is written
|
|
232
|
+
* (handed to your `onDownload` sink, or downloaded directly); rejects on
|
|
233
|
+
* build/export failure or an empty selection.
|
|
234
|
+
*/
|
|
235
|
+
exportParts(opts: ExportPartsOptions): Promise<void>;
|
|
236
|
+
/**
|
|
237
|
+
* Narrow-layout pane selection, for a host that draws its own tab bar.
|
|
238
|
+
* `null` hands selection back to partforge's built-in bar.
|
|
239
|
+
*/
|
|
240
|
+
setHostPane(pane: HostPane): void;
|
|
241
|
+
/**
|
|
242
|
+
* Part-declared animation playback, or `null` when the part declares no
|
|
243
|
+
* `animations` block.
|
|
244
|
+
*/
|
|
245
|
+
animation: AnimationRuntime | null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Mount a full parametric-part app from a `PartDefinition`. */
|
|
249
|
+
export function mount(part: PartDefinition, options: MountOptions): PartRuntime;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The sub-parts a view shows: declared in the view and `enabled` for these
|
|
253
|
+
* params, in `Object.keys(part.parts)` order. Handy for app-side view logic.
|
|
254
|
+
*/
|
|
255
|
+
export function viewSubParts(
|
|
256
|
+
part: PartDefinition,
|
|
257
|
+
view: string,
|
|
258
|
+
params: Record<string, ParamValue>,
|
|
259
|
+
): string[];
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
// Type declarations for the backend-agnostic geometry kernel.
|
|
2
|
+
//
|
|
3
|
+
// This is the TypeScript half of the contract stated twice already: the
|
|
4
|
+
// `@typedef`s in src/framework/geometry/kernel.js (signatures) and
|
|
5
|
+
// docs/KERNEL-CONTRACT.md (prose semantics). The op LISTS in kernel.js
|
|
6
|
+
// (KERNEL_OPS / KERNEL_OPTIONAL_OPS / SOLID_OPS / SOLID_OPTIONAL_OPS /
|
|
7
|
+
// SHAPE2D_OPS / OCCT_ONLY_OPS) are data, and test/types-surface.test.js holds
|
|
8
|
+
// the interfaces below to them member-for-member — so an op added to the kernel
|
|
9
|
+
// cannot silently go undeclared here.
|
|
10
|
+
//
|
|
11
|
+
// Units are millimetres throughout.
|
|
12
|
+
|
|
13
|
+
/** A 2-D point, `[x, y]`. */
|
|
14
|
+
export type Point2 = [number, number] | number[];
|
|
15
|
+
/** A 3-D point or vector, `[x, y, z]`. */
|
|
16
|
+
export type Point3 = [number, number, number] | number[];
|
|
17
|
+
|
|
18
|
+
/** A closed CCW contour as a plain point list (the `polygon.js` helpers' output). */
|
|
19
|
+
export type PointsContour = Point2[];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A curve-native contour that carries arcs/béziers SYMBOLICALLY — the output of
|
|
23
|
+
* `roundedProfile()` and `pathProfile(...).close()`. OCCT turns these into exact
|
|
24
|
+
* B-rep edges (true circles in STEP); Manifold tessellates them at mesh LOD.
|
|
25
|
+
*/
|
|
26
|
+
export interface ArcContour {
|
|
27
|
+
start: Point2;
|
|
28
|
+
segments: Array<{ to: Point2; via?: Point2; c1?: Point2; c2?: Point2 }>;
|
|
29
|
+
/** Set by `roundedProfile`; absent on a `pathProfile` contour. */
|
|
30
|
+
arc?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Either contour form. */
|
|
34
|
+
export type Contour = PointsContour | ArcContour;
|
|
35
|
+
|
|
36
|
+
/** A polygon-with-holes region: one outer contour and any number of hole contours. */
|
|
37
|
+
export interface Region2D {
|
|
38
|
+
outer: Contour;
|
|
39
|
+
holes?: Contour[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Anything the kernel accepts where a 2-D profile is wanted. */
|
|
43
|
+
export type ProfileInput = Contour | Region2D | Shape2D;
|
|
44
|
+
|
|
45
|
+
/** Mesh level of detail. Manifold bakes this in at primitive creation. */
|
|
46
|
+
export type MeshQuality = "preview" | "print";
|
|
47
|
+
|
|
48
|
+
/** A triangle soup (Manifold) or an indexed mesh (OCCT), as `Solid.toMesh()` returns it. */
|
|
49
|
+
export interface Mesh {
|
|
50
|
+
positions: Float32Array;
|
|
51
|
+
normals: Float32Array;
|
|
52
|
+
/** Present on the OCCT backend; absent for a Manifold non-indexed soup. */
|
|
53
|
+
indices?: Uint32Array;
|
|
54
|
+
triangles: number;
|
|
55
|
+
/** Feature-edge line segments (Manifold). */
|
|
56
|
+
edges?: Float32Array;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The indexed mesh 3MF export needs. */
|
|
60
|
+
export interface IndexedMesh {
|
|
61
|
+
positions: Float32Array;
|
|
62
|
+
indices: Uint32Array;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Axis-aligned bounds of a solid. */
|
|
66
|
+
export interface BoundingBox3 {
|
|
67
|
+
min: number[];
|
|
68
|
+
max: number[];
|
|
69
|
+
center: number[];
|
|
70
|
+
size: number[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Axis-aligned bounds of a 2-D shape. */
|
|
74
|
+
export interface BoundingBox2 {
|
|
75
|
+
min: number[];
|
|
76
|
+
max: number[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A materialized region: point rings only, arcs already flattened. */
|
|
80
|
+
export interface MaterializedRegion {
|
|
81
|
+
outer: number[][];
|
|
82
|
+
holes: number[][][];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Which geometry backend a part builds on. */
|
|
86
|
+
export type BackendName = "manifold" | "occt";
|
|
87
|
+
|
|
88
|
+
/** Cardinal direction for `Solid.along()`. */
|
|
89
|
+
export type AxisDirection = "+X" | "-X" | "+Y" | "-Y" | "+Z" | "-Z";
|
|
90
|
+
/** A mirror plane. */
|
|
91
|
+
export type MirrorPlane = "XY" | "XZ" | "YZ";
|
|
92
|
+
/** A named world axis. */
|
|
93
|
+
export type AxisName = "X" | "Y" | "Z";
|
|
94
|
+
|
|
95
|
+
/** Convex-corner style for an offset. */
|
|
96
|
+
export type OffsetCorners = "round" | "chamfer" | "sharp";
|
|
97
|
+
|
|
98
|
+
// --- edge / face selectors (OCCT-only ops) ---------------------------------
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Which edges a `fillet`/`chamfer` applies to. Omit for every edge. The object
|
|
102
|
+
* forms are portable across backends-by-contract; a raw replicad finder callback
|
|
103
|
+
* is an OCCT-only escape hatch (see docs/KERNEL-CONTRACT.md).
|
|
104
|
+
*/
|
|
105
|
+
export type EdgeSelector =
|
|
106
|
+
| { dir: AxisName }
|
|
107
|
+
| { inPlane: MirrorPlane; at?: number }
|
|
108
|
+
| { near: Point3 }
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- an opaque replicad EdgeFinder
|
|
110
|
+
| ((finder: any) => any);
|
|
111
|
+
|
|
112
|
+
/** Which face(s) `shell` opens. Same forms as `EdgeSelector`. */
|
|
113
|
+
export type FaceSelector = EdgeSelector;
|
|
114
|
+
|
|
115
|
+
// --- Shape2D ----------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* An opaque 2-D boolean value (Manifold wraps a CrossSection, OCCT a replicad
|
|
119
|
+
* Drawing). `_`-prefixed keys are backend internals and are not declared.
|
|
120
|
+
*/
|
|
121
|
+
export interface Shape2D {
|
|
122
|
+
union(other: Shape2D | Contour): Shape2D;
|
|
123
|
+
cut(other: Shape2D | Contour): Shape2D;
|
|
124
|
+
/** Batch subtract. */
|
|
125
|
+
cutAll(others: Array<Shape2D | Contour>): Shape2D;
|
|
126
|
+
intersect(other: Shape2D | Contour): Shape2D;
|
|
127
|
+
/**
|
|
128
|
+
* Grow (`delta > 0`) or inset (`delta < 0`). Curve-preserving on OCCT,
|
|
129
|
+
* faceted at mesh LOD on Manifold. Throws if the offset collapses the shape.
|
|
130
|
+
*/
|
|
131
|
+
offset(delta: number, opts?: { corners?: OffsetCorners; segs?: number }): Shape2D;
|
|
132
|
+
/** Net area (outers minus holes), mm². */
|
|
133
|
+
area(): number;
|
|
134
|
+
boundingBox(): BoundingBox2;
|
|
135
|
+
/** Materialize into region arrays. */
|
|
136
|
+
toRegions(): MaterializedRegion[];
|
|
137
|
+
/** `toRegions()` unwrapped — throws unless there is exactly one region. */
|
|
138
|
+
simple(): MaterializedRegion;
|
|
139
|
+
/** Scission: each disjoint region as its own live `Shape2D`. */
|
|
140
|
+
regions(): Shape2D[];
|
|
141
|
+
clone(): Shape2D;
|
|
142
|
+
/** Sugar for `k.extrude({ profile: this, ... })`. */
|
|
143
|
+
extrude(opts: { h: number; twist?: number; scaleTop?: number }): Solid;
|
|
144
|
+
/** Sugar for `k.revolve({ profile: this, ... })`. */
|
|
145
|
+
revolve(opts?: { degrees?: number }): Solid;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// --- Solid ------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* An opaque handle to a backend solid. `_`-prefixed keys are backend internals
|
|
152
|
+
* and are not declared.
|
|
153
|
+
*
|
|
154
|
+
* On the OCCT backend a transform CONSUMES its operand — never reuse a solid
|
|
155
|
+
* after transforming it; `.clone()` first.
|
|
156
|
+
*/
|
|
157
|
+
export interface Solid {
|
|
158
|
+
cut(tool: Solid): Solid;
|
|
159
|
+
/** Batch subtract (backend-optimized). */
|
|
160
|
+
cutAll(tools: Solid[]): Solid;
|
|
161
|
+
intersect(other: Solid): Solid;
|
|
162
|
+
/** Boolean union with one other solid (n-ary: `k.union([...])`). */
|
|
163
|
+
union(other: Solid): Solid;
|
|
164
|
+
/** Independent copy. */
|
|
165
|
+
clone(): Solid;
|
|
166
|
+
/**
|
|
167
|
+
* Name this solid's surface for hover/pick feature attribution. Survives
|
|
168
|
+
* transforms and booleans; the same name on several solids merges into one
|
|
169
|
+
* feature.
|
|
170
|
+
*/
|
|
171
|
+
label(name: string): Solid;
|
|
172
|
+
/** Axis-aligned bounds (a query, not a transform). */
|
|
173
|
+
boundingBox(): BoundingBox3;
|
|
174
|
+
/** Volume in mm³. */
|
|
175
|
+
volume(): number;
|
|
176
|
+
translate(v: Point3): Solid;
|
|
177
|
+
/** Internal primitive — prefer `rotateX`/`rotateY`/`rotateZ`/`rotateAbout`. */
|
|
178
|
+
rotate(deg: number, center: Point3, axis: Point3): Solid;
|
|
179
|
+
rotateX(deg: number): Solid;
|
|
180
|
+
rotateY(deg: number): Solid;
|
|
181
|
+
rotateZ(deg: number): Solid;
|
|
182
|
+
/** General rotation: `axis` names a world axis or gives a vector. */
|
|
183
|
+
rotateAbout(o: { axis: AxisName | Point3; deg: number; through?: Point3 }): Solid;
|
|
184
|
+
/** Orient the canonical +Z build axis along `dir`. */
|
|
185
|
+
along(dir: AxisDirection): Solid;
|
|
186
|
+
/** Place an origin-built solid at point `v` (readable alias of `translate`). */
|
|
187
|
+
at(v: Point3): Solid;
|
|
188
|
+
mirror(plane: MirrorPlane): Solid;
|
|
189
|
+
/** Uniform scale about `center` (default origin). */
|
|
190
|
+
scale(factor: number, center?: Point3): Solid;
|
|
191
|
+
toMesh(opts?: { quality?: MeshQuality }): Mesh;
|
|
192
|
+
toSTL(opts?: { quality?: MeshQuality }): Promise<ArrayBuffer>;
|
|
193
|
+
toIndexedMesh(): IndexedMesh;
|
|
194
|
+
/**
|
|
195
|
+
* Round edges — OCCT only (Manifold throws `KernelCapabilityError`, and the
|
|
196
|
+
* probe routes a part that calls this to OCCT). Legacy `(r, selector)` is
|
|
197
|
+
* accepted until contract v2.
|
|
198
|
+
*/
|
|
199
|
+
fillet(r: number | { r: number; edges?: EdgeSelector }): Solid;
|
|
200
|
+
/** Bevel edges — OCCT only. Legacy `(d, selector)` accepted until contract v2. */
|
|
201
|
+
chamfer(d: number | { d: number; edges?: EdgeSelector }): Solid;
|
|
202
|
+
/**
|
|
203
|
+
* Hollow inward, wall `t`, opening the faces `open` selects — OCCT only.
|
|
204
|
+
* Closed (no open face) hollows are not supported.
|
|
205
|
+
*/
|
|
206
|
+
shell(o: { t: number; open: FaceSelector }): Solid;
|
|
207
|
+
/** Through-hole count (Manifold only). */
|
|
208
|
+
genus?(): number;
|
|
209
|
+
/** No geometry at all (Manifold only). */
|
|
210
|
+
isEmpty?(): boolean;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// --- kernel op option objects ----------------------------------------------
|
|
214
|
+
|
|
215
|
+
/** `k.cylinder` — a straight cylinder (`r`|`d`) or a frustum (`r1`,`r2` / `d1`,`d2`). */
|
|
216
|
+
export interface CylinderOptions {
|
|
217
|
+
r?: number;
|
|
218
|
+
d?: number;
|
|
219
|
+
r1?: number;
|
|
220
|
+
r2?: number;
|
|
221
|
+
d1?: number;
|
|
222
|
+
d2?: number;
|
|
223
|
+
h: number;
|
|
224
|
+
/** Centre the solid on Z too (default: base at z = 0). */
|
|
225
|
+
center?: boolean;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface BoxOptions {
|
|
229
|
+
/** `[x, y, z]` — centred in X/Y with the base at z = 0. */
|
|
230
|
+
size?: Point3;
|
|
231
|
+
center?: boolean;
|
|
232
|
+
min?: Point3;
|
|
233
|
+
max?: Point3;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface PrismOptions {
|
|
237
|
+
points: PointsContour | ArcContour;
|
|
238
|
+
h: number;
|
|
239
|
+
/** Degrees of twist over the height. */
|
|
240
|
+
twist?: number;
|
|
241
|
+
/** Uniform top taper: 1 straight, < 1 taper in, 0 → a point. */
|
|
242
|
+
scaleTop?: number;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface ExtrudeOptions {
|
|
246
|
+
profile: ProfileInput;
|
|
247
|
+
h: number;
|
|
248
|
+
twist?: number;
|
|
249
|
+
scaleTop?: number;
|
|
250
|
+
/** 45° rim bevel (no `twist`/`scaleTop`; `bottom + top < h`). */
|
|
251
|
+
bevel?: number | { bottom?: number; top?: number };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface LoftRing {
|
|
255
|
+
polygon?: PointsContour;
|
|
256
|
+
sides?: number;
|
|
257
|
+
radius?: number;
|
|
258
|
+
z: number;
|
|
259
|
+
/** Degrees about Z. */
|
|
260
|
+
rotate?: number;
|
|
261
|
+
scale?: number | Point2;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface LoftOptions {
|
|
265
|
+
rings: LoftRing[];
|
|
266
|
+
/** `false` = smooth C2 blend, honoured only by OCCT. */
|
|
267
|
+
ruled?: boolean;
|
|
268
|
+
/** Capless loop — Manifold only. */
|
|
269
|
+
closed?: boolean;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface SweepOptions {
|
|
273
|
+
profile: PointsContour;
|
|
274
|
+
/** A 3-D polyline, `[[x, y, z], …]`. */
|
|
275
|
+
path: Point3[];
|
|
276
|
+
/** Capless loop (must be planar) — Manifold only. */
|
|
277
|
+
closed?: boolean;
|
|
278
|
+
cornerRadius?: number;
|
|
279
|
+
ruled?: boolean;
|
|
280
|
+
/** OCCT-native swept B-rep. */
|
|
281
|
+
smooth?: boolean;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** `k.revolve` — a lathe profile `[[r, z], …]` with `r >= 0`, revolved about Z. */
|
|
285
|
+
export interface RevolveOptions {
|
|
286
|
+
profile: ProfileInput;
|
|
287
|
+
degrees?: number;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface HelixSweptTubeOptions {
|
|
291
|
+
pathR: number;
|
|
292
|
+
profileR: number;
|
|
293
|
+
pitch: number;
|
|
294
|
+
turns: number;
|
|
295
|
+
z0: number;
|
|
296
|
+
lefthand: boolean;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export interface RoundedCylinderOptions {
|
|
300
|
+
r?: number;
|
|
301
|
+
d?: number;
|
|
302
|
+
h: number;
|
|
303
|
+
center?: boolean;
|
|
304
|
+
/** Rim round-over: a number (both rims) or per-rim. `round <= r`, `top + bottom <= h`. */
|
|
305
|
+
round: number | { top?: number; bottom?: number };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export interface RoundedBoxOptions {
|
|
309
|
+
size: Point3;
|
|
310
|
+
center?: boolean;
|
|
311
|
+
/** `side` = vertical edges, `top`/`bottom` = rims. */
|
|
312
|
+
round: number | { side?: number; top?: number; bottom?: number };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface TorusOptions {
|
|
316
|
+
rMajor: number;
|
|
317
|
+
rMinor: number;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface BoredCylinderOptions {
|
|
321
|
+
od: number;
|
|
322
|
+
h: number;
|
|
323
|
+
bore: number;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Horizontal alignment of a `text2d` block. */
|
|
327
|
+
export type TextAlign = "center" | "left" | "right";
|
|
328
|
+
/** Vertical alignment of a `text2d` block. */
|
|
329
|
+
export type TextVAlign = "middle" | "baseline" | "top" | "bottom";
|
|
330
|
+
|
|
331
|
+
export interface Text2dOptions {
|
|
332
|
+
/** Cap height in mm (the design height of a capital letter). */
|
|
333
|
+
size: number;
|
|
334
|
+
/** A name declared in the part's `fonts` map; omit for the bundled default. */
|
|
335
|
+
font?: string;
|
|
336
|
+
align?: TextAlign;
|
|
337
|
+
valign?: TextVAlign;
|
|
338
|
+
/** Baseline-to-baseline distance in mm; omit for the font-metrics default. */
|
|
339
|
+
lineHeight?: number;
|
|
340
|
+
/** Letter spacing in mm. */
|
|
341
|
+
tracking?: number;
|
|
342
|
+
/** Pair-wise kerning (default `true`). */
|
|
343
|
+
kerning?: boolean;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Anything `k.hull`/`k.hullChain` accepts as one input. */
|
|
347
|
+
export type HullInput = Shape2D | Contour;
|
|
348
|
+
|
|
349
|
+
// --- the kernel -------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* The backend-agnostic kernel handed to `build(k, p, d)`. The same code runs on
|
|
353
|
+
* Manifold (mesh CSG) and OCCT/replicad (exact B-rep).
|
|
354
|
+
*
|
|
355
|
+
* Every multi-parameter op takes a single options object — the canonical calling
|
|
356
|
+
* convention. Legacy positional forms stay silently accepted until contract v2
|
|
357
|
+
* and are deliberately NOT declared here.
|
|
358
|
+
*/
|
|
359
|
+
export interface GeometryKernel {
|
|
360
|
+
cylinder(o: CylinderOptions): Solid;
|
|
361
|
+
/** Compound: a bored-through cylinder as one cache node. */
|
|
362
|
+
boredCylinder(o: BoredCylinderOptions): Solid;
|
|
363
|
+
/** Sphere centred at the origin; the bare `sphere(r)` form stays valid. */
|
|
364
|
+
sphere(o: { r?: number; d?: number } | number): Solid;
|
|
365
|
+
box(o: BoxOptions): Solid;
|
|
366
|
+
/** Extrude a polygon (or arc profile) from z = 0. */
|
|
367
|
+
prism(o: PrismOptions): Solid;
|
|
368
|
+
/** Extrude a polygon-with-holes region from z = 0. */
|
|
369
|
+
extrude(o: ExtrudeOptions): Solid;
|
|
370
|
+
/** Revolve a lathe profile around Z. */
|
|
371
|
+
revolve(o: RevolveOptions): Solid;
|
|
372
|
+
/** Stack polygon cross-sections into a solid. */
|
|
373
|
+
loft(o: LoftOptions): Solid;
|
|
374
|
+
/** Sweep a 2-D profile along a 3-D polyline. */
|
|
375
|
+
sweep(o: SweepOptions): Solid;
|
|
376
|
+
helixSweptTube(o: HelixSweptTubeOptions): Solid;
|
|
377
|
+
/** Rim round-overs via one lathe revolve; curve-exact in STEP. */
|
|
378
|
+
roundedCylinder(o: RoundedCylinderOptions): Solid;
|
|
379
|
+
torus(o: TorusOptions): Solid;
|
|
380
|
+
/** Selective edge rounding. Stays on Manifold (unlike `Solid.fillet`). */
|
|
381
|
+
roundedBox(o: RoundedBoxOptions): Solid;
|
|
382
|
+
/** N-ary boolean union. */
|
|
383
|
+
union(solids: Solid[]): Solid;
|
|
384
|
+
/** Lift a profile into a 2-D boolean value. */
|
|
385
|
+
shape2d(profile: ProfileInput): Shape2D;
|
|
386
|
+
/** Render outline-font text as a `Shape2D`. */
|
|
387
|
+
text2d(string: string, opts?: Text2dOptions): Shape2D;
|
|
388
|
+
/** Convex hull of all inputs → a convex (faceted) `Shape2D`. */
|
|
389
|
+
hull(inputs: HullInput[]): Shape2D;
|
|
390
|
+
/** Swept hull over an ordered sequence (>= 2 inputs). */
|
|
391
|
+
hullChain(inputs: HullInput[]): Shape2D;
|
|
392
|
+
/** STEP bytes — OCCT only (Manifold throws `KernelCapabilityError`). */
|
|
393
|
+
toSTEP(named: Array<{ name: string; solid: Solid }>): Promise<ArrayBuffer>;
|
|
394
|
+
|
|
395
|
+
// Backend-optional: the sub-part cache brackets and WASM lifetime hooks. Every
|
|
396
|
+
// framework caller reaches these through `?.`, so a third-party backend may
|
|
397
|
+
// omit them entirely.
|
|
398
|
+
|
|
399
|
+
/** Open a per-sub-part solid-cache round. */
|
|
400
|
+
beginSubPart?(name: string): void;
|
|
401
|
+
/** Close the cache round — always pair with `beginSubPart`. */
|
|
402
|
+
endSubPart?(): void;
|
|
403
|
+
/** Drop cache partitions idle for 3 rebinds. Never call mid-bracket. */
|
|
404
|
+
sweepCache?(): void;
|
|
405
|
+
cacheStats?(): { hits: number; misses: number };
|
|
406
|
+
resetCacheStats?(): void;
|
|
407
|
+
/** Free per-job WASM objects (Manifold backend); call after each job. */
|
|
408
|
+
cleanup?(): void;
|
|
409
|
+
}
|