@toolpath/tool-drawing 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.
@@ -0,0 +1,353 @@
1
+ import { ViewerAssembly, Outline } from './geometry/index.js';
2
+ export { Provenance, ViewerHolder, ViewerTool } from './geometry/index.js';
3
+ import { F as Frame, S as Sheet, T as Theme, P as Padding } from './sheet-D0LSO7qP.js';
4
+ export { B as Box, E as Extent, a as FrameOptions, O as Orientation, b as SHEETS, f as frameFor, o as orientationFor, t as typeSizeFor } from './sheet-D0LSO7qP.js';
5
+ import * as react from 'react';
6
+ import { ReactNode } from 'react';
7
+
8
+ /**
9
+ * What a drawing of this tool dimensions, and where each dimension goes.
10
+ *
11
+ * **Pure, and here rather than in the component**, because where a dimension
12
+ * line sits is arithmetic: which ones apply to this tool, what each one
13
+ * measures, and — the part a component would get wrong quietly — which lane
14
+ * each length runs in so that no two lines cross. The drawing turns these into
15
+ * SVG and nothing else.
16
+ *
17
+ * The space is the outline's own: millimetres, `z` above the tip, `r` from the
18
+ * axis. Every length is measured **from the tip**, which is where a machinist
19
+ * measures from and where every rule in the sheet measures from.
20
+ *
21
+ * Only stated numbers are dimensioned. A drawing that carries a figure the
22
+ * vendor never published is worse than one that carries fewer — the note under
23
+ * the drawing already names what was assumed, and a dimension line looks like a
24
+ * measurement whatever the note says.
25
+ *
26
+ * ## Sides are `minus` and `plus`, not `left` and `right`
27
+ *
28
+ * This model was written for a drawing that only ran vertically, where the two
29
+ * flanks of the tool were reliably the left and right of the screen. They are
30
+ * not any more: the frame lays a tool along whichever axis its panel is longer
31
+ * on, so the `-r` flank is the screen's left in one orientation and its top in
32
+ * the other. The names say which flank rather than where it lands, because the
33
+ * where is `toX`/`toY`'s alone.
34
+ */
35
+ /** A length along the axis, drawn beside the stack. */
36
+ interface LengthDimension {
37
+ readonly code: string;
38
+ /** From the tip, in millimetres — always 0 for now, kept for a dimension that is not. */
39
+ readonly from: number;
40
+ readonly to: number;
41
+ /**
42
+ * Which line out from the stack this one runs in, 0 nearest.
43
+ *
44
+ * Shortest innermost, so the lines nest instead of crossing — the rule a
45
+ * drafting sheet follows and the reason this is worked out rather than
46
+ * listed in a fixed order.
47
+ */
48
+ readonly lane: number;
49
+ }
50
+ /** A width across the axis, drawn at its own height. */
51
+ interface WidthDimension {
52
+ readonly code: string;
53
+ /** Half-width, in millimetres: the dimension runs from `-radius` to `+radius`. */
54
+ readonly radius: number;
55
+ /** Where up the tool it is measured, in millimetres above the tip. */
56
+ readonly at: number;
57
+ }
58
+ /**
59
+ * An angle called out with a leader rather than measured between two lines.
60
+ *
61
+ * **A drill is its point** (Paul, 2026-09-01: "shouldn't a 2d rep of a drill be
62
+ * showing me a tip angle?"). On a ⌀1 drill the cone is three tenths of a
63
+ * millimetre tall — drawn to scale it is invisible, and the number is the only
64
+ * way the drawing says 140° rather than 118°.
65
+ */
66
+ interface AngleDimension {
67
+ readonly code: string;
68
+ readonly degrees: number;
69
+ /** Where the leader points: a radius from the axis and a height above the tip. */
70
+ readonly at: {
71
+ readonly r: number;
72
+ readonly z: number;
73
+ };
74
+ }
75
+ interface ToolDimensions {
76
+ readonly lengths: ReadonlyArray<LengthDimension>;
77
+ readonly widths: ReadonlyArray<WidthDimension>;
78
+ readonly angles: ReadonlyArray<AngleDimension>;
79
+ /** The corner radius, called out on the corner rather than dimensioned across it. */
80
+ readonly cornerRadius: number | null;
81
+ }
82
+ /**
83
+ * The dimensions for one assembly.
84
+ *
85
+ * With a holder, the tool's overall length is left off: most of the shank is
86
+ * inside the holder and a line to a face nobody can see reads as a mistake.
87
+ * What replaces it is the number the holder brings — how far the tool stands
88
+ * out of it.
89
+ */
90
+ declare const dimensionsFor: (assembly: ViewerAssembly) => ToolDimensions;
91
+ declare const dimensionLabel: (code: string) => string;
92
+ /**
93
+ * How a length is written out.
94
+ *
95
+ * A function rather than a unit, because a unit system is the application's:
96
+ * this package has no opinion on whether a shop reads millimetres or inches,
97
+ * and owning one would mean owning its rounding too. Millimetres in, a string
98
+ * out; the default is only so the package draws something on its own.
99
+ */
100
+ type FormatLength = (millimetres: number) => string;
101
+ declare const formatMillimetres: FormatLength;
102
+ /** One label's box on the drawing, before anything has been moved. */
103
+ interface LabelBox {
104
+ readonly key: string;
105
+ /** The inboard edge across the axis, and how far the box reaches outward. */
106
+ readonly across: number;
107
+ readonly width: number;
108
+ /** The end nearest the tip, and how far the box reaches along the axis. */
109
+ readonly along: number;
110
+ readonly height: number;
111
+ }
112
+ /**
113
+ * The same labels, moved apart until none covers another.
114
+ *
115
+ * **Because a dimension is only worth drawing if it can be read** (Paul,
116
+ * 2026-09-01). A tool 50 mm long with 4 mm of flute puts its flute length, its
117
+ * relief and its cutting diameter inside the bottom tenth of the drawing, and
118
+ * three figures land on each other however carefully each one is placed. Each
119
+ * label carries a box, so the boxes can be stacked: the one nearest the tip
120
+ * keeps its place, and anything that would cover it moves **away from the
121
+ * tip**, which is where the drawing has room.
122
+ *
123
+ * Pure arithmetic over rectangles — no measuring of text and no reading of the
124
+ * DOM, so it runs the same on a server as in a browser.
125
+ *
126
+ * **Along the axis, not up the screen.** The original moved a clash "up",
127
+ * which was the same direction only because the tool was always drawn
128
+ * standing. Away from the tip is what that meant, and it is what holds when
129
+ * the tool is laid on its side.
130
+ */
131
+ declare const stackLabels: (boxes: ReadonlyArray<LabelBox>, gap?: number,
132
+ /**
133
+ * Boxes that cannot move: the drawing's own lines, so a figure rises clear
134
+ * of an extension line rather than sitting on it (Paul, 2026-09-01 — the
135
+ * figures moved in beside their own lanes, and the lines outboard of them
136
+ * cross those bands).
137
+ */
138
+ fixed?: ReadonlyArray<LabelBox>) => Map<string, number>;
139
+ /**
140
+ * Where every figure on the drawing stands.
141
+ *
142
+ * **Each figure beside its own line, in the band just outboard of it** (Paul,
143
+ * 2026-09-01: "I'd love to put SFDM, LCF and shoulder dia closer to the part —
144
+ * like, inside the below holder and OAL lines"). One column in the far margin
145
+ * put the number for a dimension at the tool's edge as far from it as the
146
+ * number for the overall length, and the eye has to travel the width of the
147
+ * sheet to pair them up. So the margin is a series of bands: the widths sit in
148
+ * the first, just past their arrows, and each length's figure sits in the band
149
+ * outboard of its own lane.
150
+ *
151
+ * A band is only as wide as the widest figure in it, because the room it takes
152
+ * comes out of the tool.
153
+ */
154
+ /** Which flank of the tool a figure stands off. */
155
+ type Side = 'minus' | 'plus';
156
+ /** The type a figure is set in, given the drawing's own size. */
157
+ declare const figureType: (fontSize: number) => number;
158
+ /** One figure, and the band it stands in. */
159
+ interface DimensionFigure {
160
+ readonly code: string;
161
+ readonly side: Side;
162
+ /** 0 is the band nearest the tool — the widths'; band `i + 1` is outboard of lane `i`. */
163
+ readonly band: number;
164
+ /** The lane this figure's dimension runs in on its own side, or null for a width. */
165
+ readonly lane: number | null;
166
+ readonly lines: ReadonlyArray<string>;
167
+ /** How far the figure reaches perpendicular to the tool axis. Bands are sized by this. */
168
+ readonly across: number;
169
+ /** How far it reaches parallel to the tool axis. The stacker moves figures along this. */
170
+ readonly along: number;
171
+ }
172
+ /**
173
+ * How tall a block of this many lines is, set at that type size.
174
+ *
175
+ * The renderer sets type at {@link figureType} and leads it at 1.15, with half
176
+ * a line of padding above and below; this is that sum, and the two have to
177
+ * agree or a figure's box is not the size of the figure in it.
178
+ */
179
+ declare const figureHeight: (lines: number, type: number) => number;
180
+ interface DimensionLayout {
181
+ readonly figures: ReadonlyArray<DimensionFigure>;
182
+ /** Per side, the width of every band, nearest the tool first. */
183
+ readonly bands: Readonly<Record<Side, ReadonlyArray<number>>>;
184
+ }
185
+ /** The room a side's bands take, measured out from the edge of the stack. */
186
+ interface BandRoom {
187
+ /** How far the width dimensions' arrows reach past the tool. */
188
+ readonly arrow: number;
189
+ /** The clearance between a band and the line beside it. */
190
+ readonly gap: number;
191
+ }
192
+ /** Where a band's inboard edge is: the offset a figure in it reads outward from. */
193
+ declare const bandOffset: (bands: ReadonlyArray<number>, band: number, room: BandRoom) => number;
194
+ /** Where a lane's line runs: just outboard of the band that carries its figure. */
195
+ declare const laneOffset: (bands: ReadonlyArray<number>, lane: number, room: BandRoom) => number;
196
+ /** Everything one side needs, out to the far edge of its last band. */
197
+ declare const bandRoom: (bands: ReadonlyArray<number>, room: BandRoom) => number;
198
+ /**
199
+ * Every figure, on the side and in the band it belongs to.
200
+ *
201
+ * The sides alternate — lengths by lane, widths by their own order — so
202
+ * neither margin runs away with the whole drawing while the other stands
203
+ * empty. Where the drawing has something beside it, everything stays on the
204
+ * one flank.
205
+ */
206
+ declare const dimensionLayout: (model: ToolDimensions, format: FormatLength, fontSize: number, sides?: "one" | "both",
207
+ /**
208
+ * **Whether horizontal text runs parallel to the tool axis.**
209
+ *
210
+ * The one fact about the drawing's orientation that this model cannot do
211
+ * without, and it is here rather than in the renderer so that it is stated
212
+ * once, in a pure function, with a test on it.
213
+ *
214
+ * Everything else in this package is orientation-agnostic because a
215
+ * millimetre is a millimetre whichever way the axis runs. Type is the
216
+ * exception: **text does not rotate.** A figure two lines deep and twelve
217
+ * characters wide is wide on the screen either way, so when the tool is laid
218
+ * along the screen's width that figure reaches mostly *along* the tool, and
219
+ * when the tool stands up it reaches *across* it. The bands are sized on the
220
+ * across measure, so which of the two the type contributes decides how much
221
+ * room the margins take — and getting it backwards pads the wrong axis by a
222
+ * factor of about five.
223
+ */
224
+ textAlongAxis?: boolean) => DimensionLayout;
225
+
226
+ /**
227
+ * What `<ToolDrawing>` hands the things drawn inside it.
228
+ *
229
+ * **A child cannot work this out for itself, and should not try.** The panel is
230
+ * measured by a `ResizeObserver` on an `<svg>` the consumer never holds, and
231
+ * the chrome the dimension bands take is settled from that measurement — so a
232
+ * child that wanted to draw in the drawing's own coordinates had no way to
233
+ * learn them. The one consumer that needed them ended up re-deriving the frame
234
+ * from the same inputs and keeping a lockstep test to catch the drift, which is
235
+ * a workaround for a hole in this component's surface rather than a design.
236
+ *
237
+ * So the frame is published to the subtree instead. `frame` carries the scale,
238
+ * the viewBox and the two mapping functions; `outline` is what was drawn, for a
239
+ * child that needs the extent; `sheet` is the ink, so an overlay draws in the
240
+ * same palette without being told the theme twice.
241
+ */
242
+ interface DrawingContext {
243
+ readonly frame: Frame;
244
+ readonly outline: Outline;
245
+ readonly sheet: Sheet;
246
+ }
247
+ /**
248
+ * The frame the surrounding `<ToolDrawing>` settled on, or `null` outside one.
249
+ *
250
+ * Null rather than throwing, because a caller may legitimately pass the frame
251
+ * explicitly — a test framing a fixture, or a drawing composed by hand — and
252
+ * the props stay the override.
253
+ */
254
+ declare const useDrawingContext: () => DrawingContext | null;
255
+
256
+ /**
257
+ * The assembly, drawn.
258
+ *
259
+ * A side elevation from stated dimensions, fitted to the stack so the assembly
260
+ * fills the panel. Nothing here measures or scales: {@link frameFor} settled
261
+ * all of that, and this places what it decided.
262
+ *
263
+ * **Outlined, not blocked in** (Paul, 2026-09-01, against the drawings in the
264
+ * geometry write-up): a drawing of a tool is a silhouette with its sections
265
+ * shaded lightly, so a dimension line that crosses it stays readable.
266
+ *
267
+ * **Every line is solid**: flutes pale yellow, shank one light grey whatever
268
+ * its provenance, the holder grey up to the spindle connection, which is
269
+ * darker. What was derived or assumed is on the element as `data-provenance`,
270
+ * and named in the note under the drawing.
271
+ */
272
+ interface ToolDrawingProps {
273
+ readonly assembly: ViewerAssembly;
274
+ /**
275
+ * The sheet to draw on. A package cannot reach the application's theme hook,
276
+ * so the application passes its own; dark is the default because the sheet
277
+ * that needed stating was the dark one.
278
+ */
279
+ readonly theme?: Theme;
280
+ /** The name over the drawing. Falls back to the tool's own label. */
281
+ readonly caption?: string;
282
+ /**
283
+ * Draw the dimensions: every stated length and width, on the tool.
284
+ *
285
+ * Off by default, because the drawing is also used small — on a card beside
286
+ * a list, where a dimension line is noise. The panel that has room turns it
287
+ * on (Paul, 2026-09-01).
288
+ */
289
+ readonly dimensions?: boolean;
290
+ /**
291
+ * Which flanks the dimension lanes may use.
292
+ *
293
+ * **Both, wherever both are free** (Paul, 2026-09-01): four lengths stacked
294
+ * down one side push the tool into the other half of the panel and read as a
295
+ * ladder. Where the drawing has something beside it — a feature section —
296
+ * the far flank belongs to that and the lanes stay on one.
297
+ */
298
+ readonly dimensionSides?: 'one' | 'both';
299
+ /**
300
+ * How a length is written out. Millimetres in, a string out.
301
+ *
302
+ * The application's, because the unit a shop reads in is the application's
303
+ * and owning one would mean owning its rounding too.
304
+ */
305
+ readonly formatLength?: FormatLength;
306
+ /**
307
+ * Extra room for chrome around the drawing, in pixels, on top of whatever
308
+ * the dimension bands ask for.
309
+ *
310
+ * Per flank where the caller needs it asymmetrically — drawing a feature
311
+ * section beside the tool means reserving the `plus` flank for it, and the
312
+ * overlay draws in exactly the room reserved.
313
+ */
314
+ readonly padding?: number | Partial<Padding>;
315
+ /**
316
+ * What the sweep found fouling the material, as data.
317
+ *
318
+ * The verdict itself is not this package's: whether an assembly clears is
319
+ * answered by the caller's own engine, for a dozen callers that draw
320
+ * nothing. This is that answer's picture — the sections it names are painted
321
+ * as struck rather than as metal.
322
+ *
323
+ * **Matched by height as well as by part**, because one part can emit
324
+ * several segments: a holder's stated body and the diameter carried up above
325
+ * it are both `body`, and only one of them may be in the material.
326
+ */
327
+ readonly collisions?: ReadonlyArray<{
328
+ readonly part: string;
329
+ readonly height: number;
330
+ }>;
331
+ /**
332
+ * The clearance verdict, for the caption: whether it cleared, and a sentence
333
+ * saying by how much. `@toolpath/tool-drawing/clearance` writes the sentence.
334
+ */
335
+ readonly verdict?: {
336
+ readonly clears: boolean;
337
+ readonly note?: string | null;
338
+ } | null;
339
+ /**
340
+ * Drawn inside the sheet, over the tool: the clearance overlay, or anything
341
+ * else the caller wants in the drawing's own coordinates.
342
+ *
343
+ * A child rather than a prop of its own, so this module never imports the
344
+ * overlay and a consumer that does not draw one never loads it. The frame,
345
+ * the outline and the sheet reach it through context — see
346
+ * `drawing-context.tsx` for why a child cannot work them out for itself.
347
+ */
348
+ readonly children?: ReactNode;
349
+ readonly className?: string;
350
+ }
351
+ declare const ToolDrawing: ({ assembly, theme, caption, dimensions, dimensionSides, formatLength, padding, collisions, verdict, children, className, }: ToolDrawingProps) => react.JSX.Element;
352
+
353
+ export { type AngleDimension, type BandRoom, type DimensionFigure, type DimensionLayout, type DrawingContext, type FormatLength, Frame, type LabelBox, type LengthDimension, Padding, Sheet, type Side, Theme, type ToolDimensions, ToolDrawing, type ToolDrawingProps, ViewerAssembly, type WidthDimension, bandOffset, bandRoom, dimensionLabel, dimensionLayout, dimensionsFor, figureHeight, figureType, formatMillimetres, laneOffset, stackLabels, useDrawingContext };