@toolpath/tool-drawing 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -1
- package/dist/{chunk-OUMNS4RI.js → chunk-UBENO6GN.js} +96 -52
- package/dist/geometry/index.d.ts +58 -2
- package/dist/geometry/index.js +5 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -3
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -45,6 +45,30 @@ chamfer. Where a number has to be assumed to draw at all — a drill point angle
|
|
|
45
45
|
the vendor never published — the segment says so in its `provenance`, and a
|
|
46
46
|
renderer is expected to show it.
|
|
47
47
|
|
|
48
|
+
## Two kinds of holder
|
|
49
|
+
|
|
50
|
+
`holder` is a union, and the two members are different inputs rather than two
|
|
51
|
+
grades of one:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// What a vendor's table states: a nose, a body, a flange.
|
|
55
|
+
holder: { noseDiameter: 28, noseLength: null, gaugeLength: 50, /* … */ }
|
|
56
|
+
|
|
57
|
+
// What the vendor's own CAD model measures: the silhouette, `[z, r]` in mm.
|
|
58
|
+
holder: { points: [[-48.4, 16], /* … */ [60, 8.5]], datum: 'gage-line', /* … */ }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A measured profile is **not** projected onto the parametric fields — the
|
|
62
|
+
V-flange groove and the thread relief are the reason to measure at all, and a
|
|
63
|
+
nose diameter and a body length cannot carry them. It is drawn as measured,
|
|
64
|
+
vertex for vertex, with its nose face at the stickout. `isHolderProfile`
|
|
65
|
+
narrows the union for an adapter that holds both.
|
|
66
|
+
|
|
67
|
+
On a `gage-line` profile the drawing splits at `z = 0` — the spindle face — so
|
|
68
|
+
everything above it is shaded as the spindle connection, exactly as the
|
|
69
|
+
parametric flange is. A `nose`-datumed profile has no spindle face to split on
|
|
70
|
+
and no gauge length to state, and the note under the drawing says so.
|
|
71
|
+
|
|
48
72
|
`geometry` keys are the scraper's own field names (`DC`, `SFDM`, `OAL`, `LCF`,
|
|
49
73
|
`RE`, `SIG`, `NOF`, `shoulder-diameter`, `shoulder-length`). They are not
|
|
50
74
|
renamed here: a translation table between two vocabularies is where an `SFDM`
|
|
@@ -80,7 +104,6 @@ unit a shop reads in is the application's.
|
|
|
80
104
|
|
|
81
105
|
```tsx
|
|
82
106
|
import { ClearanceOverlay, tightestGaps, describeGaps } from '@toolpath/tool-drawing/clearance'
|
|
83
|
-
|
|
84
107
|
;<ToolDrawing assembly={assembly} collisions={collisions} verdict={{ clears, note }}>
|
|
85
108
|
<ClearanceOverlay
|
|
86
109
|
profile={profile}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/model/types.ts
|
|
2
|
+
var isHolderProfile = (holder) => "points" in holder;
|
|
3
|
+
|
|
1
4
|
// src/model/outline.ts
|
|
2
5
|
var EPSILON = 1e-6;
|
|
3
6
|
var stated = (tool, code) => tool.provenance?.[code] ?? "vendor-stated";
|
|
@@ -96,6 +99,93 @@ var tip = (tool, LCF) => {
|
|
|
96
99
|
return null;
|
|
97
100
|
}
|
|
98
101
|
};
|
|
102
|
+
var profileSegments = (holder, stickout) => {
|
|
103
|
+
const nose = holder.points[holder.points.length - 1];
|
|
104
|
+
if (holder.points.length < 2 || nose === void 0) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const provenance = holder.provenance?.["points"] ?? "vendor-stated";
|
|
108
|
+
const gage = stickout + nose[0];
|
|
109
|
+
const up = [...holder.points].reverse().map(([z, r]) => ({ r, z: stickout + (nose[0] - z) }));
|
|
110
|
+
const cut = holder.datum === "gage-line" ? up.findIndex((point) => point.z > gage) : -1;
|
|
111
|
+
if (cut <= 0) {
|
|
112
|
+
return [{ part: "body", points: up, provenance }];
|
|
113
|
+
}
|
|
114
|
+
const below = up.slice(0, cut);
|
|
115
|
+
const above = up.slice(cut);
|
|
116
|
+
const last = below[below.length - 1];
|
|
117
|
+
const first = above[0];
|
|
118
|
+
const meet = last.z === gage ? last : {
|
|
119
|
+
r: last.r + (gage - last.z) / (first.z - last.z) * (first.r - last.r),
|
|
120
|
+
z: gage
|
|
121
|
+
};
|
|
122
|
+
if (meet !== last) {
|
|
123
|
+
below.push(meet);
|
|
124
|
+
}
|
|
125
|
+
above.unshift(meet);
|
|
126
|
+
return [
|
|
127
|
+
{ part: "body", points: below, provenance },
|
|
128
|
+
{ part: "flange", points: above, provenance }
|
|
129
|
+
];
|
|
130
|
+
};
|
|
131
|
+
var parametricSegments = (holder, stickout, DC) => {
|
|
132
|
+
if (holder.noseDiameter === null) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
const segments = [];
|
|
136
|
+
const rh = holder.noseDiameter / 2;
|
|
137
|
+
const holderStated = (code) => holder.provenance?.[code] ?? "vendor-stated";
|
|
138
|
+
let top = stickout;
|
|
139
|
+
const noseLength = holder.noseLength ?? holder.gaugeLength ?? Math.max(20, DC * 3);
|
|
140
|
+
segments.push({
|
|
141
|
+
part: "nose",
|
|
142
|
+
points: [
|
|
143
|
+
{ r: rh, z: stickout },
|
|
144
|
+
{ r: rh, z: stickout + noseLength }
|
|
145
|
+
],
|
|
146
|
+
provenance: holder.noseLength !== null ? holderStated("noseLength") : holder.gaugeLength === null ? "assumed" : holderStated("noseDiameter")
|
|
147
|
+
});
|
|
148
|
+
top = stickout + noseLength;
|
|
149
|
+
let radius = rh;
|
|
150
|
+
if (holder.bodyDiameter !== null && holder.bodyLength !== null) {
|
|
151
|
+
const rb = holder.bodyDiameter / 2;
|
|
152
|
+
segments.push({
|
|
153
|
+
part: "body",
|
|
154
|
+
points: [
|
|
155
|
+
{ r: rb, z: top },
|
|
156
|
+
{ r: rb, z: top + holder.bodyLength }
|
|
157
|
+
],
|
|
158
|
+
provenance: holderStated("bodyDiameter")
|
|
159
|
+
});
|
|
160
|
+
top += holder.bodyLength;
|
|
161
|
+
radius = rb;
|
|
162
|
+
}
|
|
163
|
+
if (holder.projection !== null && holder.flangeDiameter !== null) {
|
|
164
|
+
const flangeAt = stickout + holder.projection;
|
|
165
|
+
const rf = holder.flangeDiameter / 2;
|
|
166
|
+
if (flangeAt > top) {
|
|
167
|
+
segments.push({
|
|
168
|
+
part: "body",
|
|
169
|
+
points: [
|
|
170
|
+
{ r: radius, z: top },
|
|
171
|
+
{ r: radius, z: flangeAt }
|
|
172
|
+
],
|
|
173
|
+
provenance: "assumed"
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const flangeTop = holder.gaugeLength !== null && holder.gaugeLength > holder.projection ? stickout + holder.gaugeLength : flangeAt + 20;
|
|
177
|
+
segments.push({
|
|
178
|
+
part: "flange",
|
|
179
|
+
points: [
|
|
180
|
+
{ r: rf, z: flangeAt },
|
|
181
|
+
{ r: rf, z: flangeTop }
|
|
182
|
+
],
|
|
183
|
+
provenance: holder.gaugeLength === null ? "assumed" : holderStated("flangeDiameter")
|
|
184
|
+
});
|
|
185
|
+
top = flangeTop;
|
|
186
|
+
}
|
|
187
|
+
return segments;
|
|
188
|
+
};
|
|
99
189
|
var assemblyOutline = (assembly) => {
|
|
100
190
|
const { tool, holder, stickout } = assembly;
|
|
101
191
|
const { DC, LCF, SFDM } = tool.geometry;
|
|
@@ -151,9 +241,8 @@ var assemblyOutline = (assembly) => {
|
|
|
151
241
|
});
|
|
152
242
|
top = shankTop;
|
|
153
243
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const holderStated = (code) => holder.provenance?.[code] ?? "vendor-stated";
|
|
244
|
+
const holderSegments = stickout === null || holder === null ? [] : isHolderProfile(holder) ? profileSegments(holder, stickout) : parametricSegments(holder, stickout, DC);
|
|
245
|
+
if (stickout !== null && holder !== null && holderSegments.length > 0) {
|
|
157
246
|
const series = /(\d+(?:\.\d+)?)/.exec(holder.colletSeries ?? "");
|
|
158
247
|
if (holder.colletProtrusion !== null && series) {
|
|
159
248
|
const rc = Number(series[1]) / 2;
|
|
@@ -163,63 +252,18 @@ var assemblyOutline = (assembly) => {
|
|
|
163
252
|
{ r: rc, z: stickout - holder.colletProtrusion },
|
|
164
253
|
{ r: rc, z: stickout }
|
|
165
254
|
],
|
|
166
|
-
provenance:
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
const noseLength = holder.noseLength ?? holder.gaugeLength ?? Math.max(20, DC * 3);
|
|
170
|
-
segments.push({
|
|
171
|
-
part: "nose",
|
|
172
|
-
points: [
|
|
173
|
-
{ r: rh, z: stickout },
|
|
174
|
-
{ r: rh, z: stickout + noseLength }
|
|
175
|
-
],
|
|
176
|
-
provenance: holder.noseLength !== null ? holderStated("noseLength") : holder.gaugeLength === null ? "assumed" : holderStated("noseDiameter")
|
|
177
|
-
});
|
|
178
|
-
top = stickout + noseLength;
|
|
179
|
-
let radius2 = rh;
|
|
180
|
-
if (holder.bodyDiameter !== null && holder.bodyLength !== null) {
|
|
181
|
-
const rb = holder.bodyDiameter / 2;
|
|
182
|
-
segments.push({
|
|
183
|
-
part: "body",
|
|
184
|
-
points: [
|
|
185
|
-
{ r: rb, z: top },
|
|
186
|
-
{ r: rb, z: top + holder.bodyLength }
|
|
187
|
-
],
|
|
188
|
-
provenance: holderStated("bodyDiameter")
|
|
189
|
-
});
|
|
190
|
-
top += holder.bodyLength;
|
|
191
|
-
radius2 = rb;
|
|
192
|
-
}
|
|
193
|
-
if (holder.projection !== null && holder.flangeDiameter !== null) {
|
|
194
|
-
const flangeAt = stickout + holder.projection;
|
|
195
|
-
const rf = holder.flangeDiameter / 2;
|
|
196
|
-
if (flangeAt > top) {
|
|
197
|
-
segments.push({
|
|
198
|
-
part: "body",
|
|
199
|
-
points: [
|
|
200
|
-
{ r: radius2, z: top },
|
|
201
|
-
{ r: radius2, z: flangeAt }
|
|
202
|
-
],
|
|
203
|
-
provenance: "assumed"
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
const flangeTop = holder.gaugeLength !== null && holder.gaugeLength > holder.projection ? stickout + holder.gaugeLength : flangeAt + 20;
|
|
207
|
-
segments.push({
|
|
208
|
-
part: "flange",
|
|
209
|
-
points: [
|
|
210
|
-
{ r: rf, z: flangeAt },
|
|
211
|
-
{ r: rf, z: flangeTop }
|
|
212
|
-
],
|
|
213
|
-
provenance: holder.gaugeLength === null ? "assumed" : holderStated("flangeDiameter")
|
|
255
|
+
provenance: holder.provenance?.["colletProtrusion"] ?? "vendor-stated"
|
|
214
256
|
});
|
|
215
|
-
top = flangeTop;
|
|
216
257
|
}
|
|
258
|
+
segments.push(...holderSegments);
|
|
259
|
+
top = holderSegments[holderSegments.length - 1].points.at(-1)?.z ?? top;
|
|
217
260
|
}
|
|
218
261
|
const radius = Math.max(...segments.flatMap((segment) => segment.points.map((point2) => point2.r)));
|
|
219
262
|
return { segments, height: top, radius };
|
|
220
263
|
};
|
|
221
264
|
|
|
222
265
|
export {
|
|
266
|
+
isHolderProfile,
|
|
223
267
|
hasNeck,
|
|
224
268
|
assemblyOutline
|
|
225
269
|
};
|
package/dist/geometry/index.d.ts
CHANGED
|
@@ -36,9 +36,65 @@ interface ViewerHolder {
|
|
|
36
36
|
readonly colletProtrusion: number | null;
|
|
37
37
|
readonly provenance?: Readonly<Record<string, Provenance>>;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A holder as its own CAD model measures it: the silhouette, and nothing
|
|
41
|
+
* parametric.
|
|
42
|
+
*
|
|
43
|
+
* A {@link ViewerHolder} is a handful of numbers a vendor publishes in a table,
|
|
44
|
+
* and a drawing built from them is a stylised holder. This is the other thing a
|
|
45
|
+
* catalog can have — the envelope measured off the vendor's STEP model, a
|
|
46
|
+
* hundred-odd vertices carrying the V-flange groove and the thread relief that
|
|
47
|
+
* a machinist actually looks for. **It is not a refinement of the parametric
|
|
48
|
+
* form and does not project onto it**: reducing it to a nose and a body throws
|
|
49
|
+
* away the only reason to measure.
|
|
50
|
+
*
|
|
51
|
+
* So the two are a union rather than one shape with optional extras, and
|
|
52
|
+
* {@link isHolderProfile} tells them apart. A consumer that has both picks one;
|
|
53
|
+
* a consumer that has neither passes `null` and the tool is drawn alone.
|
|
54
|
+
*/
|
|
55
|
+
interface ViewerHolderProfile {
|
|
56
|
+
/**
|
|
57
|
+
* The silhouette as `[z, r]` in millimetres, `z` ascending.
|
|
58
|
+
*
|
|
59
|
+
* Two vertices share a `z` where the solid steps, so this is a polyline and
|
|
60
|
+
* not a function of `z`. Fewer than two vertices is no holder and draws none.
|
|
61
|
+
*/
|
|
62
|
+
readonly points: ReadonlyArray<readonly [z: number, r: number]>;
|
|
63
|
+
/**
|
|
64
|
+
* What `z = 0` means.
|
|
65
|
+
*
|
|
66
|
+
* `gage-line` is the spindle face, with `z` increasing toward the cutting end
|
|
67
|
+
* — so the taper is negative, the nose positive, and the holder's gauge
|
|
68
|
+
* length is the last vertex's `z`. `nose` is the frame a holder with no taper
|
|
69
|
+
* to solve a gauge plane on is measured in, and it is stated rather than
|
|
70
|
+
* silently referenced to an arbitrary end: there is no gauge length to read
|
|
71
|
+
* off it, and the drawing says so instead of printing one.
|
|
72
|
+
*/
|
|
73
|
+
readonly datum: 'gage-line' | 'nose';
|
|
74
|
+
/** The series the holder takes, as {@link ViewerHolder} means it. */
|
|
75
|
+
readonly colletSeries: string | null;
|
|
76
|
+
/** How far the seated collet stands proud of the nose, in millimetres. */
|
|
77
|
+
readonly colletProtrusion: number | null;
|
|
78
|
+
/**
|
|
79
|
+
* Keyed as {@link ViewerHolder}'s is, plus `points` for the measurement
|
|
80
|
+
* itself — which is `vendor-stated` unless a caller says otherwise, because
|
|
81
|
+
* the shape measured is the vendor's own model rather than a derivation from
|
|
82
|
+
* its table.
|
|
83
|
+
*/
|
|
84
|
+
readonly provenance?: Readonly<Record<string, Provenance>>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Which of the two holder forms this is.
|
|
88
|
+
*
|
|
89
|
+
* On the presence of `points` rather than on a `kind` tag, because a tag would
|
|
90
|
+
* have to be added to {@link ViewerHolder} as well and every existing adapter
|
|
91
|
+
* would stop compiling to gain nothing a structural check does not already
|
|
92
|
+
* give.
|
|
93
|
+
*/
|
|
94
|
+
declare const isHolderProfile: (holder: ViewerHolder | ViewerHolderProfile) => holder is ViewerHolderProfile;
|
|
39
95
|
interface ViewerAssembly {
|
|
40
96
|
readonly tool: ViewerTool;
|
|
41
|
-
readonly holder: ViewerHolder | null;
|
|
97
|
+
readonly holder: ViewerHolder | ViewerHolderProfile | null;
|
|
42
98
|
/**
|
|
43
99
|
* How far the tool stands out of the holder nose, in millimetres — the
|
|
44
100
|
* shop's number, nobody's else. Null draws the tool alone.
|
|
@@ -99,4 +155,4 @@ interface Outline {
|
|
|
99
155
|
*/
|
|
100
156
|
declare const assemblyOutline: (assembly: ViewerAssembly) => Outline | null;
|
|
101
157
|
|
|
102
|
-
export { type Outline, type OutlinePart, type OutlinePoint, type OutlineSegment, type Provenance, type ViewerAssembly, type ViewerHolder, type ViewerTool, assemblyOutline };
|
|
158
|
+
export { type Outline, type OutlinePart, type OutlinePoint, type OutlineSegment, type Provenance, type ViewerAssembly, type ViewerHolder, type ViewerHolderProfile, type ViewerTool, assemblyOutline, isHolderProfile };
|
package/dist/geometry/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ViewerAssembly, Outline } from './geometry/index.js';
|
|
2
|
-
export { Provenance, ViewerHolder, ViewerTool } from './geometry/index.js';
|
|
2
|
+
export { Provenance, ViewerHolder, ViewerHolderProfile, ViewerTool, isHolderProfile } from './geometry/index.js';
|
|
3
3
|
import { F as Frame, S as Sheet, T as Theme, P as Padding } from './sheet-D0LSO7qP.js';
|
|
4
4
|
export { B as Box, E as Extent, a as FrameOptions, O as Orientation, b as SHEETS, f as frameFor, o as orientationFor, t as typeSizeFor } from './sheet-D0LSO7qP.js';
|
|
5
5
|
import * as react from 'react';
|
package/dist/index.js
CHANGED
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
} from "./chunk-7ZID4QRO.js";
|
|
10
10
|
import {
|
|
11
11
|
assemblyOutline,
|
|
12
|
-
hasNeck
|
|
13
|
-
|
|
12
|
+
hasNeck,
|
|
13
|
+
isHolderProfile
|
|
14
|
+
} from "./chunk-UBENO6GN.js";
|
|
14
15
|
|
|
15
16
|
// src/model/frame.ts
|
|
16
17
|
var DEFAULT_PADDING = 16;
|
|
@@ -629,6 +630,7 @@ var ToolDrawing = ({
|
|
|
629
630
|
const { tool, holder } = assembly;
|
|
630
631
|
const name = caption ?? tool.label ?? tool.form;
|
|
631
632
|
const outline = assemblyOutline(assembly);
|
|
633
|
+
const unstatedLength = holder !== null && (isHolderProfile(holder) ? holder.datum !== "gage-line" : holder.gaugeLength === null);
|
|
632
634
|
if (outline === null) {
|
|
633
635
|
const missing = tool.geometry.DC === void 0 || tool.geometry.LCF === void 0;
|
|
634
636
|
return /* @__PURE__ */ jsx2(
|
|
@@ -818,7 +820,7 @@ var ToolDrawing = ({
|
|
|
818
820
|
"Drawn from stated dimensions",
|
|
819
821
|
assumed.length > 0 ? `; ${assumed.join(", ")} assumed` : "",
|
|
820
822
|
".",
|
|
821
|
-
|
|
823
|
+
unstatedLength ? " The holder length is not stated." : ""
|
|
822
824
|
]
|
|
823
825
|
}
|
|
824
826
|
)
|
|
@@ -838,6 +840,7 @@ export {
|
|
|
838
840
|
figureType,
|
|
839
841
|
formatMillimetres,
|
|
840
842
|
frameFor,
|
|
843
|
+
isHolderProfile,
|
|
841
844
|
laneOffset,
|
|
842
845
|
orientationFor,
|
|
843
846
|
stackLabels,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toolpath/tool-drawing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "2D elevation drawing of a cutting tool and its holder",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -43,11 +43,6 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"sideEffects": false,
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "tsup src/index.ts src/geometry/index.ts src/clearance/index.ts --format esm --dts --clean --external react --external react-dom",
|
|
48
|
-
"check-types": "tsc --noEmit",
|
|
49
|
-
"test": "vitest run"
|
|
50
|
-
},
|
|
51
46
|
"peerDependencies": {
|
|
52
47
|
"react": "^19.0.0",
|
|
53
48
|
"react-dom": "^19.0.0"
|
|
@@ -63,5 +58,10 @@
|
|
|
63
58
|
"tsup": "8.5.1",
|
|
64
59
|
"typescript": "5.9.3",
|
|
65
60
|
"vitest": "4.1.10"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsup src/index.ts src/geometry/index.ts src/clearance/index.ts --format esm --dts --clean --external react --external react-dom",
|
|
64
|
+
"check-types": "tsc --noEmit",
|
|
65
|
+
"test": "vitest run"
|
|
66
66
|
}
|
|
67
|
-
}
|
|
67
|
+
}
|