partforge 0.150.0 → 0.151.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 +114 -4
- package/docs/KERNEL-CONTRACT.md +4 -2
- package/package.json +1 -1
- package/src/framework/app.css +6 -4
- package/src/framework/appearance-card.js +26 -2
- package/src/framework/export/bundle.js +57 -14
- package/src/framework/export/drawing.js +38 -0
- package/src/framework/export/dxf.js +45 -8
- package/src/framework/export/formats.js +7 -4
- package/src/framework/export/readme.js +24 -7
- package/src/framework/export/svg.js +14 -2
- package/src/framework/geometry/polygon.js +1 -0
- package/src/framework/oracle/measure.js +2 -2
- package/src/framework/process/cnc-router/descriptor.js +174 -0
- package/src/framework/process/cnc-router/export.js +74 -0
- package/src/framework/process/exporters.js +1 -0
- package/src/framework/process/registry.js +2 -1
- package/src/framework/sheet/constants.js +7 -0
- package/src/framework/sheet/part.js +19 -8
- package/src/framework/sheet/resolve.js +13 -2
- package/src/framework/sheet/routed-resolve.js +62 -0
- package/src/framework/sheet/routed.js +44 -0
- package/types/geometry.d.ts +44 -1
- package/types/index.d.ts +2 -2
- package/types/part.d.ts +6 -0
- package/types/testing.d.ts +21 -2
package/types/geometry.d.ts
CHANGED
|
@@ -315,7 +315,13 @@ export interface SheetPartSpec<P = ResolvedParams, D = Derived> {
|
|
|
315
315
|
engrave?: ((k: GeometryKernel, p: P, d: D) => Shape2D | ProfileInput | null) | null;
|
|
316
316
|
/** Where the piece sits, for display AND export. Omitted: flat, no transform. */
|
|
317
317
|
pose?: SheetPose | ((p: P, d: D) => SheetPose | null) | null;
|
|
318
|
-
process?: "laser";
|
|
318
|
+
process?: "laser" | "cnc-router";
|
|
319
|
+
/** `process: "cnc-router"` only: flat-floored pockets at a depth (as `routedPart`). */
|
|
320
|
+
pockets?: ((k: GeometryKernel, p: P, d: D) => (RoutedPocket | null)[] | null) | null;
|
|
321
|
+
/** `process: "cnc-router"` only: drilled holes at a depth (as `routedPart`). */
|
|
322
|
+
drills?: ((p: P, d: D) => (RoutedDrill | null)[] | null) | null;
|
|
323
|
+
/** `process: "cnc-router"` only: operations done with another tool (as `routedPart`). */
|
|
324
|
+
extra?: ((k: GeometryKernel, p: P, d: D) => (RoutedExtra | null)[] | null) | null;
|
|
319
325
|
label?: string;
|
|
320
326
|
views: string[] | Record<string, true | ((s: Solid, p: P, d: D) => Solid)>;
|
|
321
327
|
display?: SubPartDefinition<P, D>["display"];
|
|
@@ -330,6 +336,43 @@ export interface SheetPartSpec<P = ResolvedParams, D = Derived> {
|
|
|
330
336
|
/** A sub-part cut from flat stock: an ordinary sub-part with `build`, `place` and `sheet` filled in. */
|
|
331
337
|
export function sheetPart<P = ResolvedParams, D = Derived>(spec: SheetPartSpec<P, D>): SubPartDefinition<P, D>;
|
|
332
338
|
|
|
339
|
+
// --- routed parts (docs/AUTHORING-PARTS.md "Routed parts") --------------------
|
|
340
|
+
|
|
341
|
+
/** A flat-floored pocket machined `depth` mm down from the machined face. */
|
|
342
|
+
export interface RoutedPocket { region: Shape2D | ProfileInput; depth: number; label?: string }
|
|
343
|
+
/** A drilled hole; `depth` equal to the stock thickness drills through — deeper is a `routed-drill-depth` lint error. */
|
|
344
|
+
export interface RoutedDrill { at: Point2; d: number; depth: number; label?: string }
|
|
345
|
+
/** An operation done with another tool after the flat milling: the solid is cut in the preview and the STEP, the footprint is drawn, label and method go in the README. */
|
|
346
|
+
export interface RoutedExtra { solid: Solid; footprint: Shape2D | ProfileInput; label: string; method: string }
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* What `routedPart` accepts: `sheetPart`'s stock, profile and pose, plus pockets,
|
|
350
|
+
* drills and extra ops at depth. `score`/`engrave`/`process` are refused at load.
|
|
351
|
+
*/
|
|
352
|
+
export interface RoutedPartSpec<P = ResolvedParams, D = Derived> {
|
|
353
|
+
material: string | ((p: P, d: D) => string);
|
|
354
|
+
/** The MEASURED stock thickness in mm. */
|
|
355
|
+
thickness: number | ((p: P, d: D) => number);
|
|
356
|
+
/** The THROUGH cut: outline plus through holes, seen from the machined face. */
|
|
357
|
+
profile: (k: GeometryKernel, p: P, d: D) => Shape2D | ProfileInput;
|
|
358
|
+
pockets?: ((k: GeometryKernel, p: P, d: D) => (RoutedPocket | null)[] | null) | null;
|
|
359
|
+
drills?: ((p: P, d: D) => (RoutedDrill | null)[] | null) | null;
|
|
360
|
+
extra?: ((k: GeometryKernel, p: P, d: D) => (RoutedExtra | null)[] | null) | null;
|
|
361
|
+
/** Centerlines and layout marks: drawn on REFERENCE, cut nothing. Same entries as `score`. */
|
|
362
|
+
reference?: ((k: GeometryKernel, p: P, d: D) => SheetScoreEntry[] | null) | null;
|
|
363
|
+
pose?: SheetPose | ((p: P, d: D) => SheetPose | null) | null;
|
|
364
|
+
label?: string;
|
|
365
|
+
views: string[] | Record<string, true | ((s: Solid, p: P, d: D) => Solid)>;
|
|
366
|
+
display?: SubPartDefinition<P, D>["display"];
|
|
367
|
+
export?: { name: string };
|
|
368
|
+
enabled?: (p: P) => unknown;
|
|
369
|
+
exportable?: boolean;
|
|
370
|
+
place?: (solid: Solid, ctx: PlaceContext<P, D>) => Solid;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** A sub-part CNC-routed from flat stock: an ordinary sub-part with `build`, `place` and a `sheet` record of process `"cnc-router"`. */
|
|
374
|
+
export function routedPart<P = ResolvedParams, D = Derived>(spec: RoutedPartSpec<P, D>): SubPartDefinition<P, D>;
|
|
375
|
+
|
|
333
376
|
/** A metric screw size `tSlots` knows. */
|
|
334
377
|
export type ScrewSize = "M2.5" | "M3" | "M4";
|
|
335
378
|
|
package/types/index.d.ts
CHANGED
|
@@ -51,8 +51,8 @@ export interface ExportableSheetInfo {
|
|
|
51
51
|
* starting `"cut kit options:"` for anything it cannot honour.
|
|
52
52
|
*/
|
|
53
53
|
export interface KitOptions {
|
|
54
|
-
/** `"own-laser"` (default): SVG, laid out on sheets. `"service"`: DXF, one cut-only file per piece. */
|
|
55
|
-
destination?: "own-laser" | "service";
|
|
54
|
+
/** `"own-laser"` (default): SVG, laid out on sheets. `"service"`: DXF, one cut-only file per piece. `"cnc-router"`: DXF, one layered file per routed piece; kerf must be 0. */
|
|
55
|
+
destination?: "own-laser" | "service" | "cnc-router";
|
|
56
56
|
/** Derived from `destination`; when given it must match. */
|
|
57
57
|
cutFormat?: "svg" | "dxf";
|
|
58
58
|
/** mm, 0–0.5: the cut lines move out by half of it. Default 0 (nominal geometry). */
|
package/types/part.d.ts
CHANGED
|
@@ -556,6 +556,12 @@ export interface SubPartExpectations {
|
|
|
556
556
|
sheetPieces?: Expectation;
|
|
557
557
|
/** Sheet parts with a custom `build` only: % volume drift from profile area × thickness minus the marks (volunteered check: `<=2`). */
|
|
558
558
|
sheetSolidMatch?: Expectation;
|
|
559
|
+
/** Routed parts only: pocket, drill or extra-op footprint regions outside the profile (volunteered check: `0`). */
|
|
560
|
+
routedOutside?: Expectation;
|
|
561
|
+
/** Routed parts only: pockets whose region resolved empty (volunteered check: `0`). */
|
|
562
|
+
routedEmpty?: Expectation;
|
|
563
|
+
/** Routed parts only: separate regions in the cut profile (volunteered check: `1`). */
|
|
564
|
+
routedPieces?: Expectation;
|
|
559
565
|
}
|
|
560
566
|
|
|
561
567
|
/**
|
package/types/testing.d.ts
CHANGED
|
@@ -340,14 +340,33 @@ export interface SheetFacts {
|
|
|
340
340
|
marksOutside: number | null;
|
|
341
341
|
/** Custom builds only: `100·|volume − (area·thickness − 0.2·marksArea)| / (area·thickness − 0.2·marksArea)`. */
|
|
342
342
|
solidMatchPct: number | null;
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
/** `outside` is a routed part's (the 2-D spot of the first op found outside the profile); a laser part's facts leave it out. */
|
|
344
|
+
at2d: { bridge: [number, number] | null; gap: [number, number] | null; marks: [number, number] | null; outside?: [number, number] | null };
|
|
345
|
+
/** `outside`: the 3-D place of `at2d.outside` when the part's pose is trusted, else `null`. */
|
|
346
|
+
at: { bridge: number[] | null; gap: number[] | null; marks: number[] | null; outside: [number, number, number] | null };
|
|
345
347
|
/**
|
|
346
348
|
* Why a reading is `null` although `evaluated` is true — the geometry engine refused
|
|
347
349
|
* the profile, or the reading threw — or `null` for each one that was taken.
|
|
348
350
|
*/
|
|
349
351
|
readErrors: { bridge: string | null; gap: string | null; marks: string | null; marksArea: string | null };
|
|
350
352
|
evaluated: boolean;
|
|
353
|
+
/** A routed part's (`process: "cnc-router"`) operations table; absent on a laser part. */
|
|
354
|
+
routed?: RoutedFacts;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** A routed part's operations table, as the README's ROUTER OPERATIONS block prints it. Sizes in mm to two decimals. */
|
|
358
|
+
export interface RoutedFacts {
|
|
359
|
+
profile: { w: number; h: number };
|
|
360
|
+
/** One row per non-empty pocket. */
|
|
361
|
+
pockets: Array<{ label: string | null; depth: number; w: number; h: number; area: number }>;
|
|
362
|
+
/** One row per tool setting (d, depth, through), labels joined with ", ". */
|
|
363
|
+
drills: Array<{ label: string | null; d: number; depth: number; through: boolean; count: number }>;
|
|
364
|
+
/** One row per extra op with a non-empty footprint; `maxDepth` is how far its solid reaches below the machined face, within the stock. */
|
|
365
|
+
extra: Array<{ label: string; method: string; w: number; h: number; maxDepth: number }>;
|
|
366
|
+
/** How many pockets, drills and extra footprints leave the profile. */
|
|
367
|
+
outside: number;
|
|
368
|
+
/** How many pockets and extra footprints resolved to an empty region. */
|
|
369
|
+
empty: number;
|
|
351
370
|
}
|
|
352
371
|
|
|
353
372
|
export interface SubPartFacts {
|