@toolpath/dfm 0.3.1 → 0.4.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.
|
@@ -232,6 +232,38 @@ interface FeatureSheet {
|
|
|
232
232
|
noToolFits?: true;
|
|
233
233
|
/** For a hole the model threads: its thread. */
|
|
234
234
|
threading?: FeatureThreading;
|
|
235
|
+
/** For an undercut — a T-slot or a dovetail: what its cutter is held to. */
|
|
236
|
+
undercut?: FeatureUndercut;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* An undercut's own figures, from its `facts`: a T-slot's or a dovetail's.
|
|
240
|
+
* Millimetres and degrees. Each is left out where the Engine did not measure
|
|
241
|
+
* it, or where nothing limits it.
|
|
242
|
+
*/
|
|
243
|
+
interface FeatureUndercut {
|
|
244
|
+
/** T-slot: how far the groove runs back from its opening, radially (`facts.undercutDepth`). */
|
|
245
|
+
undercutDepth?: number;
|
|
246
|
+
/**
|
|
247
|
+
* T-slot: the widest tool that comes down through the opening above it
|
|
248
|
+
* (`facts.maxEntryCd`), which holds the cutter's shaft. Left out where
|
|
249
|
+
* nothing above limits it; zero where nothing fits.
|
|
250
|
+
*/
|
|
251
|
+
maxEntry?: number;
|
|
252
|
+
/** Dovetail: between the overhanging wall and the tool axis, degrees (`facts.taperDeg`). */
|
|
253
|
+
taperDeg?: number;
|
|
254
|
+
/** Dovetail: the widest clearance over the floor, the width the groove cuts at. */
|
|
255
|
+
floorWidth?: number;
|
|
256
|
+
/** Dovetail: the clearance through the opening at the top of the groove. */
|
|
257
|
+
topOpeningWidth?: number;
|
|
258
|
+
/** Dovetail: the groove's widths vary from place to place — it runs out somewhere. */
|
|
259
|
+
isExternal?: true;
|
|
260
|
+
/** T-slot: its walls close on themselves in plan view, so a cutter's head comes down through its opening. */
|
|
261
|
+
isClosed?: true;
|
|
262
|
+
/**
|
|
263
|
+
* The Engine could not measure it (`facts.isInvalidGeometry` or
|
|
264
|
+
* `facts.cd.measurementFailed`), so its figures offer no cutter.
|
|
265
|
+
*/
|
|
266
|
+
unmeasured?: true;
|
|
235
267
|
}
|
|
236
268
|
/** A modelled thread, from a hole's `facts.threading`. Millimetres. */
|
|
237
269
|
interface FeatureThreading {
|
|
@@ -500,4 +532,4 @@ interface FeatureRecord {
|
|
|
500
532
|
readonly datasheet: unknown;
|
|
501
533
|
}
|
|
502
534
|
|
|
503
|
-
export {
|
|
535
|
+
export { dfmFeatures as A, type BrokenRule as B, CRITICAL_COLOR as C, type DfmRule as D, featureColor as E, type FeatureSheets as F, featureMeasurements as G, featureProfile as H, featureSheet as I, featureTypeLabel as J, isNoReading as K, isSurfaceType as L, type Measurement as M, makeRule as N, maxBallDiameter as O, PINCH_TOOL_MM as P, parseRules as Q, type ReachCurve as R, type SurfaceToolKind as S, peakHeight as T, type Units as U, readPinch as V, WARNING_COLOR as W, readReachCurve as X, runStart as Y, trimmedCurve as Z, withSurroundedFaces as _, type DfmCheck as a, type DfmFeature as b, type FeatureProfile as c, type FeatureRecord as d, type DfmHit as e, type FeaturePinch as f, type FeatureSheet as g, type ClearanceFit as h, type DfmMetric as i, type DfmOp as j, type DfmQuantity as k, type DfmSources as l, type FactsKind as m, type FeatureAcross as n, type FeatureThreading as o, type FeatureUndercut as p, type PinchDisc as q, RULE_COLORS as r, type RangeHigh as s, type RangeLow as t, type ReportFeature as u, type RuleResult as v, brokenRules as w, checkPart as x, compareColors as y, describeRule as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, FC, KeyboardEvent, Ref, ComponentType, ReactElement, HTMLAttributes } from 'react';
|
|
3
|
-
import { D as DfmRule, a as DfmCheck, b as DfmFeature, F as FeatureSheets, U as Units, R as ReachCurve, c as FeatureProfile, B as BrokenRule, M as Measurement, d as FeatureRecord } from './feature-record-
|
|
3
|
+
import { D as DfmRule, a as DfmCheck, b as DfmFeature, F as FeatureSheets, U as Units, R as ReachCurve, c as FeatureProfile, B as BrokenRule, M as Measurement, d as FeatureRecord } from './feature-record-DHbkVpx0.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The rules the list shows, and how to change them. The app owns them —
|
package/dist/model/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { e as DfmHit, U as Units, f as FeaturePinch, g as FeatureSheet, D as DfmRule } from '../feature-record-
|
|
2
|
-
export { B as BrokenRule, C as CRITICAL_COLOR, h as ClearanceFit, a as DfmCheck, b as DfmFeature, i as DfmMetric, j as DfmOp, k as DfmQuantity, l as DfmSources, m as FactsKind, n as FeatureAcross, c as FeatureProfile, d as FeatureRecord, F as FeatureSheets, o as FeatureThreading, M as Measurement, P as PINCH_TOOL_MM,
|
|
1
|
+
import { e as DfmHit, U as Units, f as FeaturePinch, g as FeatureSheet, D as DfmRule } from '../feature-record-DHbkVpx0.js';
|
|
2
|
+
export { B as BrokenRule, C as CRITICAL_COLOR, h as ClearanceFit, a as DfmCheck, b as DfmFeature, i as DfmMetric, j as DfmOp, k as DfmQuantity, l as DfmSources, m as FactsKind, n as FeatureAcross, c as FeatureProfile, d as FeatureRecord, F as FeatureSheets, o as FeatureThreading, p as FeatureUndercut, M as Measurement, P as PINCH_TOOL_MM, q as PinchDisc, r as RULE_COLORS, s as RangeHigh, t as RangeLow, R as ReachCurve, u as ReportFeature, v as RuleResult, S as SurfaceToolKind, W as WARNING_COLOR, w as brokenRules, x as checkPart, y as compareColors, z as describeRule, A as dfmFeatures, E as featureColor, G as featureMeasurements, H as featureProfile, I as featureSheet, J as featureTypeLabel, K as isNoReading, L as isSurfaceType, N as makeRule, O as maxBallDiameter, Q as parseRules, T as peakHeight, V as readPinch, X as readReachCurve, Y as runStart, Z as trimmedCurve, _ as withSurroundedFaces } from '../feature-record-DHbkVpx0.js';
|
|
3
3
|
|
|
4
4
|
/** The figure that broke a rule, bare: `7.5`, `0.05"`. Empty for a yes/no or "any size". */
|
|
5
5
|
declare const hitFigure: (hit: DfmHit, units: Units) => string;
|
package/dist/model/index.js
CHANGED
|
@@ -327,6 +327,164 @@ var checkPart = (features, sources, rules) => {
|
|
|
327
327
|
};
|
|
328
328
|
};
|
|
329
329
|
|
|
330
|
+
// src/model/pinch.ts
|
|
331
|
+
var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
332
|
+
var recordOf = (value) => typeof value === "object" && value !== null ? value : {};
|
|
333
|
+
var clearanceFit = (datasheet) => {
|
|
334
|
+
const sheet = recordOf(datasheet);
|
|
335
|
+
const facts = recordOf(sheet.facts);
|
|
336
|
+
const chamfer = facts.kind === "Chamfer";
|
|
337
|
+
const band = recordOf(chamfer ? recordOf(facts.three).cd : facts.cd);
|
|
338
|
+
const tolerance = recordOf(sheet.toleranceBand);
|
|
339
|
+
const strict = asNumber(recordOf(band.ignore).min);
|
|
340
|
+
if (strict === void 0) return void 0;
|
|
341
|
+
const bandPath = chamfer ? "facts.three.cd" : "facts.cd";
|
|
342
|
+
const loose = asNumber(recordOf(band.deviate).min);
|
|
343
|
+
const atStrict = asNumber(tolerance.atolIgnore);
|
|
344
|
+
const atLoose = asNumber(tolerance.atolDeviate);
|
|
345
|
+
const source = { bandPath, strict, loose, atStrict, atLoose };
|
|
346
|
+
if (loose === void 0 || atStrict === void 0 || atLoose === void 0 || atLoose <= atStrict)
|
|
347
|
+
return { ...source, slack: 0, resolved: false };
|
|
348
|
+
const growth = (loose - strict) / (atLoose - atStrict);
|
|
349
|
+
const slack = growth > 0 ? Math.min(growth * atStrict, strict) : 0;
|
|
350
|
+
return { ...source, slack, resolved: true };
|
|
351
|
+
};
|
|
352
|
+
var readPinch = (datasheet) => {
|
|
353
|
+
const sheet = recordOf(datasheet);
|
|
354
|
+
const zMin = asNumber(sheet.zMin);
|
|
355
|
+
const zMax = asNumber(sheet.zMax);
|
|
356
|
+
if (zMin === void 0 || zMax === void 0 || !Array.isArray(sheet.pinchPoints)) return null;
|
|
357
|
+
const fit = clearanceFit(datasheet);
|
|
358
|
+
const slack = fit?.slack ?? 0;
|
|
359
|
+
const discs = sheet.pinchPoints.flatMap((point) => {
|
|
360
|
+
const centre = recordOf(recordOf(point).center);
|
|
361
|
+
const x = asNumber(centre.x);
|
|
362
|
+
const y = asNumber(centre.y);
|
|
363
|
+
const diameter = asNumber(recordOf(point).diameter);
|
|
364
|
+
return x === void 0 || y === void 0 || diameter === void 0 || diameter < 0 ? [] : [{ x, y, diameter: Math.max(diameter - slack, 0) }];
|
|
365
|
+
});
|
|
366
|
+
if (discs.length === 0) return null;
|
|
367
|
+
return { discs, zMin, zMax, ...fit && !fit.resolved ? { unresolved: true } : {} };
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/model/feature-sheet.ts
|
|
371
|
+
var SHARP_MM = 0.1;
|
|
372
|
+
var SHARP_RADIUS_MM = 0.01;
|
|
373
|
+
var PINCH_TOOL_MM = 0.01;
|
|
374
|
+
var asNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
375
|
+
var recordOf2 = (value) => typeof value === "object" && value !== null ? value : {};
|
|
376
|
+
var definedOnly = (fields) => Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== void 0));
|
|
377
|
+
var undercutOf = (facts) => {
|
|
378
|
+
const unmeasured = facts.isInvalidGeometry === true || recordOf2(facts.cd).measurementFailed === true ? true : void 0;
|
|
379
|
+
if (facts.kind === "Tslot")
|
|
380
|
+
return definedOnly({
|
|
381
|
+
undercutDepth: asNumber2(facts.undercutDepth),
|
|
382
|
+
maxEntry: asNumber2(facts.maxEntryCd),
|
|
383
|
+
isClosed: facts.isClosed === true ? true : void 0,
|
|
384
|
+
unmeasured
|
|
385
|
+
});
|
|
386
|
+
if (facts.kind === "Dovetail")
|
|
387
|
+
return definedOnly({
|
|
388
|
+
taperDeg: asNumber2(facts.taperDeg),
|
|
389
|
+
floorWidth: asNumber2(facts.floorWidth),
|
|
390
|
+
topOpeningWidth: asNumber2(facts.topOpeningWidth),
|
|
391
|
+
isExternal: facts.isExternal === true ? true : void 0,
|
|
392
|
+
unmeasured
|
|
393
|
+
});
|
|
394
|
+
return void 0;
|
|
395
|
+
};
|
|
396
|
+
var threadingOf = (facts) => {
|
|
397
|
+
const spec = recordOf2(recordOf2(facts.threading).spec);
|
|
398
|
+
const basicDiameter = asNumber2(spec.basicDiameter);
|
|
399
|
+
const threadPitch = asNumber2(spec.threadPitch);
|
|
400
|
+
if (basicDiameter === void 0 || threadPitch === void 0 || threadPitch <= 0) return void 0;
|
|
401
|
+
return { basicDiameter, threadPitch };
|
|
402
|
+
};
|
|
403
|
+
var noToolFits = (band) => {
|
|
404
|
+
const { max } = band;
|
|
405
|
+
if (typeof max === "string") return /^-inf/i.test(max.trim());
|
|
406
|
+
return typeof max === "number" && (max === Number.NEGATIVE_INFINITY || Number.isFinite(max) && max < SHARP_MM);
|
|
407
|
+
};
|
|
408
|
+
var insideCornerRadius = (fit) => Math.max((fit.strict - fit.slack) / 2, 0);
|
|
409
|
+
var pinchCentres = (points) => Array.isArray(points) ? points.flatMap((point) => {
|
|
410
|
+
const centre = recordOf2(recordOf2(point).center);
|
|
411
|
+
const x = asNumber2(centre.x);
|
|
412
|
+
const y = asNumber2(centre.y);
|
|
413
|
+
return x === void 0 || y === void 0 ? [] : [{ x, y }];
|
|
414
|
+
}) : [];
|
|
415
|
+
var ZERO_MM = 5e-5;
|
|
416
|
+
var isCutterlessTslot = (datasheet) => {
|
|
417
|
+
const facts = recordOf2(recordOf2(datasheet).facts);
|
|
418
|
+
if (facts.kind !== "Tslot") return false;
|
|
419
|
+
const leaves = (value) => typeof value === "object" && value !== null ? Object.values(value).flatMap(leaves) : [value];
|
|
420
|
+
const band = leaves(facts.cd);
|
|
421
|
+
return band.length > 0 && band.every((each) => {
|
|
422
|
+
const value = typeof each === "string" && each.trim() !== "" ? Number(each) : each;
|
|
423
|
+
return typeof value === "number" && Math.abs(value) < ZERO_MM;
|
|
424
|
+
});
|
|
425
|
+
};
|
|
426
|
+
var isSharpCorneredFillet = (featureType, datasheet) => {
|
|
427
|
+
if (typeof featureType !== "string" || !featureType.toLowerCase().split("_").includes("fillet"))
|
|
428
|
+
return false;
|
|
429
|
+
const facts = recordOf2(recordOf2(datasheet).facts);
|
|
430
|
+
return facts.hasSharpCorner === true || recordOf2(facts.three).hasSharpCorner === true;
|
|
431
|
+
};
|
|
432
|
+
var isNoReading = (featureType, datasheet) => isCutterlessTslot(datasheet) || isSharpCorneredFillet(featureType, datasheet);
|
|
433
|
+
var featureSheet = (datasheet) => {
|
|
434
|
+
const sheet = recordOf2(datasheet);
|
|
435
|
+
const facts = recordOf2(sheet.facts);
|
|
436
|
+
const kind = typeof facts.kind === "string" ? facts.kind : void 0;
|
|
437
|
+
const band = recordOf2(kind === "Chamfer" ? recordOf2(facts.three).cd : facts.cd);
|
|
438
|
+
const ignoreBand = recordOf2(band.ignore);
|
|
439
|
+
const fit = clearanceFit(datasheet);
|
|
440
|
+
const fields = {
|
|
441
|
+
kind,
|
|
442
|
+
zMin: asNumber2(sheet.zMin),
|
|
443
|
+
zMax: asNumber2(sheet.zMax),
|
|
444
|
+
extendedZMax: asNumber2(sheet.extendedZMax),
|
|
445
|
+
maxTool: asNumber2(ignoreBand.min),
|
|
446
|
+
diameter: kind === "Hole" ? asNumber2(facts.diameter) : void 0,
|
|
447
|
+
tipAngle: kind === "Hole" ? asNumber2(facts.fullConeDeg) : void 0,
|
|
448
|
+
filletRadius: asNumber2(facts.filletRadius),
|
|
449
|
+
// A bevel's angle wherever the facts carry one: a chamfer's, and any other kind that bevels.
|
|
450
|
+
chamferAngle: asNumber2(recordOf2(facts.bevel).angleDeg)
|
|
451
|
+
};
|
|
452
|
+
const toolFitPath = facts.toolFit !== void 0 ? "facts.toolFit" : "facts.three.toolFit";
|
|
453
|
+
const toolFit = recordOf2(facts.toolFit ?? recordOf2(facts.three).toolFit);
|
|
454
|
+
fields.fitToolDiameter = asNumber2(toolFit.toolDiameter);
|
|
455
|
+
fields.fitCornerRadius = asNumber2(toolFit.cornerRadius);
|
|
456
|
+
const ballOnly = (facts.useOnlyBallToolsForFinish ?? recordOf2(facts.three).useOnlyBallToolsForFinish) === true;
|
|
457
|
+
const measured = Object.fromEntries(
|
|
458
|
+
Object.entries(fields).filter(([, value]) => value !== void 0)
|
|
459
|
+
);
|
|
460
|
+
const cramped = Object.keys(ignoreBand).length > 0 && noToolFits(ignoreBand);
|
|
461
|
+
const walled = kind !== "Hole" && sheet.hasWall === true;
|
|
462
|
+
const clearance = walled ? fit : void 0;
|
|
463
|
+
const cornerRadius = clearance ? insideCornerRadius(clearance) : void 0;
|
|
464
|
+
const sharp = cornerRadius !== void 0 && cornerRadius < SHARP_RADIUS_MM && !cramped;
|
|
465
|
+
const threading = kind === "Hole" ? threadingOf(facts) : void 0;
|
|
466
|
+
const undercut = undercutOf(facts);
|
|
467
|
+
const pinched = (readPinch(datasheet)?.discs ?? []).filter(
|
|
468
|
+
(disc) => disc.diameter >= PINCH_TOOL_MM
|
|
469
|
+
);
|
|
470
|
+
return {
|
|
471
|
+
...measured,
|
|
472
|
+
...pinched.length > 0 ? {
|
|
473
|
+
pinchDiameter: Math.min(...pinched.map((disc) => disc.diameter)),
|
|
474
|
+
pinchPlaces: pinched.length
|
|
475
|
+
} : {},
|
|
476
|
+
...cornerRadius !== void 0 ? { cornerRadius } : {},
|
|
477
|
+
...clearance && !clearance.resolved ? { cornerRadiusUnresolved: true } : {},
|
|
478
|
+
...clearance ? { clearance } : {},
|
|
479
|
+
...Object.keys(toolFit).length > 0 ? { toolFitPath } : {},
|
|
480
|
+
...sharp ? { sharpCorners: pinchCentres(sheet.pinchPoints) } : {},
|
|
481
|
+
...cramped ? { noToolFits: true } : {},
|
|
482
|
+
...ballOnly ? { ballOnly: true } : {},
|
|
483
|
+
...threading ? { threading } : {},
|
|
484
|
+
...undercut ? { undercut } : {}
|
|
485
|
+
};
|
|
486
|
+
};
|
|
487
|
+
|
|
330
488
|
// src/model/feature-details.ts
|
|
331
489
|
import { MM_PER_INCH } from "@toolpath/tool-support";
|
|
332
490
|
var mm = (value) => `${trim(value, 4)} mm`;
|
|
@@ -375,6 +533,11 @@ var maxBallDiameter = (sheet) => {
|
|
|
375
533
|
};
|
|
376
534
|
var ROW_ORDER = [
|
|
377
535
|
"featureDepth",
|
|
536
|
+
"undercutWidth",
|
|
537
|
+
"floorWidth",
|
|
538
|
+
"openingWidth",
|
|
539
|
+
"undercutDepth",
|
|
540
|
+
"taper",
|
|
378
541
|
"diameter",
|
|
379
542
|
"cornerRadius",
|
|
380
543
|
"filletRadius",
|
|
@@ -383,6 +546,8 @@ var ROW_ORDER = [
|
|
|
383
546
|
"thread",
|
|
384
547
|
"bevelAngle",
|
|
385
548
|
"finishTool",
|
|
549
|
+
"undercutCutter",
|
|
550
|
+
"maxShaft",
|
|
386
551
|
"pinch",
|
|
387
552
|
"fitCornerRadius",
|
|
388
553
|
"maxBull",
|
|
@@ -406,6 +571,7 @@ var featureMeasurements = ({
|
|
|
406
571
|
});
|
|
407
572
|
const sheet = sheets[feature.tag.toLowerCase()];
|
|
408
573
|
const rows = [];
|
|
574
|
+
const undercut = sheet?.undercut;
|
|
409
575
|
const top = sheet ? partTop(features, sheets, feature.machiningDirection) : null;
|
|
410
576
|
const bottom = sheet?.zMin;
|
|
411
577
|
const topFrom = top === null ? void 0 : features.find((each) => sheets[each.tag.toLowerCase()]?.extendedZMax === top);
|
|
@@ -419,7 +585,7 @@ var featureMeasurements = ({
|
|
|
419
585
|
derivation: [topLine, `Top \u2212 zMin = ${mm(top)} \u2212 ${mm(bottom)} = ${mm(top - bottom)}`]
|
|
420
586
|
});
|
|
421
587
|
}
|
|
422
|
-
if (sheet?.zMax !== void 0 && bottom !== void 0) {
|
|
588
|
+
if (sheet?.zMax !== void 0 && bottom !== void 0 && !(undercut && sheet?.kind === "Tslot")) {
|
|
423
589
|
rows.push({
|
|
424
590
|
key: "featureDepth",
|
|
425
591
|
label: "Feature depth",
|
|
@@ -435,7 +601,7 @@ var featureMeasurements = ({
|
|
|
435
601
|
derivation: [`facts.diameter = ${mm(sheet.diameter)}`]
|
|
436
602
|
});
|
|
437
603
|
}
|
|
438
|
-
if (sheet?.cornerRadius !== void 0) {
|
|
604
|
+
if (sheet?.cornerRadius !== void 0 && !undercut) {
|
|
439
605
|
const radius = length(sheet.cornerRadius);
|
|
440
606
|
rows.push({
|
|
441
607
|
key: "cornerRadius",
|
|
@@ -449,7 +615,7 @@ var featureMeasurements = ({
|
|
|
449
615
|
]
|
|
450
616
|
});
|
|
451
617
|
}
|
|
452
|
-
if (!sheet?.sharpCorners) {
|
|
618
|
+
if (!sheet?.sharpCorners && !undercut) {
|
|
453
619
|
const clearance = sheet?.pinchDiameter ?? (sheet?.cornerRadius !== void 0 ? 2 * sheet.cornerRadius : void 0);
|
|
454
620
|
if (clearance !== void 0) {
|
|
455
621
|
const width = length(clearance);
|
|
@@ -479,6 +645,7 @@ var featureMeasurements = ({
|
|
|
479
645
|
});
|
|
480
646
|
}
|
|
481
647
|
}
|
|
648
|
+
if (sheet && undercut) rows.push(...undercutRows(sheet, undercut, top, length));
|
|
482
649
|
const surface = SURFACE_TYPES.has(feature.featureType);
|
|
483
650
|
if (surface && sheet) {
|
|
484
651
|
const diameter = sheet.fitToolDiameter;
|
|
@@ -544,7 +711,7 @@ var featureMeasurements = ({
|
|
|
544
711
|
});
|
|
545
712
|
}
|
|
546
713
|
}
|
|
547
|
-
const ld = featureLd(features, feature, sheets);
|
|
714
|
+
const ld = undercut ? null : featureLd(features, feature, sheets);
|
|
548
715
|
if (ld && top !== null && bottom !== void 0) {
|
|
549
716
|
const across = ld.basis === "bore" ? `D = facts.diameter = ${mm(ld.across)}` : ld.basis === "pinch" ? `D = widest tool (the tightest pinch disc, tolerance taken out) = ${mm(ld.across)}${ld.atLeast ? " (at most, so L/D is at least)" : ""}` : `D = widest tool = 2 \xD7 inside corner radius = ${mm(ld.across)}${ld.atLeast ? " (at most, so L/D is at least)" : ""} (no pinch points reported)`;
|
|
550
717
|
const ratioOf = (depth) => {
|
|
@@ -641,141 +808,173 @@ var featureMeasurements = ({
|
|
|
641
808
|
}
|
|
642
809
|
return rows.sort((a, b) => ROW_ORDER.indexOf(a.key) - ROW_ORDER.indexOf(b.key));
|
|
643
810
|
};
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
const leaves = (value) => typeof value === "object" && value !== null ? Object.values(value).flatMap(leaves) : [value];
|
|
715
|
-
const band = leaves(facts.cd);
|
|
716
|
-
return band.length > 0 && band.every((each) => {
|
|
717
|
-
const value = typeof each === "string" && each.trim() !== "" ? Number(each) : each;
|
|
718
|
-
return typeof value === "number" && Math.abs(value) < ZERO_MM;
|
|
811
|
+
var undercutRows = (sheet, undercut, top, length) => {
|
|
812
|
+
const rows = [];
|
|
813
|
+
const tslot = sheet.kind === "Tslot";
|
|
814
|
+
const { zMin, zMax } = sheet;
|
|
815
|
+
const { floorWidth: floor, topOpeningWidth: opening } = undercut;
|
|
816
|
+
const angle = undercut.taperDeg !== void 0 ? `${trim(undercut.taperDeg, 3)}\xB0` : void 0;
|
|
817
|
+
if (tslot && zMin !== void 0 && zMax !== void 0) {
|
|
818
|
+
rows.push({
|
|
819
|
+
key: "undercutWidth",
|
|
820
|
+
label: "Undercut width",
|
|
821
|
+
...length(zMax - zMin),
|
|
822
|
+
derivation: [
|
|
823
|
+
"The groove\u2019s height along the tool: the widest cutter it takes in one pass.",
|
|
824
|
+
`zMax \u2212 zMin = ${mm(zMax)} \u2212 ${mm(zMin)} = ${mm(zMax - zMin)}`
|
|
825
|
+
]
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
if (floor !== void 0) {
|
|
829
|
+
rows.push({
|
|
830
|
+
key: "floorWidth",
|
|
831
|
+
label: "Floor width",
|
|
832
|
+
...length(floor),
|
|
833
|
+
derivation: ["The widest clearance over the floor", `facts.floorWidth = ${mm(floor)}`]
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
if (opening !== void 0) {
|
|
837
|
+
rows.push({
|
|
838
|
+
key: "openingWidth",
|
|
839
|
+
label: "Opening width",
|
|
840
|
+
...length(opening),
|
|
841
|
+
derivation: [
|
|
842
|
+
"The clearance through the opening at the top of the groove",
|
|
843
|
+
`facts.topOpeningWidth = ${mm(opening)}`
|
|
844
|
+
]
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
let depth = undercut.undercutDepth;
|
|
848
|
+
let depthLine = depth !== void 0 ? `facts.undercutDepth = ${mm(depth)}` : "";
|
|
849
|
+
if (depth === void 0 && floor !== void 0 && opening !== void 0) {
|
|
850
|
+
if (floor > opening && !undercut.isExternal) {
|
|
851
|
+
depth = (floor - opening) / 2;
|
|
852
|
+
depthLine = `(facts.floorWidth \u2212 facts.topOpeningWidth) / 2 = (${mm(floor)} \u2212 ${mm(opening)}) / 2 = ${mm(depth)}`;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (depth !== void 0) {
|
|
856
|
+
rows.push({
|
|
857
|
+
key: "undercutDepth",
|
|
858
|
+
label: "Undercut depth",
|
|
859
|
+
...length(depth),
|
|
860
|
+
derivation: [
|
|
861
|
+
"How far the groove runs back from its opening: what the cutter\u2019s head reaches past its shaft.",
|
|
862
|
+
depthLine
|
|
863
|
+
]
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
if (angle) {
|
|
867
|
+
rows.push({
|
|
868
|
+
key: "taper",
|
|
869
|
+
label: "Dovetail angle",
|
|
870
|
+
value: `${angle} from the tool axis`,
|
|
871
|
+
derivation: [`facts.taperDeg = ${angle}, between the overhanging wall and the tool axis`]
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
const cutter = (value, derivation, alt) => ({
|
|
875
|
+
key: "undercutCutter",
|
|
876
|
+
label: "Max cutting diameter",
|
|
877
|
+
milling: true,
|
|
878
|
+
value,
|
|
879
|
+
...alt !== void 0 ? { alt } : {},
|
|
880
|
+
derivation
|
|
719
881
|
});
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
882
|
+
if (undercut.unmeasured) {
|
|
883
|
+
return [
|
|
884
|
+
...rows,
|
|
885
|
+
cutter("Not measured", [
|
|
886
|
+
"The Engine could not measure the groove (facts.isInvalidGeometry or facts.cd.measurementFailed), so its figures offer no cutter."
|
|
887
|
+
])
|
|
888
|
+
];
|
|
889
|
+
}
|
|
890
|
+
if (sheet.noToolFits) {
|
|
891
|
+
return [
|
|
892
|
+
...rows,
|
|
893
|
+
cutter("None fits", [
|
|
894
|
+
"facts.cd.ignore.max is next to nothing: no cutter fits anywhere in the groove."
|
|
895
|
+
])
|
|
896
|
+
];
|
|
897
|
+
}
|
|
898
|
+
const band = sheet.maxTool !== void 0 && sheet.maxTool > 0 ? sheet.maxTool : void 0;
|
|
899
|
+
const dropIn = undercut.isClosed ? undercut.maxEntry : void 0;
|
|
900
|
+
const head = band === void 0 ? void 0 : Math.min(band, dropIn ?? Infinity);
|
|
901
|
+
const opensTo = undercut.maxEntry ?? opening;
|
|
902
|
+
const behind = head !== void 0 && depth !== void 0 ? head - 2 * depth : void 0;
|
|
903
|
+
const limits = [opensTo, behind].filter((each) => each !== void 0);
|
|
904
|
+
const shaft = limits.length > 0 ? Math.max(Math.min(...limits), 0) : void 0;
|
|
905
|
+
const shaftLines = [
|
|
906
|
+
"The most the cutter\u2019s shaft can be: what comes down through the opening above the groove, and leaves the head reaching the back of it.",
|
|
907
|
+
...opensTo !== void 0 ? [`Opening: ${tslot ? "facts.maxEntryCd" : "facts.topOpeningWidth"} = ${mm(opensTo)}`] : [],
|
|
908
|
+
...behind !== void 0 && head !== void 0 && depth !== void 0 ? [
|
|
909
|
+
`Reach: max cutting diameter \u2212 2 \xD7 undercut depth = ${mm(head)} \u2212 2 \xD7 ${mm(depth)} = ${mm(behind)}`
|
|
910
|
+
] : [],
|
|
911
|
+
...shaft !== void 0 ? [`Max shaft = ${mm(shaft)}`] : []
|
|
912
|
+
];
|
|
913
|
+
if (head !== void 0 && band !== void 0) {
|
|
914
|
+
const headLines = [
|
|
915
|
+
"The biggest head that reaches every point of the groove.",
|
|
916
|
+
`facts.cd.ignore.min = ${mm(band)}`,
|
|
917
|
+
...dropIn !== void 0 ? [
|
|
918
|
+
`A closed slot: the head comes down through the opening above it, facts.maxEntryCd = ${mm(dropIn)}`
|
|
919
|
+
] : [],
|
|
920
|
+
`\u2300 = ${mm(head)}`
|
|
921
|
+
];
|
|
922
|
+
if (head < SHARP_MM || shaft !== void 0 && shaft < SHARP_MM) {
|
|
923
|
+
return [
|
|
924
|
+
...rows,
|
|
925
|
+
cutter("None fits", [
|
|
926
|
+
...headLines,
|
|
927
|
+
...shaft !== void 0 ? shaftLines : [],
|
|
928
|
+
"No head that fits the groove leaves room for a shaft to carry it."
|
|
929
|
+
])
|
|
930
|
+
];
|
|
931
|
+
}
|
|
932
|
+
const size = length(head);
|
|
933
|
+
const filletRadius = sheet.filletRadius !== void 0 && sheet.filletRadius > 0 ? sheet.filletRadius : void 0;
|
|
934
|
+
const fillet = filletRadius !== void 0 ? length(filletRadius) : void 0;
|
|
935
|
+
const describe = (unit2) => [`\u2300 ${size[unit2]}`, ...angle ? [angle] : [], ...fillet ? [`R ${fillet[unit2]}`] : []].join(
|
|
936
|
+
" \xB7 "
|
|
937
|
+
);
|
|
938
|
+
rows.push(
|
|
939
|
+
cutter(
|
|
940
|
+
describe("value"),
|
|
941
|
+
[
|
|
942
|
+
...headLines,
|
|
943
|
+
...angle ? [`Angle = facts.taperDeg = ${angle}, from the tool axis`] : [],
|
|
944
|
+
...filletRadius !== void 0 ? [`R = facts.filletRadius = ${mm(filletRadius)}, the blend the head\u2019s corners cut`] : []
|
|
945
|
+
],
|
|
946
|
+
describe("alt")
|
|
947
|
+
)
|
|
948
|
+
);
|
|
949
|
+
if (top !== null && zMin !== void 0) {
|
|
950
|
+
const reach = top - zMin;
|
|
951
|
+
const ratio = (reach / head).toFixed(3);
|
|
952
|
+
rows.push({
|
|
953
|
+
key: "topLd",
|
|
954
|
+
label: "L/D to top of part",
|
|
955
|
+
milling: true,
|
|
956
|
+
value: ratio,
|
|
957
|
+
derivation: [
|
|
958
|
+
`L = depth below top of part = ${mm(reach)}`,
|
|
959
|
+
`D = max cutting diameter = ${mm(head)}`,
|
|
960
|
+
`L / D = ${mm(reach)} / ${mm(head)} = ${ratio}`
|
|
961
|
+
]
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (shaft !== void 0 || tslot) {
|
|
966
|
+
const size = shaft !== void 0 ? length(shaft) : void 0;
|
|
967
|
+
rows.push({
|
|
968
|
+
key: "maxShaft",
|
|
969
|
+
label: "Max shaft diameter",
|
|
970
|
+
milling: true,
|
|
971
|
+
...size ? { value: `\u2300 ${size.value}`, alt: `\u2300 ${size.alt}` } : { value: "No limit" },
|
|
972
|
+
derivation: size ? shaftLines : [
|
|
973
|
+
"facts.maxEntryCd is not reported and the head or undercut depth is not known: nothing limits the shaft."
|
|
974
|
+
]
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
return rows;
|
|
779
978
|
};
|
|
780
979
|
|
|
781
980
|
// src/model/pinch-mark.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toolpath/dfm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Toolpath DFM rules: the rule list, the rules and the part checker, and the feature panel",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"tsup": "8.5.1",
|
|
65
65
|
"typescript": "5.9.3",
|
|
66
66
|
"vitest": "4.1.10",
|
|
67
|
-
"@toolpath/
|
|
68
|
-
"@toolpath/
|
|
67
|
+
"@toolpath/ui": "^1.0.0",
|
|
68
|
+
"@toolpath/api": "^0.7.0"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "tsup src/index.ts src/model/index.ts --format esm --dts --clean --external react --external react-dom --external @toolpath/ui",
|