@toolpath/tool-support 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/assembly-fit.d.ts +54 -0
- package/dist/assembly-fit.js +55 -0
- package/dist/clamping.d.ts +55 -0
- package/dist/clamping.js +70 -0
- package/dist/clearance.d.ts +115 -0
- package/dist/clearance.js +209 -0
- package/dist/demand.d.ts +51 -0
- package/dist/demand.js +23 -0
- package/dist/fit.d.ts +69 -0
- package/dist/fit.js +90 -0
- package/dist/forms.d.ts +130 -0
- package/dist/forms.js +44 -0
- package/dist/geometry.d.ts +191 -0
- package/dist/geometry.js +124 -0
- package/dist/holding.d.ts +266 -0
- package/dist/holding.js +203 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +62 -0
- package/dist/material.d.ts +29 -0
- package/dist/material.js +30 -0
- package/dist/parts.d.ts +56 -0
- package/dist/parts.js +38 -0
- package/dist/profile.d.ts +104 -0
- package/dist/profile.js +68 -0
- package/dist/provenance.d.ts +25 -0
- package/dist/provenance.js +15 -0
- package/dist/reach.d.ts +46 -0
- package/dist/reach.js +37 -0
- package/dist/section.d.ts +67 -0
- package/dist/section.js +185 -0
- package/dist/stickout.d.ts +187 -0
- package/dist/stickout.js +161 -0
- package/dist/tool.d.ts +80 -0
- package/dist/tool.js +89 -0
- package/dist/units.d.ts +73 -0
- package/dist/units.js +82 -0
- package/package.json +48 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a stated fact came from.
|
|
3
|
+
*
|
|
4
|
+
* Carried per constant by the scraper and kept all the way through, for one
|
|
5
|
+
* reason: a number a shop cannot trace is a number they have to take on faith.
|
|
6
|
+
* A derived or assumed value has to be visibly not the vendor's.
|
|
7
|
+
*
|
|
8
|
+
* The scraper calls this `FactSource` and the drawing calls it `Provenance`;
|
|
9
|
+
* they are the same three strings, and this is the one declaration. `Provenance`
|
|
10
|
+
* is the name kept because it is what the values describe — the vendor's
|
|
11
|
+
* `vendor-stated` is not a *source* in the sense the scraper's transport means
|
|
12
|
+
* one.
|
|
13
|
+
*/
|
|
14
|
+
/** Every {@link Provenance}, in decreasing order of how much a shop can lean on it. */
|
|
15
|
+
export declare const PROVENANCE: readonly ["vendor-stated", "derived", "assumed"];
|
|
16
|
+
export type Provenance = (typeof PROVENANCE)[number];
|
|
17
|
+
/**
|
|
18
|
+
* Which values are the vendor's and which this pipeline decided, keyed the same
|
|
19
|
+
* way the record they describe is.
|
|
20
|
+
*
|
|
21
|
+
* A key with no entry is not a claim of `vendor-stated`: it is nobody having
|
|
22
|
+
* said. Defaulting an absent provenance to the vendor's is how a derived number
|
|
23
|
+
* ends up presented as published.
|
|
24
|
+
*/
|
|
25
|
+
export type ProvenanceMap = Readonly<Record<string, Provenance>>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a stated fact came from.
|
|
3
|
+
*
|
|
4
|
+
* Carried per constant by the scraper and kept all the way through, for one
|
|
5
|
+
* reason: a number a shop cannot trace is a number they have to take on faith.
|
|
6
|
+
* A derived or assumed value has to be visibly not the vendor's.
|
|
7
|
+
*
|
|
8
|
+
* The scraper calls this `FactSource` and the drawing calls it `Provenance`;
|
|
9
|
+
* they are the same three strings, and this is the one declaration. `Provenance`
|
|
10
|
+
* is the name kept because it is what the values describe — the vendor's
|
|
11
|
+
* `vendor-stated` is not a *source* in the sense the scraper's transport means
|
|
12
|
+
* one.
|
|
13
|
+
*/
|
|
14
|
+
/** Every {@link Provenance}, in decreasing order of how much a shop can lean on it. */
|
|
15
|
+
export const PROVENANCE = ['vendor-stated', 'derived', 'assumed'];
|
package/dist/reach.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worst-case material around a feature, and what a stack has to get past.
|
|
3
|
+
*
|
|
4
|
+
* **Declared structurally, on purpose.** The shape is exactly what the Toolpath
|
|
5
|
+
* part contracts call a `ReachCurve`, and naming it here rather than importing
|
|
6
|
+
* it is what keeps this package free of the API's schema: a `ReachCurve` off a
|
|
7
|
+
* report satisfies this by structure, with no adapter, and a consumer that only
|
|
8
|
+
* draws a tool pulls in no OpenAPI contract to do it.
|
|
9
|
+
*
|
|
10
|
+
* That seam is what makes the whole package possible. Reading a curve *off a
|
|
11
|
+
* report* is an adapter and stays with whoever holds the report; reading a
|
|
12
|
+
* curve is arithmetic and belongs here.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Material heights by distance out from the cut, as a staircase.
|
|
16
|
+
*
|
|
17
|
+
* Read as "material within `horizontalOffset[i]` of the cut rises to
|
|
18
|
+
* `verticalOffset[i]`". Both are in millimetres; the offsets run outward from
|
|
19
|
+
* the cutting edge and the heights up from the bottom of the feature.
|
|
20
|
+
*
|
|
21
|
+
* The rise comes at the **start** of each run, so everything out to a knot is
|
|
22
|
+
* already as tall as that knot says. Anything that reads this curve has to
|
|
23
|
+
* agree about that, or a drawn staircase and a clearance verdict describe two
|
|
24
|
+
* different pockets.
|
|
25
|
+
*/
|
|
26
|
+
export interface ReachCurve {
|
|
27
|
+
readonly horizontalOffset: readonly number[];
|
|
28
|
+
readonly verticalOffset: readonly number[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The tallest material within `offset` mm of the cut, above the feature's
|
|
32
|
+
* bottom.
|
|
33
|
+
*
|
|
34
|
+
* **The twin that had nowhere else to go.** One copy decided the verdict — the
|
|
35
|
+
* clearance sweep, with a dozen callers that never draw anything — and the
|
|
36
|
+
* other was in the drawing package, because the gaps an overlay dimensions
|
|
37
|
+
* cannot be measured without it. Neither could depend on the other: putting the
|
|
38
|
+
* verdict behind a rendering package is the thing the whole split exists to
|
|
39
|
+
* avoid, and a drawing may not depend on a catalog's data package. So there
|
|
40
|
+
* were two, and the note beside each said they must agree exactly.
|
|
41
|
+
*
|
|
42
|
+
* The rise comes at the **start** of each run: for an offset between knots the
|
|
43
|
+
* material could be anywhere out to the next knot, so the next knot's height is
|
|
44
|
+
* the bound. Past the last knot the curve clamps.
|
|
45
|
+
*/
|
|
46
|
+
export declare const heightAt: (curve: ReachCurve, offset: number) => number;
|
package/dist/reach.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The worst-case material around a feature, and what a stack has to get past.
|
|
3
|
+
*
|
|
4
|
+
* **Declared structurally, on purpose.** The shape is exactly what the Toolpath
|
|
5
|
+
* part contracts call a `ReachCurve`, and naming it here rather than importing
|
|
6
|
+
* it is what keeps this package free of the API's schema: a `ReachCurve` off a
|
|
7
|
+
* report satisfies this by structure, with no adapter, and a consumer that only
|
|
8
|
+
* draws a tool pulls in no OpenAPI contract to do it.
|
|
9
|
+
*
|
|
10
|
+
* That seam is what makes the whole package possible. Reading a curve *off a
|
|
11
|
+
* report* is an adapter and stays with whoever holds the report; reading a
|
|
12
|
+
* curve is arithmetic and belongs here.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The tallest material within `offset` mm of the cut, above the feature's
|
|
16
|
+
* bottom.
|
|
17
|
+
*
|
|
18
|
+
* **The twin that had nowhere else to go.** One copy decided the verdict — the
|
|
19
|
+
* clearance sweep, with a dozen callers that never draw anything — and the
|
|
20
|
+
* other was in the drawing package, because the gaps an overlay dimensions
|
|
21
|
+
* cannot be measured without it. Neither could depend on the other: putting the
|
|
22
|
+
* verdict behind a rendering package is the thing the whole split exists to
|
|
23
|
+
* avoid, and a drawing may not depend on a catalog's data package. So there
|
|
24
|
+
* were two, and the note beside each said they must agree exactly.
|
|
25
|
+
*
|
|
26
|
+
* The rise comes at the **start** of each run: for an offset between knots the
|
|
27
|
+
* material could be anywhere out to the next knot, so the next knot's height is
|
|
28
|
+
* the bound. Past the last knot the curve clamps.
|
|
29
|
+
*/
|
|
30
|
+
export const heightAt = (curve, offset) => {
|
|
31
|
+
for (let index = 0; index < curve.horizontalOffset.length; index += 1) {
|
|
32
|
+
if ((curve.horizontalOffset[index] ?? 0) >= offset) {
|
|
33
|
+
return curve.verticalOffset[index] ?? 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return curve.verticalOffset[curve.verticalOffset.length - 1] ?? 0;
|
|
37
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type ReachCurve } from './reach.js';
|
|
2
|
+
/**
|
|
3
|
+
* The feature in section, with the tool in it.
|
|
4
|
+
*
|
|
5
|
+
* A cross-section through the feature at its **worst-case place** — the
|
|
6
|
+
* tightest spot the tool has to reach — drawn the way a machinist sketches a
|
|
7
|
+
* setup: the tool's tip on the feature's bottom, its cutting edge against the
|
|
8
|
+
* wall, the wall up to the feature's top, and the part above and beyond it
|
|
9
|
+
* standing as tall as the reach curve says it can. A drill sits on the hole's
|
|
10
|
+
* axis instead, because a drill does not hug a wall.
|
|
11
|
+
*
|
|
12
|
+
* Everything is from the datasheet: the depth from `zMax − zMin`, the width
|
|
13
|
+
* from the tightest clearance (`cd.ignore.min`) or a hole's diameter, the
|
|
14
|
+
* fillet from `filletRadius`, a hole's bottom from `fullConeDeg`, the top of
|
|
15
|
+
* the part from the highest feature cut the same way, and the material
|
|
16
|
+
* beyond the wall from the reach curve — the same staircase the sweep walks,
|
|
17
|
+
* so what the drawing shows the holder clearing is what the check cleared.
|
|
18
|
+
*
|
|
19
|
+
* Coordinates are millimetres in the drawing's own frame: `x` across from the
|
|
20
|
+
* tool's axis, `z` up from the feature's bottom.
|
|
21
|
+
*/
|
|
22
|
+
export type SectionKind = 'pocket' | 'hole' | 'wall' | 'face';
|
|
23
|
+
export interface FeatureSection {
|
|
24
|
+
readonly kind: SectionKind;
|
|
25
|
+
/** The feature's own height, `zMax − zMin`. */
|
|
26
|
+
readonly depth: number;
|
|
27
|
+
/** False for a through feature: nothing is drawn under the bottom. */
|
|
28
|
+
readonly hasFloor: boolean;
|
|
29
|
+
/** Across the tightest place, or a hole's diameter; null where the datasheet does not say. */
|
|
30
|
+
readonly width: number | null;
|
|
31
|
+
/** The floor fillet, 0 for a sharp corner. */
|
|
32
|
+
readonly filletRadius: number;
|
|
33
|
+
/** A hole's bottom cone, full angle; 180 or null is flat. */
|
|
34
|
+
readonly coneDeg: number | null;
|
|
35
|
+
/** The top of the part above the feature's bottom, which is what the holder must clear. */
|
|
36
|
+
readonly topAbove: number;
|
|
37
|
+
readonly curve: ReachCurve | null;
|
|
38
|
+
}
|
|
39
|
+
export interface SectionPoint {
|
|
40
|
+
readonly x: number;
|
|
41
|
+
readonly z: number;
|
|
42
|
+
}
|
|
43
|
+
export interface Section {
|
|
44
|
+
/** Closed polygons of material, to hatch. */
|
|
45
|
+
readonly material: ReadonlyArray<ReadonlyArray<SectionPoint>>;
|
|
46
|
+
/** The surface the tip sits on, across, or null for a through feature. */
|
|
47
|
+
readonly floor: {
|
|
48
|
+
readonly from: number;
|
|
49
|
+
readonly to: number;
|
|
50
|
+
} | null;
|
|
51
|
+
readonly leftWall: number | null;
|
|
52
|
+
readonly rightWall: number | null;
|
|
53
|
+
readonly extent: {
|
|
54
|
+
readonly left: number;
|
|
55
|
+
readonly right: number;
|
|
56
|
+
readonly top: number;
|
|
57
|
+
readonly bottom: number;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Stock drawn under a floor, mm: enough to read as solid, not a claim about thickness. */
|
|
61
|
+
export declare const FLOOR_BAND = 6;
|
|
62
|
+
/** Part drawn beyond the last thing the datasheet states, mm. */
|
|
63
|
+
export declare const REACH = 12;
|
|
64
|
+
export declare const sectionOutline: (section: FeatureSection, tool: {
|
|
65
|
+
readonly diameter: number;
|
|
66
|
+
readonly form: string;
|
|
67
|
+
}) => Section;
|
package/dist/section.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { heightAt } from './reach.js';
|
|
2
|
+
/** Forms that sit on a hole's axis rather than against its wall. */
|
|
3
|
+
const ON_AXIS = new Set([
|
|
4
|
+
'drill',
|
|
5
|
+
'spot drill',
|
|
6
|
+
'center drill',
|
|
7
|
+
'reamer',
|
|
8
|
+
'counter sink',
|
|
9
|
+
'counter bore',
|
|
10
|
+
'boring bar',
|
|
11
|
+
'tap right hand',
|
|
12
|
+
'tap left hand',
|
|
13
|
+
'thread mill',
|
|
14
|
+
]);
|
|
15
|
+
/** Stock drawn under a floor, mm: enough to read as solid, not a claim about thickness. */
|
|
16
|
+
export const FLOOR_BAND = 6;
|
|
17
|
+
/** Part drawn beyond the last thing the datasheet states, mm. */
|
|
18
|
+
export const REACH = 12;
|
|
19
|
+
const exact = (value) => Math.round(value * 1e6) / 1e6;
|
|
20
|
+
/** A quarter arc of a floor fillet, from the floor tangent up to the wall tangent. */
|
|
21
|
+
const filletArc = (corner, radius, side, steps = 6) => Array.from({ length: steps + 1 }, (_, index) => {
|
|
22
|
+
// From straight down (on the floor, one radius in from the wall) round to
|
|
23
|
+
// straight across (on the wall, one radius up).
|
|
24
|
+
const angle = (-90 + (90 * index) / steps) * (Math.PI / 180);
|
|
25
|
+
const centre = { x: corner.x + side * radius, z: corner.z + radius };
|
|
26
|
+
return {
|
|
27
|
+
x: exact(centre.x - side * radius * Math.cos(angle)),
|
|
28
|
+
z: exact(centre.z + radius * Math.sin(angle)),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* The material beyond one wall, as a staircase from the tool's edge outward.
|
|
33
|
+
*
|
|
34
|
+
* `fromEdge` is how far the wall stands from the tool's cutting edge; the
|
|
35
|
+
* curve's offsets are from that edge. Heights are the sweep's own reading —
|
|
36
|
+
* every offset up to a knot is as tall as the knot says — so the staircase
|
|
37
|
+
* rises at the start of each run, exactly as `materialProfile` draws it.
|
|
38
|
+
* Returns (distance beyond the wall, height) pairs, ending `reach` past the
|
|
39
|
+
* last knot.
|
|
40
|
+
*/
|
|
41
|
+
const staircase = (curve, fromEdge, floorAt, topAbove, reach) => {
|
|
42
|
+
if (!curve || curve.horizontalOffset.length === 0) {
|
|
43
|
+
return [
|
|
44
|
+
{ s: 0, z: Math.max(floorAt, topAbove) },
|
|
45
|
+
{ s: reach, z: Math.max(floorAt, topAbove) },
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
const out = [];
|
|
49
|
+
let from = fromEdge;
|
|
50
|
+
curve.horizontalOffset.forEach((offset, index) => {
|
|
51
|
+
if (offset < fromEdge) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const height = Math.max(floorAt, curve.verticalOffset[index] ?? 0);
|
|
55
|
+
out.push({ s: from - fromEdge, z: height }, { s: offset - fromEdge, z: height });
|
|
56
|
+
from = offset;
|
|
57
|
+
});
|
|
58
|
+
const last = out[out.length - 1];
|
|
59
|
+
const height = last ? last.z : Math.max(floorAt, heightAt(curve, fromEdge));
|
|
60
|
+
if (!last) {
|
|
61
|
+
out.push({ s: 0, z: height });
|
|
62
|
+
}
|
|
63
|
+
out.push({ s: (last?.s ?? 0) + reach, z: height });
|
|
64
|
+
return out;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* One wall and the material behind it, as a closed polygon.
|
|
68
|
+
*
|
|
69
|
+
* `side` is −1 for the wall on the tool's left, +1 on its right. From the
|
|
70
|
+
* floor tangent of the fillet, round the fillet, up the wall to the feature's
|
|
71
|
+
* top, then outward along the staircase, then down to the bottom of the
|
|
72
|
+
* drawing and back under the fillet.
|
|
73
|
+
*/
|
|
74
|
+
const wallPolygon = (wallX, side, section, toolEdge, bottom, floorAt) => {
|
|
75
|
+
const r = Math.max(0, Math.min(section.filletRadius, section.depth));
|
|
76
|
+
const corner = { x: wallX, z: floorAt };
|
|
77
|
+
const points = [];
|
|
78
|
+
if (r > 0) {
|
|
79
|
+
points.push(...filletArc(corner, r, side === -1 ? 1 : -1, 6));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
points.push(corner);
|
|
83
|
+
}
|
|
84
|
+
points.push({ x: wallX, z: floorAt + section.depth });
|
|
85
|
+
const fromEdge = Math.abs(wallX - toolEdge);
|
|
86
|
+
for (const step of staircase(section.curve, fromEdge, floorAt + section.depth, section.topAbove, REACH)) {
|
|
87
|
+
points.push({ x: exact(wallX + side * step.s), z: exact(step.z) });
|
|
88
|
+
}
|
|
89
|
+
const far = points[points.length - 1].x;
|
|
90
|
+
points.push({ x: far, z: bottom });
|
|
91
|
+
points.push({ x: exact(wallX + side * -1 * r), z: bottom });
|
|
92
|
+
return points;
|
|
93
|
+
};
|
|
94
|
+
export const sectionOutline = (section, tool) => {
|
|
95
|
+
const R = tool.diameter / 2;
|
|
96
|
+
const bottom = section.hasFloor ? -FLOOR_BAND : 0;
|
|
97
|
+
const material = [];
|
|
98
|
+
if (section.kind === 'face') {
|
|
99
|
+
// The feature is the floor: stock under it across the whole drawing, and
|
|
100
|
+
// whatever stands around it either side, from the reach curve.
|
|
101
|
+
const walls = [-1, 1].map((side) => {
|
|
102
|
+
const rise = staircase(section.curve, 0, 0, section.topAbove, REACH);
|
|
103
|
+
const points = rise.map((step) => ({ x: exact(side * (R + step.s)), z: exact(step.z) }));
|
|
104
|
+
return { side, points, far: points[points.length - 1].x };
|
|
105
|
+
});
|
|
106
|
+
const left = walls[0].far;
|
|
107
|
+
const right = walls[1].far;
|
|
108
|
+
material.push([
|
|
109
|
+
{ x: left, z: 0 },
|
|
110
|
+
{ x: right, z: 0 },
|
|
111
|
+
{ x: right, z: bottom },
|
|
112
|
+
{ x: left, z: bottom },
|
|
113
|
+
]);
|
|
114
|
+
for (const wall of walls) {
|
|
115
|
+
if (wall.points.some((point) => point.z > 0)) {
|
|
116
|
+
material.push([{ x: exact(wall.side * R), z: 0 }, ...wall.points, { x: wall.far, z: 0 }]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
material,
|
|
121
|
+
floor: { from: left, to: right },
|
|
122
|
+
leftWall: null,
|
|
123
|
+
rightWall: null,
|
|
124
|
+
extent: {
|
|
125
|
+
left,
|
|
126
|
+
right,
|
|
127
|
+
top: Math.max(0, ...walls.flatMap((w) => w.points.map((p) => p.z))),
|
|
128
|
+
bottom,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const onAxis = section.kind === 'hole' && ON_AXIS.has(tool.form) && section.width !== null;
|
|
133
|
+
const leftWall = onAxis ? -(section.width ?? tool.diameter) / 2 : -R;
|
|
134
|
+
const width = section.width === null ? null : Math.max(section.width, tool.diameter);
|
|
135
|
+
const rightWall = section.kind === 'wall' || width === null ? null : onAxis ? width / 2 : leftWall + width;
|
|
136
|
+
// A drilled bottom: the walls stand on the cone's rim, the tip in its apex.
|
|
137
|
+
const coneRise = section.kind === 'hole' &&
|
|
138
|
+
section.hasFloor &&
|
|
139
|
+
section.coneDeg !== null &&
|
|
140
|
+
section.coneDeg < 180 &&
|
|
141
|
+
section.filletRadius <= 0
|
|
142
|
+
? Math.abs(leftWall) / Math.tan(((section.coneDeg / 2) * Math.PI) / 180)
|
|
143
|
+
: 0;
|
|
144
|
+
const left = wallPolygon(leftWall, -1, section, -R, bottom, coneRise);
|
|
145
|
+
material.push(left);
|
|
146
|
+
const leftFar = left.find((p) => p.z === bottom).x;
|
|
147
|
+
let rightFar = leftWall + Math.max(width ?? 0, tool.diameter) + REACH * 2;
|
|
148
|
+
if (rightWall !== null) {
|
|
149
|
+
const right = wallPolygon(rightWall, 1, section, R, bottom, coneRise);
|
|
150
|
+
material.push(right);
|
|
151
|
+
rightFar = right.find((p) => p.z === bottom).x;
|
|
152
|
+
}
|
|
153
|
+
let floor = null;
|
|
154
|
+
if (section.hasFloor) {
|
|
155
|
+
const r = Math.max(0, Math.min(section.filletRadius, section.depth));
|
|
156
|
+
const from = leftWall + r;
|
|
157
|
+
const to = rightWall === null ? rightFar : rightWall - r;
|
|
158
|
+
if (coneRise > 0 && rightWall !== null) {
|
|
159
|
+
material.push([
|
|
160
|
+
{ x: leftWall, z: exact(coneRise) },
|
|
161
|
+
{ x: 0, z: 0 },
|
|
162
|
+
{ x: rightWall, z: exact(coneRise) },
|
|
163
|
+
{ x: rightWall, z: bottom },
|
|
164
|
+
{ x: leftWall, z: bottom },
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
material.push([
|
|
169
|
+
{ x: from, z: 0 },
|
|
170
|
+
{ x: to, z: 0 },
|
|
171
|
+
{ x: to, z: bottom },
|
|
172
|
+
{ x: from, z: bottom },
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
floor = { from, to };
|
|
176
|
+
}
|
|
177
|
+
const top = Math.max(...material.flatMap((polygon) => polygon.map((p) => p.z)));
|
|
178
|
+
return {
|
|
179
|
+
material,
|
|
180
|
+
floor,
|
|
181
|
+
leftWall,
|
|
182
|
+
rightWall,
|
|
183
|
+
extent: { left: leftFar, right: rightFar, top, bottom },
|
|
184
|
+
};
|
|
185
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How far a tool stands out of whatever holds it — the one answer.
|
|
3
|
+
*
|
|
4
|
+
* **There used to be four.** The same question was worked out in four
|
|
5
|
+
* unconnected places, and they disagreed by a factor of two on an ordinary
|
|
6
|
+
* tool:
|
|
7
|
+
*
|
|
8
|
+
* | where | what it computed | on a ⌀1 in end mill, `OAL` 5, `LCF` 1.25, `SFDM` 1 |
|
|
9
|
+
* | --------------------------------- | ----------------------------- | -------------------------------------------------- |
|
|
10
|
+
* | the clamping rule → `geometry.LBH` | `OAL − 3×SFDM` | 2.000 in |
|
|
11
|
+
* | the holding module's `max` | `OAL − OAL×heldShare` | 3.333 in |
|
|
12
|
+
* | its `default` → the drawing | flutes, floored and stepped | 1.250 in |
|
|
13
|
+
* | the hole-mode reach check | read `geometry.LBH` as a ceiling | 2.000 in |
|
|
14
|
+
*
|
|
15
|
+
* The details table printed the first and the drawing beside it drew the third,
|
|
16
|
+
* so a dimension line for `LBH` ran up past the holder nose and into the holder
|
|
17
|
+
* body. Neither of the first two consulted the other, and the two knobs behind
|
|
18
|
+
* them — a minimum clamping length (a length of **shank**) and a good hold (a
|
|
19
|
+
* share of the **overall length**) — were combined nowhere.
|
|
20
|
+
*
|
|
21
|
+
* **That bug is the single strongest argument for this package existing.** It
|
|
22
|
+
* was fixed inside one application, which left the next consumer of a tool
|
|
23
|
+
* catalog and a tool drawing to reproduce it from scratch: the quantity is a
|
|
24
|
+
* pure function of the tool, the collet and a shop's policy, and it had no home
|
|
25
|
+
* until this one.
|
|
26
|
+
*
|
|
27
|
+
* So this module owns the quantity outright and every other number is this same
|
|
28
|
+
* function with more arguments:
|
|
29
|
+
*
|
|
30
|
+
* ```
|
|
31
|
+
* geometry.LBH ≡ stickoutRange(tool).setup — no holder, no feature
|
|
32
|
+
* Assembly.stickout ≡ stickoutRange(tool, { grip, required }).setup
|
|
33
|
+
* the ceiling ≡ stickoutRange(tool, …).max
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* `min ≤ setup ≤ max` holds by construction, so a drawn stickout can never
|
|
37
|
+
* exceed the length a table prints beside it. That invariant is a test rather
|
|
38
|
+
* than this sentence.
|
|
39
|
+
*
|
|
40
|
+
* **`LBH` is the setup length, not the ceiling.** The "below holder" column
|
|
41
|
+
* answers what a machinist would set the tool up at; the most it *could* stand
|
|
42
|
+
* out is {@link StickoutRange.max}, which is checked and reported but is not
|
|
43
|
+
* the column. What makes that reading workable is that the floor and the step
|
|
44
|
+
* reach it — {@link DEFAULT_STICKOUT_POLICY} carries `least` and `step`, and an
|
|
45
|
+
* earlier default carried zero for both and so produced the bare flute length.
|
|
46
|
+
*/
|
|
47
|
+
import { type ClampingRule } from './clamping.js';
|
|
48
|
+
import type { Geometry } from './geometry.js';
|
|
49
|
+
import type { UnitSystem } from './units.js';
|
|
50
|
+
/**
|
|
51
|
+
* What this module needs of a tool: its geometry, and which unit system its
|
|
52
|
+
* step is counted in.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately narrower than a whole {@link Tool} so a build can ask before a
|
|
55
|
+
* tool is finished being built, and so nothing here can reach for a catalog
|
|
56
|
+
* number or a vendor.
|
|
57
|
+
*/
|
|
58
|
+
export interface StickoutTool {
|
|
59
|
+
readonly geometry: Geometry;
|
|
60
|
+
readonly unitSystem: UnitSystem;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The share of a tool's overall length a holder must always have hold of.
|
|
64
|
+
*
|
|
65
|
+
* A third. **A shop's figure, not a vendor's** — no vendor in the scraped
|
|
66
|
+
* catalog publishes a minimum engagement — which is why it is named here and
|
|
67
|
+
* every control that shows it should say whose it is. Deliberately a share of
|
|
68
|
+
* the length and not a multiple of the shank diameter: how much of a tool a
|
|
69
|
+
* collet needs is about the tool's leverage, not its shank. That other reading
|
|
70
|
+
* is {@link ClampingRule}, and both are honoured — see {@link StickoutLimit}.
|
|
71
|
+
*/
|
|
72
|
+
export declare const HELD_SHARE: number;
|
|
73
|
+
/**
|
|
74
|
+
* How the setup stickout is set.
|
|
75
|
+
*
|
|
76
|
+
* Nobody sets a tool up 6 mm out, so the stickout stands out at least `least`
|
|
77
|
+
* (half an inch) where the tool's length allows, and lands on a round number —
|
|
78
|
+
* the `step` for the tool's unit system (an eighth of an inch, or 3 mm) nearest
|
|
79
|
+
* what the holder needs, never under it.
|
|
80
|
+
*/
|
|
81
|
+
export interface StickoutPolicy {
|
|
82
|
+
/** Share of the overall length that must stay in the holder. */
|
|
83
|
+
readonly heldShare: number;
|
|
84
|
+
/** The shortest stickout worth setting up, mm; zero for none. */
|
|
85
|
+
readonly least: number;
|
|
86
|
+
/**
|
|
87
|
+
* The increment the setup lands on, mm, by the tool's unit system; zero for
|
|
88
|
+
* none.
|
|
89
|
+
*
|
|
90
|
+
* Keyed by {@link UnitSystem} rather than by a pair of its own, so a policy
|
|
91
|
+
* cannot name a system this package does not have.
|
|
92
|
+
*/
|
|
93
|
+
readonly step: Readonly<Record<UnitSystem, number>>;
|
|
94
|
+
}
|
|
95
|
+
/** What a dataset is built with, and what a page starts at. */
|
|
96
|
+
export declare const DEFAULT_STICKOUT_POLICY: StickoutPolicy;
|
|
97
|
+
/**
|
|
98
|
+
* Which rule set the ceiling, so a control can say why rather than showing a
|
|
99
|
+
* number nobody can trace.
|
|
100
|
+
*
|
|
101
|
+
* `clamp` is the shop's clamping length, or the vendor's own `LSCN`; `hold` is
|
|
102
|
+
* {@link HELD_SHARE}; `collet` is the collet's published grip. They used to be
|
|
103
|
+
* a ceiling each in a different file; here they are three caps and the tightest
|
|
104
|
+
* wins.
|
|
105
|
+
*/
|
|
106
|
+
export type StickoutLimit = 'clamp' | 'hold' | 'collet';
|
|
107
|
+
export interface StickoutRange {
|
|
108
|
+
/** Shortest, mm: the flutes out of the collet, or the neck where there is one. */
|
|
109
|
+
readonly min: number;
|
|
110
|
+
/**
|
|
111
|
+
* The length to set the tool up at, mm: the least that works for this
|
|
112
|
+
* feature, floored and stepped by the policy, held under {@link max}.
|
|
113
|
+
*
|
|
114
|
+
* This is `geometry.LBH` when asked with no holder and no feature, and an
|
|
115
|
+
* assembly's stickout when asked with both.
|
|
116
|
+
*/
|
|
117
|
+
readonly setup: number;
|
|
118
|
+
/**
|
|
119
|
+
* Longest, mm: the tightest of the three caps, and never under {@link min} —
|
|
120
|
+
* a tool that cannot meet the rule at any depth is gripped as short as the
|
|
121
|
+
* grip allows and {@link gripShort} says so. Null where the tool states no
|
|
122
|
+
* overall length, which is an unbounded range rather than a bound of nothing.
|
|
123
|
+
*/
|
|
124
|
+
readonly max: number | null;
|
|
125
|
+
/** Which cap {@link max} came from, or null where nothing capped it. */
|
|
126
|
+
readonly limitedBy: StickoutLimit | null;
|
|
127
|
+
/** The parallel shank behind {@link min}, mm: all a holder can ever grip. */
|
|
128
|
+
readonly grip: number | null;
|
|
129
|
+
/** How much of the tool the tightest cap asks to keep in the holder, mm. */
|
|
130
|
+
readonly wantedGrip: number | null;
|
|
131
|
+
/**
|
|
132
|
+
* True when the rule cannot be met at any depth: the range collapses onto
|
|
133
|
+
* {@link min}, and a control should say why rather than refuse.
|
|
134
|
+
*/
|
|
135
|
+
readonly gripShort: boolean;
|
|
136
|
+
}
|
|
137
|
+
export interface StickoutRequest {
|
|
138
|
+
/**
|
|
139
|
+
* How much shank the holder actually grips, mm — a collet's published grip
|
|
140
|
+
* length. Null where the vendor does not publish one, which REGO-FIX's
|
|
141
|
+
* powRgrip line does not, and null for a bore or shrink holder, whose grip
|
|
142
|
+
* this package does not carry.
|
|
143
|
+
*
|
|
144
|
+
* **A length rather than a {@link Collet}**, so this module depends on
|
|
145
|
+
* nothing in `holding.ts` and the two cannot form a cycle. `stickoutLimits`
|
|
146
|
+
* there is the collet-shaped way in.
|
|
147
|
+
*/
|
|
148
|
+
readonly grip?: number | null;
|
|
149
|
+
/** What the holder needs to clear the part, mm, from the sweep. */
|
|
150
|
+
readonly required?: number | null;
|
|
151
|
+
/** What the shop keeps clamped. */
|
|
152
|
+
readonly rule?: ClampingRule;
|
|
153
|
+
/** The floor, step and hold share. */
|
|
154
|
+
readonly policy?: StickoutPolicy;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The least a tool can stand out: its flutes, or its neck where it has one.
|
|
158
|
+
*
|
|
159
|
+
* The collet face sits at the end of the flutes, and a stated neck — which a
|
|
160
|
+
* collet must not close on — pushes it back to the shoulder. A tool that states
|
|
161
|
+
* no flute length has no known head, so it has no known stickout at all and
|
|
162
|
+
* this answers `null`; it carries no `LBH` either, rather than one derived from
|
|
163
|
+
* `OAL` and `SFDM` alone.
|
|
164
|
+
*/
|
|
165
|
+
export declare const minStickout: (tool: StickoutTool) => number | null;
|
|
166
|
+
/**
|
|
167
|
+
* Every stickout this tool has, in one answer.
|
|
168
|
+
*
|
|
169
|
+
* `null` only when the tool states no flute length, because then nothing about
|
|
170
|
+
* where it stands out of a holder can be worked out at all.
|
|
171
|
+
*/
|
|
172
|
+
export declare const stickoutRange: (tool: StickoutTool, request?: StickoutRequest) => StickoutRange | null;
|
|
173
|
+
/**
|
|
174
|
+
* What this tool would be set up at on its own: no holder chosen and no feature
|
|
175
|
+
* to reach. This is `geometry.LBH`, and a build writes it with exactly this
|
|
176
|
+
* call.
|
|
177
|
+
*/
|
|
178
|
+
export declare const setupStickout: (tool: StickoutTool, rule?: ClampingRule, policy?: StickoutPolicy) => number | null;
|
|
179
|
+
/**
|
|
180
|
+
* The furthest this tool can ever stand out of a holder, mm.
|
|
181
|
+
*
|
|
182
|
+
* **A reach check's number, not `LBH`.** A tap that will not reach the bottom of
|
|
183
|
+
* a hole at its setup length may reach it pulled further out, and asking `LBH` —
|
|
184
|
+
* which is the setup — would refuse it. Anything asking "could this tool get
|
|
185
|
+
* down there at all" asks this.
|
|
186
|
+
*/
|
|
187
|
+
export declare const stickoutCeiling: (tool: StickoutTool, rule?: ClampingRule, policy?: StickoutPolicy) => number | null;
|