@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.
- package/README.md +47 -5
- package/dist/{chunk-5MPHT7BC.js → chunk-4IRNLDE6.js} +548 -142
- package/dist/{feature-record-BUoFFjN6.d.ts → feature-record-CfjgDCJU.d.ts} +195 -99
- package/dist/index.d.ts +18 -2
- package/dist/index.js +97 -76
- package/dist/model/index.d.ts +61 -15
- package/dist/model/index.js +575 -166
- package/package.json +2 -2
|
@@ -1,5 +1,85 @@
|
|
|
1
|
+
// src/model/geometry.ts
|
|
2
|
+
var dfmFeatures = (features) => {
|
|
3
|
+
const read = features.map(
|
|
4
|
+
({ featureTag, featureType, machiningDirection: { x, y, z }, regionIdxs }) => ({
|
|
5
|
+
tag: featureTag,
|
|
6
|
+
featureType,
|
|
7
|
+
machiningDirection: { x, y, z },
|
|
8
|
+
regionIdxs
|
|
9
|
+
})
|
|
10
|
+
);
|
|
11
|
+
const directions = [];
|
|
12
|
+
read.forEach(({ machiningDirection }) => {
|
|
13
|
+
if (!directions.some((each) => sameDirection(each, machiningDirection)))
|
|
14
|
+
directions.push(machiningDirection);
|
|
15
|
+
});
|
|
16
|
+
const numbered = directions.flatMap(
|
|
17
|
+
(direction) => read.filter((feature) => sameDirection(feature.machiningDirection, direction))
|
|
18
|
+
);
|
|
19
|
+
const numberOf = new Map(numbered.map((feature, index) => [feature, index + 1]));
|
|
20
|
+
return read.map((feature) => ({ ...feature, number: numberOf.get(feature) ?? 0 }));
|
|
21
|
+
};
|
|
22
|
+
var TOLERANCE = 1e-6;
|
|
23
|
+
var sameDirection = (a, b) => Math.abs(a.x - b.x) < TOLERANCE && Math.abs(a.y - b.y) < TOLERANCE && Math.abs(a.z - b.z) < TOLERANCE;
|
|
24
|
+
var featureTypeLabel = (featureType) => {
|
|
25
|
+
const words = featureType.replace(/_/g, " ").trim();
|
|
26
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
27
|
+
};
|
|
28
|
+
var partTop = (features, sheets, direction) => {
|
|
29
|
+
let top = null;
|
|
30
|
+
for (const other of features) {
|
|
31
|
+
const zMax = sheets[other.tag.toLowerCase()]?.extendedZMax;
|
|
32
|
+
if (zMax !== void 0 && sameDirection(other.machiningDirection, direction)) {
|
|
33
|
+
top = top === null ? zMax : Math.max(top, zMax);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return top;
|
|
37
|
+
};
|
|
38
|
+
var FLAT_TIP_DEG = 0.5;
|
|
39
|
+
var isDrilled = (sheet) => sheet?.diameter !== void 0 && sheet.diameter > 0 && !(sheet.tipAngle !== void 0 && sheet.tipAngle >= 180 - FLAT_TIP_DEG);
|
|
40
|
+
var featureLd = (features, feature, sheets) => {
|
|
41
|
+
const sheet = sheets[feature.tag.toLowerCase()];
|
|
42
|
+
const top = sheet ? partTop(features, sheets, feature.machiningDirection) : null;
|
|
43
|
+
const bottom = sheet?.zMin;
|
|
44
|
+
if (!sheet || top === null || bottom === void 0) return null;
|
|
45
|
+
const drilling = isDrilled(sheet);
|
|
46
|
+
const [basis, across] = sheet.diameter !== void 0 ? ["bore", sheet.diameter] : sheet.pinchDiameter !== void 0 ? ["pinch", sheet.pinchDiameter] : [
|
|
47
|
+
"radius",
|
|
48
|
+
sheet.sharpCorners || sheet.cornerRadius === void 0 ? void 0 : 2 * sheet.cornerRadius
|
|
49
|
+
];
|
|
50
|
+
if (across === void 0 || across <= 0) return null;
|
|
51
|
+
const atLeast = basis !== "bore" && sheet.cornerRadiusUnresolved === true;
|
|
52
|
+
return {
|
|
53
|
+
ratio: (top - bottom) / across,
|
|
54
|
+
...sheet.zMax !== void 0 ? { featureRatio: (sheet.zMax - bottom) / across } : {},
|
|
55
|
+
drilling,
|
|
56
|
+
atLeast,
|
|
57
|
+
basis,
|
|
58
|
+
across
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
1
62
|
// src/model/dfm-metrics.ts
|
|
2
63
|
import { MM_PER_INCH } from "@toolpath/tool-support";
|
|
64
|
+
|
|
65
|
+
// src/model/direction-label.ts
|
|
66
|
+
var EPSILON = 1e-6;
|
|
67
|
+
var trim = (value) => Number.parseFloat(value.toFixed(3)).toString();
|
|
68
|
+
var directionLabel = (direction) => {
|
|
69
|
+
const axes = [
|
|
70
|
+
["X", direction.x],
|
|
71
|
+
["Y", direction.y],
|
|
72
|
+
["Z", direction.z]
|
|
73
|
+
];
|
|
74
|
+
const nonZero = axes.filter(([, value]) => Math.abs(value) > EPSILON);
|
|
75
|
+
const [axis] = nonZero;
|
|
76
|
+
if (nonZero.length === 1 && axis && Math.abs(Math.abs(axis[1]) - 1) < EPSILON) {
|
|
77
|
+
return `${axis[1] > 0 ? "+" : "\u2212"}${axis[0]}`;
|
|
78
|
+
}
|
|
79
|
+
return axes.map(([, value]) => trim(value)).join(", ");
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/model/dfm-metrics.ts
|
|
3
83
|
var define = ({ start, path, ...spec }) => ({
|
|
4
84
|
...spec,
|
|
5
85
|
start: typeof start === "number" ? { mm: start, inch: start } : start,
|
|
@@ -988,12 +1068,26 @@ var QUICK_METRICS = [
|
|
|
988
1068
|
"undercutDepth",
|
|
989
1069
|
"tilted"
|
|
990
1070
|
];
|
|
991
|
-
var
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1071
|
+
var MILLING_RULES = {
|
|
1072
|
+
process: "milling",
|
|
1073
|
+
metrics: FEATURE_METRICS,
|
|
1074
|
+
subjects: FEATURE_SUBJECTS,
|
|
1075
|
+
quickSubjects: QUICK_SUBJECTS,
|
|
1076
|
+
quickMetrics: QUICK_METRICS,
|
|
1077
|
+
fallbackMetric: LD_METRIC,
|
|
1078
|
+
featureLabel: (feature) => ({
|
|
1079
|
+
name: featureTypeLabel(feature.featureType),
|
|
1080
|
+
detail: `from ${directionLabel(feature.machiningDirection)}`
|
|
1081
|
+
}),
|
|
1082
|
+
considerationsTitle: "Milling considerations",
|
|
1083
|
+
fileName: "dfm-rules.json"
|
|
1084
|
+
};
|
|
1085
|
+
var subjectInfo = (value, vocabulary = MILLING_RULES) => vocabulary.subjects.find((each) => each.value === value);
|
|
1086
|
+
var metricInfo = (key, vocabulary = MILLING_RULES) => vocabulary.metrics.find((each) => each.key === key);
|
|
1087
|
+
var metricsForSubject = (subject, vocabulary = MILLING_RULES) => {
|
|
1088
|
+
const kinds = subjectInfo(subject, vocabulary)?.kinds;
|
|
1089
|
+
if (!kinds) return vocabulary.metrics;
|
|
1090
|
+
return vocabulary.metrics.filter(
|
|
997
1091
|
(each) => !each.kinds || each.kinds.some((kind) => kinds.includes(kind))
|
|
998
1092
|
);
|
|
999
1093
|
};
|
|
@@ -1065,8 +1159,8 @@ var RETIRED_COLORS = {
|
|
|
1065
1159
|
"#68b688": "#30a46c",
|
|
1066
1160
|
"#6bb0b3": "#00a2c7"
|
|
1067
1161
|
};
|
|
1068
|
-
var subjectLabel = (value) => subjectInfo(value)?.label ?? value;
|
|
1069
|
-
var
|
|
1162
|
+
var subjectLabel = (value, vocabulary = MILLING_RULES) => subjectInfo(value, vocabulary)?.label ?? value;
|
|
1163
|
+
var trim2 = (value, places) => String(Number(value.toFixed(places)));
|
|
1070
1164
|
var SQUARE_MM_PER_SQUARE_INCH = MM_PER_INCH3 ** 2;
|
|
1071
1165
|
var quantityUnit = (quantity, units) => {
|
|
1072
1166
|
switch (quantity) {
|
|
@@ -1086,7 +1180,7 @@ var withUnit = (value, unit) => unit === "" ? value : unit === "\xB0" || unit ==
|
|
|
1086
1180
|
var toUnits = (quantity, value, units) => units !== "inch" ? value : quantity === "length" ? value / MM_PER_INCH3 : quantity === "area" ? value / SQUARE_MM_PER_SQUARE_INCH : value;
|
|
1087
1181
|
var toField = (quantity, value, units) => {
|
|
1088
1182
|
const shown = toUnits(quantity, value, units);
|
|
1089
|
-
return quantity === "length" ?
|
|
1183
|
+
return quantity === "length" ? trim2(shown, FIELD_LENGTH_PLACES[units]) : quantity === "count" ? trim2(shown, 0) : trim2(shown, 3);
|
|
1090
1184
|
};
|
|
1091
1185
|
var fromField = (metric, text, units) => {
|
|
1092
1186
|
const parsed = Number(text.trim());
|
|
@@ -1099,25 +1193,25 @@ var formatMeasured = (quantity, value, units) => {
|
|
|
1099
1193
|
const shown = toUnits(quantity, value, units);
|
|
1100
1194
|
switch (quantity) {
|
|
1101
1195
|
case "length":
|
|
1102
|
-
return units === "inch" ? `${
|
|
1196
|
+
return units === "inch" ? `${trim2(shown, LENGTH_PLACES.inch)}"` : `${trim2(shown, LENGTH_PLACES.mm)} mm`;
|
|
1103
1197
|
case "area":
|
|
1104
1198
|
return `${Number(shown.toFixed(3)).toLocaleString(void 0, { maximumFractionDigits: 3 })} ${quantityUnit("area", units)}`;
|
|
1105
1199
|
case "angle":
|
|
1106
|
-
return `${
|
|
1200
|
+
return `${trim2(shown, 3)}\xB0`;
|
|
1107
1201
|
case "percent":
|
|
1108
|
-
return `${
|
|
1202
|
+
return `${trim2(shown, 3)}%`;
|
|
1109
1203
|
case "count":
|
|
1110
|
-
return
|
|
1204
|
+
return trim2(shown, 0);
|
|
1111
1205
|
case "flag":
|
|
1112
1206
|
case "presence":
|
|
1113
1207
|
return "";
|
|
1114
1208
|
default:
|
|
1115
|
-
return
|
|
1209
|
+
return trim2(shown, 3);
|
|
1116
1210
|
}
|
|
1117
1211
|
};
|
|
1118
|
-
var describeRule = (rule, units) => {
|
|
1119
|
-
const info = metricInfo(rule.metric);
|
|
1120
|
-
const subject = subjectLabel(rule.subject);
|
|
1212
|
+
var describeRule = (rule, units, vocabulary = MILLING_RULES) => {
|
|
1213
|
+
const info = metricInfo(rule.metric, vocabulary);
|
|
1214
|
+
const subject = subjectLabel(rule.subject, vocabulary);
|
|
1121
1215
|
if (!info) return subject;
|
|
1122
1216
|
if (info.quantity === "presence") return `${subject}, ${info.label}`;
|
|
1123
1217
|
if (info.quantity === "flag") return `${subject} with ${info.label}`;
|
|
@@ -1126,8 +1220,8 @@ var describeRule = (rule, units) => {
|
|
|
1126
1220
|
return rule.op === "between" ? `${subject} with ${shown(rule.value)} ${lowOf(rule) === "gt" ? "<" : "\u2264"} ${info.label} ${opSymbol(highOf(rule))} ${shown(upperOf(rule))}` : `${subject} with ${info.label} ${opSymbol(rule.op)} ${shown(rule.value)}`;
|
|
1127
1221
|
};
|
|
1128
1222
|
var newRuleId = () => Math.random().toString(36).slice(2, 10);
|
|
1129
|
-
var makeRule = (subject, metricKey, units, color = WARNING_COLOR) => {
|
|
1130
|
-
const info = metricInfo(metricKey) ??
|
|
1223
|
+
var makeRule = (subject, metricKey, units, color = WARNING_COLOR, vocabulary = MILLING_RULES) => {
|
|
1224
|
+
const info = metricInfo(metricKey, vocabulary) ?? vocabulary.fallbackMetric;
|
|
1131
1225
|
return {
|
|
1132
1226
|
id: newRuleId(),
|
|
1133
1227
|
subject,
|
|
@@ -1141,16 +1235,16 @@ var upperOf = (rule) => rule.max ?? rule.value + Math.max(Math.abs(rule.value),
|
|
|
1141
1235
|
var lowOf = (rule) => rule.lowOp ?? "gte";
|
|
1142
1236
|
var highOf = (rule) => rule.highOp ?? "lt";
|
|
1143
1237
|
var withoutRange = ({ max: _max, lowOp: _low, highOp: _high, ...rest }) => rest;
|
|
1144
|
-
var withMetric = (rule, metricKey, units) => {
|
|
1145
|
-
const info = metricInfo(metricKey);
|
|
1238
|
+
var withMetric = (rule, metricKey, units, vocabulary = MILLING_RULES) => {
|
|
1239
|
+
const info = metricInfo(metricKey, vocabulary);
|
|
1146
1240
|
if (!info) return rule;
|
|
1147
1241
|
return { ...withoutRange(rule), metric: info.key, op: info.op, value: info.start[units] };
|
|
1148
1242
|
};
|
|
1149
1243
|
var withOp = (rule, op) => op === "between" ? { ...rule, op, max: upperOf(rule), lowOp: lowOf(rule), highOp: highOf(rule) } : { ...withoutRange(rule), op };
|
|
1150
|
-
var withSubject = (rule, subject, units) => {
|
|
1151
|
-
const metrics = metricsForSubject(subject);
|
|
1244
|
+
var withSubject = (rule, subject, units, vocabulary = MILLING_RULES) => {
|
|
1245
|
+
const metrics = metricsForSubject(subject, vocabulary);
|
|
1152
1246
|
const moved = { ...rule, subject };
|
|
1153
|
-
return metrics.some((each) => each.key === rule.metric) ? moved : withMetric(moved, (metrics[0] ??
|
|
1247
|
+
return metrics.some((each) => each.key === rule.metric) ? moved : withMetric(moved, (metrics[0] ?? vocabulary.fallbackMetric).key, units, vocabulary);
|
|
1154
1248
|
};
|
|
1155
1249
|
var breaks = (rule, quantity, value) => {
|
|
1156
1250
|
if (quantity === "presence") return true;
|
|
@@ -1171,7 +1265,7 @@ var breaks = (rule, quantity, value) => {
|
|
|
1171
1265
|
var worseUpward = (rule, metric) => rule.op === "gte" || rule.op === "gt" ? true : rule.op === "lte" || rule.op === "lt" ? false : metric.op !== "lte" && metric.op !== "lt";
|
|
1172
1266
|
var COLOR = /^#[0-9a-f]{6}$/i;
|
|
1173
1267
|
var isValue = (value, metric) => typeof value === "number" && Number.isFinite(value) && (value >= 0 || metric?.signed === true) && (metric?.quantity !== "count" || Number.isInteger(value));
|
|
1174
|
-
var parseRule = (raw) => {
|
|
1268
|
+
var parseRule = (raw, vocabulary) => {
|
|
1175
1269
|
if (!raw || typeof raw !== "object") return null;
|
|
1176
1270
|
const {
|
|
1177
1271
|
id,
|
|
@@ -1184,8 +1278,8 @@ var parseRule = (raw) => {
|
|
|
1184
1278
|
highOp,
|
|
1185
1279
|
color
|
|
1186
1280
|
} = raw;
|
|
1187
|
-
const info = typeof key === "string" ? metricInfo(key) : void 0;
|
|
1188
|
-
const valid = typeof id === "string" && typeof subject === "string" && subjectInfo(subject) !== void 0 && typeof key === "string" && info !== void 0 && OPS.includes(op) && isValue(value, info) && (op !== "between" || isValue(max, info)) && typeof color === "string" && COLOR.test(color);
|
|
1281
|
+
const info = typeof key === "string" ? metricInfo(key, vocabulary) : void 0;
|
|
1282
|
+
const valid = typeof id === "string" && typeof subject === "string" && subjectInfo(subject, vocabulary) !== void 0 && typeof key === "string" && info !== void 0 && OPS.includes(op) && isValue(value, info) && (op !== "between" || isValue(max, info)) && typeof color === "string" && COLOR.test(color);
|
|
1189
1283
|
if (!valid) return null;
|
|
1190
1284
|
const hex = color.toLowerCase();
|
|
1191
1285
|
return {
|
|
@@ -1202,7 +1296,7 @@ var parseRule = (raw) => {
|
|
|
1202
1296
|
color: RETIRED_COLORS[hex] ?? hex
|
|
1203
1297
|
};
|
|
1204
1298
|
};
|
|
1205
|
-
var parseRules = (raw) => Array.isArray(raw) ? raw.flatMap((each) => parseRule(each) ?? []) : null;
|
|
1299
|
+
var parseRules = (raw, vocabulary = MILLING_RULES) => Array.isArray(raw) ? raw.flatMap((each) => parseRule(each, vocabulary) ?? []) : null;
|
|
1206
1300
|
|
|
1207
1301
|
// src/model/hole-groups.ts
|
|
1208
1302
|
var holeSize = (sheets, tag) => {
|
|
@@ -1214,9 +1308,9 @@ var holeSize = (sheets, tag) => {
|
|
|
1214
1308
|
};
|
|
1215
1309
|
var SAME_DEPTH = 0.05;
|
|
1216
1310
|
var DEPTH_FLOOR = 0.01;
|
|
1217
|
-
var
|
|
1311
|
+
var TOLERANCE2 = 1e-6;
|
|
1218
1312
|
var sameDepth = (a, b) => Math.abs(a - b) <= Math.max(DEPTH_FLOOR, Math.max(Math.abs(a), Math.abs(b)) * SAME_DEPTH);
|
|
1219
|
-
var
|
|
1313
|
+
var sameDirection2 = (a, b) => Math.abs(a.x - b.x) < TOLERANCE2 && Math.abs(a.y - b.y) < TOLERANCE2 && Math.abs(a.z - b.z) < TOLERANCE2;
|
|
1220
1314
|
var sameHoleSets = (features, sheets) => {
|
|
1221
1315
|
const sizes = features.map((feature) => holeSize(sheets, feature.tag));
|
|
1222
1316
|
const root = features.map((_, at) => at);
|
|
@@ -1230,7 +1324,7 @@ var sameHoleSets = (features, sheets) => {
|
|
|
1230
1324
|
for (let b = a + 1; b < features.length; b += 1) {
|
|
1231
1325
|
const [first, second] = [sizes[a], sizes[b]];
|
|
1232
1326
|
const [one, other] = [features[a], features[b]];
|
|
1233
|
-
if (first && second && one && other &&
|
|
1327
|
+
if (first && second && one && other && sameDirection2(one.machiningDirection, other.machiningDirection) && first.diameter.toFixed(3) === second.diameter.toFixed(3) && sameDepth(first.depth, second.depth)) {
|
|
1234
1328
|
const [low, high] = [find(a), find(b)].sort((x, y) => x - y);
|
|
1235
1329
|
if (high !== void 0 && low !== void 0) root[high] = low;
|
|
1236
1330
|
}
|
|
@@ -1269,77 +1363,17 @@ var featureRows = (tags, features, sheets) => {
|
|
|
1269
1363
|
});
|
|
1270
1364
|
};
|
|
1271
1365
|
|
|
1272
|
-
// src/model/geometry.ts
|
|
1273
|
-
var dfmFeatures = (features) => {
|
|
1274
|
-
const read = features.map(
|
|
1275
|
-
({ featureTag, featureType, machiningDirection: { x, y, z }, regionIdxs }) => ({
|
|
1276
|
-
tag: featureTag,
|
|
1277
|
-
featureType,
|
|
1278
|
-
machiningDirection: { x, y, z },
|
|
1279
|
-
regionIdxs
|
|
1280
|
-
})
|
|
1281
|
-
);
|
|
1282
|
-
const directions = [];
|
|
1283
|
-
read.forEach(({ machiningDirection }) => {
|
|
1284
|
-
if (!directions.some((each) => sameDirection2(each, machiningDirection)))
|
|
1285
|
-
directions.push(machiningDirection);
|
|
1286
|
-
});
|
|
1287
|
-
const numbered = directions.flatMap(
|
|
1288
|
-
(direction) => read.filter((feature) => sameDirection2(feature.machiningDirection, direction))
|
|
1289
|
-
);
|
|
1290
|
-
const numberOf = new Map(numbered.map((feature, index) => [feature, index + 1]));
|
|
1291
|
-
return read.map((feature) => ({ ...feature, number: numberOf.get(feature) ?? 0 }));
|
|
1292
|
-
};
|
|
1293
|
-
var TOLERANCE2 = 1e-6;
|
|
1294
|
-
var sameDirection2 = (a, b) => Math.abs(a.x - b.x) < TOLERANCE2 && Math.abs(a.y - b.y) < TOLERANCE2 && Math.abs(a.z - b.z) < TOLERANCE2;
|
|
1295
|
-
var featureTypeLabel = (featureType) => {
|
|
1296
|
-
const words = featureType.replace(/_/g, " ").trim();
|
|
1297
|
-
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
1298
|
-
};
|
|
1299
|
-
var partTop = (features, sheets, direction) => {
|
|
1300
|
-
let top = null;
|
|
1301
|
-
for (const other of features) {
|
|
1302
|
-
const zMax = sheets[other.tag.toLowerCase()]?.extendedZMax;
|
|
1303
|
-
if (zMax !== void 0 && sameDirection2(other.machiningDirection, direction)) {
|
|
1304
|
-
top = top === null ? zMax : Math.max(top, zMax);
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
return top;
|
|
1308
|
-
};
|
|
1309
|
-
var FLAT_TIP_DEG = 0.5;
|
|
1310
|
-
var isDrilled = (sheet) => sheet?.diameter !== void 0 && sheet.diameter > 0 && !(sheet.tipAngle !== void 0 && sheet.tipAngle >= 180 - FLAT_TIP_DEG);
|
|
1311
|
-
var featureLd = (features, feature, sheets) => {
|
|
1312
|
-
const sheet = sheets[feature.tag.toLowerCase()];
|
|
1313
|
-
const top = sheet ? partTop(features, sheets, feature.machiningDirection) : null;
|
|
1314
|
-
const bottom = sheet?.zMin;
|
|
1315
|
-
if (!sheet || top === null || bottom === void 0) return null;
|
|
1316
|
-
const drilling = isDrilled(sheet);
|
|
1317
|
-
const [basis, across] = sheet.diameter !== void 0 ? ["bore", sheet.diameter] : sheet.pinchDiameter !== void 0 ? ["pinch", sheet.pinchDiameter] : [
|
|
1318
|
-
"radius",
|
|
1319
|
-
sheet.sharpCorners || sheet.cornerRadius === void 0 ? void 0 : 2 * sheet.cornerRadius
|
|
1320
|
-
];
|
|
1321
|
-
if (across === void 0 || across <= 0) return null;
|
|
1322
|
-
const atLeast = basis !== "bore" && sheet.cornerRadiusUnresolved === true;
|
|
1323
|
-
return {
|
|
1324
|
-
ratio: (top - bottom) / across,
|
|
1325
|
-
...sheet.zMax !== void 0 ? { featureRatio: (sheet.zMax - bottom) / across } : {},
|
|
1326
|
-
drilling,
|
|
1327
|
-
atLeast,
|
|
1328
|
-
basis,
|
|
1329
|
-
across
|
|
1330
|
-
};
|
|
1331
|
-
};
|
|
1332
|
-
|
|
1333
1366
|
// src/model/dfm-explain.ts
|
|
1334
1367
|
var figureless = (hit) => hit.metric.quantity === "flag" || hit.metric.quantity === "presence";
|
|
1335
1368
|
var hitFigure = (hit, units) => figureless(hit) ? "" : formatMeasured(hit.metric.quantity, hit.value, units);
|
|
1336
1369
|
|
|
1337
1370
|
// src/model/rule-set-file.ts
|
|
1338
1371
|
var FILE_KIND = "toolpath-dfm-rules";
|
|
1339
|
-
var serializeRuleSet = (rules, meta = {}) => JSON.stringify(
|
|
1372
|
+
var serializeRuleSet = (rules, meta = {}, vocabulary = MILLING_RULES) => JSON.stringify(
|
|
1340
1373
|
{
|
|
1341
1374
|
kind: FILE_KIND,
|
|
1342
1375
|
version: 1,
|
|
1376
|
+
process: vocabulary.process,
|
|
1343
1377
|
...meta.name ? { name: meta.name } : {},
|
|
1344
1378
|
...meta.description ? { description: meta.description } : {},
|
|
1345
1379
|
units: "mm",
|
|
@@ -1348,7 +1382,13 @@ var serializeRuleSet = (rules, meta = {}) => JSON.stringify(
|
|
|
1348
1382
|
null,
|
|
1349
1383
|
2
|
|
1350
1384
|
);
|
|
1351
|
-
var
|
|
1385
|
+
var PROCESS_NAMES = { milling: "Milling", turning: "Turning" };
|
|
1386
|
+
var fileProcess = (raw) => {
|
|
1387
|
+
if (Array.isArray(raw) || typeof raw !== "object" || raw === null) return "milling";
|
|
1388
|
+
const { process } = raw;
|
|
1389
|
+
return process === void 0 || process === null ? "milling" : process;
|
|
1390
|
+
};
|
|
1391
|
+
var parseRuleSetFile = (text, vocabulary = MILLING_RULES) => {
|
|
1352
1392
|
let raw;
|
|
1353
1393
|
try {
|
|
1354
1394
|
raw = JSON.parse(text);
|
|
@@ -1360,6 +1400,14 @@ var parseRuleSetFile = (text) => {
|
|
|
1360
1400
|
const units = Array.isArray(raw) ? void 0 : raw.units;
|
|
1361
1401
|
if (units !== void 0 && units !== "mm")
|
|
1362
1402
|
return { error: `Its lengths are in ${JSON.stringify(units)}; only "mm" can be read.` };
|
|
1403
|
+
const process = fileProcess(raw);
|
|
1404
|
+
if (process !== vocabulary.process) {
|
|
1405
|
+
return process === "milling" || process === "turning" ? {
|
|
1406
|
+
error: `This is a ${process} rule set \u2014 import it in ${PROCESS_NAMES[process]} mode.`
|
|
1407
|
+
} : {
|
|
1408
|
+
error: `Its process is ${JSON.stringify(process)}; only "milling" or "turning" can be read.`
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1363
1411
|
const seen = /* @__PURE__ */ new Set();
|
|
1364
1412
|
const withIds = list.map((each) => {
|
|
1365
1413
|
if (typeof each !== "object" || each === null) return each;
|
|
@@ -1368,7 +1416,7 @@ var parseRuleSetFile = (text) => {
|
|
|
1368
1416
|
seen.add(id);
|
|
1369
1417
|
return { ...rule, id };
|
|
1370
1418
|
});
|
|
1371
|
-
const rules = parseRules(withIds) ?? [];
|
|
1419
|
+
const rules = parseRules(withIds, vocabulary) ?? [];
|
|
1372
1420
|
if (rules.length === 0) return { error: "None of its rules could be read." };
|
|
1373
1421
|
const name = typeof raw === "object" && raw !== null && typeof raw.name === "string" ? raw.name : void 0;
|
|
1374
1422
|
return { rules, skipped: list.length - rules.length, ...name ? { name } : {} };
|
|
@@ -1382,17 +1430,36 @@ var OP_WORDS = {
|
|
|
1382
1430
|
ne: "not equal (\u2260)",
|
|
1383
1431
|
between: "in a range: `value` \u2264 x < `max` by default; `lowOp` (gte|gt) and `highOp` (lt|lte) change the ends"
|
|
1384
1432
|
};
|
|
1385
|
-
var
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1433
|
+
var SCRIPTS = {
|
|
1434
|
+
milling: {
|
|
1435
|
+
intro: "You are helping a machinist or designer set up design-for-manufacturing (DFM) checks for CNC-milled parts in the Toolpath CAD viewer. Interview them, then produce ONE JSON document in the format below.",
|
|
1436
|
+
opening: "Begin the interview in your very first reply \u2014 do not summarise this prompt, ask whether to start, or explain the format. Run it in four rounds, in a conversational tone, following up where an answer needs it: an opening round about the shop, then a milling round, then a drilling round, then a closing round. Ask only about things the metrics below can measure from the model geometry: depths, diameters, corner radii, tool sizes, areas, feature kinds. Do not ask about surface finish, tolerances, material hardness, fixturing, quantities or lead time \u2014 nothing here measures them.",
|
|
1437
|
+
rounds: [
|
|
1438
|
+
"### Round 1 \u2014 the shop",
|
|
1439
|
+
"1. What they make and in what materials. This sets the defaults, not a rule.",
|
|
1440
|
+
"2. Examples of features that have challenged them before: a deep pocket that snapped a tool, a hole that needed a special drill, a corner radius that forced a re-quote, an undercut nobody saw until CAM. Ask for the feature and its rough size, then turn each into one rule: a deep pocket is `pocket` with `featureLd` or `depthBelowTop`; a small hole is `hole` with `diameter`; a deep hole is `hole` with `ld`; a tight corner is `milled` with `cornerRadius` or `maxTool`. If no metric below measures the example, say so and move on; never invent a metric.",
|
|
1441
|
+
"",
|
|
1442
|
+
"### Round 2 \u2014 milling (pockets, slots, walls, bosses, profiles, 3D surfaces)",
|
|
1443
|
+
"3. The smallest end mill they are happy to run: `milled` with `maxTool` below it, and `milled` with `cornerRadius` below half of it.",
|
|
1444
|
+
"4. The depth-to-diameter ratio where milled work starts to cost them, and where it becomes a problem: two or three bands of `milled` with `ld` (warning, caution, critical).",
|
|
1445
|
+
"5. The deepest their end mills reach below the top of the part: `milled` with `depthBelowTop`. Whether they also want a feature's own depth capped: `milled` with `featureDepth`.",
|
|
1446
|
+
"6. Sharp inside corners: whether every one should be flagged (`milled` with `sharpCorner`), or only on certain feature kinds.",
|
|
1447
|
+
"7. Floor fillets: whether a floor fillet larger than their ball or bull-nose tooling should be flagged (`pocket` with `floorFillet` above a radius).",
|
|
1448
|
+
"8. Milled features to call out on sight regardless of size: `undercut` with `present`; `contour` with `present` for 3D surfaces; `any` with `tilted` for 5-axis work; `boss` with `present` for thin standing material.",
|
|
1449
|
+
"9. Large features that drive cycle time, if they quote: `pocket` with `projectedFloorArea` or `milled` with `surfaceArea` above a size.",
|
|
1450
|
+
"",
|
|
1451
|
+
"### Round 3 \u2014 drilling (holes, threads, countersinks)",
|
|
1452
|
+
"10. The smallest drill they stock: `hole` with `diameter` below it. Whether they want holes that have no drill fit, so must be milled, called out: `hole` with `maxDrill` at 0.",
|
|
1453
|
+
"11. The depth-to-diameter ratio where holes get hard, and where they need gun drilling or a second setup: two bands of `hole` with `ld`, usually looser than milled work.",
|
|
1454
|
+
"12. Threads: the smallest thread they tap (`hole` with `threadDiameter` below it), and whether every threaded hole should be marked (`threadedHole` with `present`).",
|
|
1455
|
+
"13. Countersinks and counterbores: whether to call out countersinks (`chamfer` with `countersink`) or counterbored holes (`hole` with `counterbore`).",
|
|
1456
|
+
"",
|
|
1457
|
+
"### Round 4 \u2014 closing",
|
|
1458
|
+
"14. Anything they want flagged across every feature, milled or drilled \u2014 for example one overall depth below the top of the part their longest tooling cannot reach: `any` with `depthBelowTop`.",
|
|
1459
|
+
"15. Which of the rules so far are critical (red), which are cautions (orange) and which are just worth a look (yellow)."
|
|
1460
|
+
],
|
|
1461
|
+
counting: "A rule only fires on features that have the measurement; a feature that lacks it is not flagged. Only *required* features count \u2014 ones that own a face no other feature could machine.",
|
|
1462
|
+
example: [
|
|
1396
1463
|
{ id: "r1", subject: "hole", metric: "ld", op: "gte", value: 8, color: "#ffd60a" },
|
|
1397
1464
|
{
|
|
1398
1465
|
id: "r2",
|
|
@@ -1407,42 +1474,82 @@ var llmRulePrompt = (current = []) => {
|
|
|
1407
1474
|
},
|
|
1408
1475
|
{ id: "r3", subject: "milled", metric: "sharpCorner", op: "gte", value: 0, color: "#e5484d" },
|
|
1409
1476
|
{ id: "r4", subject: "undercut", metric: "present", op: "gte", value: 0, color: "#8e4ec6" }
|
|
1477
|
+
]
|
|
1478
|
+
},
|
|
1479
|
+
turning: {
|
|
1480
|
+
intro: "You are helping a machinist or designer set up design-for-manufacturing (DFM) checks for parts turned on a lathe in the Toolpath CAD viewer. Interview them, then produce ONE JSON document in the format below.",
|
|
1481
|
+
opening: "Begin the interview in your very first reply \u2014 do not summarise this prompt, ask whether to start, or explain the format. Run it in three rounds, in a conversational tone, following up where an answer needs it: an opening round about the shop, then a turning round, then a closing round. Ask only about things the metrics below can measure from the model geometry: diameters, lengths, grooves, threads, axial holes, inside corner radii, tolerance, feature kinds. Do not ask about surface finish, material hardness, workholding, quantities or lead time \u2014 nothing here measures them.",
|
|
1482
|
+
rounds: [
|
|
1483
|
+
"### Round 1 \u2014 the shop",
|
|
1484
|
+
"1. What they make, in what materials, and on what lathes: the largest bar or chuck, and whether they have live tooling. This sets the defaults, not a rule.",
|
|
1485
|
+
"2. Examples of turned features that have challenged them before: a narrow groove that broke an insert, a long small bore that chattered, a fine thread, a sharp inside corner no insert could make. Ask for the feature and its rough size, then turn each into one rule: a narrow groove is `groove` with `grooveWidth`; a deep bore is `id` with `ld`; a fine thread is `thread` with `threadPitch`; a small drilled hole is `hole` with `holeDiameter`. If no metric below measures the example, say so and move on; never invent a metric.",
|
|
1486
|
+
"",
|
|
1487
|
+
"### Round 2 \u2014 turning (outside, inside, faces, grooves, threads, axial holes)",
|
|
1488
|
+
"3. The largest diameter their lathes take: `any` with `maxDiameter` above it. The longest stretch they turn without support: `od` with `length`.",
|
|
1489
|
+
"4. Grooves: the narrowest grooving insert they stock (`groove` with `grooveWidth` below it), and how far they can plunge (`groove` with `grooveDepth`, or `grooveAspect` for depth over width).",
|
|
1490
|
+
"5. Boring: the depth-to-diameter ratio where a boring bar starts to chatter, and where it becomes a problem: two bands of `id` with `ld`. The narrowest bore their smallest bar passes: `id` with `entryDiameter` below it.",
|
|
1491
|
+
"6. Threads: the finest pitch they cut (`thread` with `threadPitch` below it), and the smallest thread (`thread` with `threadDiameter` below it).",
|
|
1492
|
+
"7. Axial holes: the smallest drill they stock (`hole` with `holeDiameter` below it), the deepest they drill (`hole` with `holeDepth`, or `hole` with `ld`), and whether to call out stepped holes (`hole` with `holeSteps`), flat bottoms (`blindHole` with `flatBottom`) or tapped holes (`threadedHole` with `present`).",
|
|
1493
|
+
"8. Inside corners: the smallest nose radius they run (`any` with `cornerRadius` below it), and whether every sharp inside corner should be flagged (`any` with `sharpCorner`).",
|
|
1494
|
+
"9. Turned features to call out on sight regardless of size: `face` with `axialTool` for work only an axially mounted tool reaches; `type:unknown` with `present` for a stretch the Engine could not name.",
|
|
1495
|
+
"",
|
|
1496
|
+
"### Round 3 \u2014 closing",
|
|
1497
|
+
"10. The tightest tolerance they hold without special care: `any` with `tolerance` below it.",
|
|
1498
|
+
"11. Which of the rules so far are critical (red), which are cautions (orange) and which are just worth a look (yellow)."
|
|
1410
1499
|
],
|
|
1411
|
-
|
|
1500
|
+
counting: "A rule only fires on features that have the measurement; a feature that lacks it is not flagged. A stretch both ends of the part reach is one feature, and breaks a rule only if it breaks it from both ends.",
|
|
1501
|
+
example: [
|
|
1502
|
+
{
|
|
1503
|
+
id: "r1",
|
|
1504
|
+
subject: "groove",
|
|
1505
|
+
metric: "grooveWidth",
|
|
1506
|
+
op: "lte",
|
|
1507
|
+
value: 1.5,
|
|
1508
|
+
color: "#ffd60a"
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
id: "r2",
|
|
1512
|
+
subject: "id",
|
|
1513
|
+
metric: "ld",
|
|
1514
|
+
op: "between",
|
|
1515
|
+
value: 4,
|
|
1516
|
+
max: 8,
|
|
1517
|
+
lowOp: "gte",
|
|
1518
|
+
highOp: "lt",
|
|
1519
|
+
color: "#f76b15"
|
|
1520
|
+
},
|
|
1521
|
+
{ id: "r3", subject: "any", metric: "sharpCorner", op: "gte", value: 0, color: "#e5484d" },
|
|
1522
|
+
{ id: "r4", subject: "thread", metric: "present", op: "gte", value: 0, color: "#8e4ec6" }
|
|
1523
|
+
]
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
var llmRulePrompt = (current = [], vocabulary = MILLING_RULES) => {
|
|
1527
|
+
const script = SCRIPTS[vocabulary.process];
|
|
1528
|
+
const groups = vocabulary.subjects.filter((each) => !each.value.startsWith(TYPE_SUBJECT));
|
|
1529
|
+
const types = vocabulary.subjects.filter((each) => each.value.startsWith(TYPE_SUBJECT));
|
|
1530
|
+
const metricLines = vocabulary.metrics.map((metric) => {
|
|
1531
|
+
const unit = quantityUnit(metric.quantity, "mm");
|
|
1532
|
+
const kind = metric.quantity === "flag" ? 'yes/no \u2014 write `op: "gte"` and `value: 0`; the rule fires whenever it is so' : metric.quantity === "presence" ? 'no measurement \u2014 write `op: "gte"` and `value: 0`; flags every feature of the subject' : `${metric.quantity}${unit ? `, in ${unit}` : ""}; a new rule usually starts around ${metric.start.mm}`;
|
|
1533
|
+
const kinds = metric.kinds ? ` Only features of kind ${metric.kinds.join("/")} report it.` : "";
|
|
1534
|
+
return `- \`${metric.key}\` \u2014 "${metric.label}" (${kind}). ${metric.hint}.${kinds}`;
|
|
1535
|
+
});
|
|
1536
|
+
const example = serializeRuleSet(
|
|
1537
|
+
script.example,
|
|
1538
|
+
{ name: "Example set", description: "Shows one rule of each shape" },
|
|
1539
|
+
vocabulary
|
|
1412
1540
|
);
|
|
1413
1541
|
return [
|
|
1414
1542
|
"# Write a Toolpath DFM rule set",
|
|
1415
1543
|
"",
|
|
1416
|
-
|
|
1544
|
+
script.intro,
|
|
1417
1545
|
"",
|
|
1418
|
-
|
|
1546
|
+
script.opening,
|
|
1419
1547
|
"",
|
|
1420
1548
|
'Where the user has current rules (listed at the end), do not ask whether to keep them. Weave each one into the relevant question as a suggested starting point \u2014 "your set currently warns at 4\xD7D; where does deep work start to cost you?" \u2014 so the user accepts, adjusts or drops it in their own words. The finished set should feel like theirs, not a copy of the old one.',
|
|
1421
1549
|
"",
|
|
1422
1550
|
"Each question is tied to the subject and metric it becomes. Skip one when an earlier answer has already settled it, and offer sensible defaults from their answers rather than asking about every metric. Every rule needs an `op` and a `value`, even a yes/no one.",
|
|
1423
1551
|
"",
|
|
1424
|
-
|
|
1425
|
-
"1. What they make and in what materials. This sets the defaults, not a rule.",
|
|
1426
|
-
"2. Examples of features that have challenged them before: a deep pocket that snapped a tool, a hole that needed a special drill, a corner radius that forced a re-quote, an undercut nobody saw until CAM. Ask for the feature and its rough size, then turn each into one rule: a deep pocket is `pocket` with `featureLd` or `depthBelowTop`; a small hole is `hole` with `diameter`; a deep hole is `hole` with `ld`; a tight corner is `milled` with `cornerRadius` or `maxTool`. If no metric below measures the example, say so and move on; never invent a metric.",
|
|
1427
|
-
"",
|
|
1428
|
-
"### Round 2 \u2014 milling (pockets, slots, walls, bosses, profiles, 3D surfaces)",
|
|
1429
|
-
"3. The smallest end mill they are happy to run: `milled` with `maxTool` below it, and `milled` with `cornerRadius` below half of it.",
|
|
1430
|
-
"4. The depth-to-diameter ratio where milled work starts to cost them, and where it becomes a problem: two or three bands of `milled` with `ld` (warning, caution, critical).",
|
|
1431
|
-
"5. The deepest their end mills reach below the top of the part: `milled` with `depthBelowTop`. Whether they also want a feature's own depth capped: `milled` with `featureDepth`.",
|
|
1432
|
-
"6. Sharp inside corners: whether every one should be flagged (`milled` with `sharpCorner`), or only on certain feature kinds.",
|
|
1433
|
-
"7. Floor fillets: whether a floor fillet larger than their ball or bull-nose tooling should be flagged (`pocket` with `floorFillet` above a radius).",
|
|
1434
|
-
"8. Milled features to call out on sight regardless of size: `undercut` with `present`; `contour` with `present` for 3D surfaces; `any` with `tilted` for 5-axis work; `boss` with `present` for thin standing material.",
|
|
1435
|
-
"9. Large features that drive cycle time, if they quote: `pocket` with `projectedFloorArea` or `milled` with `surfaceArea` above a size.",
|
|
1436
|
-
"",
|
|
1437
|
-
"### Round 3 \u2014 drilling (holes, threads, countersinks)",
|
|
1438
|
-
"10. The smallest drill they stock: `hole` with `diameter` below it. Whether they want holes that have no drill fit, so must be milled, called out: `hole` with `maxDrill` at 0.",
|
|
1439
|
-
"11. The depth-to-diameter ratio where holes get hard, and where they need gun drilling or a second setup: two bands of `hole` with `ld`, usually looser than milled work.",
|
|
1440
|
-
"12. Threads: the smallest thread they tap (`hole` with `threadDiameter` below it), and whether every threaded hole should be marked (`threadedHole` with `present`).",
|
|
1441
|
-
"13. Countersinks and counterbores: whether to call out countersinks (`chamfer` with `countersink`) or counterbored holes (`hole` with `counterbore`).",
|
|
1442
|
-
"",
|
|
1443
|
-
"### Round 4 \u2014 closing",
|
|
1444
|
-
"14. Anything they want flagged across every feature, milled or drilled \u2014 for example one overall depth below the top of the part their longest tooling cannot reach: `any` with `depthBelowTop`.",
|
|
1445
|
-
"15. Which of the rules so far are critical (red), which are cautions (orange) and which are just worth a look (yellow).",
|
|
1552
|
+
...script.rounds,
|
|
1446
1553
|
"",
|
|
1447
1554
|
`The user may stop at any point \u2014 "that's enough", "just give me the rules", "generate it" \u2014 and you then finish from what you have: rules for every answer so far, their current rules carried over for anything not yet discussed, and sensible defaults from the shop description for the rest. Never insist on completing a round first, and never leave a placeholder for an unanswered question.`,
|
|
1448
1555
|
"",
|
|
@@ -1450,7 +1557,7 @@ var llmRulePrompt = (current = []) => {
|
|
|
1450
1557
|
"",
|
|
1451
1558
|
"## Format",
|
|
1452
1559
|
"",
|
|
1453
|
-
|
|
1560
|
+
`Top level: \`{"kind":"toolpath-dfm-rules","version":1,"process":"${vocabulary.process}","name":"\u2026","description":"\u2026","units":"mm","rules":[\u2026]}\`.`,
|
|
1454
1561
|
"Every length is in millimetres, areas in mm\xB2, angles in degrees, ratios plain. Convert from inches yourself (1 in = 25.4 mm).",
|
|
1455
1562
|
"",
|
|
1456
1563
|
"Each rule is one sentence \u2014 *{subject} with {metric} {op} {value}* \u2014 with the fields:",
|
|
@@ -1462,7 +1569,7 @@ var llmRulePrompt = (current = []) => {
|
|
|
1462
1569
|
"- `value`: the threshold (canonical units). For `between`, the low end; add `max` for the high end.",
|
|
1463
1570
|
"- `color`: `#rrggbb`, from the palette: " + RULE_COLORS.map((each) => `${each.name} \`${each.value}\``).join(", ") + ". Red for critical, orange for caution, yellow for warning; purple, green and cyan for anything else. Where a feature breaks several rules the part shows the earliest palette colour.",
|
|
1464
1571
|
"",
|
|
1465
|
-
|
|
1572
|
+
script.counting,
|
|
1466
1573
|
"",
|
|
1467
1574
|
"## Subjects (`subject`)",
|
|
1468
1575
|
"",
|
|
@@ -1488,13 +1595,309 @@ var llmRulePrompt = (current = []) => {
|
|
|
1488
1595
|
"Use these as the suggested starting points in your questions, as described above. Do not ask whether to keep them; do not copy them unchanged unless the user confirms each threshold:",
|
|
1489
1596
|
"",
|
|
1490
1597
|
"```json",
|
|
1491
|
-
serializeRuleSet(current),
|
|
1598
|
+
serializeRuleSet(current, {}, vocabulary),
|
|
1492
1599
|
"```"
|
|
1493
1600
|
] : [],
|
|
1494
1601
|
""
|
|
1495
1602
|
].join("\n");
|
|
1496
1603
|
};
|
|
1497
1604
|
|
|
1605
|
+
// src/model/turning-metrics.ts
|
|
1606
|
+
var OD_KINDS = ["OdProfile", "OdGroove", "OdThread"];
|
|
1607
|
+
var ID_KINDS = ["IdProfile", "IdGroove", "IdThread"];
|
|
1608
|
+
var FACE_KINDS = ["Face", "FaceProfile", "FaceGroove"];
|
|
1609
|
+
var GROOVE_KINDS = ["OdGroove", "IdGroove", "FaceGroove"];
|
|
1610
|
+
var THREAD_KINDS = ["OdThread", "IdThread", "AxialHole"];
|
|
1611
|
+
var PROFILE_KINDS = ["OdProfile", "IdProfile", "FaceProfile"];
|
|
1612
|
+
var HOLE_KINDS2 = ["AxialHole"];
|
|
1613
|
+
var SIZE2 = "Size";
|
|
1614
|
+
var GROOVES = "Grooves";
|
|
1615
|
+
var THREADS = "Threads";
|
|
1616
|
+
var HOLES = "Holes";
|
|
1617
|
+
var TOOLS2 = "Tool access";
|
|
1618
|
+
var TOLERANCE3 = "Tolerance";
|
|
1619
|
+
var TURNING_METRICS = [
|
|
1620
|
+
PRESENCE_METRIC,
|
|
1621
|
+
define({
|
|
1622
|
+
key: "maxDiameter",
|
|
1623
|
+
label: "diameter",
|
|
1624
|
+
group: SIZE2,
|
|
1625
|
+
quantity: "length",
|
|
1626
|
+
op: "gte",
|
|
1627
|
+
start: { mm: 100, inch: 101.6 },
|
|
1628
|
+
hint: "Its largest diameter: what the lathe must swing, or the bar it is cut from"
|
|
1629
|
+
}),
|
|
1630
|
+
define({
|
|
1631
|
+
key: "minDiameter",
|
|
1632
|
+
label: "smallest diameter",
|
|
1633
|
+
group: SIZE2,
|
|
1634
|
+
quantity: "length",
|
|
1635
|
+
op: "lte",
|
|
1636
|
+
start: { mm: 6, inch: 6.35 },
|
|
1637
|
+
kinds: ["OdProfile", "IdProfile", "OdGroove", "IdGroove", "OdThread", "IdThread", "AxialHole"],
|
|
1638
|
+
hint: "Its smallest diameter: how thin the outside gets, or how narrow a bore. Not for faces"
|
|
1639
|
+
}),
|
|
1640
|
+
define({
|
|
1641
|
+
key: "length",
|
|
1642
|
+
label: "length along the axis",
|
|
1643
|
+
group: SIZE2,
|
|
1644
|
+
quantity: "length",
|
|
1645
|
+
op: "gte",
|
|
1646
|
+
start: { mm: 50, inch: 50.8 },
|
|
1647
|
+
kinds: [
|
|
1648
|
+
"AxialHole",
|
|
1649
|
+
"FaceGroove",
|
|
1650
|
+
"FaceProfile",
|
|
1651
|
+
"IdGroove",
|
|
1652
|
+
"IdProfile",
|
|
1653
|
+
"IdThread",
|
|
1654
|
+
"OdGroove",
|
|
1655
|
+
"OdProfile",
|
|
1656
|
+
"OdThread",
|
|
1657
|
+
"Unknown"
|
|
1658
|
+
],
|
|
1659
|
+
hint: "How far it runs along the spindle axis. None for a flat face"
|
|
1660
|
+
}),
|
|
1661
|
+
define({
|
|
1662
|
+
key: "ld",
|
|
1663
|
+
label: "L/D from the end",
|
|
1664
|
+
group: SIZE2,
|
|
1665
|
+
quantity: "ratio",
|
|
1666
|
+
op: "gte",
|
|
1667
|
+
start: 4,
|
|
1668
|
+
kinds: ["AxialHole", "IdProfile", "IdGroove", "IdThread"],
|
|
1669
|
+
hint: "How far in a boring bar or drill reaches over the bore it goes through: a hole\u2019s depth over its narrowest step; inside work, from the end it is cut from to its far end, over its entry diameter. Cut from both ends, the easier end"
|
|
1670
|
+
}),
|
|
1671
|
+
define({
|
|
1672
|
+
key: "grooveWidth",
|
|
1673
|
+
label: "groove width",
|
|
1674
|
+
group: GROOVES,
|
|
1675
|
+
quantity: "length",
|
|
1676
|
+
op: "lte",
|
|
1677
|
+
start: { mm: 1.5, inch: 1.5875 },
|
|
1678
|
+
kinds: GROOVE_KINDS,
|
|
1679
|
+
hint: "How wide the groove is: the narrowest insert that cuts it in one plunge"
|
|
1680
|
+
}),
|
|
1681
|
+
define({
|
|
1682
|
+
key: "grooveDepth",
|
|
1683
|
+
label: "groove depth",
|
|
1684
|
+
group: GROOVES,
|
|
1685
|
+
quantity: "length",
|
|
1686
|
+
op: "gte",
|
|
1687
|
+
start: { mm: 5, inch: 5.08 },
|
|
1688
|
+
kinds: GROOVE_KINDS,
|
|
1689
|
+
hint: "How far the groove goes in: radially on the outside or in a bore, along the axis in a face"
|
|
1690
|
+
}),
|
|
1691
|
+
define({
|
|
1692
|
+
key: "grooveAspect",
|
|
1693
|
+
label: "groove depth/width",
|
|
1694
|
+
group: GROOVES,
|
|
1695
|
+
quantity: "ratio",
|
|
1696
|
+
op: "gte",
|
|
1697
|
+
start: 3,
|
|
1698
|
+
kinds: GROOVE_KINDS,
|
|
1699
|
+
hint: "Its depth over its width: how far a grooving insert reaches past its own width"
|
|
1700
|
+
}),
|
|
1701
|
+
define({
|
|
1702
|
+
key: "threadPitch",
|
|
1703
|
+
label: "thread pitch",
|
|
1704
|
+
group: THREADS,
|
|
1705
|
+
quantity: "length",
|
|
1706
|
+
op: "lte",
|
|
1707
|
+
start: { mm: 0.5, inch: 0.4233 },
|
|
1708
|
+
kinds: THREAD_KINDS,
|
|
1709
|
+
hint: "The distance between thread crests (0.423 mm is 60 TPI)"
|
|
1710
|
+
}),
|
|
1711
|
+
define({
|
|
1712
|
+
key: "threadDiameter",
|
|
1713
|
+
label: "thread diameter",
|
|
1714
|
+
group: THREADS,
|
|
1715
|
+
quantity: "length",
|
|
1716
|
+
op: "lte",
|
|
1717
|
+
start: { mm: 6, inch: 6.35 },
|
|
1718
|
+
kinds: THREAD_KINDS,
|
|
1719
|
+
hint: "The catalog thread\u2019s nominal major diameter. None where the catalog lists no thread for it"
|
|
1720
|
+
}),
|
|
1721
|
+
define({
|
|
1722
|
+
key: "threadDepth",
|
|
1723
|
+
label: "thread depth",
|
|
1724
|
+
group: THREADS,
|
|
1725
|
+
quantity: "length",
|
|
1726
|
+
op: "gte",
|
|
1727
|
+
start: { mm: 1, inch: 1.27 },
|
|
1728
|
+
kinds: ["OdThread", "IdThread"],
|
|
1729
|
+
hint: "How deep the thread form is cut"
|
|
1730
|
+
}),
|
|
1731
|
+
define({
|
|
1732
|
+
key: "holeDiameter",
|
|
1733
|
+
label: "hole diameter",
|
|
1734
|
+
group: HOLES,
|
|
1735
|
+
quantity: "length",
|
|
1736
|
+
op: "lte",
|
|
1737
|
+
start: { mm: 3, inch: 3.175 },
|
|
1738
|
+
kinds: HOLE_KINDS2,
|
|
1739
|
+
hint: "Its narrowest step: the drill it is drilled with"
|
|
1740
|
+
}),
|
|
1741
|
+
define({
|
|
1742
|
+
key: "holeDepth",
|
|
1743
|
+
label: "hole depth",
|
|
1744
|
+
group: HOLES,
|
|
1745
|
+
quantity: "length",
|
|
1746
|
+
op: "gte",
|
|
1747
|
+
start: { mm: 50, inch: 50.8 },
|
|
1748
|
+
kinds: HOLE_KINDS2,
|
|
1749
|
+
hint: "How far it runs along the axis"
|
|
1750
|
+
}),
|
|
1751
|
+
define({
|
|
1752
|
+
key: "holeSteps",
|
|
1753
|
+
label: "number of steps",
|
|
1754
|
+
group: HOLES,
|
|
1755
|
+
quantity: "count",
|
|
1756
|
+
op: "gte",
|
|
1757
|
+
start: 2,
|
|
1758
|
+
kinds: HOLE_KINDS2,
|
|
1759
|
+
hint: "How many diameters it steps through, from the mouth inward"
|
|
1760
|
+
}),
|
|
1761
|
+
define({
|
|
1762
|
+
key: "flatBottom",
|
|
1763
|
+
label: "a flat bottom",
|
|
1764
|
+
group: HOLES,
|
|
1765
|
+
quantity: "flag",
|
|
1766
|
+
op: "gte",
|
|
1767
|
+
start: 0,
|
|
1768
|
+
kinds: HOLE_KINDS2,
|
|
1769
|
+
hint: "A blind hole that ends flat rather than in a drill point"
|
|
1770
|
+
}),
|
|
1771
|
+
define({
|
|
1772
|
+
key: "cornerRadius",
|
|
1773
|
+
label: "inside corner radius",
|
|
1774
|
+
group: TOOLS2,
|
|
1775
|
+
quantity: "length",
|
|
1776
|
+
op: "lte",
|
|
1777
|
+
start: { mm: 0.4, inch: 0.3969 },
|
|
1778
|
+
hint: "The radius of its tightest concave corner, which an insert\u2019s nose radius must fit \u2014 zero when sharp. None where it has no inside corner"
|
|
1779
|
+
}),
|
|
1780
|
+
define({
|
|
1781
|
+
key: "sharpCorner",
|
|
1782
|
+
label: "a sharp inside corner",
|
|
1783
|
+
group: TOOLS2,
|
|
1784
|
+
quantity: "flag",
|
|
1785
|
+
op: "gte",
|
|
1786
|
+
start: 0,
|
|
1787
|
+
hint: "A concave corner with no radius, which no insert makes"
|
|
1788
|
+
}),
|
|
1789
|
+
define({
|
|
1790
|
+
key: "entryDiameter",
|
|
1791
|
+
label: "entry diameter",
|
|
1792
|
+
group: TOOLS2,
|
|
1793
|
+
quantity: "length",
|
|
1794
|
+
op: "lte",
|
|
1795
|
+
start: { mm: 10, inch: 9.525 },
|
|
1796
|
+
hint: "For a feature in a hole, the narrowest diameter a bar passes on its way in from the end it is cut from. None where nothing stands between"
|
|
1797
|
+
}),
|
|
1798
|
+
define({
|
|
1799
|
+
key: "axialTool",
|
|
1800
|
+
label: "an axially mounted tool",
|
|
1801
|
+
group: TOOLS2,
|
|
1802
|
+
quantity: "flag",
|
|
1803
|
+
op: "gte",
|
|
1804
|
+
start: 0,
|
|
1805
|
+
hint: "Only a tool mounted along the spindle axis reaches it: a drill, or a face-grooving tool"
|
|
1806
|
+
}),
|
|
1807
|
+
define({
|
|
1808
|
+
key: "tolerance",
|
|
1809
|
+
label: "tolerance",
|
|
1810
|
+
group: TOLERANCE3,
|
|
1811
|
+
quantity: "length",
|
|
1812
|
+
op: "lte",
|
|
1813
|
+
start: { mm: 0.01, inch: 0.0127 },
|
|
1814
|
+
hint: "The most it may deviate from the model"
|
|
1815
|
+
})
|
|
1816
|
+
];
|
|
1817
|
+
var GROUP_SUBJECTS2 = [
|
|
1818
|
+
{ value: "any", label: "Any turned feature", group: "Groups" },
|
|
1819
|
+
{ value: "od", label: "Outside (OD) features", group: "Groups", kinds: OD_KINDS },
|
|
1820
|
+
{ value: "id", label: "Inside (ID) features", group: "Groups", kinds: ID_KINDS },
|
|
1821
|
+
{ value: "face", label: "End-face features", group: "Groups", kinds: FACE_KINDS },
|
|
1822
|
+
{ value: "groove", label: "Grooves", group: "Groups", kinds: GROOVE_KINDS },
|
|
1823
|
+
{ value: "thread", label: "Threads", group: "Groups", kinds: THREAD_KINDS },
|
|
1824
|
+
{ value: "hole", label: "Axial holes", group: "Groups", kinds: HOLE_KINDS2 },
|
|
1825
|
+
{ value: "throughHole", label: "Through holes", group: "Groups", kinds: HOLE_KINDS2 },
|
|
1826
|
+
{ value: "blindHole", label: "Blind holes", group: "Groups", kinds: HOLE_KINDS2 },
|
|
1827
|
+
{ value: "threadedHole", label: "Threaded holes", group: "Groups", kinds: HOLE_KINDS2 },
|
|
1828
|
+
{ value: "profile", label: "Profiles", group: "Groups", kinds: PROFILE_KINDS }
|
|
1829
|
+
];
|
|
1830
|
+
var TYPES2 = [
|
|
1831
|
+
["od_profile", "OD profiles", "OdProfile"],
|
|
1832
|
+
["od_groove", "OD grooves", "OdGroove"],
|
|
1833
|
+
["od_thread", "OD threads", "OdThread"],
|
|
1834
|
+
["face", "Faces", "Face"],
|
|
1835
|
+
["face_profile", "Face profiles", "FaceProfile"],
|
|
1836
|
+
["face_groove", "Face grooves", "FaceGroove"],
|
|
1837
|
+
["axial_hole", "Axial hole features", "AxialHole"],
|
|
1838
|
+
["id_profile", "ID profiles", "IdProfile"],
|
|
1839
|
+
["id_groove", "ID grooves", "IdGroove"],
|
|
1840
|
+
["id_thread", "ID threads", "IdThread"],
|
|
1841
|
+
["unknown", "Unknown features", "Unknown"]
|
|
1842
|
+
];
|
|
1843
|
+
var TURNING_SUBJECTS = [
|
|
1844
|
+
...GROUP_SUBJECTS2,
|
|
1845
|
+
...TYPES2.map(([type, label, kind]) => ({
|
|
1846
|
+
value: `${TYPE_SUBJECT}${type}`,
|
|
1847
|
+
label,
|
|
1848
|
+
group: "Feature types",
|
|
1849
|
+
kinds: [kind]
|
|
1850
|
+
}))
|
|
1851
|
+
];
|
|
1852
|
+
var QUICK_TURNING_SUBJECTS = [
|
|
1853
|
+
"any",
|
|
1854
|
+
"od",
|
|
1855
|
+
"id",
|
|
1856
|
+
"face",
|
|
1857
|
+
"groove",
|
|
1858
|
+
"thread",
|
|
1859
|
+
"hole"
|
|
1860
|
+
];
|
|
1861
|
+
var QUICK_TURNING_METRICS = [
|
|
1862
|
+
"present",
|
|
1863
|
+
"maxDiameter",
|
|
1864
|
+
"length",
|
|
1865
|
+
"ld",
|
|
1866
|
+
"grooveWidth",
|
|
1867
|
+
"grooveDepth",
|
|
1868
|
+
"threadPitch",
|
|
1869
|
+
"holeDiameter",
|
|
1870
|
+
"holeDepth",
|
|
1871
|
+
"cornerRadius",
|
|
1872
|
+
"sharpCorner",
|
|
1873
|
+
"entryDiameter"
|
|
1874
|
+
];
|
|
1875
|
+
var CAPITALS = { od: "OD", id: "ID" };
|
|
1876
|
+
var turnedTypeLabel = (featureType) => {
|
|
1877
|
+
const words = featureType.split("_").filter((word) => word !== "").map((word) => CAPITALS[word] ?? word).join(" ");
|
|
1878
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
1879
|
+
};
|
|
1880
|
+
var endLabel = (feature) => {
|
|
1881
|
+
const approaches = "approaches" in feature && Array.isArray(feature.approaches) ? feature.approaches : feature.machiningDirection.z > 0 ? ["PlusZ"] : feature.machiningDirection.z < 0 ? ["MinusZ"] : [];
|
|
1882
|
+
const plus = approaches.includes("PlusZ");
|
|
1883
|
+
const minus = approaches.includes("MinusZ");
|
|
1884
|
+
return plus && minus ? "from both ends" : plus ? "from the +Z end" : minus ? "from the \u2212Z end" : "";
|
|
1885
|
+
};
|
|
1886
|
+
var TURNING_RULES = {
|
|
1887
|
+
process: "turning",
|
|
1888
|
+
metrics: TURNING_METRICS,
|
|
1889
|
+
subjects: TURNING_SUBJECTS,
|
|
1890
|
+
quickSubjects: QUICK_TURNING_SUBJECTS,
|
|
1891
|
+
quickMetrics: QUICK_TURNING_METRICS,
|
|
1892
|
+
fallbackMetric: PRESENCE_METRIC,
|
|
1893
|
+
featureLabel: (feature) => ({
|
|
1894
|
+
name: turnedTypeLabel(feature.featureType),
|
|
1895
|
+
detail: endLabel(feature)
|
|
1896
|
+
}),
|
|
1897
|
+
considerationsTitle: "Turning considerations",
|
|
1898
|
+
fileName: "dfm-turning-rules.json"
|
|
1899
|
+
};
|
|
1900
|
+
|
|
1498
1901
|
// src/model/reach.ts
|
|
1499
1902
|
var numbers = (value) => Array.isArray(value) && value.every((each) => typeof each === "number" && Number.isFinite(each)) ? value : null;
|
|
1500
1903
|
var readReachCurve = (datasheet) => {
|
|
@@ -1545,11 +1948,16 @@ var featureProfile = (sheet, featureType) => {
|
|
|
1545
1948
|
};
|
|
1546
1949
|
|
|
1547
1950
|
export {
|
|
1951
|
+
directionLabel,
|
|
1952
|
+
dfmFeatures,
|
|
1953
|
+
featureTypeLabel,
|
|
1954
|
+
partTop,
|
|
1955
|
+
isDrilled,
|
|
1956
|
+
featureLd,
|
|
1548
1957
|
PRESENCE_METRIC,
|
|
1549
1958
|
TYPE_SUBJECT,
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
QUICK_METRICS,
|
|
1959
|
+
MILLING_RULES,
|
|
1960
|
+
subjectInfo,
|
|
1553
1961
|
metricInfo,
|
|
1554
1962
|
metricsForSubject,
|
|
1555
1963
|
LENGTH_PLACES,
|
|
@@ -1581,16 +1989,14 @@ export {
|
|
|
1581
1989
|
parseRules,
|
|
1582
1990
|
sameDepth,
|
|
1583
1991
|
featureRows,
|
|
1584
|
-
dfmFeatures,
|
|
1585
|
-
featureTypeLabel,
|
|
1586
|
-
partTop,
|
|
1587
|
-
isDrilled,
|
|
1588
|
-
featureLd,
|
|
1589
1992
|
hitFigure,
|
|
1590
1993
|
FILE_KIND,
|
|
1591
1994
|
serializeRuleSet,
|
|
1592
1995
|
parseRuleSetFile,
|
|
1593
1996
|
llmRulePrompt,
|
|
1997
|
+
ID_KINDS,
|
|
1998
|
+
turnedTypeLabel,
|
|
1999
|
+
TURNING_RULES,
|
|
1594
2000
|
readReachCurve,
|
|
1595
2001
|
runStart,
|
|
1596
2002
|
peakHeight,
|