@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/stickout.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
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 { DEFAULT_CLAMPING, clampWanted } from './clamping.js';
|
|
48
|
+
import { hasNeck } from './tool.js';
|
|
49
|
+
/**
|
|
50
|
+
* The share of a tool's overall length a holder must always have hold of.
|
|
51
|
+
*
|
|
52
|
+
* A third. **A shop's figure, not a vendor's** — no vendor in the scraped
|
|
53
|
+
* catalog publishes a minimum engagement — which is why it is named here and
|
|
54
|
+
* every control that shows it should say whose it is. Deliberately a share of
|
|
55
|
+
* the length and not a multiple of the shank diameter: how much of a tool a
|
|
56
|
+
* collet needs is about the tool's leverage, not its shank. That other reading
|
|
57
|
+
* is {@link ClampingRule}, and both are honoured — see {@link StickoutLimit}.
|
|
58
|
+
*/
|
|
59
|
+
export const HELD_SHARE = 1 / 3;
|
|
60
|
+
/** What a dataset is built with, and what a page starts at. */
|
|
61
|
+
export const DEFAULT_STICKOUT_POLICY = {
|
|
62
|
+
heldShare: HELD_SHARE,
|
|
63
|
+
least: 12.7,
|
|
64
|
+
step: { inches: 3.175, millimeters: 3 },
|
|
65
|
+
};
|
|
66
|
+
const round = (value) => Math.round(value * 100) / 100;
|
|
67
|
+
/** A hair, so a rounded stickout a femtometre under what is needed is not stepped up. */
|
|
68
|
+
const STICKOUT_TOLERANCE = 1e-6;
|
|
69
|
+
/**
|
|
70
|
+
* The least a tool can stand out: its flutes, or its neck where it has one.
|
|
71
|
+
*
|
|
72
|
+
* The collet face sits at the end of the flutes, and a stated neck — which a
|
|
73
|
+
* collet must not close on — pushes it back to the shoulder. A tool that states
|
|
74
|
+
* no flute length has no known head, so it has no known stickout at all and
|
|
75
|
+
* this answers `null`; it carries no `LBH` either, rather than one derived from
|
|
76
|
+
* `OAL` and `SFDM` alone.
|
|
77
|
+
*/
|
|
78
|
+
export const minStickout = (tool) => {
|
|
79
|
+
const { LCF } = tool.geometry;
|
|
80
|
+
if (LCF === undefined) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const shoulder = tool.geometry['shoulder-length'];
|
|
84
|
+
return hasNeck(tool) && shoulder !== undefined ? shoulder : LCF;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* The setup length before the ceiling: what is needed, no shorter than the
|
|
88
|
+
* policy's least, on the policy's step for this tool — the nearest step, or the
|
|
89
|
+
* one above it where the nearest falls short of what is needed.
|
|
90
|
+
*/
|
|
91
|
+
const steppedTo = (tool, needed, policy) => {
|
|
92
|
+
const step = policy.step[tool.unitSystem];
|
|
93
|
+
const preferred = Math.max(needed, policy.least);
|
|
94
|
+
if (step <= 0) {
|
|
95
|
+
return preferred;
|
|
96
|
+
}
|
|
97
|
+
const nearest = Math.round(preferred / step) * step;
|
|
98
|
+
return nearest + STICKOUT_TOLERANCE < needed ? nearest + step : nearest;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Every stickout this tool has, in one answer.
|
|
102
|
+
*
|
|
103
|
+
* `null` only when the tool states no flute length, because then nothing about
|
|
104
|
+
* where it stands out of a holder can be worked out at all.
|
|
105
|
+
*/
|
|
106
|
+
export const stickoutRange = (tool, request = {}) => {
|
|
107
|
+
const min = minStickout(tool);
|
|
108
|
+
if (min === null) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const { grip = null, required = null, rule = DEFAULT_CLAMPING, policy = DEFAULT_STICKOUT_POLICY, } = request;
|
|
112
|
+
const { OAL } = tool.geometry;
|
|
113
|
+
/**
|
|
114
|
+
* The three ways of saying "this much stays in the holder", as three caps on
|
|
115
|
+
* one number. The tightest wins and says its name — which is the whole point
|
|
116
|
+
* of the module: a shop's sheet carries a minimum clamping length and a good
|
|
117
|
+
* hold as separate knobs, and before this they capped separate numbers in
|
|
118
|
+
* separate files and nothing ever compared them.
|
|
119
|
+
*/
|
|
120
|
+
const caps = [];
|
|
121
|
+
if (OAL !== undefined) {
|
|
122
|
+
const clamp = clampWanted(tool.geometry, rule);
|
|
123
|
+
if (clamp !== null) {
|
|
124
|
+
caps.push({ by: 'clamp', at: OAL - clamp });
|
|
125
|
+
}
|
|
126
|
+
if (policy.heldShare > 0) {
|
|
127
|
+
caps.push({ by: 'hold', at: OAL * (1 - policy.heldShare) });
|
|
128
|
+
}
|
|
129
|
+
if (grip !== null) {
|
|
130
|
+
caps.push({ by: 'collet', at: OAL - grip });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const tightest = caps.reduce((best, cap) => (best === null || cap.at < best.at ? cap : best), null);
|
|
134
|
+
const gripShort = tightest !== null && tightest.at < min;
|
|
135
|
+
const max = tightest === null ? null : round(gripShort ? min : tightest.at);
|
|
136
|
+
const wanted = steppedTo(tool, Math.max(min, required ?? min), policy);
|
|
137
|
+
return {
|
|
138
|
+
min: round(min),
|
|
139
|
+
setup: round(max === null ? wanted : Math.min(wanted, max)),
|
|
140
|
+
max,
|
|
141
|
+
limitedBy: tightest?.by ?? null,
|
|
142
|
+
grip: OAL === undefined ? null : round(Math.max(0, OAL - min)),
|
|
143
|
+
wantedGrip: OAL === undefined || tightest === null ? null : round(OAL - tightest.at),
|
|
144
|
+
gripShort,
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* What this tool would be set up at on its own: no holder chosen and no feature
|
|
149
|
+
* to reach. This is `geometry.LBH`, and a build writes it with exactly this
|
|
150
|
+
* call.
|
|
151
|
+
*/
|
|
152
|
+
export const setupStickout = (tool, rule = DEFAULT_CLAMPING, policy = DEFAULT_STICKOUT_POLICY) => stickoutRange(tool, { rule, policy })?.setup ?? null;
|
|
153
|
+
/**
|
|
154
|
+
* The furthest this tool can ever stand out of a holder, mm.
|
|
155
|
+
*
|
|
156
|
+
* **A reach check's number, not `LBH`.** A tap that will not reach the bottom of
|
|
157
|
+
* a hole at its setup length may reach it pulled further out, and asking `LBH` —
|
|
158
|
+
* which is the setup — would refuse it. Anything asking "could this tool get
|
|
159
|
+
* down there at all" asks this.
|
|
160
|
+
*/
|
|
161
|
+
export const stickoutCeiling = (tool, rule = DEFAULT_CLAMPING, policy = DEFAULT_STICKOUT_POLICY) => stickoutRange(tool, { rule, policy })?.max ?? null;
|
package/dist/tool.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cutting tool, as everything downstream of a scrape needs one.
|
|
3
|
+
*
|
|
4
|
+
* ## Deliberately not a catalog's record
|
|
5
|
+
*
|
|
6
|
+
* A catalog's tool carries identity and commerce — a guid, a brand, a catalog
|
|
7
|
+
* number, a product link, which material groups a vendor rates it for. None of
|
|
8
|
+
* that is arithmetic: nothing that draws a tool, fits it to a feature or works
|
|
9
|
+
* out how far it stands out of a holder reads a single one of those fields.
|
|
10
|
+
*
|
|
11
|
+
* So this is the *domain* shape and a catalog's record **extends** it, rather
|
|
12
|
+
* than this being a projection of a record. That direction matters: a record
|
|
13
|
+
* simply _is_ a {@link Tool}, with no adapter, which is what lets one number be
|
|
14
|
+
* computed once and drawn, printed and checked against the same value.
|
|
15
|
+
*
|
|
16
|
+
* `geometry` keeps the scraper's own field names — `DC`, `SFDM`, `OAL`, `LCF`,
|
|
17
|
+
* `RE`, `SIG`, `NOF`, `shoulder-diameter`, `shoulder-length`. See
|
|
18
|
+
* `geometry.ts` for why they are not renamed here.
|
|
19
|
+
*
|
|
20
|
+
* All lengths are in millimetres and all angles in degrees. `UnitSystem` is a
|
|
21
|
+
* fact about the vendor's sheet and is not carried here: it decides how a
|
|
22
|
+
* number is rounded and shown rather than what it is, and the arithmetic that
|
|
23
|
+
* needs it takes it as its own argument.
|
|
24
|
+
*/
|
|
25
|
+
import type { Geometry } from './geometry.js';
|
|
26
|
+
import type { ProvenanceMap } from './provenance.js';
|
|
27
|
+
export interface Tool {
|
|
28
|
+
/** The CAM-library name for what the tool is: `flat end mill`, `drill`, `slot mill`. */
|
|
29
|
+
readonly form: string;
|
|
30
|
+
/** What a machinist calls this one tool — a catalog number, usually. */
|
|
31
|
+
readonly label?: string;
|
|
32
|
+
readonly geometry: Geometry;
|
|
33
|
+
readonly provenance?: ProvenanceMap;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Whether the section between the flutes and the shank is a neck: a stated
|
|
37
|
+
* shoulder past the flutes, narrower than the shank.
|
|
38
|
+
*
|
|
39
|
+
* A collet cannot close on a neck, so the tool stands out to its shoulder at
|
|
40
|
+
* least, and a sweep meets the wall with the neck at its own radius. A shoulder
|
|
41
|
+
* as wide as the shank is still a relief worth drawing, but it is plain shank.
|
|
42
|
+
*
|
|
43
|
+
* **This had a twin, and the twin is why this package exists.** One copy drew
|
|
44
|
+
* the picture and the other decided the verdict, and the note beside the second
|
|
45
|
+
* said it outright: *"If the rule ever changes, it changes in both places or the
|
|
46
|
+
* picture and the verdict disagree about the same tool."* Nothing was watching
|
|
47
|
+
* that. Now there is one rule, and a drawing and a clearance check cannot read a
|
|
48
|
+
* different tool out of the same numbers.
|
|
49
|
+
*
|
|
50
|
+
* Takes the geometry structurally rather than a whole {@link Tool}, so a
|
|
51
|
+
* catalog record with a narrower `Record<string, number>` satisfies it with no
|
|
52
|
+
* adapter.
|
|
53
|
+
*/
|
|
54
|
+
export declare const hasNeck: (tool: {
|
|
55
|
+
readonly geometry: Geometry;
|
|
56
|
+
}) => boolean;
|
|
57
|
+
/** Whether the shank behind the flutes is reduced, or the full cutting diameter. */
|
|
58
|
+
export type Shank = 'reduced' | 'full';
|
|
59
|
+
/**
|
|
60
|
+
* Whether the shank behind the flutes is reduced.
|
|
61
|
+
*
|
|
62
|
+
* A **real relief**: a section immediately above the flutes that is a smaller
|
|
63
|
+
* diameter than the flute diameter *and has a length*. The distinction matters
|
|
64
|
+
* because vendors state a shoulder two different ways — 74 end mills in the
|
|
65
|
+
* scraped corpus have a genuine reduced shank, while 171 state a shoulder
|
|
66
|
+
* narrower than the cut whose shoulder length equals the flute length, which is
|
|
67
|
+
* no section to draw or to sweep. The second kind is not called reduced.
|
|
68
|
+
*
|
|
69
|
+
* `null` where no shoulder is stated at all, which is not a claim that the
|
|
70
|
+
* shank is full: it is nobody having said.
|
|
71
|
+
*
|
|
72
|
+
* Distinct from {@link hasNeck}, and both are needed. `hasNeck` asks whether
|
|
73
|
+
* there is a narrower section to draw and sweep *below the shank*; this asks
|
|
74
|
+
* whether the shank itself is reduced against the *cut*. A tool can have a
|
|
75
|
+
* relief wider than its cutting diameter — 860 end mills do, 245 of them under
|
|
76
|
+
* the cut — which is a neck to draw and not a reduced shank.
|
|
77
|
+
*/
|
|
78
|
+
export declare const shankOf: (tool: {
|
|
79
|
+
readonly geometry: Geometry;
|
|
80
|
+
}) => Shank | null;
|
package/dist/tool.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A cutting tool, as everything downstream of a scrape needs one.
|
|
3
|
+
*
|
|
4
|
+
* ## Deliberately not a catalog's record
|
|
5
|
+
*
|
|
6
|
+
* A catalog's tool carries identity and commerce — a guid, a brand, a catalog
|
|
7
|
+
* number, a product link, which material groups a vendor rates it for. None of
|
|
8
|
+
* that is arithmetic: nothing that draws a tool, fits it to a feature or works
|
|
9
|
+
* out how far it stands out of a holder reads a single one of those fields.
|
|
10
|
+
*
|
|
11
|
+
* So this is the *domain* shape and a catalog's record **extends** it, rather
|
|
12
|
+
* than this being a projection of a record. That direction matters: a record
|
|
13
|
+
* simply _is_ a {@link Tool}, with no adapter, which is what lets one number be
|
|
14
|
+
* computed once and drawn, printed and checked against the same value.
|
|
15
|
+
*
|
|
16
|
+
* `geometry` keeps the scraper's own field names — `DC`, `SFDM`, `OAL`, `LCF`,
|
|
17
|
+
* `RE`, `SIG`, `NOF`, `shoulder-diameter`, `shoulder-length`. See
|
|
18
|
+
* `geometry.ts` for why they are not renamed here.
|
|
19
|
+
*
|
|
20
|
+
* All lengths are in millimetres and all angles in degrees. `UnitSystem` is a
|
|
21
|
+
* fact about the vendor's sheet and is not carried here: it decides how a
|
|
22
|
+
* number is rounded and shown rather than what it is, and the arithmetic that
|
|
23
|
+
* needs it takes it as its own argument.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* A hair, because a shoulder and a shank that are the same nominal size are the
|
|
27
|
+
* same float only until one of them has been through a unit conversion: 3/8" is
|
|
28
|
+
* 9.525 on the collet's sheet and 9.524999999999999 on the tool's.
|
|
29
|
+
*/
|
|
30
|
+
const EPSILON = 1e-6;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the section between the flutes and the shank is a neck: a stated
|
|
33
|
+
* shoulder past the flutes, narrower than the shank.
|
|
34
|
+
*
|
|
35
|
+
* A collet cannot close on a neck, so the tool stands out to its shoulder at
|
|
36
|
+
* least, and a sweep meets the wall with the neck at its own radius. A shoulder
|
|
37
|
+
* as wide as the shank is still a relief worth drawing, but it is plain shank.
|
|
38
|
+
*
|
|
39
|
+
* **This had a twin, and the twin is why this package exists.** One copy drew
|
|
40
|
+
* the picture and the other decided the verdict, and the note beside the second
|
|
41
|
+
* said it outright: *"If the rule ever changes, it changes in both places or the
|
|
42
|
+
* picture and the verdict disagree about the same tool."* Nothing was watching
|
|
43
|
+
* that. Now there is one rule, and a drawing and a clearance check cannot read a
|
|
44
|
+
* different tool out of the same numbers.
|
|
45
|
+
*
|
|
46
|
+
* Takes the geometry structurally rather than a whole {@link Tool}, so a
|
|
47
|
+
* catalog record with a narrower `Record<string, number>` satisfies it with no
|
|
48
|
+
* adapter.
|
|
49
|
+
*/
|
|
50
|
+
export const hasNeck = (tool) => {
|
|
51
|
+
const { LCF, SFDM, DC } = tool.geometry;
|
|
52
|
+
const shoulder = tool.geometry['shoulder-length'];
|
|
53
|
+
const relief = tool.geometry['shoulder-diameter'];
|
|
54
|
+
if (shoulder === undefined || relief === undefined || LCF === undefined || shoulder <= LCF) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const shank = SFDM ?? DC;
|
|
58
|
+
return shank === undefined ? true : relief < shank - EPSILON;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Whether the shank behind the flutes is reduced.
|
|
62
|
+
*
|
|
63
|
+
* A **real relief**: a section immediately above the flutes that is a smaller
|
|
64
|
+
* diameter than the flute diameter *and has a length*. The distinction matters
|
|
65
|
+
* because vendors state a shoulder two different ways — 74 end mills in the
|
|
66
|
+
* scraped corpus have a genuine reduced shank, while 171 state a shoulder
|
|
67
|
+
* narrower than the cut whose shoulder length equals the flute length, which is
|
|
68
|
+
* no section to draw or to sweep. The second kind is not called reduced.
|
|
69
|
+
*
|
|
70
|
+
* `null` where no shoulder is stated at all, which is not a claim that the
|
|
71
|
+
* shank is full: it is nobody having said.
|
|
72
|
+
*
|
|
73
|
+
* Distinct from {@link hasNeck}, and both are needed. `hasNeck` asks whether
|
|
74
|
+
* there is a narrower section to draw and sweep *below the shank*; this asks
|
|
75
|
+
* whether the shank itself is reduced against the *cut*. A tool can have a
|
|
76
|
+
* relief wider than its cutting diameter — 860 end mills do, 245 of them under
|
|
77
|
+
* the cut — which is a neck to draw and not a reduced shank.
|
|
78
|
+
*/
|
|
79
|
+
export const shankOf = (tool) => {
|
|
80
|
+
const { DC, LCF } = tool.geometry;
|
|
81
|
+
const shoulder = tool.geometry['shoulder-diameter'];
|
|
82
|
+
const length = tool.geometry['shoulder-length'];
|
|
83
|
+
if (DC === undefined || shoulder === undefined) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const narrower = shoulder < DC - EPSILON;
|
|
87
|
+
const real = length !== undefined && LCF !== undefined && length > LCF + EPSILON;
|
|
88
|
+
return narrower && real ? 'reduced' : 'full';
|
|
89
|
+
};
|
package/dist/units.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one constant between the two measuring systems, and how a family states
|
|
3
|
+
* which it was published in.
|
|
4
|
+
*
|
|
5
|
+
* Three names for two values stood in this tree before this module: the
|
|
6
|
+
* scraper's `'millimeters' | 'inches'`, the catalog's `'metric' | 'inch'`, and
|
|
7
|
+
* a display unit spelled `'mm' | 'in'` — reconciled by a lookup table on
|
|
8
|
+
* ingest. A lookup table between two spellings of one axis is where a metric
|
|
9
|
+
* family quietly becomes an inch one, and nothing was watching it.
|
|
10
|
+
*
|
|
11
|
+
* The scraper's spelling wins because the scraper originates the fact: a vendor
|
|
12
|
+
* publishes a family in one system or the other, and every name downstream is a
|
|
13
|
+
* rename of what it said.
|
|
14
|
+
*
|
|
15
|
+
* ## The unit a number is *stored* in is not on this axis
|
|
16
|
+
*
|
|
17
|
+
* Every length this domain states is in millimetres and every angle in
|
|
18
|
+
* degrees, whatever system the vendor published — that is what lets an inch
|
|
19
|
+
* tool and a metric tool compare at all. {@link UnitSystem} is a fact *about
|
|
20
|
+
* the tool*: which system its vendor's sheet was written in, which is what
|
|
21
|
+
* decides how a number is rounded, stepped and shown. It is never the unit a
|
|
22
|
+
* stored value is in.
|
|
23
|
+
*/
|
|
24
|
+
/** How a vendor publishes a family: not the unit a number is stored in. */
|
|
25
|
+
export type UnitSystem = 'millimeters' | 'inches';
|
|
26
|
+
/** Every {@link UnitSystem}, for a control that offers them or a message that lists them. */
|
|
27
|
+
export declare const UNIT_SYSTEMS: readonly UnitSystem[];
|
|
28
|
+
/** Exact by definition: the inch has been 25.4 mm since 1959. */
|
|
29
|
+
export declare const MM_PER_INCH = 25.4;
|
|
30
|
+
/**
|
|
31
|
+
* The short form, as a machinist writes it beside a number.
|
|
32
|
+
*
|
|
33
|
+
* **This constant is the third vocabulary, made into a map.** `'mm'` and
|
|
34
|
+
* `'in'` are not a second unit axis — they are how {@link UnitSystem} is
|
|
35
|
+
* spelled on a drawing — so they live here as a projection of it rather than
|
|
36
|
+
* as a type of their own that something has to convert onto.
|
|
37
|
+
*/
|
|
38
|
+
export declare const UNIT_ABBREVIATION: Readonly<Record<UnitSystem, 'mm' | 'in'>>;
|
|
39
|
+
/** `value`, converted from `from` to `to`. A no-op when they agree. */
|
|
40
|
+
export declare const convertLength: (value: number, from: UnitSystem, to: UnitSystem) => number;
|
|
41
|
+
/**
|
|
42
|
+
* Decimals worth showing at a given size.
|
|
43
|
+
*
|
|
44
|
+
* A thousandth of an inch and a hundredth of a millimetre are about the same
|
|
45
|
+
* distance, and both are near the limit of what a mill holds — so each system
|
|
46
|
+
* gets the precision a machinist actually reads, rather than a fixed number of
|
|
47
|
+
* decimals that is either noise in one or useless in the other.
|
|
48
|
+
*
|
|
49
|
+
* The rounding is domain, not presentation: two consumers that round the same
|
|
50
|
+
* dimension differently print two different numbers for one tool.
|
|
51
|
+
*/
|
|
52
|
+
export declare const decimalsFor: (system: UnitSystem) => number;
|
|
53
|
+
/**
|
|
54
|
+
* The same conversion, applied to an area.
|
|
55
|
+
*
|
|
56
|
+
* Areas scale with the *square* of the length conversion: 1 in² is 645.16 mm²,
|
|
57
|
+
* not 25.4. An area put through {@link convertLength} is wrong by a factor of
|
|
58
|
+
* an inch, which is large enough to read as a different pocket and small
|
|
59
|
+
* enough that nobody checks it.
|
|
60
|
+
*/
|
|
61
|
+
export declare const convertArea: (value: number, from: UnitSystem, to: UnitSystem) => number;
|
|
62
|
+
/**
|
|
63
|
+
* A stored length, written the way a machinist reads it in `system`.
|
|
64
|
+
*
|
|
65
|
+
* The value is in millimetres, because every length this domain states is —
|
|
66
|
+
* see the note at the top of this module. What `system` decides is the unit it
|
|
67
|
+
* is *shown* in, the decimals it is rounded to, and the abbreviation beside it,
|
|
68
|
+
* which are three parts of one answer and belong together: two consumers that
|
|
69
|
+
* round the same dimension differently print two different numbers for one tool.
|
|
70
|
+
*/
|
|
71
|
+
export declare const formatLength: (value: number, system: UnitSystem) => string;
|
|
72
|
+
/** {@link formatLength} for an area, squared units and all. */
|
|
73
|
+
export declare const formatArea: (value: number, system: UnitSystem) => string;
|
package/dist/units.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one constant between the two measuring systems, and how a family states
|
|
3
|
+
* which it was published in.
|
|
4
|
+
*
|
|
5
|
+
* Three names for two values stood in this tree before this module: the
|
|
6
|
+
* scraper's `'millimeters' | 'inches'`, the catalog's `'metric' | 'inch'`, and
|
|
7
|
+
* a display unit spelled `'mm' | 'in'` — reconciled by a lookup table on
|
|
8
|
+
* ingest. A lookup table between two spellings of one axis is where a metric
|
|
9
|
+
* family quietly becomes an inch one, and nothing was watching it.
|
|
10
|
+
*
|
|
11
|
+
* The scraper's spelling wins because the scraper originates the fact: a vendor
|
|
12
|
+
* publishes a family in one system or the other, and every name downstream is a
|
|
13
|
+
* rename of what it said.
|
|
14
|
+
*
|
|
15
|
+
* ## The unit a number is *stored* in is not on this axis
|
|
16
|
+
*
|
|
17
|
+
* Every length this domain states is in millimetres and every angle in
|
|
18
|
+
* degrees, whatever system the vendor published — that is what lets an inch
|
|
19
|
+
* tool and a metric tool compare at all. {@link UnitSystem} is a fact *about
|
|
20
|
+
* the tool*: which system its vendor's sheet was written in, which is what
|
|
21
|
+
* decides how a number is rounded, stepped and shown. It is never the unit a
|
|
22
|
+
* stored value is in.
|
|
23
|
+
*/
|
|
24
|
+
/** Every {@link UnitSystem}, for a control that offers them or a message that lists them. */
|
|
25
|
+
export const UNIT_SYSTEMS = ['millimeters', 'inches'];
|
|
26
|
+
/** Exact by definition: the inch has been 25.4 mm since 1959. */
|
|
27
|
+
export const MM_PER_INCH = 25.4;
|
|
28
|
+
/**
|
|
29
|
+
* The short form, as a machinist writes it beside a number.
|
|
30
|
+
*
|
|
31
|
+
* **This constant is the third vocabulary, made into a map.** `'mm'` and
|
|
32
|
+
* `'in'` are not a second unit axis — they are how {@link UnitSystem} is
|
|
33
|
+
* spelled on a drawing — so they live here as a projection of it rather than
|
|
34
|
+
* as a type of their own that something has to convert onto.
|
|
35
|
+
*/
|
|
36
|
+
export const UNIT_ABBREVIATION = {
|
|
37
|
+
millimeters: 'mm',
|
|
38
|
+
inches: 'in',
|
|
39
|
+
};
|
|
40
|
+
/** `value`, converted from `from` to `to`. A no-op when they agree. */
|
|
41
|
+
export const convertLength = (value, from, to) => {
|
|
42
|
+
if (from === to)
|
|
43
|
+
return value;
|
|
44
|
+
return to === 'inches' ? value / MM_PER_INCH : value * MM_PER_INCH;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Decimals worth showing at a given size.
|
|
48
|
+
*
|
|
49
|
+
* A thousandth of an inch and a hundredth of a millimetre are about the same
|
|
50
|
+
* distance, and both are near the limit of what a mill holds — so each system
|
|
51
|
+
* gets the precision a machinist actually reads, rather than a fixed number of
|
|
52
|
+
* decimals that is either noise in one or useless in the other.
|
|
53
|
+
*
|
|
54
|
+
* The rounding is domain, not presentation: two consumers that round the same
|
|
55
|
+
* dimension differently print two different numbers for one tool.
|
|
56
|
+
*/
|
|
57
|
+
export const decimalsFor = (system) => (system === 'inches' ? 3 : 2);
|
|
58
|
+
/**
|
|
59
|
+
* The same conversion, applied to an area.
|
|
60
|
+
*
|
|
61
|
+
* Areas scale with the *square* of the length conversion: 1 in² is 645.16 mm²,
|
|
62
|
+
* not 25.4. An area put through {@link convertLength} is wrong by a factor of
|
|
63
|
+
* an inch, which is large enough to read as a different pocket and small
|
|
64
|
+
* enough that nobody checks it.
|
|
65
|
+
*/
|
|
66
|
+
export const convertArea = (value, from, to) => {
|
|
67
|
+
if (from === to)
|
|
68
|
+
return value;
|
|
69
|
+
return to === 'inches' ? value / MM_PER_INCH ** 2 : value * MM_PER_INCH ** 2;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* A stored length, written the way a machinist reads it in `system`.
|
|
73
|
+
*
|
|
74
|
+
* The value is in millimetres, because every length this domain states is —
|
|
75
|
+
* see the note at the top of this module. What `system` decides is the unit it
|
|
76
|
+
* is *shown* in, the decimals it is rounded to, and the abbreviation beside it,
|
|
77
|
+
* which are three parts of one answer and belong together: two consumers that
|
|
78
|
+
* round the same dimension differently print two different numbers for one tool.
|
|
79
|
+
*/
|
|
80
|
+
export const formatLength = (value, system) => `${convertLength(value, 'millimeters', system).toFixed(decimalsFor(system))} ${UNIT_ABBREVIATION[system]}`;
|
|
81
|
+
/** {@link formatLength} for an area, squared units and all. */
|
|
82
|
+
export const formatArea = (value, system) => `${convertArea(value, 'millimeters', system).toFixed(decimalsFor(system))} ${UNIT_ABBREVIATION[system]}²`;
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@toolpath/tool-support",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The cutting-tool domain: what a tool, holder, collet and assembly are",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/toolpath/ui-packages.git",
|
|
12
|
+
"directory": "packages/tool-support"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://developers.toolpath.com",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/toolpath/ui-packages/issues"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public",
|
|
20
|
+
"registry": "https://registry.npmjs.org"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.build.json",
|
|
40
|
+
"check-types": "tsc --noEmit",
|
|
41
|
+
"test": "vitest run"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "24.10.1",
|
|
45
|
+
"typescript": "5.9.3",
|
|
46
|
+
"vitest": "4.1.10"
|
|
47
|
+
}
|
|
48
|
+
}
|