@toolpath/dfm 0.1.0 → 0.2.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 +122 -6
- package/dfm.css +3 -2
- package/dist/{reach-CfmYU_yQ.d.ts → feature-record-BUoFFjN6.d.ts} +89 -1
- package/dist/index.d.ts +254 -3
- package/dist/index.js +875 -17
- package/dist/model/index.d.ts +62 -43
- package/dist/model/index.js +276 -4
- package/package.json +2 -2
package/dist/model/index.d.ts
CHANGED
|
@@ -1,57 +1,76 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { C as CRITICAL_COLOR,
|
|
1
|
+
import { e as DfmHit, U as Units, f as FeaturePinch, g as FeatureSheet, D as DfmRule } from '../feature-record-BUoFFjN6.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, M as Measurement, P as PINCH_TOOL_MM, p as PinchDisc, q as RULE_COLORS, r as RangeHigh, s as RangeLow, R as ReachCurve, t as ReportFeature, u as RuleResult, S as SurfaceToolKind, W as WARNING_COLOR, v as brokenRules, w as checkPart, x as compareColors, y as describeRule, z as dfmFeatures, A as featureColor, E as featureMeasurements, G as featureProfile, H as featureSheet, I as featureTypeLabel, J as isNoReading, K as isSurfaceType, L as makeRule, N as maxBallDiameter, O as parseRules, Q as peakHeight, T as readPinch, V as readReachCurve, X as runStart, Y as trimmedCurve, Z as withSurroundedFaces } from '../feature-record-BUoFFjN6.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;
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* How it was worked out from the datasheet, a line at a time: the fields it
|
|
17
|
-
* reads, by their path in the raw record, and the arithmetic, in the
|
|
18
|
-
* Engine's millimetres.
|
|
19
|
-
*/
|
|
20
|
-
readonly derivation: readonly string[];
|
|
7
|
+
/** Whether two depths are one depth, as the quoting app counts them: within a twentieth, or a hundredth of a mm. */
|
|
8
|
+
declare const sameDepth: (a: number, b: number) => boolean;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A feature's pinch discs as the widest tool that fits stands in them: the
|
|
12
|
+
* datasheet's discs a tool can stand in, in its tool frame, mm, with how deep
|
|
13
|
+
* the feature runs and the corner the tool has.
|
|
14
|
+
*/
|
|
15
|
+
interface PinchMark extends FeaturePinch {
|
|
21
16
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* these under their own heading, for every kind of feature.
|
|
17
|
+
* The radius on the tool's corner, mm: a filleted pocket's floor fillet,
|
|
18
|
+
* which a bull nose of that radius cuts. Absent for a flat-bottomed tool.
|
|
25
19
|
*/
|
|
26
|
-
readonly
|
|
27
|
-
/** A row that stands for a tool the viewer can draw on the part, by its kind. */
|
|
28
|
-
readonly tool?: SurfaceToolKind;
|
|
20
|
+
readonly corner?: number;
|
|
29
21
|
}
|
|
30
|
-
/** Which of a surface's finishing tools: a ball nose, or a bull nose with a flat between its corners. */
|
|
31
|
-
type SurfaceToolKind = 'ball' | 'bull';
|
|
32
|
-
/** Whether a feature type is read as a surface: see `SURFACE_TYPES`. */
|
|
33
|
-
declare const isSurfaceType: (featureType: string) => boolean;
|
|
34
22
|
/**
|
|
35
|
-
* The
|
|
36
|
-
*
|
|
37
|
-
*
|
|
23
|
+
* The pinch discs worth drawing a tool in: none for a feature with no discs,
|
|
24
|
+
* only a sharp corner's (no tool at all), or tools wider than
|
|
25
|
+
* {@link MAX_DRAWN_TOOL_MM}. A walled feature's floor fillet rounds the tool's
|
|
26
|
+
* corner; a surface's fillet radius is its own shape, and does not.
|
|
38
27
|
*/
|
|
39
|
-
declare const
|
|
28
|
+
declare const pinchMark: (datasheet: unknown, sheet: FeatureSheet | undefined, featureType: string) => PinchMark | null;
|
|
40
29
|
/**
|
|
41
|
-
*
|
|
42
|
-
* where
|
|
43
|
-
*
|
|
44
|
-
* field this kind never reports reads as a measurement that failed.
|
|
30
|
+
* What a pinch mark's tool is labelled: its diameter at the tightest disc —
|
|
31
|
+
* "≤" where the clearance was measured at one tolerance only — and, quieter,
|
|
32
|
+
* the kind of tool and how many places are that tight.
|
|
45
33
|
*/
|
|
46
|
-
declare const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
units: Units;
|
|
51
|
-
}) => Measurement[];
|
|
34
|
+
declare const pinchLabel: (mark: PinchMark, units: Units) => {
|
|
35
|
+
label: string;
|
|
36
|
+
note: string;
|
|
37
|
+
};
|
|
52
38
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
39
|
+
/** A direction or a point. */
|
|
40
|
+
interface Vec3 {
|
|
41
|
+
readonly x: number;
|
|
42
|
+
readonly y: number;
|
|
43
|
+
readonly z: number;
|
|
44
|
+
}
|
|
45
|
+
/** A feature's faces as triangles: nine numbers each for the corners, three for the outward normal. */
|
|
46
|
+
interface FaceTriangles {
|
|
47
|
+
readonly positions: ArrayLike<number>;
|
|
48
|
+
readonly normals: ArrayLike<number>;
|
|
49
|
+
}
|
|
50
|
+
/** The widest tool a feature admits, placed on the part: what to draw, in the drawn coordinates. */
|
|
51
|
+
interface PlacedTool {
|
|
52
|
+
/** The centre of the tool's bottom, on the feature's floor. */
|
|
53
|
+
readonly base: Vec3;
|
|
54
|
+
/** Up the tool. */
|
|
55
|
+
readonly axis: Vec3;
|
|
56
|
+
readonly diameter: number;
|
|
57
|
+
readonly height: number;
|
|
58
|
+
/** The radius on its corner: a filleted floor's, no more than the tool's own radius. */
|
|
59
|
+
readonly cornerRadius: number;
|
|
60
|
+
/** Across the tool, toward where it touches the wall. */
|
|
61
|
+
readonly across: Vec3;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The widest tool a feature admits, standing on its tightest pinch disc, ready
|
|
65
|
+
* to draw: the datasheet's frame found against the feature's faces, the disc
|
|
66
|
+
* placed in it, and the tool turned toward the wall that pinches it.
|
|
67
|
+
*
|
|
68
|
+
* `origin` is where the CAD file's own zero sits in the coordinates the part
|
|
69
|
+
* is drawn in — moved by whatever centred it — since a datasheet is in the
|
|
70
|
+
* file's coordinates and the triangles and the answer are in the drawn ones.
|
|
71
|
+
* Null for a feature with no faces or no discs.
|
|
72
|
+
*/
|
|
73
|
+
declare const placePinchTool: (mark: PinchMark, triangles: FaceTriangles, direction: Vec3, origin?: Vec3) => PlacedTool | null;
|
|
55
74
|
|
|
56
75
|
/**
|
|
57
76
|
* A set of DFM rules as a file: what Export writes, Import reads, and what
|
|
@@ -106,4 +125,4 @@ declare const parseRuleSetFile: (text: string) => {
|
|
|
106
125
|
*/
|
|
107
126
|
declare const llmRulePrompt: (current?: readonly DfmRule[]) => string;
|
|
108
127
|
|
|
109
|
-
export {
|
|
128
|
+
export { DfmHit, DfmRule, FILE_KIND, type FaceTriangles, FeaturePinch, FeatureSheet, type PinchMark, type PlacedTool, type RuleSetFile, Units, type Vec3, hitFigure, llmRulePrompt, parseRuleSetFile, pinchLabel, pinchMark, placePinchTool, sameDepth, serializeRuleSet };
|
package/dist/model/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
featureTypeLabel,
|
|
15
15
|
hitFigure,
|
|
16
16
|
isDrilled,
|
|
17
|
+
lengthValue,
|
|
17
18
|
llmRulePrompt,
|
|
18
19
|
makeRule,
|
|
19
20
|
metricInfo,
|
|
@@ -26,9 +27,39 @@ import {
|
|
|
26
27
|
sameDepth,
|
|
27
28
|
serializeRuleSet,
|
|
28
29
|
trimmedCurve,
|
|
30
|
+
unitLabel,
|
|
29
31
|
worseUpward
|
|
30
32
|
} from "../chunk-5MPHT7BC.js";
|
|
31
33
|
|
|
34
|
+
// src/model/broken-rules.ts
|
|
35
|
+
var byColor = (hits) => [...hits].sort((a, b) => compareColors(a.rule.color, b.rule.color));
|
|
36
|
+
var worstHits = (hits) => {
|
|
37
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38
|
+
return byColor(hits).filter((hit) => {
|
|
39
|
+
if (seen.has(hit.metric.key)) return false;
|
|
40
|
+
seen.add(hit.metric.key);
|
|
41
|
+
return true;
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var brokenRules = (check, tag, units) => worstHits(check.hitsByTag.get(tag.toLowerCase()) ?? []).map((hit) => {
|
|
45
|
+
const figure = hitFigure(hit, units);
|
|
46
|
+
return {
|
|
47
|
+
key: hit.rule.id,
|
|
48
|
+
color: hit.rule.color,
|
|
49
|
+
text: describeRule(hit.rule, units),
|
|
50
|
+
...figure ? { figure } : {}
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
var featureColor = (check, tag, region = null) => {
|
|
54
|
+
const key = tag.toLowerCase();
|
|
55
|
+
const face = region === null ? void 0 : check.paintedFaces.get(region);
|
|
56
|
+
if (face && face.tag.toLowerCase() === key) return face.color;
|
|
57
|
+
const whole = check.paintedWhole.get(key);
|
|
58
|
+
if (whole) return whole.color;
|
|
59
|
+
if (!check.required.has(key)) return void 0;
|
|
60
|
+
return byColor(check.hitsByTag.get(key) ?? [])[0]?.rule.color;
|
|
61
|
+
};
|
|
62
|
+
|
|
32
63
|
// src/model/dfm-flags.ts
|
|
33
64
|
var howCut = (sheet) => {
|
|
34
65
|
const drilled = isDrilled(sheet);
|
|
@@ -329,7 +360,7 @@ var clearanceDerivation = (fit, clearance) => {
|
|
|
329
360
|
return lines;
|
|
330
361
|
};
|
|
331
362
|
var trim = (value, places) => String(Number(value.toFixed(places)));
|
|
332
|
-
var formatLength = (mm2, units) => units === "inch" ? `${trim(mm2 / MM_PER_INCH, LENGTH_PLACES.inch)} in` : `${trim(mm2, LENGTH_PLACES.mm)} mm`;
|
|
363
|
+
var formatLength = (mm2, units, inchMark = false) => units === "inch" ? `${trim(mm2 / MM_PER_INCH, LENGTH_PLACES.inch)}${inchMark ? '"' : " in"}` : `${trim(mm2, LENGTH_PLACES.mm)} mm`;
|
|
333
364
|
var SURFACE_TYPES = /* @__PURE__ */ new Set([
|
|
334
365
|
"contour_surface",
|
|
335
366
|
"inner_fillet",
|
|
@@ -365,10 +396,14 @@ var featureMeasurements = ({
|
|
|
365
396
|
features,
|
|
366
397
|
feature,
|
|
367
398
|
sheets,
|
|
368
|
-
units
|
|
399
|
+
units,
|
|
400
|
+
inchMark = false
|
|
369
401
|
}) => {
|
|
370
402
|
const other = units === "inch" ? "mm" : "inch";
|
|
371
|
-
const length = (mm2) => ({
|
|
403
|
+
const length = (mm2) => ({
|
|
404
|
+
value: formatLength(mm2, units, inchMark),
|
|
405
|
+
alt: formatLength(mm2, other, inchMark)
|
|
406
|
+
});
|
|
372
407
|
const sheet = sheets[feature.tag.toLowerCase()];
|
|
373
408
|
const rows = [];
|
|
374
409
|
const top = sheet ? partTop(features, sheets, feature.machiningDirection) : null;
|
|
@@ -570,7 +605,7 @@ var featureMeasurements = ({
|
|
|
570
605
|
rows.push({
|
|
571
606
|
key: "thread",
|
|
572
607
|
label: "Thread",
|
|
573
|
-
value: `${formatLength(sheet.threading.basicDiameter, units)} \xD7 ${formatLength(sheet.threading.threadPitch, units)} pitch`,
|
|
608
|
+
value: `${formatLength(sheet.threading.basicDiameter, units, inchMark)} \xD7 ${formatLength(sheet.threading.threadPitch, units, inchMark)} pitch`,
|
|
574
609
|
derivation: [
|
|
575
610
|
`facts.threading.spec.basicDiameter = ${mm(sheet.threading.basicDiameter)}`,
|
|
576
611
|
`facts.threading.spec.threadPitch = ${mm(sheet.threading.threadPitch)}`
|
|
@@ -742,16 +777,250 @@ var featureSheet = (datasheet) => {
|
|
|
742
777
|
...threading ? { threading } : {}
|
|
743
778
|
};
|
|
744
779
|
};
|
|
780
|
+
|
|
781
|
+
// src/model/pinch-mark.ts
|
|
782
|
+
import { MM_PER_INCH as MM_PER_INCH2 } from "@toolpath/tool-support";
|
|
783
|
+
var MAX_DRAWN_TOOL_MM = 3 * MM_PER_INCH2;
|
|
784
|
+
var pinchMark = (datasheet, sheet, featureType) => {
|
|
785
|
+
const pinch = readPinch(datasheet);
|
|
786
|
+
const discs = pinch?.discs.filter(
|
|
787
|
+
(disc) => disc.diameter >= PINCH_TOOL_MM && disc.diameter <= MAX_DRAWN_TOOL_MM
|
|
788
|
+
) ?? [];
|
|
789
|
+
if (!pinch || discs.length === 0) return null;
|
|
790
|
+
const fillet = sheet?.filletRadius;
|
|
791
|
+
const corner = !isSurfaceType(featureType) && fillet !== void 0 && fillet > 0 ? fillet : void 0;
|
|
792
|
+
return { ...pinch, discs, ...corner !== void 0 ? { corner } : {} };
|
|
793
|
+
};
|
|
794
|
+
var pinchLabel = (mark, units) => {
|
|
795
|
+
const length = (mm2) => `${lengthValue(mm2, units)} ${unitLabel(units)}`;
|
|
796
|
+
const tightest = Math.min(...mark.discs.map((disc) => disc.diameter));
|
|
797
|
+
const tool = mark.corner !== void 0 ? `bull nose R ${length(mark.corner)}` : "widest tool";
|
|
798
|
+
const places = mark.discs.length;
|
|
799
|
+
return {
|
|
800
|
+
label: `${mark.unresolved ? "\u2264 " : ""}\u2300 ${length(tightest)}`,
|
|
801
|
+
note: places > 1 ? `${tool} \xB7 ${places} places` : tool
|
|
802
|
+
};
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
// src/model/tool-frame.ts
|
|
806
|
+
var extent = (values) => {
|
|
807
|
+
let low = Infinity;
|
|
808
|
+
let high = -Infinity;
|
|
809
|
+
for (const value of values) {
|
|
810
|
+
if (value < low) low = value;
|
|
811
|
+
if (value > high) high = value;
|
|
812
|
+
}
|
|
813
|
+
return { low, high };
|
|
814
|
+
};
|
|
815
|
+
var dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
816
|
+
var cross = (a, b) => [
|
|
817
|
+
a[1] * b[2] - a[2] * b[1],
|
|
818
|
+
a[2] * b[0] - a[0] * b[2],
|
|
819
|
+
a[0] * b[1] - a[1] * b[0]
|
|
820
|
+
];
|
|
821
|
+
var scale = (a, by) => [a[0] * by, a[1] * by, a[2] * by];
|
|
822
|
+
var add = (a, b) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
823
|
+
var unit = (a) => scale(a, 1 / Math.hypot(a[0], a[1], a[2]));
|
|
824
|
+
var ON_DEPTH_MM = 0.05;
|
|
825
|
+
var ON_CORNER_MM = 0.02;
|
|
826
|
+
var POINT_DISC_MM = 0.01;
|
|
827
|
+
var ON_WALL_MM = 0.1;
|
|
828
|
+
var ON_WALL_SHARE = 0.02;
|
|
829
|
+
var WALL_NORMAL_ALONG = 0.7;
|
|
830
|
+
var SAME_PLACE_MM = 0.05;
|
|
831
|
+
var MIN_HEIGHT_MM = 0.05;
|
|
832
|
+
var readTriangles = ({ positions, normals }, origin) => {
|
|
833
|
+
const triangles = [];
|
|
834
|
+
for (let at2 = 0; at2 + 8 < positions.length; at2 += 9) {
|
|
835
|
+
const corner = (k) => [
|
|
836
|
+
(positions[at2 + k] ?? 0) - origin[0],
|
|
837
|
+
(positions[at2 + k + 1] ?? 0) - origin[1],
|
|
838
|
+
(positions[at2 + k + 2] ?? 0) - origin[2]
|
|
839
|
+
];
|
|
840
|
+
const n = at2 / 9 * 3;
|
|
841
|
+
triangles.push({
|
|
842
|
+
a: corner(0),
|
|
843
|
+
b: corner(3),
|
|
844
|
+
c: corner(6),
|
|
845
|
+
normal: [normals[n] ?? 0, normals[n + 1] ?? 0, normals[n + 2] ?? 0]
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
return triangles;
|
|
849
|
+
};
|
|
850
|
+
var pointsOf = (triangles) => triangles.flatMap(({ a, b, c }) => [a, b, c]);
|
|
851
|
+
var flatten = (point, { u, v }) => ({ x: dot(point, u), y: dot(point, v) });
|
|
852
|
+
var wallSegments = (triangles, frame) => triangles.filter(({ normal }) => Math.abs(dot(normal, frame.w)) < WALL_NORMAL_ALONG).flatMap(({ a, b, c, normal }) => {
|
|
853
|
+
const [fa, fb, fc] = [flatten(a, frame), flatten(b, frame), flatten(c, frame)];
|
|
854
|
+
const air = flatten(normal, frame);
|
|
855
|
+
return [
|
|
856
|
+
{ p: fa, q: fb, air },
|
|
857
|
+
{ p: fb, q: fc, air },
|
|
858
|
+
{ p: fc, q: fa, air }
|
|
859
|
+
];
|
|
860
|
+
});
|
|
861
|
+
var nearestOn = ({ p, q }, at2) => {
|
|
862
|
+
const dx = q.x - p.x;
|
|
863
|
+
const dy = q.y - p.y;
|
|
864
|
+
const length = dx * dx + dy * dy;
|
|
865
|
+
const t = length === 0 ? 0 : Math.min(1, Math.max(0, ((at2.x - p.x) * dx + (at2.y - p.y) * dy) / length));
|
|
866
|
+
return { x: p.x + t * dx, y: p.y + t * dy };
|
|
867
|
+
};
|
|
868
|
+
var nearestWall = (segments, at2) => segments.reduce((best, segment) => {
|
|
869
|
+
const point = nearestOn(segment, at2);
|
|
870
|
+
const distance2 = Math.hypot(point.x - at2.x, point.y - at2.y);
|
|
871
|
+
if (best && distance2 >= best.distance) return best;
|
|
872
|
+
const open = (at2.x - point.x) * segment.air.x + (at2.y - point.y) * segment.air.y >= 0;
|
|
873
|
+
return { point, distance: distance2, open };
|
|
874
|
+
}, null);
|
|
875
|
+
var fitsDiscs = (triangles, frame, discs) => {
|
|
876
|
+
const points = discs.filter((disc) => disc.diameter < POINT_DISC_MM);
|
|
877
|
+
const wide = discs.filter((disc) => disc.diameter >= POINT_DISC_MM);
|
|
878
|
+
if (points.length > 0) {
|
|
879
|
+
const flat = pointsOf(triangles).map((point) => flatten(point, frame));
|
|
880
|
+
const onCorners = points.every(
|
|
881
|
+
(disc) => flat.some((point) => Math.hypot(point.x - disc.x, point.y - disc.y) <= ON_CORNER_MM)
|
|
882
|
+
);
|
|
883
|
+
if (!onCorners) return false;
|
|
884
|
+
}
|
|
885
|
+
if (wide.length === 0) return points.length > 0;
|
|
886
|
+
const segments = wallSegments(triangles, frame);
|
|
887
|
+
if (segments.length === 0) return false;
|
|
888
|
+
let touches = false;
|
|
889
|
+
for (const disc of wide) {
|
|
890
|
+
const nearest = nearestWall(segments, disc);
|
|
891
|
+
if (!nearest || !nearest.open) return false;
|
|
892
|
+
const slack = ON_WALL_MM + ON_WALL_SHARE * disc.diameter;
|
|
893
|
+
const radius = disc.diameter / 2;
|
|
894
|
+
if (nearest.distance < radius - slack) return false;
|
|
895
|
+
if (nearest.distance <= radius + slack) touches = true;
|
|
896
|
+
}
|
|
897
|
+
return touches;
|
|
898
|
+
};
|
|
899
|
+
var placeInFrame = ({ u, v, w }, x, y, z) => add(add(scale(u, x), scale(v, y)), scale(w, z));
|
|
900
|
+
var AXES = [
|
|
901
|
+
[1, 0, 0],
|
|
902
|
+
[0, 1, 0],
|
|
903
|
+
[0, 0, 1]
|
|
904
|
+
];
|
|
905
|
+
var candidateFrames = (points, zMin, zMax, direction, strict = true) => {
|
|
906
|
+
const along = unit(direction);
|
|
907
|
+
const frames = [];
|
|
908
|
+
for (const sign of [1, -1]) {
|
|
909
|
+
const w = scale(along, sign);
|
|
910
|
+
const { low, high } = extent(points.map((point) => dot(point, w)));
|
|
911
|
+
if (strict && (Math.abs(low - zMin) > ON_DEPTH_MM || Math.abs(high - zMax) > ON_DEPTH_MM)) {
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
const across = AXES.reduce(
|
|
915
|
+
(best, axis) => Math.abs(dot(axis, w)) < Math.abs(dot(best, w)) ? axis : best
|
|
916
|
+
);
|
|
917
|
+
const u0 = unit(add(across, scale(w, -dot(across, w))));
|
|
918
|
+
const v0 = cross(w, u0);
|
|
919
|
+
for (let quarter = 0; quarter < 4; quarter++) {
|
|
920
|
+
const angle = quarter * Math.PI / 2;
|
|
921
|
+
const u = add(scale(u0, Math.cos(angle)), scale(v0, Math.sin(angle)));
|
|
922
|
+
for (const hand of [1, -1]) frames.push({ u, v: scale(cross(w, u), hand), w });
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return frames;
|
|
926
|
+
};
|
|
927
|
+
var distance = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
928
|
+
var exactFrame = (triangles, discs, zMin, zMax, direction) => {
|
|
929
|
+
const fits = candidateFrames(pointsOf(triangles), zMin, zMax, direction).filter(
|
|
930
|
+
(frame) => fitsDiscs(triangles, frame, discs)
|
|
931
|
+
);
|
|
932
|
+
const [first] = fits;
|
|
933
|
+
if (!first) return null;
|
|
934
|
+
const placed = (frame) => discs.map((disc) => placeInFrame(frame, disc.x, disc.y, zMin));
|
|
935
|
+
const firstPlaces = placed(first);
|
|
936
|
+
const agree = fits.every(
|
|
937
|
+
(frame) => placed(frame).every((at2, index) => distance(at2, firstPlaces[index] ?? at2) <= SAME_PLACE_MM)
|
|
938
|
+
);
|
|
939
|
+
return agree ? first : null;
|
|
940
|
+
};
|
|
941
|
+
var discMiss = (triangles, frame, discs) => {
|
|
942
|
+
let miss = 0;
|
|
943
|
+
const points = discs.filter((disc) => disc.diameter < POINT_DISC_MM);
|
|
944
|
+
if (points.length > 0) {
|
|
945
|
+
const flat = pointsOf(triangles).map((point) => flatten(point, frame));
|
|
946
|
+
for (const disc of points) {
|
|
947
|
+
miss += extent(flat.map((point) => Math.hypot(point.x - disc.x, point.y - disc.y))).low;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const wide = discs.filter((disc) => disc.diameter >= POINT_DISC_MM);
|
|
951
|
+
if (wide.length === 0) return miss;
|
|
952
|
+
const segments = wallSegments(triangles, frame);
|
|
953
|
+
let touch = Infinity;
|
|
954
|
+
for (const disc of wide) {
|
|
955
|
+
const radius = disc.diameter / 2;
|
|
956
|
+
const nearest = nearestWall(segments, disc);
|
|
957
|
+
if (!nearest) return Infinity;
|
|
958
|
+
if (!nearest.open) miss += disc.diameter * 4;
|
|
959
|
+
miss += Math.max(0, radius - nearest.distance);
|
|
960
|
+
touch = Math.min(touch, Math.abs(nearest.distance - radius));
|
|
961
|
+
}
|
|
962
|
+
return miss + touch;
|
|
963
|
+
};
|
|
964
|
+
var bestFrame = (triangles, discs, zMin, zMax, direction) => {
|
|
965
|
+
if (triangles.length === 0 || discs.length === 0) return null;
|
|
966
|
+
const exact = exactFrame(triangles, discs, zMin, zMax, direction);
|
|
967
|
+
if (exact) return { frame: exact, lift: 0 };
|
|
968
|
+
const points = pointsOf(triangles);
|
|
969
|
+
const fitting = candidateFrames(points, zMin, zMax, direction);
|
|
970
|
+
const frames = fitting.length > 0 ? fitting : candidateFrames(points, zMin, zMax, direction, false);
|
|
971
|
+
const best = frames.reduce((was, frame) => {
|
|
972
|
+
const miss = discMiss(triangles, frame, discs);
|
|
973
|
+
return !was || miss < was.miss ? { frame, miss } : was;
|
|
974
|
+
}, null);
|
|
975
|
+
if (!best) return null;
|
|
976
|
+
const { low } = extent(points.map((point) => dot(point, best.frame.w)));
|
|
977
|
+
return { frame: best.frame, lift: fitting.length > 0 ? 0 : low - zMin };
|
|
978
|
+
};
|
|
979
|
+
var asVec = ([x, y, z]) => ({ x, y, z });
|
|
980
|
+
var placePinchTool = (mark, triangles, direction, origin = { x: 0, y: 0, z: 0 }) => {
|
|
981
|
+
const shift = [origin.x, origin.y, origin.z];
|
|
982
|
+
const faces = readTriangles(triangles, shift);
|
|
983
|
+
const found = bestFrame(faces, mark.discs, mark.zMin, mark.zMax, [
|
|
984
|
+
direction.x,
|
|
985
|
+
direction.y,
|
|
986
|
+
direction.z
|
|
987
|
+
]);
|
|
988
|
+
const disc = mark.discs.reduce(
|
|
989
|
+
(tightest, each) => !tightest || each.diameter < tightest.diameter ? each : tightest,
|
|
990
|
+
null
|
|
991
|
+
);
|
|
992
|
+
if (!found || !disc) return null;
|
|
993
|
+
const { frame, lift } = found;
|
|
994
|
+
const radius = disc.diameter / 2;
|
|
995
|
+
const base = add(placeInFrame(frame, disc.x, disc.y, mark.zMin + lift), shift);
|
|
996
|
+
const contact = nearestWall(wallSegments(faces, frame), disc)?.point;
|
|
997
|
+
const toward = contact ? { x: contact.x - disc.x, y: contact.y - disc.y } : { x: 1, y: 0 };
|
|
998
|
+
const length = Math.hypot(toward.x, toward.y);
|
|
999
|
+
const across = add(
|
|
1000
|
+
scale(frame.u, length > 1e-9 ? toward.x / length : 1),
|
|
1001
|
+
scale(frame.v, length > 1e-9 ? toward.y / length : 0)
|
|
1002
|
+
);
|
|
1003
|
+
return {
|
|
1004
|
+
base: asVec(base),
|
|
1005
|
+
axis: asVec(frame.w),
|
|
1006
|
+
diameter: disc.diameter,
|
|
1007
|
+
height: Math.max(mark.zMax - mark.zMin, MIN_HEIGHT_MM),
|
|
1008
|
+
cornerRadius: Math.min(Math.max(mark.corner ?? 0, 0), radius),
|
|
1009
|
+
across: asVec(across)
|
|
1010
|
+
};
|
|
1011
|
+
};
|
|
745
1012
|
export {
|
|
746
1013
|
CRITICAL_COLOR,
|
|
747
1014
|
FILE_KIND,
|
|
748
1015
|
PINCH_TOOL_MM,
|
|
749
1016
|
RULE_COLORS,
|
|
750
1017
|
WARNING_COLOR,
|
|
1018
|
+
brokenRules,
|
|
751
1019
|
checkPart,
|
|
752
1020
|
compareColors,
|
|
753
1021
|
describeRule,
|
|
754
1022
|
dfmFeatures,
|
|
1023
|
+
featureColor,
|
|
755
1024
|
featureMeasurements,
|
|
756
1025
|
featureProfile,
|
|
757
1026
|
featureSheet,
|
|
@@ -765,6 +1034,9 @@ export {
|
|
|
765
1034
|
parseRuleSetFile,
|
|
766
1035
|
parseRules,
|
|
767
1036
|
peakHeight,
|
|
1037
|
+
pinchLabel,
|
|
1038
|
+
pinchMark,
|
|
1039
|
+
placePinchTool,
|
|
768
1040
|
readPinch,
|
|
769
1041
|
readReachCurve,
|
|
770
1042
|
runStart,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toolpath/dfm",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Toolpath DFM rules: the rule list
|
|
3
|
+
"version": "0.2.0",
|
|
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": {
|
|
7
7
|
"node": ">=20"
|