@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
package/dist/holding.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What holds a tool, and the stack the two make together.
|
|
3
|
+
*
|
|
4
|
+
* ## The holder is a union, not one shape with optional extras
|
|
5
|
+
*
|
|
6
|
+
* A holder arrives in one of two forms and they are alternatives rather than a
|
|
7
|
+
* refinement of one by the other: {@link Holder} is what a vendor *publishes*
|
|
8
|
+
* about it, and {@link HolderProfile} is what its own CAD model *measures*.
|
|
9
|
+
* {@link isHolderProfile} tells them apart. A consumer that has both picks one;
|
|
10
|
+
* a consumer that has neither passes `null` and the tool stands alone.
|
|
11
|
+
*
|
|
12
|
+
* ## What is here and what is not
|
|
13
|
+
*
|
|
14
|
+
* Three shapes in this tree called themselves a holder and no two agreed on
|
|
15
|
+
* which fields exist — sixteen, nine and nineteen. What they *did* agree on is
|
|
16
|
+
* the geometry below, which is also exactly what a drawing and a clearance
|
|
17
|
+
* sweep read. Identity and commerce — a guid, a brand, a catalog number, the
|
|
18
|
+
* vendor's own CAD download — belong to a catalog's record, which extends this.
|
|
19
|
+
*
|
|
20
|
+
* ## How a holder grips is optional, and the absence is load bearing
|
|
21
|
+
*
|
|
22
|
+
* {@link Holder.clamping}, {@link Holder.boreDiameter} and {@link Holder.taper}
|
|
23
|
+
* arrived with the functions that read them, and they are optional because a
|
|
24
|
+
* drawing has never needed them: a consumer that hands over nine numbers to get
|
|
25
|
+
* a picture must not have to invent a clamping mode to do it.
|
|
26
|
+
*
|
|
27
|
+
* **Absent means nobody has said, and nobody-has-said refuses.** A holder that
|
|
28
|
+
* does not state how it clamps takes no tool, offers no grip range and matches
|
|
29
|
+
* no taper. That is the same rule {@link holderTakesTool} applies to a tool
|
|
30
|
+
* with no stated shank, and for the same reason: the unchecked case here is a
|
|
31
|
+
* cutter falling out of a spindle.
|
|
32
|
+
*/
|
|
33
|
+
import { DEFAULT_STICKOUT_POLICY, stickoutRange, } from './stickout.js';
|
|
34
|
+
import { DEFAULT_CLAMPING } from './clamping.js';
|
|
35
|
+
/**
|
|
36
|
+
* Which of the two holder forms this is.
|
|
37
|
+
*
|
|
38
|
+
* On the presence of `points` rather than on a `kind` tag, because a tag would
|
|
39
|
+
* have to be added to {@link Holder} as well and every existing adapter would
|
|
40
|
+
* stop compiling to gain nothing a structural check does not already give.
|
|
41
|
+
*/
|
|
42
|
+
export const isHolderProfile = (holder) => 'points' in holder;
|
|
43
|
+
/**
|
|
44
|
+
* A collet fits a holder when the holder takes collets of exactly its series.
|
|
45
|
+
*
|
|
46
|
+
* A series is a mechanical interface and not a size class: an `ER16` collet
|
|
47
|
+
* does not go in an `ER20` nose.
|
|
48
|
+
*/
|
|
49
|
+
export const colletFitsHolder = (collet, holder) => holder.clamping === 'collet' && holder.colletSeries === collet.series;
|
|
50
|
+
/**
|
|
51
|
+
* A hair of tolerance, because 3/8" is 9.525 on the collet's sheet and
|
|
52
|
+
* 9.524999999999999 on the tool's after a conversion. Strict, 350 tools in the
|
|
53
|
+
* scraped catalog had no collet in the crib.
|
|
54
|
+
*/
|
|
55
|
+
const GRIP_TOLERANCE = 1e-6;
|
|
56
|
+
/** Whether a collet grips a given shank diameter, in millimetres. */
|
|
57
|
+
export const gripsShank = (collet, shank) => shank >= collet.clampMin - GRIP_TOLERANCE && shank <= collet.clampMax + GRIP_TOLERANCE;
|
|
58
|
+
/**
|
|
59
|
+
* Whether a bore, shrink or hydraulic holder's one diameter is this shank.
|
|
60
|
+
*
|
|
61
|
+
* The same hair of tolerance {@link gripsShank} carries, for the same reason: a
|
|
62
|
+
* ½" bore is 12.7 on the holder's sheet and 12.699999999999999 on the tool's
|
|
63
|
+
* after a conversion. Exact, an inch holder matches an inch tool only by luck.
|
|
64
|
+
*/
|
|
65
|
+
const boreTakesShank = (bore, shank) => Math.abs(bore - shank) <= GRIP_TOLERANCE;
|
|
66
|
+
/**
|
|
67
|
+
* Whether a holder takes this tool's shank, with the collet if it needs one.
|
|
68
|
+
*
|
|
69
|
+
* **A tool whose shank the vendor does not state is refused, not assumed to
|
|
70
|
+
* fit.** This is the one place the domain differs from "what is not stated is
|
|
71
|
+
* not checked", because here the unchecked case is a cutter falling out of a
|
|
72
|
+
* spindle.
|
|
73
|
+
*/
|
|
74
|
+
export const holderTakesTool = (holder, collet, tool) => {
|
|
75
|
+
const shank = tool.geometry.SFDM;
|
|
76
|
+
if (shank === undefined) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (holder.clamping === 'collet') {
|
|
80
|
+
return collet !== null && colletFitsHolder(collet, holder) && gripsShank(collet, shank);
|
|
81
|
+
}
|
|
82
|
+
// A holder that states no clamping mode falls here and is refused: absent
|
|
83
|
+
// means nobody has said, and nobody-has-said takes no tool.
|
|
84
|
+
if (collet !== null || holder.boreDiameter === null || holder.boreDiameter === undefined) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return boreTakesShank(holder.boreDiameter, shank);
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The furthest a tool can stand out of its holder, in millimetres: overall
|
|
91
|
+
* length less the length that has to stay gripped.
|
|
92
|
+
*
|
|
93
|
+
* `null` when either is unstated. A maximum stickout is exactly the number
|
|
94
|
+
* somebody would use to decide a deep pocket is reachable, and a guessed one is
|
|
95
|
+
* worse than an absent one. A bore or shrink holder's grip length is the
|
|
96
|
+
* holder's rather than a collet's, and this package does not carry it — so
|
|
97
|
+
* those answer `null` too, honestly, until the contract gains it.
|
|
98
|
+
*/
|
|
99
|
+
export const maxStickout = (tool, collet) => {
|
|
100
|
+
const overall = tool.geometry.OAL;
|
|
101
|
+
if (overall === undefined || collet === null || collet.clampLength === null) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const stickout = overall - collet.clampLength;
|
|
105
|
+
return stickout > 0 ? stickout : null;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* How well the holder has hold of the tool at this stickout, by the share of
|
|
109
|
+
* the overall length left in the holder.
|
|
110
|
+
*
|
|
111
|
+
* At or above `good` is good; between `least` and that is possible but bad;
|
|
112
|
+
* below `least` is not compatible. The thresholds are a shop's, handed in as
|
|
113
|
+
* fractions rather than named here, because they are the same knob
|
|
114
|
+
* {@link StickoutPolicy.heldShare} is and a package must not carry two.
|
|
115
|
+
*/
|
|
116
|
+
export const holdBand = (tool, stickout, thresholds) => {
|
|
117
|
+
const { OAL } = tool.geometry;
|
|
118
|
+
if (OAL === undefined || OAL <= 0) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const held = (OAL - stickout) / OAL;
|
|
122
|
+
return held >= thresholds.good - 1e-9
|
|
123
|
+
? 'good'
|
|
124
|
+
: held >= thresholds.least - 1e-9
|
|
125
|
+
? 'medium'
|
|
126
|
+
: 'bad';
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* How far this tool may stand out of this holder — the collet-shaped way into
|
|
130
|
+
* {@link stickoutRange}.
|
|
131
|
+
*
|
|
132
|
+
* **The arithmetic is not here.** This was one of the four places that worked
|
|
133
|
+
* out a stickout, and the one that capped at a share of the overall length
|
|
134
|
+
* while the clamping rule capped at a length of shank and neither knew about
|
|
135
|
+
* the other. `stickout.ts` owns the quantity and combines the two knobs in one
|
|
136
|
+
* place; this maps a collet onto the grip length that module asks for, which is
|
|
137
|
+
* all a collet was ever contributing.
|
|
138
|
+
*/
|
|
139
|
+
export const stickoutLimits = (tool, collet,
|
|
140
|
+
/** What the holder needs to clear the part, from the sweep: the setup stands out at least this far. */
|
|
141
|
+
required = null, policy = DEFAULT_STICKOUT_POLICY, rule = DEFAULT_CLAMPING) => stickoutRange(tool, { grip: collet?.clampLength ?? null, required, policy, rule });
|
|
142
|
+
/**
|
|
143
|
+
* The stickout an assembly starts at: the setup length for this tool, held
|
|
144
|
+
* within what the grip allows.
|
|
145
|
+
*
|
|
146
|
+
* A tool whose setup outruns its grip is gripped as short as the grip lets it
|
|
147
|
+
* and no shorter — rather than refused, because the shop is the one who knows
|
|
148
|
+
* whether that is a problem.
|
|
149
|
+
*/
|
|
150
|
+
export const defaultStickout = (tool, collet) => stickoutLimits(tool, collet)?.setup ?? maxStickout(tool, collet);
|
|
151
|
+
/** `taper` narrows to one spindle interface, `colletSeries` to one collet family; either left out means "any". */
|
|
152
|
+
export const gripRanges = (holders, collets, want = {}) => {
|
|
153
|
+
const spans = [];
|
|
154
|
+
const bores = [];
|
|
155
|
+
for (const holder of holders) {
|
|
156
|
+
if (want.taper && holder.taper !== want.taper) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (holder.clamping === 'collet') {
|
|
160
|
+
for (const collet of collets) {
|
|
161
|
+
if (want.colletSeries && collet.series !== want.colletSeries) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!colletFitsHolder(collet, holder)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
spans.push([collet.clampMin, collet.clampMax]);
|
|
168
|
+
}
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// A bore or shrink holder takes one nominal diameter, so it can never
|
|
172
|
+
// satisfy a request for a particular collet series.
|
|
173
|
+
if (want.colletSeries) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (holder.boreDiameter !== null && holder.boreDiameter !== undefined) {
|
|
177
|
+
bores.push(holder.boreDiameter);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { spans, bores };
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Whether anything in {@link gripRanges} holds this shank, in millimetres.
|
|
184
|
+
*
|
|
185
|
+
* **Through {@link gripsShank} and {@link boreTakesShank}, not a second
|
|
186
|
+
* comparison.** This is the fast filter and `holderTakesTool` is the exact
|
|
187
|
+
* check, and the two must agree: asked strictly, a ⅜" shank that converts to
|
|
188
|
+
* 9.524999999999999 misses a collet whose sheet says 9.525, so the crib reports
|
|
189
|
+
* no holder for a tool the holder plainly takes. That is the failure the
|
|
190
|
+
* tolerance above was introduced for, and it belongs on both paths.
|
|
191
|
+
*/
|
|
192
|
+
export const gripsAnyShank = (ranges, shank) => ranges.spans.some(([clampMin, clampMax]) => gripsShank({ clampMin, clampMax }, shank)) ||
|
|
193
|
+
ranges.bores.some((bore) => boreTakesShank(bore, shank));
|
|
194
|
+
/**
|
|
195
|
+
* Whether the crib can hold this tool at all.
|
|
196
|
+
*
|
|
197
|
+
* A tool whose shank the vendor does not state is refused, for the same reason
|
|
198
|
+
* {@link holderTakesTool} refuses it.
|
|
199
|
+
*/
|
|
200
|
+
export const canHold = (ranges, tool) => {
|
|
201
|
+
const shank = tool.geometry.SFDM;
|
|
202
|
+
return shank !== undefined && gripsAnyShank(ranges, shank);
|
|
203
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cutting-tool domain: what a tool, a holder, a collet and an assembly
|
|
3
|
+
* _are_, and the arithmetic that follows from them.
|
|
4
|
+
*
|
|
5
|
+
* This package takes no runtime dependencies and imports no React, no DOM, no
|
|
6
|
+
* `fs` and no Toolpath SDK. Everything else that speaks about cutting tools
|
|
7
|
+
* depends on it and it depends on nothing — which is what lets a Node ingest
|
|
8
|
+
* script, a server route and a React renderer share one answer instead of three.
|
|
9
|
+
*
|
|
10
|
+
* ## Why it exists
|
|
11
|
+
*
|
|
12
|
+
* A scraper produced tool data, a drawing consumed it, and every application in
|
|
13
|
+
* between re-derived what a tool assembly is. The same fact was declared three
|
|
14
|
+
* times over — two names for one unit constant, three for one unit vocabulary,
|
|
15
|
+
* three provenance types, two `PROFILES_VERSION`s compared against each other
|
|
16
|
+
* under an alias, and three shapes called "holder" of which no two agreed on
|
|
17
|
+
* which fields exist.
|
|
18
|
+
*
|
|
19
|
+
* That is not a tidiness complaint. How far a tool stands out of its holder was
|
|
20
|
+
* computed in four unconnected places and disagreed by a factor of two on an
|
|
21
|
+
* ordinary tool: a details table printed one number and the drawing beside it
|
|
22
|
+
* drew another, and the dimension line ran past the holder nose into the holder
|
|
23
|
+
* body. It was fixed inside one application, so the next consumer of the same
|
|
24
|
+
* two packages reproduces it from scratch. The quantity that went wrong is a
|
|
25
|
+
* pure function of the tool, the collet and a shop's policy, and it had no home.
|
|
26
|
+
*
|
|
27
|
+
* ## What is here
|
|
28
|
+
*
|
|
29
|
+
* The vocabulary and the contracts: units, provenance, the geometry dictionary,
|
|
30
|
+
* the form list, the holder union, the collet, the profile, the reach curve and
|
|
31
|
+
* what a feature demands of a tool. Everything is a readonly interface or a pure
|
|
32
|
+
* function over one — no classes, deliberately, because a class loses structural
|
|
33
|
+
* typing at a package boundary and `instanceof` breaks across duplicate installs.
|
|
34
|
+
*
|
|
35
|
+
* On them, the arithmetic that had been written more than once: {@link hasNeck}
|
|
36
|
+
* and {@link shankOf}, {@link heightAt} and {@link belowGageLine} each had two
|
|
37
|
+
* copies with a note beside each saying the two must agree and nothing watching
|
|
38
|
+
* whether they did — and {@link stickoutRange}, which had four.
|
|
39
|
+
*
|
|
40
|
+
* With those, the decisions that follow from them: what holds what
|
|
41
|
+
* ({@link holderTakesTool}, {@link gripRanges}), what a shop keeps clamped
|
|
42
|
+
* ({@link clampWanted}), whether a tool cuts a feature ({@link fitAgainst}),
|
|
43
|
+
* whether the whole stack clears the material around it ({@link clearance},
|
|
44
|
+
* {@link assemblyAgainst}), and the feature in section ({@link sectionOutline})
|
|
45
|
+
* that draws what the sweep checked.
|
|
46
|
+
*/
|
|
47
|
+
export { MM_PER_INCH, UNIT_ABBREVIATION, UNIT_SYSTEMS, convertArea, convertLength, decimalsFor, formatArea, formatLength, type UnitSystem, } from './units.js';
|
|
48
|
+
export { PROVENANCE, type Provenance, type ProvenanceMap } from './provenance.js';
|
|
49
|
+
export { GEOMETRY_FIELDS, convertGeometry, geometryField, isLengthField, type Geometry, type GeometryCode, type GeometryField, type GeometryUnit, } from './geometry.js';
|
|
50
|
+
export { MILLING_FORMS, TOOL_FORMS, isToolForm, type ToolForm, type ToolFormEntry, } from './forms.js';
|
|
51
|
+
export { hasNeck, shankOf, type Shank, type Tool } from './tool.js';
|
|
52
|
+
export { PROFILES_VERSION, belowGageLine, type HolderProfile, type ProfileDatum, type ProfilePoint, } from './profile.js';
|
|
53
|
+
export { canHold, colletFitsHolder, defaultStickout, gripRanges, gripsAnyShank, gripsShank, holdBand, holderTakesTool, isHolderProfile, maxStickout, stickoutLimits, type Assembly, type Clamping, type Collet, type GripRanges, type HoldBand, type Holder, } from './holding.js';
|
|
54
|
+
export { DEFAULT_CLAMPING, clampShortfall, clampWanted, headLength, heldDiameter, type ClampingRule, } from './clamping.js';
|
|
55
|
+
export { DEFAULT_STICKOUT_POLICY, HELD_SHARE, minStickout, setupStickout, stickoutCeiling, stickoutRange, type StickoutLimit, type StickoutPolicy, type StickoutRange, type StickoutRequest, type StickoutTool, } from './stickout.js';
|
|
56
|
+
export { heightAt, type ReachCurve } from './reach.js';
|
|
57
|
+
export type { FeatureDemand } from './demand.js';
|
|
58
|
+
export { DRILLING_FORMS, fitAgainst, fitTools, type FitFailure, type ToolFit } from './fit.js';
|
|
59
|
+
export { ASSEMBLY_PARTS, NO_MARGINS, SILHOUETTE_PARTS, type AssemblyPart, type Margins, type Silhouette, type SilhouettePart, } from './parts.js';
|
|
60
|
+
export { materialProfile, type OutlinePoint } from './material.js';
|
|
61
|
+
export { clearance, describeCollision, holderSilhouette, toolCollisions, toolSilhouette, type Clearance, type Collision, type SweptAssembly, } from './clearance.js';
|
|
62
|
+
export { FLOOR_BAND, REACH, sectionOutline, type FeatureSection, type Section, type SectionKind, type SectionPoint, } from './section.js';
|
|
63
|
+
export { NOT_MODELLED, assemblyAgainst, type AssemblyFit } from './assembly-fit.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cutting-tool domain: what a tool, a holder, a collet and an assembly
|
|
3
|
+
* _are_, and the arithmetic that follows from them.
|
|
4
|
+
*
|
|
5
|
+
* This package takes no runtime dependencies and imports no React, no DOM, no
|
|
6
|
+
* `fs` and no Toolpath SDK. Everything else that speaks about cutting tools
|
|
7
|
+
* depends on it and it depends on nothing — which is what lets a Node ingest
|
|
8
|
+
* script, a server route and a React renderer share one answer instead of three.
|
|
9
|
+
*
|
|
10
|
+
* ## Why it exists
|
|
11
|
+
*
|
|
12
|
+
* A scraper produced tool data, a drawing consumed it, and every application in
|
|
13
|
+
* between re-derived what a tool assembly is. The same fact was declared three
|
|
14
|
+
* times over — two names for one unit constant, three for one unit vocabulary,
|
|
15
|
+
* three provenance types, two `PROFILES_VERSION`s compared against each other
|
|
16
|
+
* under an alias, and three shapes called "holder" of which no two agreed on
|
|
17
|
+
* which fields exist.
|
|
18
|
+
*
|
|
19
|
+
* That is not a tidiness complaint. How far a tool stands out of its holder was
|
|
20
|
+
* computed in four unconnected places and disagreed by a factor of two on an
|
|
21
|
+
* ordinary tool: a details table printed one number and the drawing beside it
|
|
22
|
+
* drew another, and the dimension line ran past the holder nose into the holder
|
|
23
|
+
* body. It was fixed inside one application, so the next consumer of the same
|
|
24
|
+
* two packages reproduces it from scratch. The quantity that went wrong is a
|
|
25
|
+
* pure function of the tool, the collet and a shop's policy, and it had no home.
|
|
26
|
+
*
|
|
27
|
+
* ## What is here
|
|
28
|
+
*
|
|
29
|
+
* The vocabulary and the contracts: units, provenance, the geometry dictionary,
|
|
30
|
+
* the form list, the holder union, the collet, the profile, the reach curve and
|
|
31
|
+
* what a feature demands of a tool. Everything is a readonly interface or a pure
|
|
32
|
+
* function over one — no classes, deliberately, because a class loses structural
|
|
33
|
+
* typing at a package boundary and `instanceof` breaks across duplicate installs.
|
|
34
|
+
*
|
|
35
|
+
* On them, the arithmetic that had been written more than once: {@link hasNeck}
|
|
36
|
+
* and {@link shankOf}, {@link heightAt} and {@link belowGageLine} each had two
|
|
37
|
+
* copies with a note beside each saying the two must agree and nothing watching
|
|
38
|
+
* whether they did — and {@link stickoutRange}, which had four.
|
|
39
|
+
*
|
|
40
|
+
* With those, the decisions that follow from them: what holds what
|
|
41
|
+
* ({@link holderTakesTool}, {@link gripRanges}), what a shop keeps clamped
|
|
42
|
+
* ({@link clampWanted}), whether a tool cuts a feature ({@link fitAgainst}),
|
|
43
|
+
* whether the whole stack clears the material around it ({@link clearance},
|
|
44
|
+
* {@link assemblyAgainst}), and the feature in section ({@link sectionOutline})
|
|
45
|
+
* that draws what the sweep checked.
|
|
46
|
+
*/
|
|
47
|
+
export { MM_PER_INCH, UNIT_ABBREVIATION, UNIT_SYSTEMS, convertArea, convertLength, decimalsFor, formatArea, formatLength, } from './units.js';
|
|
48
|
+
export { PROVENANCE } from './provenance.js';
|
|
49
|
+
export { GEOMETRY_FIELDS, convertGeometry, geometryField, isLengthField, } from './geometry.js';
|
|
50
|
+
export { MILLING_FORMS, TOOL_FORMS, isToolForm, } from './forms.js';
|
|
51
|
+
export { hasNeck, shankOf } from './tool.js';
|
|
52
|
+
export { PROFILES_VERSION, belowGageLine, } from './profile.js';
|
|
53
|
+
export { canHold, colletFitsHolder, defaultStickout, gripRanges, gripsAnyShank, gripsShank, holdBand, holderTakesTool, isHolderProfile, maxStickout, stickoutLimits, } from './holding.js';
|
|
54
|
+
export { DEFAULT_CLAMPING, clampShortfall, clampWanted, headLength, heldDiameter, } from './clamping.js';
|
|
55
|
+
export { DEFAULT_STICKOUT_POLICY, HELD_SHARE, minStickout, setupStickout, stickoutCeiling, stickoutRange, } from './stickout.js';
|
|
56
|
+
export { heightAt } from './reach.js';
|
|
57
|
+
export { DRILLING_FORMS, fitAgainst, fitTools } from './fit.js';
|
|
58
|
+
export { ASSEMBLY_PARTS, NO_MARGINS, SILHOUETTE_PARTS, } from './parts.js';
|
|
59
|
+
export { materialProfile } from './material.js';
|
|
60
|
+
export { clearance, describeCollision, holderSilhouette, toolCollisions, toolSilhouette, } from './clearance.js';
|
|
61
|
+
export { FLOOR_BAND, REACH, sectionOutline, } from './section.js';
|
|
62
|
+
export { NOT_MODELLED, assemblyAgainst } from './assembly-fit.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ReachCurve } from './reach.js';
|
|
2
|
+
/**
|
|
3
|
+
* The material around a feature, as a drawing.
|
|
4
|
+
*
|
|
5
|
+
* The one reading of a reach curve with two consumers that are not both
|
|
6
|
+
* drawings: a clearance overlay draws the wall from it, and `section.ts` draws
|
|
7
|
+
* a feature section from it. It sat in an application because moving it into a
|
|
8
|
+
* rendering package would have put the second of those behind a dependency it
|
|
9
|
+
* has no use for; here it is behind nothing.
|
|
10
|
+
*/
|
|
11
|
+
/** Radius out from the axis and height above the tip, both in millimetres. */
|
|
12
|
+
export interface OutlinePoint {
|
|
13
|
+
readonly r: number;
|
|
14
|
+
readonly z: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The material around the feature as a drawing beside the tool.
|
|
18
|
+
*
|
|
19
|
+
* The reach curve's offsets are from the wall of the cut, so each knot lands at
|
|
20
|
+
* `cuttingRadius + offset` across. **The staircase is the sweep's, exactly**:
|
|
21
|
+
* `heightAt` reads "material within h[i] rises to v[i]" as *every* offset up
|
|
22
|
+
* to the knot being that tall, so the rise comes at the start of each run,
|
|
23
|
+
* not at its end. Drawn the other way round — up at the knot, as this once
|
|
24
|
+
* was — the picture showed a nose clearing material the sweep had already
|
|
25
|
+
* failed it on, and a drawing that disagrees with its own verdict is worse
|
|
26
|
+
* than none. Past the last knot the material stays at the last height, which
|
|
27
|
+
* is the renderer's to extend to its edge.
|
|
28
|
+
*/
|
|
29
|
+
export declare const materialProfile: (curve: ReachCurve, cuttingRadius: number) => Array<OutlinePoint>;
|
package/dist/material.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The material around the feature as a drawing beside the tool.
|
|
3
|
+
*
|
|
4
|
+
* The reach curve's offsets are from the wall of the cut, so each knot lands at
|
|
5
|
+
* `cuttingRadius + offset` across. **The staircase is the sweep's, exactly**:
|
|
6
|
+
* `heightAt` reads "material within h[i] rises to v[i]" as *every* offset up
|
|
7
|
+
* to the knot being that tall, so the rise comes at the start of each run,
|
|
8
|
+
* not at its end. Drawn the other way round — up at the knot, as this once
|
|
9
|
+
* was — the picture showed a nose clearing material the sweep had already
|
|
10
|
+
* failed it on, and a drawing that disagrees with its own verdict is worse
|
|
11
|
+
* than none. Past the last knot the material stays at the last height, which
|
|
12
|
+
* is the renderer's to extend to its edge.
|
|
13
|
+
*/
|
|
14
|
+
export const materialProfile = (curve, cuttingRadius) => {
|
|
15
|
+
const points = [{ r: cuttingRadius, z: 0 }];
|
|
16
|
+
const push = (point) => {
|
|
17
|
+
const last = points[points.length - 1];
|
|
18
|
+
if (!last || last.r !== point.r || last.z !== point.z) {
|
|
19
|
+
points.push(point);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
let from = 0;
|
|
23
|
+
curve.horizontalOffset.forEach((offset, index) => {
|
|
24
|
+
const height = curve.verticalOffset[index] ?? 0;
|
|
25
|
+
push({ r: cuttingRadius + from, z: height });
|
|
26
|
+
push({ r: cuttingRadius + offset, z: height });
|
|
27
|
+
from = offset;
|
|
28
|
+
});
|
|
29
|
+
return points;
|
|
30
|
+
};
|
package/dist/parts.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an assembly is made of, named once.
|
|
3
|
+
*
|
|
4
|
+
* Two vocabularies for these eight words stood in this tree: a drawing's
|
|
5
|
+
* `OutlinePart`, and a clearance sweep's `SilhouettePart`, which was the same
|
|
6
|
+
* list without the two cutting parts. They were not a duplicate by accident —
|
|
7
|
+
* the sweep genuinely does not check the cutting end, because the cutting end
|
|
8
|
+
* is what is cutting — but the *words* were, and a part renamed in one would
|
|
9
|
+
* have gone on meaning the old thing in the other.
|
|
10
|
+
*
|
|
11
|
+
* ## Two representations, one vocabulary
|
|
12
|
+
*
|
|
13
|
+
* A drawing needs a polyline per part; a sweep needs one radius from one height
|
|
14
|
+
* upward. Those are different shapes for different questions and neither
|
|
15
|
+
* projects onto the other, so both survive — {@link Silhouette} here and
|
|
16
|
+
* `OutlineSegment` in `@toolpath/tool-drawing`. What is shared is the naming.
|
|
17
|
+
*/
|
|
18
|
+
/** Every part a drawn or swept assembly is made of, tip first. */
|
|
19
|
+
export declare const ASSEMBLY_PARTS: readonly ["tip", "flutes", "neck", "shank", "collet", "nose", "body", "flange"];
|
|
20
|
+
export type AssemblyPart = (typeof ASSEMBLY_PARTS)[number];
|
|
21
|
+
/**
|
|
22
|
+
* The parts a clearance sweep checks: everything but the cutting end.
|
|
23
|
+
*
|
|
24
|
+
* Derived from {@link ASSEMBLY_PARTS} rather than listed again, so a part added
|
|
25
|
+
* there is swept unless it is deliberately excluded here. The tip and the
|
|
26
|
+
* flutes are excluded because they are the cut — material at the cutting radius
|
|
27
|
+
* is what the tool is there to remove.
|
|
28
|
+
*/
|
|
29
|
+
export declare const SILHOUETTE_PARTS: readonly Exclude<AssemblyPart, "tip" | "flutes">[];
|
|
30
|
+
export type SilhouettePart = (typeof SILHOUETTE_PARTS)[number];
|
|
31
|
+
/**
|
|
32
|
+
* One step of an assembly's profile: this radius, from this height above the
|
|
33
|
+
* tip.
|
|
34
|
+
*
|
|
35
|
+
* **A radius from a height upward**, not a band: the last stated diameter
|
|
36
|
+
* carries itself up to the next step. That is the layer model a reach curve is
|
|
37
|
+
* swept against, and it is why nothing has to be invented for the shape between
|
|
38
|
+
* a holder's body and its flange.
|
|
39
|
+
*/
|
|
40
|
+
export interface Silhouette {
|
|
41
|
+
readonly part: SilhouettePart;
|
|
42
|
+
readonly radius: number;
|
|
43
|
+
readonly fromHeight: number;
|
|
44
|
+
}
|
|
45
|
+
/** Room the shop wants kept between the stack and the part, in millimetres. */
|
|
46
|
+
export interface Margins {
|
|
47
|
+
/**
|
|
48
|
+
* Widens every swept part, so it must clear the wall sideways by this much.
|
|
49
|
+
*/
|
|
50
|
+
readonly radial: number;
|
|
51
|
+
/**
|
|
52
|
+
* Lifts the material, so a part must stand this far above what it clears.
|
|
53
|
+
*/
|
|
54
|
+
readonly axial: number;
|
|
55
|
+
}
|
|
56
|
+
export declare const NO_MARGINS: Margins;
|
package/dist/parts.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an assembly is made of, named once.
|
|
3
|
+
*
|
|
4
|
+
* Two vocabularies for these eight words stood in this tree: a drawing's
|
|
5
|
+
* `OutlinePart`, and a clearance sweep's `SilhouettePart`, which was the same
|
|
6
|
+
* list without the two cutting parts. They were not a duplicate by accident —
|
|
7
|
+
* the sweep genuinely does not check the cutting end, because the cutting end
|
|
8
|
+
* is what is cutting — but the *words* were, and a part renamed in one would
|
|
9
|
+
* have gone on meaning the old thing in the other.
|
|
10
|
+
*
|
|
11
|
+
* ## Two representations, one vocabulary
|
|
12
|
+
*
|
|
13
|
+
* A drawing needs a polyline per part; a sweep needs one radius from one height
|
|
14
|
+
* upward. Those are different shapes for different questions and neither
|
|
15
|
+
* projects onto the other, so both survive — {@link Silhouette} here and
|
|
16
|
+
* `OutlineSegment` in `@toolpath/tool-drawing`. What is shared is the naming.
|
|
17
|
+
*/
|
|
18
|
+
/** Every part a drawn or swept assembly is made of, tip first. */
|
|
19
|
+
export const ASSEMBLY_PARTS = [
|
|
20
|
+
'tip',
|
|
21
|
+
'flutes',
|
|
22
|
+
'neck',
|
|
23
|
+
'shank',
|
|
24
|
+
'collet',
|
|
25
|
+
'nose',
|
|
26
|
+
'body',
|
|
27
|
+
'flange',
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* The parts a clearance sweep checks: everything but the cutting end.
|
|
31
|
+
*
|
|
32
|
+
* Derived from {@link ASSEMBLY_PARTS} rather than listed again, so a part added
|
|
33
|
+
* there is swept unless it is deliberately excluded here. The tip and the
|
|
34
|
+
* flutes are excluded because they are the cut — material at the cutting radius
|
|
35
|
+
* is what the tool is there to remove.
|
|
36
|
+
*/
|
|
37
|
+
export const SILHOUETTE_PARTS = ASSEMBLY_PARTS.filter((part) => part !== 'tip' && part !== 'flutes');
|
|
38
|
+
export const NO_MARGINS = { radial: 0, axial: 0 };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A holder as its own CAD model measures it: the silhouette, and nothing
|
|
3
|
+
* parametric.
|
|
4
|
+
*
|
|
5
|
+
* Two `PROFILES_VERSION = 1` constants stood in this tree, one of which was
|
|
6
|
+
* imported under an alias specifically so it could be compared against the
|
|
7
|
+
* other. This is the one.
|
|
8
|
+
*/
|
|
9
|
+
import type { ProvenanceMap } from './provenance.js';
|
|
10
|
+
/** Bumped when {@link HolderProfile} changes shape in a way a consumer must handle. */
|
|
11
|
+
export declare const PROFILES_VERSION = 1;
|
|
12
|
+
/** One vertex of a silhouette: `[z, r]`, both in millimetres. */
|
|
13
|
+
export type ProfilePoint = readonly [z: number, r: number];
|
|
14
|
+
/**
|
|
15
|
+
* What `z = 0` means on a profile.
|
|
16
|
+
*
|
|
17
|
+
* `gage-line` is the spindle face, with `z` increasing toward the cutting end —
|
|
18
|
+
* so the taper is negative, the nose positive, and the holder's gauge length is
|
|
19
|
+
* the last vertex's `z`. `nose` is the frame a holder with no taper to solve a
|
|
20
|
+
* gauge plane on is measured in, and it is stated rather than silently
|
|
21
|
+
* referenced to an arbitrary end: there is no gauge length to read off it, and a
|
|
22
|
+
* consumer must say so instead of printing one.
|
|
23
|
+
*
|
|
24
|
+
* **Per profile rather than per document.** One datum over a batch is only true
|
|
25
|
+
* while every holder in it has a taper, and the first Capto or straight-shank
|
|
26
|
+
* holder makes the document's own header wrong about some of its entries.
|
|
27
|
+
*/
|
|
28
|
+
export type ProfileDatum = 'gage-line' | 'nose';
|
|
29
|
+
/**
|
|
30
|
+
* The measured envelope.
|
|
31
|
+
*
|
|
32
|
+
* A parametric holder is a handful of numbers off a DIN 4000 sheet, and a
|
|
33
|
+
* drawing built from them is a stylised holder. This is the other thing a
|
|
34
|
+
* catalog can have — the envelope measured off the vendor's STEP model, a
|
|
35
|
+
* hundred-odd vertices carrying the V-flange groove and the thread relief that
|
|
36
|
+
* a machinist actually looks for. **It is not a refinement of the parametric
|
|
37
|
+
* form and does not project onto it**: reducing it to a nose and a body throws
|
|
38
|
+
* away the only reason to measure.
|
|
39
|
+
*
|
|
40
|
+
* So the two are a union rather than one shape with optional extras — see
|
|
41
|
+
* `holding.ts` — and a consumer that has both picks one.
|
|
42
|
+
*
|
|
43
|
+
* How well the model agrees with the vendor's published gauge length, and by
|
|
44
|
+
* how much it falls short when it does not, is a fact about *that measurement
|
|
45
|
+
* run* and stays on whatever document carries the run. A record that has it
|
|
46
|
+
* extends this.
|
|
47
|
+
*/
|
|
48
|
+
export interface HolderProfile {
|
|
49
|
+
/**
|
|
50
|
+
* The silhouette as `[z, r]` in millimetres, `z` ascending.
|
|
51
|
+
*
|
|
52
|
+
* Two vertices share a `z` where the solid steps, so this is a polyline and
|
|
53
|
+
* not a function of `z`. Fewer than two vertices is no holder.
|
|
54
|
+
*/
|
|
55
|
+
readonly points: readonly ProfilePoint[];
|
|
56
|
+
readonly datum: ProfileDatum;
|
|
57
|
+
/** The series the holder takes, as a parametric `Holder` means it. */
|
|
58
|
+
readonly colletSeries: string | null;
|
|
59
|
+
/** How far the seated collet stands proud of the nose, in millimetres. */
|
|
60
|
+
readonly colletProtrusion: number | null;
|
|
61
|
+
/**
|
|
62
|
+
* Keyed as a parametric `Holder`'s is, plus `points` for the measurement
|
|
63
|
+
* itself — which is `vendor-stated` unless a caller says otherwise, because
|
|
64
|
+
* the shape measured is the vendor's own model rather than a derivation from
|
|
65
|
+
* its table.
|
|
66
|
+
*/
|
|
67
|
+
readonly provenance?: ProvenanceMap;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The silhouette from the gage line out, where the measurement knows where the
|
|
71
|
+
* gage line is.
|
|
72
|
+
*
|
|
73
|
+
* A CAT40 model is measured whole, and about half of what comes back is the
|
|
74
|
+
* 7:24 cone and the retention knob — the part that is inside the spindle when
|
|
75
|
+
* the holder is in the machine. That says nothing a machinist is asking a
|
|
76
|
+
* holder drawing, and it costs the frame: the tool ends up a third of the
|
|
77
|
+
* height it could be because the picture is scaled to fit a taper nobody is
|
|
78
|
+
* looking at.
|
|
79
|
+
*
|
|
80
|
+
* So a `gage-line` profile is cut at `z = 0`, which is the spindle face, and
|
|
81
|
+
* where the polyline crosses it between two vertices the crossing point is
|
|
82
|
+
* **interpolated** so the cut is the face rather than the nearest vertex to it.
|
|
83
|
+
* Nothing below the gage line is touched — the vertices that survive are the
|
|
84
|
+
* measurement, grooves and thread reliefs included.
|
|
85
|
+
*
|
|
86
|
+
* A `nose`-datumed profile is returned whole: with no gauge plane solved there
|
|
87
|
+
* is no line to cut on, and guessing one would be inventing the very number the
|
|
88
|
+
* datum exists to say is missing. A profile that would be left shorter than a
|
|
89
|
+
* segment is also returned whole, because a holder measured entirely inside the
|
|
90
|
+
* spindle is bad data and drawing a stub of it hides that.
|
|
91
|
+
*
|
|
92
|
+
* ## The crossing is the part that has to be shared
|
|
93
|
+
*
|
|
94
|
+
* A renderer that draws the whole holder still has to find this same `z = 0`
|
|
95
|
+
* crossing, because it splits the silhouette there to draw the connection in
|
|
96
|
+
* its own shade rather than trimming it away — a different decision about the
|
|
97
|
+
* same line. The two interpolations have to land on the same radius or a holder
|
|
98
|
+
* meets its gage line in two places, and that is asserted against this function
|
|
99
|
+
* rather than left as a note in both files.
|
|
100
|
+
*
|
|
101
|
+
* Takes the profile structurally, so a measurement record that carries more
|
|
102
|
+
* than this satisfies it with no adapter.
|
|
103
|
+
*/
|
|
104
|
+
export declare const belowGageLine: (profile: Pick<HolderProfile, "points" | "datum">) => readonly ProfilePoint[];
|
package/dist/profile.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A holder as its own CAD model measures it: the silhouette, and nothing
|
|
3
|
+
* parametric.
|
|
4
|
+
*
|
|
5
|
+
* Two `PROFILES_VERSION = 1` constants stood in this tree, one of which was
|
|
6
|
+
* imported under an alias specifically so it could be compared against the
|
|
7
|
+
* other. This is the one.
|
|
8
|
+
*/
|
|
9
|
+
/** Bumped when {@link HolderProfile} changes shape in a way a consumer must handle. */
|
|
10
|
+
export const PROFILES_VERSION = 1;
|
|
11
|
+
/**
|
|
12
|
+
* The silhouette from the gage line out, where the measurement knows where the
|
|
13
|
+
* gage line is.
|
|
14
|
+
*
|
|
15
|
+
* A CAT40 model is measured whole, and about half of what comes back is the
|
|
16
|
+
* 7:24 cone and the retention knob — the part that is inside the spindle when
|
|
17
|
+
* the holder is in the machine. That says nothing a machinist is asking a
|
|
18
|
+
* holder drawing, and it costs the frame: the tool ends up a third of the
|
|
19
|
+
* height it could be because the picture is scaled to fit a taper nobody is
|
|
20
|
+
* looking at.
|
|
21
|
+
*
|
|
22
|
+
* So a `gage-line` profile is cut at `z = 0`, which is the spindle face, and
|
|
23
|
+
* where the polyline crosses it between two vertices the crossing point is
|
|
24
|
+
* **interpolated** so the cut is the face rather than the nearest vertex to it.
|
|
25
|
+
* Nothing below the gage line is touched — the vertices that survive are the
|
|
26
|
+
* measurement, grooves and thread reliefs included.
|
|
27
|
+
*
|
|
28
|
+
* A `nose`-datumed profile is returned whole: with no gauge plane solved there
|
|
29
|
+
* is no line to cut on, and guessing one would be inventing the very number the
|
|
30
|
+
* datum exists to say is missing. A profile that would be left shorter than a
|
|
31
|
+
* segment is also returned whole, because a holder measured entirely inside the
|
|
32
|
+
* spindle is bad data and drawing a stub of it hides that.
|
|
33
|
+
*
|
|
34
|
+
* ## The crossing is the part that has to be shared
|
|
35
|
+
*
|
|
36
|
+
* A renderer that draws the whole holder still has to find this same `z = 0`
|
|
37
|
+
* crossing, because it splits the silhouette there to draw the connection in
|
|
38
|
+
* its own shade rather than trimming it away — a different decision about the
|
|
39
|
+
* same line. The two interpolations have to land on the same radius or a holder
|
|
40
|
+
* meets its gage line in two places, and that is asserted against this function
|
|
41
|
+
* rather than left as a note in both files.
|
|
42
|
+
*
|
|
43
|
+
* Takes the profile structurally, so a measurement record that carries more
|
|
44
|
+
* than this satisfies it with no adapter.
|
|
45
|
+
*/
|
|
46
|
+
export const belowGageLine = (profile) => {
|
|
47
|
+
if (profile.datum !== 'gage-line') {
|
|
48
|
+
return profile.points;
|
|
49
|
+
}
|
|
50
|
+
const cut = profile.points.findIndex(([z]) => z >= 0);
|
|
51
|
+
const inside = profile.points[cut - 1];
|
|
52
|
+
const outside = profile.points[cut];
|
|
53
|
+
if (cut <= 0 || inside === undefined || outside === undefined) {
|
|
54
|
+
return profile.points;
|
|
55
|
+
}
|
|
56
|
+
const kept = profile.points.slice(cut);
|
|
57
|
+
if (kept.length < 2) {
|
|
58
|
+
return profile.points;
|
|
59
|
+
}
|
|
60
|
+
if (outside[0] === 0) {
|
|
61
|
+
return kept;
|
|
62
|
+
}
|
|
63
|
+
const meet = [
|
|
64
|
+
0,
|
|
65
|
+
inside[1] + (-inside[0] / (outside[0] - inside[0])) * (outside[1] - inside[1]),
|
|
66
|
+
];
|
|
67
|
+
return [meet, ...kept];
|
|
68
|
+
};
|