@toolpath/dfm 0.3.1 → 0.5.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.
@@ -1,8 +1,11 @@
1
1
  import {
2
2
  CRITICAL_COLOR,
3
3
  FILE_KIND,
4
+ ID_KINDS,
4
5
  LENGTH_PLACES,
6
+ MILLING_RULES,
5
7
  RULE_COLORS,
8
+ TURNING_RULES,
6
9
  TYPE_SUBJECT,
7
10
  WARNING_COLOR,
8
11
  breaks,
@@ -26,10 +29,12 @@ import {
26
29
  runStart,
27
30
  sameDepth,
28
31
  serializeRuleSet,
32
+ subjectInfo,
29
33
  trimmedCurve,
34
+ turnedTypeLabel,
30
35
  unitLabel,
31
36
  worseUpward
32
- } from "../chunk-5MPHT7BC.js";
37
+ } from "../chunk-4IRNLDE6.js";
33
38
 
34
39
  // src/model/broken-rules.ts
35
40
  var byColor = (hits) => [...hits].sort((a, b) => compareColors(a.rule.color, b.rule.color));
@@ -41,12 +46,12 @@ var worstHits = (hits) => {
41
46
  return true;
42
47
  });
43
48
  };
44
- var brokenRules = (check, tag, units) => worstHits(check.hitsByTag.get(tag.toLowerCase()) ?? []).map((hit) => {
49
+ var brokenRules = (check, tag, units, vocabulary = MILLING_RULES) => worstHits(check.hitsByTag.get(tag.toLowerCase()) ?? []).map((hit) => {
45
50
  const figure = hitFigure(hit, units);
46
51
  return {
47
52
  key: hit.rule.id,
48
53
  color: hit.rule.color,
49
- text: describeRule(hit.rule, units),
54
+ text: describeRule(hit.rule, units, vocabulary),
50
55
  ...figure ? { figure } : {}
51
56
  };
52
57
  });
@@ -106,9 +111,9 @@ var featureIs = (subject, featureType, sheet) => {
106
111
  return false;
107
112
  }
108
113
  };
109
- var at = (record, path) => path.split(".").reduce(
114
+ var at = (record2, path) => path.split(".").reduce(
110
115
  (value, key) => typeof value === "object" && value !== null ? value[key] : void 0,
111
- record
116
+ record2
112
117
  );
113
118
  var readPath = (datasheet, metric) => {
114
119
  for (const path of metric.path ?? []) {
@@ -217,6 +222,22 @@ var requiredTags = (features) => {
217
222
  features.filter((feature) => feature.regionIdxs.some((region) => owners.get(region) === 1)).map((feature) => feature.tag.toLowerCase())
218
223
  );
219
224
  };
225
+ var worstColor = (hits) => hits?.map((hit) => hit.rule.color).sort(compareColors)[0];
226
+ var ruleResults = (rules, features, hitsByTag, required) => rules.map((rule) => {
227
+ const hits = features.flatMap((feature) => {
228
+ const tag = feature.tag.toLowerCase();
229
+ const hit = hitsByTag.get(tag)?.find((each) => each.rule.id === rule.id);
230
+ return hit ? [{ tag: feature.tag, value: hit.value, metric: hit.metric, required: required.has(tag) }] : [];
231
+ });
232
+ const [first] = hits;
233
+ const up = first ? worseUpward(rule, first.metric) : true;
234
+ hits.sort((a, b) => up ? b.value - a.value : a.value - b.value);
235
+ return {
236
+ rule,
237
+ tags: hits.filter((hit) => hit.required).map((hit) => hit.tag),
238
+ otherTags: hits.filter((hit) => !hit.required).map((hit) => hit.tag)
239
+ };
240
+ });
220
241
  var QUALIFIERS = ["undercut", "filleted", "tslot", "dovetail"];
221
242
  var EDGE_TYPES = ["fillet", "chamfer", "sink"];
222
243
  var featureComplexity = (featureType) => {
@@ -226,7 +247,6 @@ var featureComplexity = (featureType) => {
226
247
  };
227
248
  var CONTOUR = "contour_surface";
228
249
  var unavoidableFaces = (features, hitsByTag, regionAreas) => {
229
- const worstColor = (feature) => hitsByTag.get(feature.tag.toLowerCase())?.map((hit) => hit.rule.color).sort(compareColors)[0];
230
250
  const areaOf = (feature) => [...new Set(feature.regionIdxs)].reduce(
231
251
  (sum, region) => sum + (regionAreas.get(region) ?? 0),
232
252
  0
@@ -242,7 +262,7 @@ var unavoidableFaces = (features, hitsByTag, regionAreas) => {
242
262
  for (const [region, candidates] of owners) {
243
263
  const colored = candidates.map((feature) => ({
244
264
  feature,
245
- color: worstColor(feature),
265
+ color: worstColor(hitsByTag.get(feature.tag.toLowerCase())),
246
266
  area: areaOf(feature)
247
267
  }));
248
268
  const isContour = ({ feature }) => feature.featureType === CONTOUR;
@@ -269,10 +289,10 @@ var unavoidableFaces = (features, hitsByTag, regionAreas) => {
269
289
  };
270
290
  var withSurroundedFaces = (check, features, adjacency) => {
271
291
  const paintedFor = new Map(check.paintedFaces);
272
- const regionsOf = /* @__PURE__ */ new Map();
292
+ const regionsOf2 = /* @__PURE__ */ new Map();
273
293
  for (const feature of features) {
274
294
  const key = feature.tag.toLowerCase();
275
- regionsOf.set(key, new Set(feature.regionIdxs));
295
+ regionsOf2.set(key, new Set(feature.regionIdxs));
276
296
  const whole = check.paintedWhole.get(key);
277
297
  if (whole) {
278
298
  for (const region of feature.regionIdxs)
@@ -290,7 +310,7 @@ var withSurroundedFaces = (check, features, adjacency) => {
290
310
  counts.set(key, { paint: paint2, count: (counts.get(key)?.count ?? 0) + 1 });
291
311
  }
292
312
  const most = Math.max(0, ...[...counts.values()].map(({ count }) => count));
293
- 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));
313
+ const [paint] = [...counts].filter(([key, { count }]) => count === most && regionsOf2.get(key)?.has(region)).map(([, { paint: paint2 }]) => paint2).sort((a, b) => compareColors(a.color, b.color));
294
314
  if (paint) surrounded.set(region, paint);
295
315
  }
296
316
  if (surrounded.size === 0) return check;
@@ -304,29 +324,172 @@ var checkPart = (features, sources, rules) => {
304
324
  featureHits(features, feature, sources, rules)
305
325
  ])
306
326
  );
307
- const byRule = rules.map((rule) => {
308
- const hits = features.flatMap((feature) => {
309
- const tag = feature.tag.toLowerCase();
310
- const hit = hitsByTag.get(tag)?.find((each) => each.rule.id === rule.id);
311
- return hit ? [{ tag: feature.tag, value: hit.value, metric: hit.metric, required: required.has(tag) }] : [];
312
- });
313
- const [first] = hits;
314
- const up = first ? worseUpward(rule, first.metric) : true;
315
- hits.sort((a, b) => up ? b.value - a.value : a.value - b.value);
316
- return {
317
- rule,
318
- tags: hits.filter((hit) => hit.required).map((hit) => hit.tag),
319
- otherTags: hits.filter((hit) => !hit.required).map((hit) => hit.tag)
320
- };
321
- });
322
327
  return {
323
328
  hitsByTag,
324
329
  required,
325
- byRule,
330
+ byRule: ruleResults(rules, features, hitsByTag, required),
326
331
  ...unavoidableFaces(features, hitsByTag, sources.regionAreas)
327
332
  };
328
333
  };
329
334
 
335
+ // src/model/pinch.ts
336
+ var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
337
+ var recordOf = (value) => typeof value === "object" && value !== null ? value : {};
338
+ var clearanceFit = (datasheet) => {
339
+ const sheet = recordOf(datasheet);
340
+ const facts = recordOf(sheet.facts);
341
+ const chamfer = facts.kind === "Chamfer";
342
+ const band = recordOf(chamfer ? recordOf(facts.three).cd : facts.cd);
343
+ const tolerance = recordOf(sheet.toleranceBand);
344
+ const strict = asNumber(recordOf(band.ignore).min);
345
+ if (strict === void 0) return void 0;
346
+ const bandPath = chamfer ? "facts.three.cd" : "facts.cd";
347
+ const loose = asNumber(recordOf(band.deviate).min);
348
+ const atStrict = asNumber(tolerance.atolIgnore);
349
+ const atLoose = asNumber(tolerance.atolDeviate);
350
+ const source = { bandPath, strict, loose, atStrict, atLoose };
351
+ if (loose === void 0 || atStrict === void 0 || atLoose === void 0 || atLoose <= atStrict)
352
+ return { ...source, slack: 0, resolved: false };
353
+ const growth = (loose - strict) / (atLoose - atStrict);
354
+ const slack = growth > 0 ? Math.min(growth * atStrict, strict) : 0;
355
+ return { ...source, slack, resolved: true };
356
+ };
357
+ var readPinch = (datasheet) => {
358
+ const sheet = recordOf(datasheet);
359
+ const zMin = asNumber(sheet.zMin);
360
+ const zMax = asNumber(sheet.zMax);
361
+ if (zMin === void 0 || zMax === void 0 || !Array.isArray(sheet.pinchPoints)) return null;
362
+ const fit = clearanceFit(datasheet);
363
+ const slack = fit?.slack ?? 0;
364
+ const discs = sheet.pinchPoints.flatMap((point) => {
365
+ const centre = recordOf(recordOf(point).center);
366
+ const x = asNumber(centre.x);
367
+ const y = asNumber(centre.y);
368
+ const diameter = asNumber(recordOf(point).diameter);
369
+ return x === void 0 || y === void 0 || diameter === void 0 || diameter < 0 ? [] : [{ x, y, diameter: Math.max(diameter - slack, 0) }];
370
+ });
371
+ if (discs.length === 0) return null;
372
+ return { discs, zMin, zMax, ...fit && !fit.resolved ? { unresolved: true } : {} };
373
+ };
374
+
375
+ // src/model/feature-sheet.ts
376
+ var SHARP_MM = 0.1;
377
+ var SHARP_RADIUS_MM = 0.01;
378
+ var PINCH_TOOL_MM = 0.01;
379
+ var asNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
380
+ var recordOf2 = (value) => typeof value === "object" && value !== null ? value : {};
381
+ var definedOnly = (fields) => Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== void 0));
382
+ var undercutOf = (facts) => {
383
+ const unmeasured = facts.isInvalidGeometry === true || recordOf2(facts.cd).measurementFailed === true ? true : void 0;
384
+ if (facts.kind === "Tslot")
385
+ return definedOnly({
386
+ undercutDepth: asNumber2(facts.undercutDepth),
387
+ maxEntry: asNumber2(facts.maxEntryCd),
388
+ isClosed: facts.isClosed === true ? true : void 0,
389
+ unmeasured
390
+ });
391
+ if (facts.kind === "Dovetail")
392
+ return definedOnly({
393
+ taperDeg: asNumber2(facts.taperDeg),
394
+ floorWidth: asNumber2(facts.floorWidth),
395
+ topOpeningWidth: asNumber2(facts.topOpeningWidth),
396
+ isExternal: facts.isExternal === true ? true : void 0,
397
+ unmeasured
398
+ });
399
+ return void 0;
400
+ };
401
+ var threadingOf = (facts) => {
402
+ const spec = recordOf2(recordOf2(facts.threading).spec);
403
+ const basicDiameter = asNumber2(spec.basicDiameter);
404
+ const threadPitch = asNumber2(spec.threadPitch);
405
+ if (basicDiameter === void 0 || threadPitch === void 0 || threadPitch <= 0) return void 0;
406
+ return { basicDiameter, threadPitch };
407
+ };
408
+ var noToolFits = (band) => {
409
+ const { max } = band;
410
+ if (typeof max === "string") return /^-inf/i.test(max.trim());
411
+ return typeof max === "number" && (max === Number.NEGATIVE_INFINITY || Number.isFinite(max) && max < SHARP_MM);
412
+ };
413
+ var insideCornerRadius = (fit) => Math.max((fit.strict - fit.slack) / 2, 0);
414
+ var pinchCentres = (points) => Array.isArray(points) ? points.flatMap((point) => {
415
+ const centre = recordOf2(recordOf2(point).center);
416
+ const x = asNumber2(centre.x);
417
+ const y = asNumber2(centre.y);
418
+ return x === void 0 || y === void 0 ? [] : [{ x, y }];
419
+ }) : [];
420
+ var ZERO_MM = 5e-5;
421
+ var isCutterlessTslot = (datasheet) => {
422
+ const facts = recordOf2(recordOf2(datasheet).facts);
423
+ if (facts.kind !== "Tslot") return false;
424
+ const leaves = (value) => typeof value === "object" && value !== null ? Object.values(value).flatMap(leaves) : [value];
425
+ const band = leaves(facts.cd);
426
+ return band.length > 0 && band.every((each) => {
427
+ const value = typeof each === "string" && each.trim() !== "" ? Number(each) : each;
428
+ return typeof value === "number" && Math.abs(value) < ZERO_MM;
429
+ });
430
+ };
431
+ var isSharpCorneredFillet = (featureType, datasheet) => {
432
+ if (typeof featureType !== "string" || !featureType.toLowerCase().split("_").includes("fillet"))
433
+ return false;
434
+ const facts = recordOf2(recordOf2(datasheet).facts);
435
+ return facts.hasSharpCorner === true || recordOf2(facts.three).hasSharpCorner === true;
436
+ };
437
+ var isNoReading = (featureType, datasheet) => isCutterlessTslot(datasheet) || isSharpCorneredFillet(featureType, datasheet);
438
+ var featureSheet = (datasheet) => {
439
+ const sheet = recordOf2(datasheet);
440
+ const facts = recordOf2(sheet.facts);
441
+ const kind = typeof facts.kind === "string" ? facts.kind : void 0;
442
+ const band = recordOf2(kind === "Chamfer" ? recordOf2(facts.three).cd : facts.cd);
443
+ const ignoreBand = recordOf2(band.ignore);
444
+ const fit = clearanceFit(datasheet);
445
+ const fields = {
446
+ kind,
447
+ zMin: asNumber2(sheet.zMin),
448
+ zMax: asNumber2(sheet.zMax),
449
+ extendedZMax: asNumber2(sheet.extendedZMax),
450
+ maxTool: asNumber2(ignoreBand.min),
451
+ diameter: kind === "Hole" ? asNumber2(facts.diameter) : void 0,
452
+ tipAngle: kind === "Hole" ? asNumber2(facts.fullConeDeg) : void 0,
453
+ filletRadius: asNumber2(facts.filletRadius),
454
+ // A bevel's angle wherever the facts carry one: a chamfer's, and any other kind that bevels.
455
+ chamferAngle: asNumber2(recordOf2(facts.bevel).angleDeg)
456
+ };
457
+ const toolFitPath = facts.toolFit !== void 0 ? "facts.toolFit" : "facts.three.toolFit";
458
+ const toolFit = recordOf2(facts.toolFit ?? recordOf2(facts.three).toolFit);
459
+ fields.fitToolDiameter = asNumber2(toolFit.toolDiameter);
460
+ fields.fitCornerRadius = asNumber2(toolFit.cornerRadius);
461
+ const ballOnly = (facts.useOnlyBallToolsForFinish ?? recordOf2(facts.three).useOnlyBallToolsForFinish) === true;
462
+ const measured = Object.fromEntries(
463
+ Object.entries(fields).filter(([, value]) => value !== void 0)
464
+ );
465
+ const cramped = Object.keys(ignoreBand).length > 0 && noToolFits(ignoreBand);
466
+ const walled = kind !== "Hole" && sheet.hasWall === true;
467
+ const clearance = walled ? fit : void 0;
468
+ const cornerRadius = clearance ? insideCornerRadius(clearance) : void 0;
469
+ const sharp = cornerRadius !== void 0 && cornerRadius < SHARP_RADIUS_MM && !cramped;
470
+ const threading = kind === "Hole" ? threadingOf(facts) : void 0;
471
+ const undercut = undercutOf(facts);
472
+ const pinched = (readPinch(datasheet)?.discs ?? []).filter(
473
+ (disc) => disc.diameter >= PINCH_TOOL_MM
474
+ );
475
+ return {
476
+ ...measured,
477
+ ...pinched.length > 0 ? {
478
+ pinchDiameter: Math.min(...pinched.map((disc) => disc.diameter)),
479
+ pinchPlaces: pinched.length
480
+ } : {},
481
+ ...cornerRadius !== void 0 ? { cornerRadius } : {},
482
+ ...clearance && !clearance.resolved ? { cornerRadiusUnresolved: true } : {},
483
+ ...clearance ? { clearance } : {},
484
+ ...Object.keys(toolFit).length > 0 ? { toolFitPath } : {},
485
+ ...sharp ? { sharpCorners: pinchCentres(sheet.pinchPoints) } : {},
486
+ ...cramped ? { noToolFits: true } : {},
487
+ ...ballOnly ? { ballOnly: true } : {},
488
+ ...threading ? { threading } : {},
489
+ ...undercut ? { undercut } : {}
490
+ };
491
+ };
492
+
330
493
  // src/model/feature-details.ts
331
494
  import { MM_PER_INCH } from "@toolpath/tool-support";
332
495
  var mm = (value) => `${trim(value, 4)} mm`;
@@ -375,6 +538,11 @@ var maxBallDiameter = (sheet) => {
375
538
  };
376
539
  var ROW_ORDER = [
377
540
  "featureDepth",
541
+ "undercutWidth",
542
+ "floorWidth",
543
+ "openingWidth",
544
+ "undercutDepth",
545
+ "taper",
378
546
  "diameter",
379
547
  "cornerRadius",
380
548
  "filletRadius",
@@ -383,6 +551,8 @@ var ROW_ORDER = [
383
551
  "thread",
384
552
  "bevelAngle",
385
553
  "finishTool",
554
+ "undercutCutter",
555
+ "maxShaft",
386
556
  "pinch",
387
557
  "fitCornerRadius",
388
558
  "maxBull",
@@ -406,6 +576,7 @@ var featureMeasurements = ({
406
576
  });
407
577
  const sheet = sheets[feature.tag.toLowerCase()];
408
578
  const rows = [];
579
+ const undercut = sheet?.undercut;
409
580
  const top = sheet ? partTop(features, sheets, feature.machiningDirection) : null;
410
581
  const bottom = sheet?.zMin;
411
582
  const topFrom = top === null ? void 0 : features.find((each) => sheets[each.tag.toLowerCase()]?.extendedZMax === top);
@@ -419,7 +590,7 @@ var featureMeasurements = ({
419
590
  derivation: [topLine, `Top \u2212 zMin = ${mm(top)} \u2212 ${mm(bottom)} = ${mm(top - bottom)}`]
420
591
  });
421
592
  }
422
- if (sheet?.zMax !== void 0 && bottom !== void 0) {
593
+ if (sheet?.zMax !== void 0 && bottom !== void 0 && !(undercut && sheet?.kind === "Tslot")) {
423
594
  rows.push({
424
595
  key: "featureDepth",
425
596
  label: "Feature depth",
@@ -435,7 +606,7 @@ var featureMeasurements = ({
435
606
  derivation: [`facts.diameter = ${mm(sheet.diameter)}`]
436
607
  });
437
608
  }
438
- if (sheet?.cornerRadius !== void 0) {
609
+ if (sheet?.cornerRadius !== void 0 && !undercut) {
439
610
  const radius = length(sheet.cornerRadius);
440
611
  rows.push({
441
612
  key: "cornerRadius",
@@ -449,7 +620,7 @@ var featureMeasurements = ({
449
620
  ]
450
621
  });
451
622
  }
452
- if (!sheet?.sharpCorners) {
623
+ if (!sheet?.sharpCorners && !undercut) {
453
624
  const clearance = sheet?.pinchDiameter ?? (sheet?.cornerRadius !== void 0 ? 2 * sheet.cornerRadius : void 0);
454
625
  if (clearance !== void 0) {
455
626
  const width = length(clearance);
@@ -479,6 +650,7 @@ var featureMeasurements = ({
479
650
  });
480
651
  }
481
652
  }
653
+ if (sheet && undercut) rows.push(...undercutRows(sheet, undercut, top, length));
482
654
  const surface = SURFACE_TYPES.has(feature.featureType);
483
655
  if (surface && sheet) {
484
656
  const diameter = sheet.fitToolDiameter;
@@ -544,12 +716,12 @@ var featureMeasurements = ({
544
716
  });
545
717
  }
546
718
  }
547
- const ld = featureLd(features, feature, sheets);
719
+ const ld = undercut ? null : featureLd(features, feature, sheets);
548
720
  if (ld && top !== null && bottom !== void 0) {
549
721
  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
722
  const ratioOf = (depth) => {
551
- const ratio = (depth / ld.across).toFixed(3);
552
- return ld.atLeast ? `\u2265 ${ratio}` : ratio;
723
+ const ratio2 = (depth / ld.across).toFixed(3);
724
+ return ld.atLeast ? `\u2265 ${ratio2}` : ratio2;
553
725
  };
554
726
  if (ld.drilling) {
555
727
  rows.push({
@@ -641,141 +813,173 @@ var featureMeasurements = ({
641
813
  }
642
814
  return rows.sort((a, b) => ROW_ORDER.indexOf(a.key) - ROW_ORDER.indexOf(b.key));
643
815
  };
644
-
645
- // src/model/pinch.ts
646
- var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
647
- var recordOf = (value) => typeof value === "object" && value !== null ? value : {};
648
- var clearanceFit = (datasheet) => {
649
- const sheet = recordOf(datasheet);
650
- const facts = recordOf(sheet.facts);
651
- const chamfer = facts.kind === "Chamfer";
652
- const band = recordOf(chamfer ? recordOf(facts.three).cd : facts.cd);
653
- const tolerance = recordOf(sheet.toleranceBand);
654
- const strict = asNumber(recordOf(band.ignore).min);
655
- if (strict === void 0) return void 0;
656
- const bandPath = chamfer ? "facts.three.cd" : "facts.cd";
657
- const loose = asNumber(recordOf(band.deviate).min);
658
- const atStrict = asNumber(tolerance.atolIgnore);
659
- const atLoose = asNumber(tolerance.atolDeviate);
660
- const source = { bandPath, strict, loose, atStrict, atLoose };
661
- if (loose === void 0 || atStrict === void 0 || atLoose === void 0 || atLoose <= atStrict)
662
- return { ...source, slack: 0, resolved: false };
663
- const growth = (loose - strict) / (atLoose - atStrict);
664
- const slack = growth > 0 ? Math.min(growth * atStrict, strict) : 0;
665
- return { ...source, slack, resolved: true };
666
- };
667
- var readPinch = (datasheet) => {
668
- const sheet = recordOf(datasheet);
669
- const zMin = asNumber(sheet.zMin);
670
- const zMax = asNumber(sheet.zMax);
671
- if (zMin === void 0 || zMax === void 0 || !Array.isArray(sheet.pinchPoints)) return null;
672
- const fit = clearanceFit(datasheet);
673
- const slack = fit?.slack ?? 0;
674
- const discs = sheet.pinchPoints.flatMap((point) => {
675
- const centre = recordOf(recordOf(point).center);
676
- const x = asNumber(centre.x);
677
- const y = asNumber(centre.y);
678
- const diameter = asNumber(recordOf(point).diameter);
679
- return x === void 0 || y === void 0 || diameter === void 0 || diameter < 0 ? [] : [{ x, y, diameter: Math.max(diameter - slack, 0) }];
680
- });
681
- if (discs.length === 0) return null;
682
- return { discs, zMin, zMax, ...fit && !fit.resolved ? { unresolved: true } : {} };
683
- };
684
-
685
- // src/model/feature-sheet.ts
686
- var SHARP_MM = 0.1;
687
- var SHARP_RADIUS_MM = 0.01;
688
- var PINCH_TOOL_MM = 0.01;
689
- var asNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
690
- var recordOf2 = (value) => typeof value === "object" && value !== null ? value : {};
691
- var threadingOf = (facts) => {
692
- const spec = recordOf2(recordOf2(facts.threading).spec);
693
- const basicDiameter = asNumber2(spec.basicDiameter);
694
- const threadPitch = asNumber2(spec.threadPitch);
695
- if (basicDiameter === void 0 || threadPitch === void 0 || threadPitch <= 0) return void 0;
696
- return { basicDiameter, threadPitch };
697
- };
698
- var noToolFits = (band) => {
699
- const { max } = band;
700
- if (typeof max === "string") return /^-inf/i.test(max.trim());
701
- return typeof max === "number" && (max === Number.NEGATIVE_INFINITY || Number.isFinite(max) && max < SHARP_MM);
702
- };
703
- var insideCornerRadius = (fit) => Math.max((fit.strict - fit.slack) / 2, 0);
704
- var pinchCentres = (points) => Array.isArray(points) ? points.flatMap((point) => {
705
- const centre = recordOf2(recordOf2(point).center);
706
- const x = asNumber2(centre.x);
707
- const y = asNumber2(centre.y);
708
- return x === void 0 || y === void 0 ? [] : [{ x, y }];
709
- }) : [];
710
- var ZERO_MM = 5e-5;
711
- var isCutterlessTslot = (datasheet) => {
712
- const facts = recordOf2(recordOf2(datasheet).facts);
713
- if (facts.kind !== "Tslot") return false;
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;
816
+ var undercutRows = (sheet, undercut, top, length) => {
817
+ const rows = [];
818
+ const tslot = sheet.kind === "Tslot";
819
+ const { zMin, zMax } = sheet;
820
+ const { floorWidth: floor, topOpeningWidth: opening } = undercut;
821
+ const angle = undercut.taperDeg !== void 0 ? `${trim(undercut.taperDeg, 3)}\xB0` : void 0;
822
+ if (tslot && zMin !== void 0 && zMax !== void 0) {
823
+ rows.push({
824
+ key: "undercutWidth",
825
+ label: "Undercut width",
826
+ ...length(zMax - zMin),
827
+ derivation: [
828
+ "The groove\u2019s height along the tool: the widest cutter it takes in one pass.",
829
+ `zMax \u2212 zMin = ${mm(zMax)} \u2212 ${mm(zMin)} = ${mm(zMax - zMin)}`
830
+ ]
831
+ });
832
+ }
833
+ if (floor !== void 0) {
834
+ rows.push({
835
+ key: "floorWidth",
836
+ label: "Floor width",
837
+ ...length(floor),
838
+ derivation: ["The widest clearance over the floor", `facts.floorWidth = ${mm(floor)}`]
839
+ });
840
+ }
841
+ if (opening !== void 0) {
842
+ rows.push({
843
+ key: "openingWidth",
844
+ label: "Opening width",
845
+ ...length(opening),
846
+ derivation: [
847
+ "The clearance through the opening at the top of the groove",
848
+ `facts.topOpeningWidth = ${mm(opening)}`
849
+ ]
850
+ });
851
+ }
852
+ let depth = undercut.undercutDepth;
853
+ let depthLine = depth !== void 0 ? `facts.undercutDepth = ${mm(depth)}` : "";
854
+ if (depth === void 0 && floor !== void 0 && opening !== void 0) {
855
+ if (floor > opening && !undercut.isExternal) {
856
+ depth = (floor - opening) / 2;
857
+ depthLine = `(facts.floorWidth \u2212 facts.topOpeningWidth) / 2 = (${mm(floor)} \u2212 ${mm(opening)}) / 2 = ${mm(depth)}`;
858
+ }
859
+ }
860
+ if (depth !== void 0) {
861
+ rows.push({
862
+ key: "undercutDepth",
863
+ label: "Undercut depth",
864
+ ...length(depth),
865
+ derivation: [
866
+ "How far the groove runs back from its opening: what the cutter\u2019s head reaches past its shaft.",
867
+ depthLine
868
+ ]
869
+ });
870
+ }
871
+ if (angle) {
872
+ rows.push({
873
+ key: "taper",
874
+ label: "Dovetail angle",
875
+ value: `${angle} from the tool axis`,
876
+ derivation: [`facts.taperDeg = ${angle}, between the overhanging wall and the tool axis`]
877
+ });
878
+ }
879
+ const cutter = (value, derivation, alt) => ({
880
+ key: "undercutCutter",
881
+ label: "Max cutting diameter",
882
+ milling: true,
883
+ value,
884
+ ...alt !== void 0 ? { alt } : {},
885
+ derivation
719
886
  });
720
- };
721
- var isSharpCorneredFillet = (featureType, datasheet) => {
722
- if (typeof featureType !== "string" || !featureType.toLowerCase().split("_").includes("fillet"))
723
- return false;
724
- const facts = recordOf2(recordOf2(datasheet).facts);
725
- return facts.hasSharpCorner === true || recordOf2(facts.three).hasSharpCorner === true;
726
- };
727
- var isNoReading = (featureType, datasheet) => isCutterlessTslot(datasheet) || isSharpCorneredFillet(featureType, datasheet);
728
- var featureSheet = (datasheet) => {
729
- const sheet = recordOf2(datasheet);
730
- const facts = recordOf2(sheet.facts);
731
- const kind = typeof facts.kind === "string" ? facts.kind : void 0;
732
- const band = recordOf2(kind === "Chamfer" ? recordOf2(facts.three).cd : facts.cd);
733
- const ignoreBand = recordOf2(band.ignore);
734
- const fit = clearanceFit(datasheet);
735
- const fields = {
736
- kind,
737
- zMin: asNumber2(sheet.zMin),
738
- zMax: asNumber2(sheet.zMax),
739
- extendedZMax: asNumber2(sheet.extendedZMax),
740
- maxTool: asNumber2(ignoreBand.min),
741
- diameter: kind === "Hole" ? asNumber2(facts.diameter) : void 0,
742
- tipAngle: kind === "Hole" ? asNumber2(facts.fullConeDeg) : void 0,
743
- filletRadius: asNumber2(facts.filletRadius),
744
- // A bevel's angle wherever the facts carry one: a chamfer's, and any other kind that bevels.
745
- chamferAngle: asNumber2(recordOf2(facts.bevel).angleDeg)
746
- };
747
- const toolFitPath = facts.toolFit !== void 0 ? "facts.toolFit" : "facts.three.toolFit";
748
- const toolFit = recordOf2(facts.toolFit ?? recordOf2(facts.three).toolFit);
749
- fields.fitToolDiameter = asNumber2(toolFit.toolDiameter);
750
- fields.fitCornerRadius = asNumber2(toolFit.cornerRadius);
751
- const ballOnly = (facts.useOnlyBallToolsForFinish ?? recordOf2(facts.three).useOnlyBallToolsForFinish) === true;
752
- const measured = Object.fromEntries(
753
- Object.entries(fields).filter(([, value]) => value !== void 0)
754
- );
755
- const cramped = Object.keys(ignoreBand).length > 0 && noToolFits(ignoreBand);
756
- const walled = kind !== "Hole" && sheet.hasWall === true;
757
- const clearance = walled ? fit : void 0;
758
- const cornerRadius = clearance ? insideCornerRadius(clearance) : void 0;
759
- const sharp = cornerRadius !== void 0 && cornerRadius < SHARP_RADIUS_MM && !cramped;
760
- const threading = kind === "Hole" ? threadingOf(facts) : void 0;
761
- const pinched = (readPinch(datasheet)?.discs ?? []).filter(
762
- (disc) => disc.diameter >= PINCH_TOOL_MM
763
- );
764
- return {
765
- ...measured,
766
- ...pinched.length > 0 ? {
767
- pinchDiameter: Math.min(...pinched.map((disc) => disc.diameter)),
768
- pinchPlaces: pinched.length
769
- } : {},
770
- ...cornerRadius !== void 0 ? { cornerRadius } : {},
771
- ...clearance && !clearance.resolved ? { cornerRadiusUnresolved: true } : {},
772
- ...clearance ? { clearance } : {},
773
- ...Object.keys(toolFit).length > 0 ? { toolFitPath } : {},
774
- ...sharp ? { sharpCorners: pinchCentres(sheet.pinchPoints) } : {},
775
- ...cramped ? { noToolFits: true } : {},
776
- ...ballOnly ? { ballOnly: true } : {},
777
- ...threading ? { threading } : {}
778
- };
887
+ if (undercut.unmeasured) {
888
+ return [
889
+ ...rows,
890
+ cutter("Not measured", [
891
+ "The Engine could not measure the groove (facts.isInvalidGeometry or facts.cd.measurementFailed), so its figures offer no cutter."
892
+ ])
893
+ ];
894
+ }
895
+ if (sheet.noToolFits) {
896
+ return [
897
+ ...rows,
898
+ cutter("None fits", [
899
+ "facts.cd.ignore.max is next to nothing: no cutter fits anywhere in the groove."
900
+ ])
901
+ ];
902
+ }
903
+ const band = sheet.maxTool !== void 0 && sheet.maxTool > 0 ? sheet.maxTool : void 0;
904
+ const dropIn = undercut.isClosed ? undercut.maxEntry : void 0;
905
+ const head = band === void 0 ? void 0 : Math.min(band, dropIn ?? Infinity);
906
+ const opensTo = undercut.maxEntry ?? opening;
907
+ const behind = head !== void 0 && depth !== void 0 ? head - 2 * depth : void 0;
908
+ const limits = [opensTo, behind].filter((each) => each !== void 0);
909
+ const shaft = limits.length > 0 ? Math.max(Math.min(...limits), 0) : void 0;
910
+ const shaftLines = [
911
+ "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.",
912
+ ...opensTo !== void 0 ? [`Opening: ${tslot ? "facts.maxEntryCd" : "facts.topOpeningWidth"} = ${mm(opensTo)}`] : [],
913
+ ...behind !== void 0 && head !== void 0 && depth !== void 0 ? [
914
+ `Reach: max cutting diameter \u2212 2 \xD7 undercut depth = ${mm(head)} \u2212 2 \xD7 ${mm(depth)} = ${mm(behind)}`
915
+ ] : [],
916
+ ...shaft !== void 0 ? [`Max shaft = ${mm(shaft)}`] : []
917
+ ];
918
+ if (head !== void 0 && band !== void 0) {
919
+ const headLines = [
920
+ "The biggest head that reaches every point of the groove.",
921
+ `facts.cd.ignore.min = ${mm(band)}`,
922
+ ...dropIn !== void 0 ? [
923
+ `A closed slot: the head comes down through the opening above it, facts.maxEntryCd = ${mm(dropIn)}`
924
+ ] : [],
925
+ `\u2300 = ${mm(head)}`
926
+ ];
927
+ if (head < SHARP_MM || shaft !== void 0 && shaft < SHARP_MM) {
928
+ return [
929
+ ...rows,
930
+ cutter("None fits", [
931
+ ...headLines,
932
+ ...shaft !== void 0 ? shaftLines : [],
933
+ "No head that fits the groove leaves room for a shaft to carry it."
934
+ ])
935
+ ];
936
+ }
937
+ const size = length(head);
938
+ const filletRadius = sheet.filletRadius !== void 0 && sheet.filletRadius > 0 ? sheet.filletRadius : void 0;
939
+ const fillet = filletRadius !== void 0 ? length(filletRadius) : void 0;
940
+ const describe = (unit2) => [`\u2300 ${size[unit2]}`, ...angle ? [angle] : [], ...fillet ? [`R ${fillet[unit2]}`] : []].join(
941
+ " \xB7 "
942
+ );
943
+ rows.push(
944
+ cutter(
945
+ describe("value"),
946
+ [
947
+ ...headLines,
948
+ ...angle ? [`Angle = facts.taperDeg = ${angle}, from the tool axis`] : [],
949
+ ...filletRadius !== void 0 ? [`R = facts.filletRadius = ${mm(filletRadius)}, the blend the head\u2019s corners cut`] : []
950
+ ],
951
+ describe("alt")
952
+ )
953
+ );
954
+ if (top !== null && zMin !== void 0) {
955
+ const reach = top - zMin;
956
+ const ratio2 = (reach / head).toFixed(3);
957
+ rows.push({
958
+ key: "topLd",
959
+ label: "L/D to top of part",
960
+ milling: true,
961
+ value: ratio2,
962
+ derivation: [
963
+ `L = depth below top of part = ${mm(reach)}`,
964
+ `D = max cutting diameter = ${mm(head)}`,
965
+ `L / D = ${mm(reach)} / ${mm(head)} = ${ratio2}`
966
+ ]
967
+ });
968
+ }
969
+ }
970
+ if (shaft !== void 0 || tslot) {
971
+ const size = shaft !== void 0 ? length(shaft) : void 0;
972
+ rows.push({
973
+ key: "maxShaft",
974
+ label: "Max shaft diameter",
975
+ milling: true,
976
+ ...size ? { value: `\u2300 ${size.value}`, alt: `\u2300 ${size.alt}` } : { value: "No limit" },
977
+ derivation: size ? shaftLines : [
978
+ "facts.maxEntryCd is not reported and the head or undercut depth is not known: nothing limits the shaft."
979
+ ]
980
+ });
981
+ }
982
+ return rows;
779
983
  };
780
984
 
781
985
  // src/model/pinch-mark.ts
@@ -1009,14 +1213,217 @@ var placePinchTool = (mark, triangles, direction, origin = { x: 0, y: 0, z: 0 })
1009
1213
  across: asVec(across)
1010
1214
  };
1011
1215
  };
1216
+
1217
+ // src/model/turned-check.ts
1218
+ var regionsOf = (feature) => Array.isArray(feature.regionIdxs) ? feature.regionIdxs : [];
1219
+ var stretchKey = (feature) => {
1220
+ const regions = regionsOf(feature);
1221
+ return regions.length === 0 ? `tag:${feature.featureTag}` : `${feature.featureType}:${[...regions].sort((a, b) => a - b).join(",")}`;
1222
+ };
1223
+ var isApproach = (value) => value === "PlusZ" || value === "MinusZ";
1224
+ var turnedStretches = (features) => {
1225
+ const groups = /* @__PURE__ */ new Map();
1226
+ for (const feature of features) {
1227
+ const key = stretchKey(feature);
1228
+ groups.set(key, [...groups.get(key) ?? [], feature]);
1229
+ }
1230
+ const merged = [...groups.values()].flatMap((copies) => {
1231
+ const [first] = copies;
1232
+ if (!first) return [];
1233
+ const approaches = [...new Set(copies.map((each) => each.approach).filter(isApproach))];
1234
+ const only = approaches.length === 1 ? approaches[0] : void 0;
1235
+ const z = only === "PlusZ" ? 1 : only === "MinusZ" ? -1 : 0;
1236
+ return [{ first, copies, approaches, machiningDirection: { x: 0, y: 0, z } }];
1237
+ });
1238
+ const numbered = dfmFeatures(
1239
+ merged.map(({ first, machiningDirection }) => ({
1240
+ featureTag: first.featureTag,
1241
+ featureType: first.featureType,
1242
+ machiningDirection,
1243
+ regionIdxs: regionsOf(first)
1244
+ }))
1245
+ );
1246
+ return merged.flatMap(({ copies, approaches }, at2) => {
1247
+ const feature = numbered[at2];
1248
+ return feature ? [{ ...feature, copies, approaches }] : [];
1249
+ });
1250
+ };
1251
+ var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1252
+ var finite = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1253
+ var ratio = (top, bottom) => top !== null && bottom !== null && bottom > 0 ? top / bottom : null;
1254
+ var narrowestStep = (facts) => {
1255
+ const steps = Array.isArray(facts.steps) ? facts.steps : [];
1256
+ const diameters = steps.map((step) => finite(record(step)?.diameter)).filter((each) => each !== null && each > 0);
1257
+ return diameters.length > 0 ? Math.min(...diameters) : null;
1258
+ };
1259
+ var threadSpec = (facts, field) => finite(record(facts.threadSpec)?.[field]) ?? finite(record(record(facts.thread)?.threadSpec)?.[field]);
1260
+ var partEnds = (stretches) => {
1261
+ const zs = stretches.flatMap(
1262
+ (stretch) => stretch.copies.flatMap((copy) => {
1263
+ const sheet = record(copy.datasheet);
1264
+ return [finite(sheet?.zMin), finite(sheet?.zMax)].filter(
1265
+ (each) => each !== null
1266
+ );
1267
+ })
1268
+ );
1269
+ return zs.length > 0 ? { low: Math.min(...zs), high: Math.max(...zs) } : null;
1270
+ };
1271
+ var turnedLd = (sheet, facts, kind, approach, ends) => {
1272
+ if (kind === "AxialHole") return ratio(finite(facts.depth), narrowestStep(facts));
1273
+ const inside = ID_KINDS;
1274
+ if (!kind || !inside.includes(kind) || !ends || !approach) return null;
1275
+ const zMin = finite(sheet.zMin);
1276
+ const zMax = finite(sheet.zMax);
1277
+ const reach = approach === "PlusZ" ? zMin === null ? null : ends.high - zMin : zMax === null ? null : zMax - ends.low;
1278
+ const rMin = finite(sheet.rMin);
1279
+ const bore = finite(sheet.entryDiameter) ?? (rMin === null ? null : 2 * rMin);
1280
+ return ratio(reach, bore);
1281
+ };
1282
+ var HAIR = 1e-3;
1283
+ var FLAT_CONE = 179.5;
1284
+ var copyMeasure = (metric, copy, ends) => {
1285
+ const sheet = record(copy.datasheet);
1286
+ if (!sheet) return null;
1287
+ const facts = record(sheet.facts) ?? {};
1288
+ const kind = typeof facts.kind === "string" ? facts.kind : kindOfType(copy.featureType);
1289
+ const kinds = metric.kinds;
1290
+ if (kinds && !(kind && kinds.includes(kind))) return null;
1291
+ const doubled = (value) => {
1292
+ const radius = finite(value);
1293
+ return radius === null ? null : 2 * radius;
1294
+ };
1295
+ switch (metric.key) {
1296
+ case "maxDiameter":
1297
+ return doubled(sheet.rMax);
1298
+ case "minDiameter":
1299
+ return doubled(sheet.rMin);
1300
+ case "length": {
1301
+ const zMin = finite(sheet.zMin);
1302
+ const zMax = finite(sheet.zMax);
1303
+ return zMin === null || zMax === null || zMax - zMin < HAIR ? null : zMax - zMin;
1304
+ }
1305
+ case "ld":
1306
+ return turnedLd(sheet, facts, kind, copy.approach ?? null, ends);
1307
+ case "grooveWidth":
1308
+ return finite(facts.width);
1309
+ case "grooveDepth":
1310
+ case "threadDepth":
1311
+ case "holeDepth":
1312
+ return finite(facts.depth);
1313
+ case "grooveAspect":
1314
+ return ratio(finite(facts.depth), finite(facts.width));
1315
+ case "threadPitch":
1316
+ return finite(facts.pitch) ?? threadSpec(facts, "threadPitch");
1317
+ case "threadDiameter":
1318
+ return threadSpec(facts, "basicDiameter");
1319
+ case "holeDiameter":
1320
+ return narrowestStep(facts);
1321
+ case "holeSteps":
1322
+ return Array.isArray(facts.steps) ? facts.steps.length : null;
1323
+ case "flatBottom": {
1324
+ if (facts.bottom === void 0) return 0;
1325
+ const cone = finite(record(facts.bottom)?.coneDeg);
1326
+ return cone === null ? null : cone >= FLAT_CONE ? 1 : 0;
1327
+ }
1328
+ // Zero is a figure here: a sharp corner, which a "radius ≤" rule is most of all about.
1329
+ case "cornerRadius":
1330
+ return finite(sheet.minConcaveRadius);
1331
+ case "sharpCorner": {
1332
+ const radius = finite(sheet.minConcaveRadius);
1333
+ return radius === null ? 0 : radius <= HAIR ? 1 : 0;
1334
+ }
1335
+ // Absent where nothing stands between the feature and its end: nothing to pass, so clean.
1336
+ case "entryDiameter":
1337
+ return finite(sheet.entryDiameter);
1338
+ case "axialTool":
1339
+ return typeof sheet.mounting === "string" ? sheet.mounting === "Axial" ? 1 : 0 : null;
1340
+ case "tolerance":
1341
+ return finite(record(sheet.toleranceBand)?.atolMax);
1342
+ default:
1343
+ return null;
1344
+ }
1345
+ };
1346
+ var kindOfType = (featureType) => featureType.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
1347
+ var stretchFacts = (stretch) => stretch.copies.map((copy) => record(record(copy.datasheet)?.facts)).find((each) => each !== void 0);
1348
+ var stretchIs = (subject, stretch) => {
1349
+ const type = stretch.featureType.toLowerCase();
1350
+ if (subject.startsWith(TYPE_SUBJECT)) return type === subject.slice(TYPE_SUBJECT.length);
1351
+ const facts = stretchFacts(stretch);
1352
+ const kind = typeof facts?.kind === "string" ? facts.kind : kindOfType(type);
1353
+ const hole = kind === "AxialHole";
1354
+ switch (subject) {
1355
+ case "any":
1356
+ return true;
1357
+ case "thread":
1358
+ return kind === "OdThread" || kind === "IdThread" || hole && record(facts?.thread) !== void 0;
1359
+ case "throughHole":
1360
+ return hole && facts?.through === true;
1361
+ case "blindHole":
1362
+ return hole && facts?.through === false;
1363
+ case "threadedHole":
1364
+ return hole && record(facts?.thread) !== void 0;
1365
+ default: {
1366
+ const kinds = subjectInfo(subject, TURNING_RULES)?.kinds;
1367
+ return kinds !== void 0 && kinds.includes(kind);
1368
+ }
1369
+ }
1370
+ };
1371
+ var stretchHit = (stretch, rule, metric, ends) => {
1372
+ if (metric.quantity === "presence") return { rule, metric, value: 1 };
1373
+ const measured = stretch.copies.filter((copy) => record(copy.datasheet) !== void 0);
1374
+ if (measured.length === 0) return null;
1375
+ const values = [];
1376
+ for (const copy of measured) {
1377
+ const value2 = copyMeasure(metric, copy, ends);
1378
+ if (value2 === null || !breaks(rule, metric.quantity, value2)) return null;
1379
+ values.push(value2);
1380
+ }
1381
+ const value = worseUpward(rule, metric) ? Math.min(...values) : Math.max(...values);
1382
+ return { rule, metric, value };
1383
+ };
1384
+ var checkTurnedPart = (stretches, rules) => {
1385
+ const ends = partEnds(stretches);
1386
+ const hitsByTag = new Map(
1387
+ stretches.map((stretch) => [
1388
+ stretch.tag.toLowerCase(),
1389
+ rules.flatMap((rule) => {
1390
+ const metric = metricInfo(rule.metric, TURNING_RULES);
1391
+ if (!metric || !stretchIs(rule.subject, stretch)) return [];
1392
+ const hit = stretchHit(stretch, rule, metric, ends);
1393
+ return hit ? [hit] : [];
1394
+ })
1395
+ ])
1396
+ );
1397
+ const required = new Set(stretches.map((stretch) => stretch.tag.toLowerCase()));
1398
+ const paintedFaces = /* @__PURE__ */ new Map();
1399
+ for (const stretch of stretches) {
1400
+ const color = worstColor(hitsByTag.get(stretch.tag.toLowerCase()));
1401
+ if (!color) continue;
1402
+ for (const region of stretch.regionIdxs) {
1403
+ const painted = paintedFaces.get(region);
1404
+ if (!painted || compareColors(color, painted.color) < 0)
1405
+ paintedFaces.set(region, { tag: stretch.tag, color });
1406
+ }
1407
+ }
1408
+ return {
1409
+ hitsByTag,
1410
+ required,
1411
+ byRule: ruleResults(rules, stretches, hitsByTag, required),
1412
+ paintedFaces,
1413
+ paintedWhole: /* @__PURE__ */ new Map()
1414
+ };
1415
+ };
1012
1416
  export {
1013
1417
  CRITICAL_COLOR,
1014
1418
  FILE_KIND,
1419
+ MILLING_RULES,
1015
1420
  PINCH_TOOL_MM,
1016
1421
  RULE_COLORS,
1422
+ TURNING_RULES,
1017
1423
  WARNING_COLOR,
1018
1424
  brokenRules,
1019
1425
  checkPart,
1426
+ checkTurnedPart,
1020
1427
  compareColors,
1021
1428
  describeRule,
1022
1429
  dfmFeatures,
@@ -1043,5 +1450,7 @@ export {
1043
1450
  sameDepth,
1044
1451
  serializeRuleSet,
1045
1452
  trimmedCurve,
1453
+ turnedStretches,
1454
+ turnedTypeLabel,
1046
1455
  withSurroundedFaces
1047
1456
  };