@toolpath/tool-drawing 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,12 @@ in 3D; this one draws a catalog tool in 2D.
14
14
  npm install @toolpath/tool-drawing react react-dom
15
15
  ```
16
16
 
17
+ Its one runtime dependency is [`@toolpath/tool-support`](../tool-support), the
18
+ shared cutting-tool domain, which itself depends on nothing. `Provenance`,
19
+ `ViewerTool`, `ViewerHolder` and `ViewerHolderProfile` are aliases of its types
20
+ and are re-exported here, so a consumer that also uses `@toolpath/tool-support`
21
+ gets the same types and needs no adapter between them.
22
+
17
23
  ## Exports
18
24
 
19
25
  | Entry point | What it is |
@@ -24,6 +30,7 @@ npm install @toolpath/tool-drawing react react-dom
24
30
 
25
31
  `/geometry` is pure and server-safe: it touches no DOM and imports no React, so
26
32
  a Node server can measure an assembly without paying for a renderer.
33
+ `tests/subpaths.test.ts` asserts that from the import graph.
27
34
 
28
35
  ## Geometry
29
36
 
@@ -92,13 +99,47 @@ as a plausible cylinder.
92
99
  ## Dimensions
93
100
 
94
101
  ```tsx
95
- <ToolDrawing assembly={assembly} dimensions dimensionSides="both" formatLength={inches} />
102
+ <ToolDrawing
103
+ assembly={assembly}
104
+ dimensions
105
+ dimensionSides="both"
106
+ highlight={hovered}
107
+ onDimensionHover={setHovered}
108
+ />
96
109
  ```
97
110
 
98
111
  Every stated length and width, each in its own lane, nested shortest-innermost
99
- so no two lines cross, with each figure in the band just outboard of its own
100
- lane. Only stated numbers are dimensioned. `formatLength` is yours, because the
101
- unit a shop reads in is the application's.
112
+ so no two lines cross. Only stated numbers are dimensioned.
113
+
114
+ **The drawing letters none of them.** The numbers belong in your own table,
115
+ where they can be read; six two-line figures fighting for the margin said the
116
+ same numbers a second time and worse. Which line is which is answered by
117
+ pointing at it instead:
118
+
119
+ - `highlight` names the dimension or dimensions to draw in the sheet's accent,
120
+ by ISO 13399 code — `DC`, `LCF`, `OAL`, `SFDM`, `LBH`, `stickout`, `SIG`, and
121
+ the two `shoulder-` codes. A code the tool does not dimension highlights
122
+ nothing.
123
+ - `onDimensionHover` is told the code under the pointer and `null` when it
124
+ leaves, so your table can be lit from the drawing as well as the other way
125
+ about. Passing it puts hit targets on the lines; leaving it off draws none.
126
+
127
+ Where two codes are one span the drawing carries one line, not two. A shop that
128
+ clamps to its own rule states the stickout and the below-holder length as the
129
+ same number, and a tool stood out to its flutes states it again as the flute
130
+ length; with nothing lettered, identical lines in two lanes cannot be told
131
+ apart. The first code named keeps the line — the tool's own number ahead of the
132
+ shop's — and the others light it too. Hover reports the code the line is drawn
133
+ under.
134
+
135
+ **The stickout is yours.** `assembly.stickout` is where the holder nose goes,
136
+ and nothing here derives it — pass `LBH` as the stickout if standing the tool
137
+ out to its below-holder length is the rule you want drawn. Where the two
138
+ disagree and `LBH` ends up inside the holder, it is not dimensioned: the
139
+ drawing will not run a line to a face it has drawn a holder over.
140
+
141
+ A highlighted line is drawn in the accent **and heavier**, so the highlight
142
+ survives a reader who cannot tell the two colours apart.
102
143
 
103
144
  ## Clearance overlay
104
145
 
@@ -1,7 +1,8 @@
1
1
  // src/model/types.ts
2
- var isHolderProfile = (holder) => "points" in holder;
2
+ import { isHolderProfile } from "@toolpath/tool-support";
3
3
 
4
4
  // src/model/outline.ts
5
+ import { hasNeck } from "@toolpath/tool-support";
5
6
  var EPSILON = 1e-6;
6
7
  var stated = (tool, code) => tool.provenance?.[code] ?? "vendor-stated";
7
8
  var arc = (centre, radius, fromDeg, toDeg, steps = 6) => Array.from({ length: steps + 1 }, (_, index) => {
@@ -12,16 +13,6 @@ var arc = (centre, radius, fromDeg, toDeg, steps = 6) => Array.from({ length: st
12
13
  z: exact(centre.z + radius * Math.sin(angle))
13
14
  };
14
15
  });
15
- var hasNeck = (tool) => {
16
- const { LCF, SFDM, DC } = tool.geometry;
17
- const shoulder = tool.geometry["shoulder-length"];
18
- const relief = tool.geometry["shoulder-diameter"];
19
- if (shoulder === void 0 || relief === void 0 || LCF === void 0 || shoulder <= LCF) {
20
- return false;
21
- }
22
- const shank = SFDM ?? DC;
23
- return shank === void 0 ? true : relief < shank - EPSILON;
24
- };
25
16
  var cone = (tool, r, whenUnstated) => {
26
17
  const angle = tool.geometry.SIG;
27
18
  const half = (angle ?? whenUnstated) / 2 * (Math.PI / 180);
@@ -261,9 +252,26 @@ var assemblyOutline = (assembly) => {
261
252
  const radius = Math.max(...segments.flatMap((segment) => segment.points.map((point2) => point2.r)));
262
253
  return { segments, height: top, radius };
263
254
  };
255
+ var edgeRadius = (from, to, z) => {
256
+ const low = Math.min(from.z, to.z);
257
+ const high = Math.max(from.z, to.z);
258
+ if (z < low - EPSILON || z > high + EPSILON) {
259
+ return null;
260
+ }
261
+ if (high - low <= EPSILON) {
262
+ return Math.max(from.r, to.r);
263
+ }
264
+ return from.r + (z - from.z) / (to.z - from.z) * (to.r - from.r);
265
+ };
266
+ var radiusAt = (outline, z) => Math.max(
267
+ 0,
268
+ ...outline.segments.flatMap(
269
+ (segment) => segment.points.slice(1).map((point, index) => edgeRadius(segment.points[index], point, z)).filter((radius) => radius !== null)
270
+ )
271
+ );
264
272
 
265
273
  export {
266
274
  isHolderProfile,
267
- hasNeck,
268
- assemblyOutline
275
+ assemblyOutline,
276
+ radiusAt
269
277
  };
@@ -1,47 +1,25 @@
1
+ import { ReachCurve } from '@toolpath/tool-support';
2
+ export { ReachCurve, heightAt } from '@toolpath/tool-support';
1
3
  import { OutlinePart, OutlineSegment, OutlinePoint } from '../geometry/index.js';
2
4
  import * as react from 'react';
3
- import { F as Frame, E as Extent, S as Sheet } from '../sheet-D0LSO7qP.js';
5
+ import { F as Frame, E as Extent, S as Sheet } from '../sheet-DicKQYfF.js';
4
6
 
5
7
  /**
6
8
  * The feature's reach curve, and how the drawing reads it.
7
9
  *
8
- * **Declared structurally, on purpose.** The shape is exactly what
9
- * `@toolpath/part-contracts` calls a `ReachCurve`, and naming it here rather
10
- * than importing it is one of the three senses in which this overlay stays
11
- * optional: a consumer that draws a tool alone pulls in no Toolpath schema.
12
- * A `ReachCurve` from the API satisfies this by structure, with no adapter.
10
+ * **Still declared structurally, one package up.** `@toolpath/tool-support`
11
+ * names the shape and imports no Toolpath schema to do it, which is one of the
12
+ * three senses in which this overlay stays optional: a consumer that draws a
13
+ * tool alone pulls in no OpenAPI contract. A `ReachCurve` off a report
14
+ * satisfies it by structure, with no adapter, exactly as before.
13
15
  */
14
- /**
15
- * The worst-case material around a feature, 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
- interface ReachCurve {
22
- readonly horizontalOffset: ReadonlyArray<number>;
23
- readonly verticalOffset: ReadonlyArray<number>;
24
- }
16
+
25
17
  /** Room the shop wants kept between the stack and the part, in millimetres. */
26
18
  interface Margins {
27
19
  readonly radial: number;
28
20
  readonly axial: number;
29
21
  }
30
22
  declare const NO_MARGINS: Margins;
31
- /**
32
- * The tallest material within `offset` mm of the cut, above the feature's bottom.
33
- *
34
- * **The one piece of the verdict's own arithmetic that had to travel.** The
35
- * decision — whether an assembly clears — stays with the catalog's
36
- * tool-selection engine, which has a dozen callers that never draw anything.
37
- * This is not that decision: it is the reading of the curve that the drawn
38
- * staircase is drawn from, and the gaps this overlay dimensions are measured
39
- * against. It is here because {@link tightestGaps} cannot be written without
40
- * it, and it must keep agreeing with whatever draws the material profile — the
41
- * rise comes at the *start* of each run, so everything out to a knot is
42
- * already as tall as that knot says.
43
- */
44
- declare const heightAt: (curve: ReachCurve, offset: number) => number;
45
23
  /**
46
24
  * Where the wall face stands at a given height, as an offset from the cut:
47
25
  * the start of the first run of the staircase that rises above that height.
@@ -202,4 +180,4 @@ declare const ClearanceOverlay: ({ profile, frame: framed, outline: extent, cutt
202
180
  */
203
181
  declare const describeGaps: (gaps: Gaps, margins: Margins, formatLength: (millimetres: number) => string) => string | null;
204
182
 
205
- export { type AxialGap, ClearanceOverlay, type ClearanceOverlayProps, type Gap, type Gaps, type Margins, NO_MARGINS, type ReachCurve, clipped, describeGaps, heightAt, lastRise, tightestGaps, wallCorners, wallFaceAt, wallPath, zigzag };
183
+ export { type AxialGap, ClearanceOverlay, type ClearanceOverlayProps, type Gap, type Gaps, type Margins, NO_MARGINS, clipped, describeGaps, lastRise, tightestGaps, wallCorners, wallFaceAt, wallPath, zigzag };
@@ -8,16 +8,9 @@ import {
8
8
  } from "../chunk-7ZID4QRO.js";
9
9
 
10
10
  // src/clearance/model/curve.ts
11
+ import { heightAt } from "@toolpath/tool-support";
11
12
  var NO_MARGINS = { radial: 0, axial: 0 };
12
13
  var GAP_TOLERANCE = 1e-6;
13
- var heightAt = (curve, offset) => {
14
- for (let index = 0; index < curve.horizontalOffset.length; index += 1) {
15
- if ((curve.horizontalOffset[index] ?? 0) >= offset) {
16
- return curve.verticalOffset[index] ?? 0;
17
- }
18
- }
19
- return curve.verticalOffset[curve.verticalOffset.length - 1] ?? 0;
20
- };
21
14
  var wallFaceAt = (curve, z) => {
22
15
  let from = 0;
23
16
  for (let index = 0; index < curve.horizontalOffset.length; index += 1) {
@@ -1,97 +1,63 @@
1
+ import { Tool, Holder, HolderProfile, Provenance } from '@toolpath/tool-support';
2
+ export { Provenance, isHolderProfile } from '@toolpath/tool-support';
3
+
1
4
  /**
2
- * What this package needs to draw an assembly, and nothing more.
5
+ * What this package needs to draw an assembly which is the shared cutting-tool
6
+ * domain, under the names this package has always published.
7
+ *
8
+ * These four were declared here until `@toolpath/tool-support` existed, and the
9
+ * declarations were three-way duplicates: the same provenance strings, the same
10
+ * geometry codes and three shapes called "holder" of which no two agreed on
11
+ * which fields exist. A drawing and the number printed beside it were free to
12
+ * disagree about one tool, and once did — the stickout the details table showed
13
+ * was not the stickout the dimension line drew.
14
+ *
15
+ * So the shapes now come from the one declaration and these are **aliases**,
16
+ * exported from `.` and from `/geometry` exactly as before. A consumer's adapter
17
+ * does not move: `ViewerTool` is `Tool`, and a catalog record that satisfied one
18
+ * satisfies the other by structure.
19
+ *
20
+ * ## Why the alias and not a rename
3
21
  *
4
- * The contract is the package's own, so nothing here depends on a particular
5
- * catalog's record types. A consumer writes one adapter that projects its own
6
- * tool onto {@link ViewerTool}; the adapter is the single file that fails
7
- * loudly when either side moves.
22
+ * A consumer's adapter is the single file that fails loudly when either side
23
+ * moves, and renaming the type it targets would be that failure for no gain. The
24
+ * `Viewer*` names are also what the two entry points publish, so dropping them
25
+ * is a major bump this change has no reason to spend. `@toolpath/tool-support`
26
+ * is where the domain is documented; this file is the seam.
8
27
  *
9
- * `geometry` keeps the scraper's own field names — `DC`, `SFDM`, `OAL`, `LCF`,
10
- * `RE`, `SIG`, `NOF`, `shoulder-diameter`, `shoulder-length`. Renaming one here
11
- * would put a translation table between two vocabularies, which is where an
12
- * `SFDM` silently becomes a `DC`.
28
+ * `geometry` still keeps the scraper's own field names — `DC`, `SFDM`, `OAL`,
29
+ * `LCF`, `RE`, `SIG`, `NOF`, `shoulder-diameter`, `shoulder-length` and
30
+ * `@toolpath/tool-support`'s `GEOMETRY_FIELDS` is now the dictionary behind
31
+ * them.
13
32
  *
14
33
  * All lengths are in millimetres and all angles in degrees, and every generator
15
34
  * works in whatever unit system it is handed: an inch tool yields an inch
16
35
  * outline. Converting is the caller's, at the seam where a tool meets a holder.
17
36
  */
18
- /** Where a stated number came from. */
19
- type Provenance = 'vendor-stated' | 'derived' | 'assumed';
20
- interface ViewerTool {
21
- /** The CAM-library name for what the tool is: `flat end mill`, `drill`, `slot mill`. */
22
- readonly form: string;
23
- readonly label?: string;
24
- readonly geometry: Readonly<Record<string, number | undefined>>;
25
- readonly provenance?: Readonly<Record<string, Provenance>>;
26
- }
27
- interface ViewerHolder {
28
- readonly noseDiameter: number | null;
29
- readonly noseLength: number | null;
30
- readonly bodyDiameter: number | null;
31
- readonly bodyLength: number | null;
32
- readonly projection: number | null;
33
- readonly flangeDiameter: number | null;
34
- readonly gaugeLength: number | null;
35
- readonly colletSeries: string | null;
36
- readonly colletProtrusion: number | null;
37
- readonly provenance?: Readonly<Record<string, Provenance>>;
38
- }
37
+
38
+ /** A cutting tool, as the drawing needs one. */
39
+ type ViewerTool = Tool;
40
+ /** A holder as its vendor publishes it: a nose, a body, a projection. */
41
+ type ViewerHolder = Holder;
39
42
  /**
40
43
  * A holder as its own CAD model measures it: the silhouette, and nothing
41
44
  * parametric.
42
45
  *
43
- * A {@link ViewerHolder} is a handful of numbers a vendor publishes in a table,
44
- * and a drawing built from them is a stylised holder. This is the other thing a
45
- * catalog can have the envelope measured off the vendor's STEP model, a
46
- * hundred-odd vertices carrying the V-flange groove and the thread relief that
47
- * a machinist actually looks for. **It is not a refinement of the parametric
48
- * form and does not project onto it**: reducing it to a nose and a body throws
49
- * away the only reason to measure.
50
- *
51
- * So the two are a union rather than one shape with optional extras, and
52
- * {@link isHolderProfile} tells them apart. A consumer that has both picks one;
46
+ * Not a refinement of {@link ViewerHolder} and it does not project onto one
47
+ * reducing a measured envelope to a nose and a body throws away the only reason
48
+ * to measure. The two are a union and {@link isHolderProfile} tells them apart;
53
49
  * a consumer that has neither passes `null` and the tool is drawn alone.
54
50
  */
55
- interface ViewerHolderProfile {
56
- /**
57
- * The silhouette as `[z, r]` in millimetres, `z` ascending.
58
- *
59
- * Two vertices share a `z` where the solid steps, so this is a polyline and
60
- * not a function of `z`. Fewer than two vertices is no holder and draws none.
61
- */
62
- readonly points: ReadonlyArray<readonly [z: number, r: number]>;
63
- /**
64
- * What `z = 0` means.
65
- *
66
- * `gage-line` is the spindle face, with `z` increasing toward the cutting end
67
- * — so the taper is negative, the nose positive, and the holder's gauge
68
- * length is the last vertex's `z`. `nose` is the frame a holder with no taper
69
- * to solve a gauge plane on is measured in, and it is stated rather than
70
- * silently referenced to an arbitrary end: there is no gauge length to read
71
- * off it, and the drawing says so instead of printing one.
72
- */
73
- readonly datum: 'gage-line' | 'nose';
74
- /** The series the holder takes, as {@link ViewerHolder} means it. */
75
- readonly colletSeries: string | null;
76
- /** How far the seated collet stands proud of the nose, in millimetres. */
77
- readonly colletProtrusion: number | null;
78
- /**
79
- * Keyed as {@link ViewerHolder}'s is, plus `points` for the measurement
80
- * itself — which is `vendor-stated` unless a caller says otherwise, because
81
- * the shape measured is the vendor's own model rather than a derivation from
82
- * its table.
83
- */
84
- readonly provenance?: Readonly<Record<string, Provenance>>;
85
- }
51
+ type ViewerHolderProfile = HolderProfile;
52
+
86
53
  /**
87
- * Which of the two holder forms this is.
54
+ * A tool, the holder it is clamped in, and how far it stands out.
88
55
  *
89
- * On the presence of `points` rather than on a `kind` tag, because a tag would
90
- * have to be added to {@link ViewerHolder} as well and every existing adapter
91
- * would stop compiling to gain nothing a structural check does not already
92
- * give.
56
+ * Deliberately **not** `@toolpath/tool-support`'s `Assembly`, which also carries
57
+ * the collet: a drawing reads a collet only through the holder's own series and
58
+ * protrusion, and taking one it never reads would be a field every adapter has
59
+ * to fill for nothing.
93
60
  */
94
- declare const isHolderProfile: (holder: ViewerHolder | ViewerHolderProfile) => holder is ViewerHolderProfile;
95
61
  interface ViewerAssembly {
96
62
  readonly tool: ViewerTool;
97
63
  readonly holder: ViewerHolder | ViewerHolderProfile | null;
@@ -155,4 +121,4 @@ interface Outline {
155
121
  */
156
122
  declare const assemblyOutline: (assembly: ViewerAssembly) => Outline | null;
157
123
 
158
- export { type Outline, type OutlinePart, type OutlinePoint, type OutlineSegment, type Provenance, type ViewerAssembly, type ViewerHolder, type ViewerHolderProfile, type ViewerTool, assemblyOutline, isHolderProfile };
124
+ export { type Outline, type OutlinePart, type OutlinePoint, type OutlineSegment, type ViewerAssembly, type ViewerHolder, type ViewerHolderProfile, type ViewerTool, assemblyOutline };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  assemblyOutline,
3
3
  isHolderProfile
4
- } from "../chunk-UBENO6GN.js";
4
+ } from "../chunk-WYHJGR7P.js";
5
5
  export {
6
6
  assemblyOutline,
7
7
  isHolderProfile