@toolpath/dfm 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +97 -2
- package/dfm.css +9 -0
- package/dist/chunk-5MPHT7BC.js +1599 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +1628 -0
- package/dist/model/index.d.ts +109 -0
- package/dist/model/index.js +775 -0
- package/dist/reach-CfmYU_yQ.d.ts +415 -0
- package/package.json +72 -3
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CRITICAL_COLOR,
|
|
3
|
+
FILE_KIND,
|
|
4
|
+
LENGTH_PLACES,
|
|
5
|
+
RULE_COLORS,
|
|
6
|
+
TYPE_SUBJECT,
|
|
7
|
+
WARNING_COLOR,
|
|
8
|
+
breaks,
|
|
9
|
+
compareColors,
|
|
10
|
+
describeRule,
|
|
11
|
+
dfmFeatures,
|
|
12
|
+
featureLd,
|
|
13
|
+
featureProfile,
|
|
14
|
+
featureTypeLabel,
|
|
15
|
+
hitFigure,
|
|
16
|
+
isDrilled,
|
|
17
|
+
llmRulePrompt,
|
|
18
|
+
makeRule,
|
|
19
|
+
metricInfo,
|
|
20
|
+
parseRuleSetFile,
|
|
21
|
+
parseRules,
|
|
22
|
+
partTop,
|
|
23
|
+
peakHeight,
|
|
24
|
+
readReachCurve,
|
|
25
|
+
runStart,
|
|
26
|
+
sameDepth,
|
|
27
|
+
serializeRuleSet,
|
|
28
|
+
trimmedCurve,
|
|
29
|
+
worseUpward
|
|
30
|
+
} from "../chunk-5MPHT7BC.js";
|
|
31
|
+
|
|
32
|
+
// src/model/dfm-flags.ts
|
|
33
|
+
var howCut = (sheet) => {
|
|
34
|
+
const drilled = isDrilled(sheet);
|
|
35
|
+
return { drilled, milled: !drilled };
|
|
36
|
+
};
|
|
37
|
+
var featureIs = (subject, featureType, sheet) => {
|
|
38
|
+
const type = featureType.toLowerCase();
|
|
39
|
+
const kind = sheet?.kind?.toLowerCase() ?? "";
|
|
40
|
+
if (subject.startsWith(TYPE_SUBJECT)) return type === subject.slice(TYPE_SUBJECT.length);
|
|
41
|
+
switch (subject) {
|
|
42
|
+
case "any":
|
|
43
|
+
return true;
|
|
44
|
+
case "milled":
|
|
45
|
+
return howCut(sheet).milled;
|
|
46
|
+
case "drilled":
|
|
47
|
+
return howCut(sheet).drilled;
|
|
48
|
+
case "hole":
|
|
49
|
+
return type.includes("hole") || kind === "hole";
|
|
50
|
+
case "threadedHole":
|
|
51
|
+
return sheet?.threading !== void 0;
|
|
52
|
+
case "pocket":
|
|
53
|
+
return type.includes("pocket") || kind === "pocket";
|
|
54
|
+
case "slot":
|
|
55
|
+
return type.includes("slot") || kind === "tslot";
|
|
56
|
+
case "undercut":
|
|
57
|
+
return kind === "tslot" || type.includes("undercut") || type.includes("tslot");
|
|
58
|
+
case "contour":
|
|
59
|
+
return type.includes("contour") || kind.includes("contour");
|
|
60
|
+
case "wall":
|
|
61
|
+
return type.includes("wall") || kind === "wall";
|
|
62
|
+
case "face":
|
|
63
|
+
return type === "face" || kind === "face";
|
|
64
|
+
case "fillet":
|
|
65
|
+
return type.split("_").includes("fillet") || kind.includes("fillet");
|
|
66
|
+
case "chamfer":
|
|
67
|
+
return type.includes("chamfer") || kind === "chamfer";
|
|
68
|
+
case "boss":
|
|
69
|
+
return type.includes("boss") || kind === "boss";
|
|
70
|
+
case "countersink":
|
|
71
|
+
return type === "sink";
|
|
72
|
+
case "profile":
|
|
73
|
+
return type === "profile" || kind === "profile";
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var at = (record, path) => path.split(".").reduce(
|
|
79
|
+
(value, key) => typeof value === "object" && value !== null ? value[key] : void 0,
|
|
80
|
+
record
|
|
81
|
+
);
|
|
82
|
+
var readPath = (datasheet, metric) => {
|
|
83
|
+
for (const path of metric.path ?? []) {
|
|
84
|
+
const value = at(datasheet, path);
|
|
85
|
+
if (value === void 0 || value === null) continue;
|
|
86
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
87
|
+
if (metric.quantity === "flag") return 1;
|
|
88
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return metric.quantity === "flag" && metric.path ? 0 : null;
|
|
92
|
+
};
|
|
93
|
+
var TILT = 1e-6;
|
|
94
|
+
var isTilted = ({ x, y, z }) => [x, y, z].filter((each) => Math.abs(each) > TILT).length > 1;
|
|
95
|
+
var featureMeasure = (metric, features, feature, { sheets, datasheets, regionAreas }) => {
|
|
96
|
+
const tag = feature.tag.toLowerCase();
|
|
97
|
+
const sheet = sheets[tag];
|
|
98
|
+
const datasheet = datasheets[tag];
|
|
99
|
+
const positive = (value) => value !== void 0 && value > 0 ? { value } : null;
|
|
100
|
+
switch (metric.key) {
|
|
101
|
+
case "present":
|
|
102
|
+
return { value: 1 };
|
|
103
|
+
case "tilted":
|
|
104
|
+
return { value: isTilted(feature.machiningDirection) ? 1 : 0 };
|
|
105
|
+
case "faceCount":
|
|
106
|
+
return { value: new Set(feature.regionIdxs).size };
|
|
107
|
+
case "ld": {
|
|
108
|
+
const ld = featureLd(features, feature, sheets);
|
|
109
|
+
return ld ? { value: ld.ratio, drilling: ld.drilling } : null;
|
|
110
|
+
}
|
|
111
|
+
case "featureLd": {
|
|
112
|
+
const ld = featureLd(features, feature, sheets);
|
|
113
|
+
return ld?.featureRatio !== void 0 ? { value: ld.featureRatio, drilling: ld.drilling } : null;
|
|
114
|
+
}
|
|
115
|
+
case "depthBelowTop": {
|
|
116
|
+
const top = partTop(features, sheets, feature.machiningDirection);
|
|
117
|
+
return top !== null && sheet?.zMin !== void 0 ? { value: top - sheet.zMin } : null;
|
|
118
|
+
}
|
|
119
|
+
case "featureDepth":
|
|
120
|
+
return sheet?.zMax !== void 0 && sheet.zMin !== void 0 ? { value: sheet.zMax - sheet.zMin } : null;
|
|
121
|
+
// Zero is no measure at all for these: no bore, no tool, a sharp floor rather than a tiny fillet.
|
|
122
|
+
case "diameter":
|
|
123
|
+
return positive(sheet?.diameter);
|
|
124
|
+
// Zero is a measure here: a sharp corner, which a "radius ≤" rule is most of all about.
|
|
125
|
+
case "cornerRadius":
|
|
126
|
+
return sheet?.cornerRadius === void 0 ? null : { value: sheet.cornerRadius };
|
|
127
|
+
case "maxTool":
|
|
128
|
+
return positive(sheet?.maxTool);
|
|
129
|
+
case "floorFillet":
|
|
130
|
+
return positive(sheet?.filletRadius);
|
|
131
|
+
case "sharpCorner":
|
|
132
|
+
return sheet ? { value: sheet.sharpCorners ? 1 : 0 } : null;
|
|
133
|
+
case "noToolFits":
|
|
134
|
+
return sheet ? { value: sheet.noToolFits ? 1 : 0 } : null;
|
|
135
|
+
case "pinchPoints": {
|
|
136
|
+
const points = at(datasheet, "pinchPoints");
|
|
137
|
+
return Array.isArray(points) ? { value: points.length } : null;
|
|
138
|
+
}
|
|
139
|
+
// A disc under a hundredth is a sharp corner, which `sharpCorner` is for: see `pinchDiameter`.
|
|
140
|
+
case "pinchDiameter":
|
|
141
|
+
return positive(sheet?.pinchDiameter);
|
|
142
|
+
case "reachHeight": {
|
|
143
|
+
const heights = at(datasheet, "reachCurve.verticalOffset");
|
|
144
|
+
const numbers = Array.isArray(heights) ? heights.filter(
|
|
145
|
+
(each) => typeof each === "number" && Number.isFinite(each)
|
|
146
|
+
) : [];
|
|
147
|
+
return numbers.length > 0 ? { value: Math.max(...numbers) } : null;
|
|
148
|
+
}
|
|
149
|
+
case "surfaceArea": {
|
|
150
|
+
const areas = at(datasheet, "areas");
|
|
151
|
+
if (typeof areas === "object" && areas !== null) {
|
|
152
|
+
const parts = Object.values(areas).filter(
|
|
153
|
+
(each) => typeof each === "number" && Number.isFinite(each)
|
|
154
|
+
);
|
|
155
|
+
if (parts.length > 0) return { value: parts.reduce((sum, each) => sum + each, 0) };
|
|
156
|
+
}
|
|
157
|
+
const faces = [...new Set(feature.regionIdxs)].flatMap(
|
|
158
|
+
(region) => regionAreas.get(region) ?? []
|
|
159
|
+
);
|
|
160
|
+
return faces.length > 0 ? { value: faces.reduce((sum, each) => sum + each, 0) } : null;
|
|
161
|
+
}
|
|
162
|
+
default: {
|
|
163
|
+
if (!datasheet) return null;
|
|
164
|
+
const value = readPath(datasheet, metric);
|
|
165
|
+
return value === null ? null : { value };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var featureHits = (features, feature, sources, rules) => {
|
|
170
|
+
const sheet = sources.sheets[feature.tag.toLowerCase()];
|
|
171
|
+
return rules.flatMap((rule) => {
|
|
172
|
+
const info = metricInfo(rule.metric);
|
|
173
|
+
if (!info || !featureIs(rule.subject, feature.featureType, sheet)) return [];
|
|
174
|
+
const measured = featureMeasure(info, features, feature, sources);
|
|
175
|
+
if (!measured || !breaks(rule, info.quantity, measured.value)) return [];
|
|
176
|
+
return [{ rule, metric: info, ...measured }];
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
var requiredTags = (features) => {
|
|
180
|
+
const owners = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const feature of features) {
|
|
182
|
+
for (const region of new Set(feature.regionIdxs))
|
|
183
|
+
owners.set(region, (owners.get(region) ?? 0) + 1);
|
|
184
|
+
}
|
|
185
|
+
return new Set(
|
|
186
|
+
features.filter((feature) => feature.regionIdxs.some((region) => owners.get(region) === 1)).map((feature) => feature.tag.toLowerCase())
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
var QUALIFIERS = ["undercut", "filleted", "tslot", "dovetail"];
|
|
190
|
+
var EDGE_TYPES = ["fillet", "chamfer", "sink"];
|
|
191
|
+
var featureComplexity = (featureType) => {
|
|
192
|
+
const words = featureType.toLowerCase().split("_");
|
|
193
|
+
const edge = EDGE_TYPES.some((type) => words.includes(type)) ? 1 : 0;
|
|
194
|
+
return edge + QUALIFIERS.filter((word) => words.includes(word)).length;
|
|
195
|
+
};
|
|
196
|
+
var CONTOUR = "contour_surface";
|
|
197
|
+
var unavoidableFaces = (features, hitsByTag, regionAreas) => {
|
|
198
|
+
const worstColor = (feature) => hitsByTag.get(feature.tag.toLowerCase())?.map((hit) => hit.rule.color).sort(compareColors)[0];
|
|
199
|
+
const areaOf = (feature) => [...new Set(feature.regionIdxs)].reduce(
|
|
200
|
+
(sum, region) => sum + (regionAreas.get(region) ?? 0),
|
|
201
|
+
0
|
|
202
|
+
);
|
|
203
|
+
const owners = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const feature of features) {
|
|
205
|
+
for (const region of new Set(feature.regionIdxs)) {
|
|
206
|
+
owners.set(region, [...owners.get(region) ?? [], feature]);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const faces = /* @__PURE__ */ new Map();
|
|
210
|
+
const whole = /* @__PURE__ */ new Map();
|
|
211
|
+
for (const [region, candidates] of owners) {
|
|
212
|
+
const colored = candidates.map((feature) => ({
|
|
213
|
+
feature,
|
|
214
|
+
color: worstColor(feature),
|
|
215
|
+
area: areaOf(feature)
|
|
216
|
+
}));
|
|
217
|
+
const isContour = ({ feature }) => feature.featureType === CONTOUR;
|
|
218
|
+
const machined = colored.filter((each) => !isContour(each));
|
|
219
|
+
const pool = machined.length > 0 ? machined : colored;
|
|
220
|
+
if (pool.some(({ color }) => color === void 0)) continue;
|
|
221
|
+
const contour = colored.filter(isContour).reduce((kept, each) => kept === void 0 || each.area > kept.area ? each : kept, void 0);
|
|
222
|
+
if (contour?.color && colored.every(({ color }) => color !== void 0)) {
|
|
223
|
+
whole.set(contour.feature.tag.toLowerCase(), {
|
|
224
|
+
tag: contour.feature.tag,
|
|
225
|
+
color: contour.color
|
|
226
|
+
});
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const best = pool.reduce((kept, each) => {
|
|
230
|
+
const plainer = featureComplexity(kept.feature.featureType) - featureComplexity(each.feature.featureType);
|
|
231
|
+
if (plainer !== 0) return plainer > 0 ? each : kept;
|
|
232
|
+
const milder = compareColors(each.color ?? "", kept.color ?? "");
|
|
233
|
+
return milder > 0 || milder === 0 && each.area > kept.area ? each : kept;
|
|
234
|
+
});
|
|
235
|
+
if (best.color) faces.set(region, { tag: best.feature.tag, color: best.color });
|
|
236
|
+
}
|
|
237
|
+
return { paintedFaces: faces, paintedWhole: whole };
|
|
238
|
+
};
|
|
239
|
+
var withSurroundedFaces = (check, features, adjacency) => {
|
|
240
|
+
const paintedFor = new Map(check.paintedFaces);
|
|
241
|
+
const regionsOf = /* @__PURE__ */ new Map();
|
|
242
|
+
for (const feature of features) {
|
|
243
|
+
const key = feature.tag.toLowerCase();
|
|
244
|
+
regionsOf.set(key, new Set(feature.regionIdxs));
|
|
245
|
+
const whole = check.paintedWhole.get(key);
|
|
246
|
+
if (whole) {
|
|
247
|
+
for (const region of feature.regionIdxs)
|
|
248
|
+
if (!paintedFor.has(region)) paintedFor.set(region, whole);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const surrounded = /* @__PURE__ */ new Map();
|
|
252
|
+
for (const [region, neighbours] of adjacency) {
|
|
253
|
+
if (paintedFor.has(region)) continue;
|
|
254
|
+
const counts = /* @__PURE__ */ new Map();
|
|
255
|
+
for (const neighbour of neighbours) {
|
|
256
|
+
const paint2 = neighbour === region ? void 0 : paintedFor.get(neighbour);
|
|
257
|
+
if (!paint2) continue;
|
|
258
|
+
const key = paint2.tag.toLowerCase();
|
|
259
|
+
counts.set(key, { paint: paint2, count: (counts.get(key)?.count ?? 0) + 1 });
|
|
260
|
+
}
|
|
261
|
+
const most = Math.max(0, ...[...counts.values()].map(({ count }) => count));
|
|
262
|
+
const [paint] = [...counts].filter(([key, { count }]) => count === most && regionsOf.get(key)?.has(region)).map(([, { paint: paint2 }]) => paint2).sort((a, b) => compareColors(a.color, b.color));
|
|
263
|
+
if (paint) surrounded.set(region, paint);
|
|
264
|
+
}
|
|
265
|
+
if (surrounded.size === 0) return check;
|
|
266
|
+
return { ...check, paintedFaces: new Map([...check.paintedFaces, ...surrounded]) };
|
|
267
|
+
};
|
|
268
|
+
var checkPart = (features, sources, rules) => {
|
|
269
|
+
const required = requiredTags(features);
|
|
270
|
+
const hitsByTag = new Map(
|
|
271
|
+
features.map((feature) => [
|
|
272
|
+
feature.tag.toLowerCase(),
|
|
273
|
+
featureHits(features, feature, sources, rules)
|
|
274
|
+
])
|
|
275
|
+
);
|
|
276
|
+
const byRule = rules.map((rule) => {
|
|
277
|
+
const hits = features.flatMap((feature) => {
|
|
278
|
+
const tag = feature.tag.toLowerCase();
|
|
279
|
+
const hit = hitsByTag.get(tag)?.find((each) => each.rule.id === rule.id);
|
|
280
|
+
return hit ? [{ tag: feature.tag, value: hit.value, metric: hit.metric, required: required.has(tag) }] : [];
|
|
281
|
+
});
|
|
282
|
+
const [first] = hits;
|
|
283
|
+
const up = first ? worseUpward(rule, first.metric) : true;
|
|
284
|
+
hits.sort((a, b) => up ? b.value - a.value : a.value - b.value);
|
|
285
|
+
return {
|
|
286
|
+
rule,
|
|
287
|
+
tags: hits.filter((hit) => hit.required).map((hit) => hit.tag),
|
|
288
|
+
otherTags: hits.filter((hit) => !hit.required).map((hit) => hit.tag)
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
return {
|
|
292
|
+
hitsByTag,
|
|
293
|
+
required,
|
|
294
|
+
byRule,
|
|
295
|
+
...unavoidableFaces(features, hitsByTag, sources.regionAreas)
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// src/model/feature-details.ts
|
|
300
|
+
import { MM_PER_INCH } from "@toolpath/tool-support";
|
|
301
|
+
var mm = (value) => `${trim(value, 4)} mm`;
|
|
302
|
+
var tol = (value) => `${trim(value, 6)} mm`;
|
|
303
|
+
var clearanceDerivation = (fit, clearance) => {
|
|
304
|
+
const { bandPath, strict, loose, atStrict, atLoose } = fit;
|
|
305
|
+
const lines = [`\u2300\u2081 = ${bandPath}.ignore.min = ${mm(strict)}`];
|
|
306
|
+
if (loose === void 0 || atStrict === void 0 || atLoose === void 0 || atLoose <= atStrict) {
|
|
307
|
+
lines.push(
|
|
308
|
+
`${bandPath}.deviate.min or a tolerance is missing, so the tolerance cannot be taken out:`,
|
|
309
|
+
`clearance \u2264 \u2300\u2081 = ${mm(clearance)}`
|
|
310
|
+
);
|
|
311
|
+
return lines;
|
|
312
|
+
}
|
|
313
|
+
const growth = (loose - strict) / (atLoose - atStrict);
|
|
314
|
+
lines.push(
|
|
315
|
+
` at t\u2081 = toleranceBand.atolIgnore = ${tol(atStrict)}`,
|
|
316
|
+
`\u2300\u2082 = ${bandPath}.deviate.min = ${mm(loose)}`,
|
|
317
|
+
` at t\u2082 = toleranceBand.atolDeviate = ${tol(atLoose)}`
|
|
318
|
+
);
|
|
319
|
+
if (growth <= 0) {
|
|
320
|
+
lines.push("The tool does not grow with tolerance (a gap, not a corner): clearance = \u2300\u2081");
|
|
321
|
+
} else {
|
|
322
|
+
lines.push(
|
|
323
|
+
`k = (\u2300\u2082 \u2212 \u2300\u2081) / (t\u2082 \u2212 t\u2081) = ${trim(growth, 3)}`,
|
|
324
|
+
`slack = k \xD7 t\u2081 = ${mm(growth * atStrict)}, what the tolerance adds to every tool`,
|
|
325
|
+
`clearance = \u2300\u2081 \u2212 slack`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
lines.push(`clearance = ${mm(clearance)}`);
|
|
329
|
+
return lines;
|
|
330
|
+
};
|
|
331
|
+
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`;
|
|
333
|
+
var SURFACE_TYPES = /* @__PURE__ */ new Set([
|
|
334
|
+
"contour_surface",
|
|
335
|
+
"inner_fillet",
|
|
336
|
+
"outer_fillet",
|
|
337
|
+
"fillet"
|
|
338
|
+
]);
|
|
339
|
+
var isSurfaceType = (featureType) => SURFACE_TYPES.has(featureType);
|
|
340
|
+
var maxBallDiameter = (sheet) => {
|
|
341
|
+
const { fitToolDiameter: diameter, fitCornerRadius: corner } = sheet;
|
|
342
|
+
if (corner === void 0 || corner <= 0) return void 0;
|
|
343
|
+
return diameter !== void 0 && diameter > 0 ? Math.min(diameter, 2 * corner) : 2 * corner;
|
|
344
|
+
};
|
|
345
|
+
var ROW_ORDER = [
|
|
346
|
+
"featureDepth",
|
|
347
|
+
"diameter",
|
|
348
|
+
"cornerRadius",
|
|
349
|
+
"filletRadius",
|
|
350
|
+
"floorFillet",
|
|
351
|
+
"tipAngle",
|
|
352
|
+
"thread",
|
|
353
|
+
"bevelAngle",
|
|
354
|
+
"finishTool",
|
|
355
|
+
"pinch",
|
|
356
|
+
"fitCornerRadius",
|
|
357
|
+
"maxBull",
|
|
358
|
+
"maxBall",
|
|
359
|
+
"depthBelowTop",
|
|
360
|
+
"ld",
|
|
361
|
+
"featureLd",
|
|
362
|
+
"topLd"
|
|
363
|
+
];
|
|
364
|
+
var featureMeasurements = ({
|
|
365
|
+
features,
|
|
366
|
+
feature,
|
|
367
|
+
sheets,
|
|
368
|
+
units
|
|
369
|
+
}) => {
|
|
370
|
+
const other = units === "inch" ? "mm" : "inch";
|
|
371
|
+
const length = (mm2) => ({ value: formatLength(mm2, units), alt: formatLength(mm2, other) });
|
|
372
|
+
const sheet = sheets[feature.tag.toLowerCase()];
|
|
373
|
+
const rows = [];
|
|
374
|
+
const top = sheet ? partTop(features, sheets, feature.machiningDirection) : null;
|
|
375
|
+
const bottom = sheet?.zMin;
|
|
376
|
+
const topFrom = top === null ? void 0 : features.find((each) => sheets[each.tag.toLowerCase()]?.extendedZMax === top);
|
|
377
|
+
const topLine = top === null ? "" : `Top of part = the highest extendedZMax of the features machined from this direction = ${mm(top)}${topFrom ? ` (${featureTypeLabel(topFrom.featureType)} ${topFrom.tag})` : ""}`;
|
|
378
|
+
if (top !== null && bottom !== void 0) {
|
|
379
|
+
rows.push({
|
|
380
|
+
key: "depthBelowTop",
|
|
381
|
+
label: "Depth below top of part",
|
|
382
|
+
milling: true,
|
|
383
|
+
...length(top - bottom),
|
|
384
|
+
derivation: [topLine, `Top \u2212 zMin = ${mm(top)} \u2212 ${mm(bottom)} = ${mm(top - bottom)}`]
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (sheet?.zMax !== void 0 && bottom !== void 0) {
|
|
388
|
+
rows.push({
|
|
389
|
+
key: "featureDepth",
|
|
390
|
+
label: "Feature depth",
|
|
391
|
+
...length(sheet.zMax - bottom),
|
|
392
|
+
derivation: [`zMax \u2212 zMin = ${mm(sheet.zMax)} \u2212 ${mm(bottom)} = ${mm(sheet.zMax - bottom)}`]
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (sheet?.diameter !== void 0) {
|
|
396
|
+
rows.push({
|
|
397
|
+
key: "diameter",
|
|
398
|
+
label: "Diameter",
|
|
399
|
+
...length(sheet.diameter),
|
|
400
|
+
derivation: [`facts.diameter = ${mm(sheet.diameter)}`]
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (sheet?.cornerRadius !== void 0) {
|
|
404
|
+
const radius = length(sheet.cornerRadius);
|
|
405
|
+
rows.push({
|
|
406
|
+
key: "cornerRadius",
|
|
407
|
+
label: sheet.sharpCorners ? "Minimum radius (sharp)" : "Minimum radius",
|
|
408
|
+
// Measured at one tolerance only, it is the most the modelled radius can be, not the radius itself.
|
|
409
|
+
...sheet.cornerRadiusUnresolved ? { value: `\u2264 ${radius.value}`, alt: `\u2264 ${radius.alt}` } : radius,
|
|
410
|
+
derivation: [
|
|
411
|
+
"The tightest corner between walls: half the minimum clearance, the widest tool that reaches every point with the tolerance it was measured to taken out.",
|
|
412
|
+
...sheet.clearance ? clearanceDerivation(sheet.clearance, 2 * sheet.cornerRadius) : [],
|
|
413
|
+
`R = clearance / 2 = ${mm(sheet.cornerRadius)}${sheet.sharpCorners ? " (under 0.01 mm: sharp)" : ""}`
|
|
414
|
+
]
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (!sheet?.sharpCorners) {
|
|
418
|
+
const clearance = sheet?.pinchDiameter ?? (sheet?.cornerRadius !== void 0 ? 2 * sheet.cornerRadius : void 0);
|
|
419
|
+
if (clearance !== void 0) {
|
|
420
|
+
const width = length(clearance);
|
|
421
|
+
const places = sheet?.pinchPlaces;
|
|
422
|
+
const floor = !SURFACE_TYPES.has(feature.featureType) && sheet?.filletRadius !== void 0 && sheet.filletRadius > 0 ? length(sheet.filletRadius) : void 0;
|
|
423
|
+
const atMost = sheet?.cornerRadiusUnresolved ? "\u2264 " : "";
|
|
424
|
+
rows.push({
|
|
425
|
+
key: "pinch",
|
|
426
|
+
label: "Max tool diameter",
|
|
427
|
+
milling: true,
|
|
428
|
+
// Measured at one tolerance only, it is the most the clearance can be, not the clearance itself.
|
|
429
|
+
value: `${atMost}\u2300 ${width.value}${floor ? ` \xB7 R ${floor.value}` : ""}`,
|
|
430
|
+
alt: `${atMost}\u2300 ${width.alt}${floor ? ` \xB7 R ${floor.alt}` : ""}`,
|
|
431
|
+
derivation: [
|
|
432
|
+
"The widest circle that fits where the feature is tightest: twice an inside corner\u2019s radius, or the width of a gap between walls.",
|
|
433
|
+
...places !== void 0 ? [
|
|
434
|
+
`pinchPoints: where the feature narrows to its tightest clearance, a disc each; ${places} ${places === 1 ? "place" : "places"} that tight, the tightest drawn.`,
|
|
435
|
+
`min(pinchPoints[].diameter) is the cutter band\u2019s min, measured to a tolerance (t) the widest tool grows with: run back to none from two tolerances.`
|
|
436
|
+
] : [
|
|
437
|
+
"No pinch points reported: the cutter band\u2019s min, measured to a tolerance (t) the widest tool grows with, run back to none from two tolerances."
|
|
438
|
+
],
|
|
439
|
+
...sheet?.clearance ? clearanceDerivation(sheet.clearance, clearance) : [],
|
|
440
|
+
...floor && sheet?.filletRadius !== void 0 ? [
|
|
441
|
+
`A bull nose, its corner radius the floor fillet\u2019s: facts.filletRadius = ${mm(sheet.filletRadius)}`
|
|
442
|
+
] : []
|
|
443
|
+
]
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const surface = SURFACE_TYPES.has(feature.featureType);
|
|
448
|
+
if (surface && sheet) {
|
|
449
|
+
const diameter = sheet.fitToolDiameter;
|
|
450
|
+
const corner = sheet.fitCornerRadius;
|
|
451
|
+
const fit = sheet.toolFitPath ?? "facts.toolFit";
|
|
452
|
+
const ball = maxBallDiameter(sheet);
|
|
453
|
+
rows.push({
|
|
454
|
+
key: "finishTool",
|
|
455
|
+
label: "Finishing tool",
|
|
456
|
+
milling: true,
|
|
457
|
+
value: sheet.ballOnly ? "Ball end mill" : "Ball or bull nose",
|
|
458
|
+
derivation: [
|
|
459
|
+
`facts.useOnlyBallToolsForFinish (or facts.three.\u2026) is ${sheet.ballOnly ? "true" : "not true"}`
|
|
460
|
+
]
|
|
461
|
+
});
|
|
462
|
+
if (corner !== void 0 && corner > 0) {
|
|
463
|
+
rows.push({
|
|
464
|
+
key: "fitCornerRadius",
|
|
465
|
+
label: "Max corner radius",
|
|
466
|
+
milling: true,
|
|
467
|
+
...length(corner),
|
|
468
|
+
derivation: [
|
|
469
|
+
"The tightest the surface turns inward: a tool with a larger corner radius cannot follow it.",
|
|
470
|
+
`${fit}.cornerRadius = ${mm(corner)}`
|
|
471
|
+
]
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
const sized = diameter !== void 0 && diameter > 0;
|
|
475
|
+
if (!sheet.ballOnly && corner !== void 0 && corner > 0 && (!sized || ball === void 0 || ball < diameter)) {
|
|
476
|
+
const size = sized ? length(diameter) : void 0;
|
|
477
|
+
const radius = length(corner);
|
|
478
|
+
rows.push({
|
|
479
|
+
key: "maxBull",
|
|
480
|
+
label: "Max bull nose",
|
|
481
|
+
milling: true,
|
|
482
|
+
// Drawable only at a diameter the fit gives: "any" is no size to draw.
|
|
483
|
+
...sized ? { tool: "bull" } : {},
|
|
484
|
+
value: `${size ? `\u2300 ${size.value}` : "any \u2300"} \xB7 R ${radius.value}`,
|
|
485
|
+
alt: `${size ? `\u2300 ${size.alt}` : "any \u2300"} \xB7 R ${radius.alt}`,
|
|
486
|
+
derivation: [
|
|
487
|
+
sized ? "The largest full-diameter tool the surface\u2019s shape admits, with the largest corner radius it admits." : "The surface limits only the tool\u2019s corner radius, not its diameter: a bull nose of any size with a corner no larger than this.",
|
|
488
|
+
...sized ? [`\u2300 = ${fit}.toolDiameter = ${mm(diameter)}`] : [`${fit}.toolDiameter is not reported`],
|
|
489
|
+
`R = ${fit}.cornerRadius = ${mm(corner)}`
|
|
490
|
+
]
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
if (ball !== void 0 && corner !== void 0) {
|
|
494
|
+
const size = length(ball);
|
|
495
|
+
rows.push({
|
|
496
|
+
key: "maxBall",
|
|
497
|
+
label: "Max ball nose",
|
|
498
|
+
milling: true,
|
|
499
|
+
tool: "ball",
|
|
500
|
+
value: `\u2300 ${size.value}`,
|
|
501
|
+
alt: `\u2300 ${size.alt}`,
|
|
502
|
+
derivation: [
|
|
503
|
+
"A ball\u2019s corner radius is half its diameter, so it must fit under the corner limit, and the diameter limit where there is one:",
|
|
504
|
+
...diameter !== void 0 && diameter > 0 ? [
|
|
505
|
+
`min(${fit}.toolDiameter, 2 \xD7 ${fit}.cornerRadius)`,
|
|
506
|
+
`= min(${mm(diameter)}, ${mm(2 * corner)}) = ${mm(ball)}`
|
|
507
|
+
] : [`2 \xD7 ${fit}.cornerRadius = ${mm(ball)} (${fit}.toolDiameter is not reported)`]
|
|
508
|
+
]
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const ld = featureLd(features, feature, sheets);
|
|
513
|
+
if (ld && top !== null && bottom !== void 0) {
|
|
514
|
+
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)`;
|
|
515
|
+
const ratioOf = (depth) => {
|
|
516
|
+
const ratio = (depth / ld.across).toFixed(3);
|
|
517
|
+
return ld.atLeast ? `\u2265 ${ratio}` : ratio;
|
|
518
|
+
};
|
|
519
|
+
if (ld.drilling) {
|
|
520
|
+
rows.push({
|
|
521
|
+
key: "ld",
|
|
522
|
+
label: "Drilling L/D",
|
|
523
|
+
milling: true,
|
|
524
|
+
value: ratioOf(top - bottom),
|
|
525
|
+
derivation: [
|
|
526
|
+
`L = depth below top of part = ${mm(top - bottom)}`,
|
|
527
|
+
across,
|
|
528
|
+
`L / D = ${mm(top - bottom)} / ${mm(ld.across)} = ${ld.ratio.toFixed(3)}`
|
|
529
|
+
]
|
|
530
|
+
});
|
|
531
|
+
} else {
|
|
532
|
+
if (sheet?.zMax !== void 0) {
|
|
533
|
+
rows.push({
|
|
534
|
+
key: "featureLd",
|
|
535
|
+
label: "Feature L/D",
|
|
536
|
+
milling: true,
|
|
537
|
+
value: ratioOf(sheet.zMax - bottom),
|
|
538
|
+
derivation: [
|
|
539
|
+
`L = feature depth = zMax \u2212 zMin = ${mm(sheet.zMax - bottom)}`,
|
|
540
|
+
across,
|
|
541
|
+
`L / D = ${mm(sheet.zMax - bottom)} / ${mm(ld.across)} = ${((sheet.zMax - bottom) / ld.across).toFixed(3)}`
|
|
542
|
+
]
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
rows.push({
|
|
546
|
+
key: "topLd",
|
|
547
|
+
label: "L/D to top of part",
|
|
548
|
+
milling: true,
|
|
549
|
+
value: ratioOf(top - bottom),
|
|
550
|
+
derivation: [
|
|
551
|
+
`L = depth below top of part = ${mm(top - bottom)}`,
|
|
552
|
+
across,
|
|
553
|
+
`L / D = ${mm(top - bottom)} / ${mm(ld.across)} = ${ld.ratio.toFixed(3)}`
|
|
554
|
+
]
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (sheet?.tipAngle !== void 0) {
|
|
559
|
+
const flat = Math.abs(sheet.tipAngle - 180) < 0.05;
|
|
560
|
+
rows.push({
|
|
561
|
+
key: "tipAngle",
|
|
562
|
+
label: "Tip angle",
|
|
563
|
+
value: `${trim(sheet.tipAngle, 3)}\xB0${flat ? " (flat)" : ""}`,
|
|
564
|
+
derivation: [
|
|
565
|
+
`facts.fullConeDeg = ${trim(sheet.tipAngle, 3)}\xB0${flat ? " (180\xB0: flat bottom)" : ""}`
|
|
566
|
+
]
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
if (sheet?.threading) {
|
|
570
|
+
rows.push({
|
|
571
|
+
key: "thread",
|
|
572
|
+
label: "Thread",
|
|
573
|
+
value: `${formatLength(sheet.threading.basicDiameter, units)} \xD7 ${formatLength(sheet.threading.threadPitch, units)} pitch`,
|
|
574
|
+
derivation: [
|
|
575
|
+
`facts.threading.spec.basicDiameter = ${mm(sheet.threading.basicDiameter)}`,
|
|
576
|
+
`facts.threading.spec.threadPitch = ${mm(sheet.threading.threadPitch)}`
|
|
577
|
+
]
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (surface) {
|
|
581
|
+
if (sheet?.filletRadius !== void 0 && sheet.filletRadius !== 0) {
|
|
582
|
+
rows.push({
|
|
583
|
+
key: "filletRadius",
|
|
584
|
+
label: "Fillet radius",
|
|
585
|
+
...length(Math.abs(sheet.filletRadius)),
|
|
586
|
+
derivation: [
|
|
587
|
+
`|facts.filletRadius| = |${mm(sheet.filletRadius)}| (signed: + over an edge, \u2212 into a corner)`
|
|
588
|
+
]
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
} else if (sheet?.filletRadius !== void 0 && sheet.filletRadius > 0) {
|
|
592
|
+
rows.push({
|
|
593
|
+
key: "floorFillet",
|
|
594
|
+
label: "Floor fillet radius",
|
|
595
|
+
...length(sheet.filletRadius),
|
|
596
|
+
derivation: [`facts.filletRadius = ${mm(sheet.filletRadius)}`]
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
if (sheet?.chamferAngle !== void 0) {
|
|
600
|
+
rows.push({
|
|
601
|
+
key: "bevelAngle",
|
|
602
|
+
label: "Chamfer angle",
|
|
603
|
+
value: `${sheet.chamferAngle.toFixed(3)}\xB0`,
|
|
604
|
+
derivation: [`facts.bevel.angleDeg = ${trim(sheet.chamferAngle, 3)}\xB0`]
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
return rows.sort((a, b) => ROW_ORDER.indexOf(a.key) - ROW_ORDER.indexOf(b.key));
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// src/model/pinch.ts
|
|
611
|
+
var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
612
|
+
var recordOf = (value) => typeof value === "object" && value !== null ? value : {};
|
|
613
|
+
var clearanceFit = (datasheet) => {
|
|
614
|
+
const sheet = recordOf(datasheet);
|
|
615
|
+
const facts = recordOf(sheet.facts);
|
|
616
|
+
const chamfer = facts.kind === "Chamfer";
|
|
617
|
+
const band = recordOf(chamfer ? recordOf(facts.three).cd : facts.cd);
|
|
618
|
+
const tolerance = recordOf(sheet.toleranceBand);
|
|
619
|
+
const strict = asNumber(recordOf(band.ignore).min);
|
|
620
|
+
if (strict === void 0) return void 0;
|
|
621
|
+
const bandPath = chamfer ? "facts.three.cd" : "facts.cd";
|
|
622
|
+
const loose = asNumber(recordOf(band.deviate).min);
|
|
623
|
+
const atStrict = asNumber(tolerance.atolIgnore);
|
|
624
|
+
const atLoose = asNumber(tolerance.atolDeviate);
|
|
625
|
+
const source = { bandPath, strict, loose, atStrict, atLoose };
|
|
626
|
+
if (loose === void 0 || atStrict === void 0 || atLoose === void 0 || atLoose <= atStrict)
|
|
627
|
+
return { ...source, slack: 0, resolved: false };
|
|
628
|
+
const growth = (loose - strict) / (atLoose - atStrict);
|
|
629
|
+
const slack = growth > 0 ? Math.min(growth * atStrict, strict) : 0;
|
|
630
|
+
return { ...source, slack, resolved: true };
|
|
631
|
+
};
|
|
632
|
+
var readPinch = (datasheet) => {
|
|
633
|
+
const sheet = recordOf(datasheet);
|
|
634
|
+
const zMin = asNumber(sheet.zMin);
|
|
635
|
+
const zMax = asNumber(sheet.zMax);
|
|
636
|
+
if (zMin === void 0 || zMax === void 0 || !Array.isArray(sheet.pinchPoints)) return null;
|
|
637
|
+
const fit = clearanceFit(datasheet);
|
|
638
|
+
const slack = fit?.slack ?? 0;
|
|
639
|
+
const discs = sheet.pinchPoints.flatMap((point) => {
|
|
640
|
+
const centre = recordOf(recordOf(point).center);
|
|
641
|
+
const x = asNumber(centre.x);
|
|
642
|
+
const y = asNumber(centre.y);
|
|
643
|
+
const diameter = asNumber(recordOf(point).diameter);
|
|
644
|
+
return x === void 0 || y === void 0 || diameter === void 0 || diameter < 0 ? [] : [{ x, y, diameter: Math.max(diameter - slack, 0) }];
|
|
645
|
+
});
|
|
646
|
+
if (discs.length === 0) return null;
|
|
647
|
+
return { discs, zMin, zMax, ...fit && !fit.resolved ? { unresolved: true } : {} };
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
// src/model/feature-sheet.ts
|
|
651
|
+
var SHARP_MM = 0.1;
|
|
652
|
+
var SHARP_RADIUS_MM = 0.01;
|
|
653
|
+
var PINCH_TOOL_MM = 0.01;
|
|
654
|
+
var asNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
655
|
+
var recordOf2 = (value) => typeof value === "object" && value !== null ? value : {};
|
|
656
|
+
var threadingOf = (facts) => {
|
|
657
|
+
const spec = recordOf2(recordOf2(facts.threading).spec);
|
|
658
|
+
const basicDiameter = asNumber2(spec.basicDiameter);
|
|
659
|
+
const threadPitch = asNumber2(spec.threadPitch);
|
|
660
|
+
if (basicDiameter === void 0 || threadPitch === void 0 || threadPitch <= 0) return void 0;
|
|
661
|
+
return { basicDiameter, threadPitch };
|
|
662
|
+
};
|
|
663
|
+
var noToolFits = (band) => {
|
|
664
|
+
const { max } = band;
|
|
665
|
+
if (typeof max === "string") return /^-inf/i.test(max.trim());
|
|
666
|
+
return typeof max === "number" && (max === Number.NEGATIVE_INFINITY || Number.isFinite(max) && max < SHARP_MM);
|
|
667
|
+
};
|
|
668
|
+
var insideCornerRadius = (fit) => Math.max((fit.strict - fit.slack) / 2, 0);
|
|
669
|
+
var pinchCentres = (points) => Array.isArray(points) ? points.flatMap((point) => {
|
|
670
|
+
const centre = recordOf2(recordOf2(point).center);
|
|
671
|
+
const x = asNumber2(centre.x);
|
|
672
|
+
const y = asNumber2(centre.y);
|
|
673
|
+
return x === void 0 || y === void 0 ? [] : [{ x, y }];
|
|
674
|
+
}) : [];
|
|
675
|
+
var ZERO_MM = 5e-5;
|
|
676
|
+
var isCutterlessTslot = (datasheet) => {
|
|
677
|
+
const facts = recordOf2(recordOf2(datasheet).facts);
|
|
678
|
+
if (facts.kind !== "Tslot") return false;
|
|
679
|
+
const leaves = (value) => typeof value === "object" && value !== null ? Object.values(value).flatMap(leaves) : [value];
|
|
680
|
+
const band = leaves(facts.cd);
|
|
681
|
+
return band.length > 0 && band.every((each) => {
|
|
682
|
+
const value = typeof each === "string" && each.trim() !== "" ? Number(each) : each;
|
|
683
|
+
return typeof value === "number" && Math.abs(value) < ZERO_MM;
|
|
684
|
+
});
|
|
685
|
+
};
|
|
686
|
+
var isSharpCorneredFillet = (featureType, datasheet) => {
|
|
687
|
+
if (typeof featureType !== "string" || !featureType.toLowerCase().split("_").includes("fillet"))
|
|
688
|
+
return false;
|
|
689
|
+
const facts = recordOf2(recordOf2(datasheet).facts);
|
|
690
|
+
return facts.hasSharpCorner === true || recordOf2(facts.three).hasSharpCorner === true;
|
|
691
|
+
};
|
|
692
|
+
var isNoReading = (featureType, datasheet) => isCutterlessTslot(datasheet) || isSharpCorneredFillet(featureType, datasheet);
|
|
693
|
+
var featureSheet = (datasheet) => {
|
|
694
|
+
const sheet = recordOf2(datasheet);
|
|
695
|
+
const facts = recordOf2(sheet.facts);
|
|
696
|
+
const kind = typeof facts.kind === "string" ? facts.kind : void 0;
|
|
697
|
+
const band = recordOf2(kind === "Chamfer" ? recordOf2(facts.three).cd : facts.cd);
|
|
698
|
+
const ignoreBand = recordOf2(band.ignore);
|
|
699
|
+
const fit = clearanceFit(datasheet);
|
|
700
|
+
const fields = {
|
|
701
|
+
kind,
|
|
702
|
+
zMin: asNumber2(sheet.zMin),
|
|
703
|
+
zMax: asNumber2(sheet.zMax),
|
|
704
|
+
extendedZMax: asNumber2(sheet.extendedZMax),
|
|
705
|
+
maxTool: asNumber2(ignoreBand.min),
|
|
706
|
+
diameter: kind === "Hole" ? asNumber2(facts.diameter) : void 0,
|
|
707
|
+
tipAngle: kind === "Hole" ? asNumber2(facts.fullConeDeg) : void 0,
|
|
708
|
+
filletRadius: asNumber2(facts.filletRadius),
|
|
709
|
+
// A bevel's angle wherever the facts carry one: a chamfer's, and any other kind that bevels.
|
|
710
|
+
chamferAngle: asNumber2(recordOf2(facts.bevel).angleDeg)
|
|
711
|
+
};
|
|
712
|
+
const toolFitPath = facts.toolFit !== void 0 ? "facts.toolFit" : "facts.three.toolFit";
|
|
713
|
+
const toolFit = recordOf2(facts.toolFit ?? recordOf2(facts.three).toolFit);
|
|
714
|
+
fields.fitToolDiameter = asNumber2(toolFit.toolDiameter);
|
|
715
|
+
fields.fitCornerRadius = asNumber2(toolFit.cornerRadius);
|
|
716
|
+
const ballOnly = (facts.useOnlyBallToolsForFinish ?? recordOf2(facts.three).useOnlyBallToolsForFinish) === true;
|
|
717
|
+
const measured = Object.fromEntries(
|
|
718
|
+
Object.entries(fields).filter(([, value]) => value !== void 0)
|
|
719
|
+
);
|
|
720
|
+
const cramped = Object.keys(ignoreBand).length > 0 && noToolFits(ignoreBand);
|
|
721
|
+
const walled = kind !== "Hole" && sheet.hasWall === true;
|
|
722
|
+
const clearance = walled ? fit : void 0;
|
|
723
|
+
const cornerRadius = clearance ? insideCornerRadius(clearance) : void 0;
|
|
724
|
+
const sharp = cornerRadius !== void 0 && cornerRadius < SHARP_RADIUS_MM && !cramped;
|
|
725
|
+
const threading = kind === "Hole" ? threadingOf(facts) : void 0;
|
|
726
|
+
const pinched = (readPinch(datasheet)?.discs ?? []).filter(
|
|
727
|
+
(disc) => disc.diameter >= PINCH_TOOL_MM
|
|
728
|
+
);
|
|
729
|
+
return {
|
|
730
|
+
...measured,
|
|
731
|
+
...pinched.length > 0 ? {
|
|
732
|
+
pinchDiameter: Math.min(...pinched.map((disc) => disc.diameter)),
|
|
733
|
+
pinchPlaces: pinched.length
|
|
734
|
+
} : {},
|
|
735
|
+
...cornerRadius !== void 0 ? { cornerRadius } : {},
|
|
736
|
+
...clearance && !clearance.resolved ? { cornerRadiusUnresolved: true } : {},
|
|
737
|
+
...clearance ? { clearance } : {},
|
|
738
|
+
...Object.keys(toolFit).length > 0 ? { toolFitPath } : {},
|
|
739
|
+
...sharp ? { sharpCorners: pinchCentres(sheet.pinchPoints) } : {},
|
|
740
|
+
...cramped ? { noToolFits: true } : {},
|
|
741
|
+
...ballOnly ? { ballOnly: true } : {},
|
|
742
|
+
...threading ? { threading } : {}
|
|
743
|
+
};
|
|
744
|
+
};
|
|
745
|
+
export {
|
|
746
|
+
CRITICAL_COLOR,
|
|
747
|
+
FILE_KIND,
|
|
748
|
+
PINCH_TOOL_MM,
|
|
749
|
+
RULE_COLORS,
|
|
750
|
+
WARNING_COLOR,
|
|
751
|
+
checkPart,
|
|
752
|
+
compareColors,
|
|
753
|
+
describeRule,
|
|
754
|
+
dfmFeatures,
|
|
755
|
+
featureMeasurements,
|
|
756
|
+
featureProfile,
|
|
757
|
+
featureSheet,
|
|
758
|
+
featureTypeLabel,
|
|
759
|
+
hitFigure,
|
|
760
|
+
isNoReading,
|
|
761
|
+
isSurfaceType,
|
|
762
|
+
llmRulePrompt,
|
|
763
|
+
makeRule,
|
|
764
|
+
maxBallDiameter,
|
|
765
|
+
parseRuleSetFile,
|
|
766
|
+
parseRules,
|
|
767
|
+
peakHeight,
|
|
768
|
+
readPinch,
|
|
769
|
+
readReachCurve,
|
|
770
|
+
runStart,
|
|
771
|
+
sameDepth,
|
|
772
|
+
serializeRuleSet,
|
|
773
|
+
trimmedCurve,
|
|
774
|
+
withSurroundedFaces
|
|
775
|
+
};
|