@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.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/chunk-7ZID4QRO.js +34 -0
- package/dist/chunk-OUMNS4RI.js +225 -0
- package/dist/clearance/index.d.ts +205 -0
- package/dist/clearance/index.js +487 -0
- package/dist/geometry/index.d.ts +102 -0
- package/dist/geometry/index.js +6 -0
- package/dist/index.d.ts +353 -0
- package/dist/index.js +846 -0
- package/dist/sheet-D0LSO7qP.d.ts +183 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AWAY_FROM_TIP,
|
|
3
|
+
DrawingProvider,
|
|
4
|
+
TOWARD_MINUS,
|
|
5
|
+
TOWARD_PLUS,
|
|
6
|
+
TOWARD_TIP,
|
|
7
|
+
arrowhead,
|
|
8
|
+
useDrawingContext
|
|
9
|
+
} from "./chunk-7ZID4QRO.js";
|
|
10
|
+
import {
|
|
11
|
+
assemblyOutline,
|
|
12
|
+
hasNeck
|
|
13
|
+
} from "./chunk-OUMNS4RI.js";
|
|
14
|
+
|
|
15
|
+
// src/model/frame.ts
|
|
16
|
+
var DEFAULT_PADDING = 16;
|
|
17
|
+
var DEFAULT_BOX = { width: 640, height: 240 };
|
|
18
|
+
var TYPE_FRACTION = 0.045;
|
|
19
|
+
var TYPE_MIN_PX = 9;
|
|
20
|
+
var TYPE_MAX_PX = 14;
|
|
21
|
+
var MOST_OF_A_PANEL = 0.6;
|
|
22
|
+
var clamp = (value, low, high) => Math.min(Math.max(value, low), high);
|
|
23
|
+
var fitted = (low, high, axis) => {
|
|
24
|
+
const total = low + high;
|
|
25
|
+
const most = axis * MOST_OF_A_PANEL;
|
|
26
|
+
return total <= most || total <= 0 ? [low, high] : [low * most / total, high * most / total];
|
|
27
|
+
};
|
|
28
|
+
var round = (value) => Math.round(value * 1e4) / 1e4;
|
|
29
|
+
var boxFor = (box, options) => box.width > 0 && box.height > 0 ? box : options.defaultBox ?? DEFAULT_BOX;
|
|
30
|
+
var typeSizeFor = (box, options = {}) => {
|
|
31
|
+
const measured = boxFor(box, options);
|
|
32
|
+
return clamp(Math.min(measured.width, measured.height) * TYPE_FRACTION, TYPE_MIN_PX, TYPE_MAX_PX);
|
|
33
|
+
};
|
|
34
|
+
var orientationFor = (box, options = {}) => {
|
|
35
|
+
const measured = boxFor(box, options);
|
|
36
|
+
return measured.width >= measured.height ? "horizontal" : "vertical";
|
|
37
|
+
};
|
|
38
|
+
var frameFor = (outline, box, options = {}) => {
|
|
39
|
+
const given = options.padding ?? DEFAULT_PADDING;
|
|
40
|
+
const pad = typeof given === "number" ? { minus: given, plus: given, along: given } : {
|
|
41
|
+
minus: given.minus ?? DEFAULT_PADDING,
|
|
42
|
+
plus: given.plus ?? DEFAULT_PADDING,
|
|
43
|
+
along: given.along ?? DEFAULT_PADDING
|
|
44
|
+
};
|
|
45
|
+
const measured = boxFor(box, options);
|
|
46
|
+
const orientation = orientationFor(box, options);
|
|
47
|
+
const alongPx = orientation === "horizontal" ? measured.width : measured.height;
|
|
48
|
+
const acrossPx = orientation === "horizontal" ? measured.height : measured.width;
|
|
49
|
+
const alongMm = outline.height;
|
|
50
|
+
const acrossMm = outline.radius * 2;
|
|
51
|
+
const [minus, plus] = fitted(pad.minus, pad.plus, acrossPx);
|
|
52
|
+
const [ends] = fitted(pad.along, pad.along, alongPx);
|
|
53
|
+
const padding = { minus, plus, along: ends };
|
|
54
|
+
const room = (px, chrome) => Math.max(1, px - chrome);
|
|
55
|
+
const ratios = [
|
|
56
|
+
room(alongPx, padding.along * 2) / alongMm,
|
|
57
|
+
room(acrossPx, padding.minus + padding.plus) / acrossMm
|
|
58
|
+
].filter((ratio) => Number.isFinite(ratio) && ratio > 0);
|
|
59
|
+
const scale = ratios.length > 0 ? Math.min(...ratios) : 1;
|
|
60
|
+
const endMargin = padding.along / scale;
|
|
61
|
+
const minusMargin = padding.minus / scale;
|
|
62
|
+
const plusMargin = padding.plus / scale;
|
|
63
|
+
const along = alongMm + endMargin * 2;
|
|
64
|
+
const across = acrossMm + minusMargin + plusMargin;
|
|
65
|
+
const viewBox = orientation === "horizontal" ? [-endMargin, -outline.radius - minusMargin, along, across] : [-outline.radius - minusMargin, -endMargin, across, along];
|
|
66
|
+
const fontSize = typeSizeFor(box, options) / scale;
|
|
67
|
+
return {
|
|
68
|
+
orientation,
|
|
69
|
+
scale,
|
|
70
|
+
viewBox: viewBox.map(round).join(" "),
|
|
71
|
+
padding,
|
|
72
|
+
// The tip sits at the origin. Horizontal runs it left to right, so the
|
|
73
|
+
// business end is where reading starts; vertical runs it bottom to top, so
|
|
74
|
+
// the tool hangs from its holder the way it does in the spindle.
|
|
75
|
+
toX: orientation === "horizontal" ? (_r, z) => z : (r) => r,
|
|
76
|
+
toY: orientation === "horizontal" ? (r) => r : (_r, z) => outline.height - z,
|
|
77
|
+
fontSize
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// src/model/dimensions.ts
|
|
82
|
+
var EMPTY = { lengths: [], widths: [], angles: [], cornerRadius: null };
|
|
83
|
+
var dimensionsFor = (assembly) => {
|
|
84
|
+
const { tool, holder, stickout } = assembly;
|
|
85
|
+
const { DC, LCF, OAL, SFDM, RE } = tool.geometry;
|
|
86
|
+
if (DC === void 0 || LCF === void 0) {
|
|
87
|
+
return EMPTY;
|
|
88
|
+
}
|
|
89
|
+
const shoulderLength = tool.geometry["shoulder-length"];
|
|
90
|
+
const shoulderDiameter = tool.geometry["shoulder-diameter"];
|
|
91
|
+
const necked = hasNeck(tool) && shoulderLength !== void 0 && shoulderDiameter !== void 0;
|
|
92
|
+
const widths = [{ code: "DC", radius: DC / 2, at: 0 }];
|
|
93
|
+
if (necked && shoulderDiameter !== void 0 && shoulderLength !== void 0) {
|
|
94
|
+
widths.push({
|
|
95
|
+
code: "shoulder-diameter",
|
|
96
|
+
radius: shoulderDiameter / 2,
|
|
97
|
+
// At the top of the relief, where the shank steps down to it: measured
|
|
98
|
+
// in the middle it sat among the flute length and the corner radius,
|
|
99
|
+
// which is the busiest inch of the drawing (Paul, 2026-09-01).
|
|
100
|
+
at: shoulderLength
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (SFDM !== void 0) {
|
|
104
|
+
const shankFrom = necked && shoulderLength !== void 0 ? shoulderLength : LCF;
|
|
105
|
+
const shankTo = stickout ?? OAL ?? shankFrom;
|
|
106
|
+
widths.push({
|
|
107
|
+
code: "SFDM",
|
|
108
|
+
radius: SFDM / 2,
|
|
109
|
+
at: (shankFrom + Math.max(shankFrom, shankTo)) / 2
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const spans = [
|
|
113
|
+
{ code: "LCF", from: 0, to: LCF }
|
|
114
|
+
];
|
|
115
|
+
if (necked && shoulderLength !== void 0) {
|
|
116
|
+
spans.push({ code: "shoulder-length", from: 0, to: shoulderLength });
|
|
117
|
+
}
|
|
118
|
+
const below = tool.geometry.LBH;
|
|
119
|
+
if (below !== void 0 && below > 0) {
|
|
120
|
+
spans.push({ code: "LBH", from: 0, to: below });
|
|
121
|
+
}
|
|
122
|
+
if (holder === null) {
|
|
123
|
+
if (OAL !== void 0) {
|
|
124
|
+
spans.push({ code: "OAL", from: 0, to: OAL });
|
|
125
|
+
}
|
|
126
|
+
} else if (stickout !== null) {
|
|
127
|
+
spans.push({ code: "stickout", from: 0, to: stickout });
|
|
128
|
+
}
|
|
129
|
+
const lengths = spans.slice().sort((a, b) => a.to - a.from - (b.to - b.from)).map((span, index) => ({ ...span, lane: index }));
|
|
130
|
+
const SIG = tool.geometry.SIG;
|
|
131
|
+
const pointed = tool.form === "drill" || tool.form === "spot drill" || tool.form === "center drill";
|
|
132
|
+
const angles = pointed && SIG !== void 0 && SIG > 0 ? [
|
|
133
|
+
{
|
|
134
|
+
code: "SIG",
|
|
135
|
+
degrees: SIG,
|
|
136
|
+
at: { r: DC / 4, z: DC / 2 / Math.tan(SIG / 2 * Math.PI / 180) / 2 }
|
|
137
|
+
}
|
|
138
|
+
] : [];
|
|
139
|
+
return {
|
|
140
|
+
lengths,
|
|
141
|
+
widths,
|
|
142
|
+
angles,
|
|
143
|
+
cornerRadius: RE !== void 0 && RE > 0 ? RE : null
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
var DIMENSION_LABEL = {
|
|
147
|
+
LBH: "below holder",
|
|
148
|
+
"shoulder-length": "shoulder",
|
|
149
|
+
"shoulder-diameter": "shoulder \u2300",
|
|
150
|
+
stickout: "stickout",
|
|
151
|
+
// A drill's included angle. "Tip angle" is what the table beside it calls
|
|
152
|
+
// the same number (Paul, 2026-09-01).
|
|
153
|
+
SIG: "tip angle"
|
|
154
|
+
};
|
|
155
|
+
var dimensionLabel = (code) => DIMENSION_LABEL[code] ?? code;
|
|
156
|
+
var formatMillimetres = (millimetres) => `${String(Math.round(millimetres * 100) / 100)} mm`;
|
|
157
|
+
var stackLabels = (boxes, gap = 0, fixed = []) => {
|
|
158
|
+
const placed = [...fixed];
|
|
159
|
+
const moved = /* @__PURE__ */ new Map();
|
|
160
|
+
const clashes = (one, two) => one.across < two.across + two.width && two.across < one.across + one.width && one.along < two.along + two.height + gap && two.along < one.along + one.height + gap;
|
|
161
|
+
for (const box of [...boxes].sort((a, b) => a.along - b.along)) {
|
|
162
|
+
let along = box.along;
|
|
163
|
+
let clash = true;
|
|
164
|
+
while (clash) {
|
|
165
|
+
clash = false;
|
|
166
|
+
for (const other of placed) {
|
|
167
|
+
if (clashes({ ...box, along }, other)) {
|
|
168
|
+
along = other.along + other.height + gap;
|
|
169
|
+
clash = true;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
placed.push({ ...box, along });
|
|
175
|
+
moved.set(box.key, along);
|
|
176
|
+
}
|
|
177
|
+
return moved;
|
|
178
|
+
};
|
|
179
|
+
var figureType = (fontSize) => fontSize * 0.85;
|
|
180
|
+
var figureWidth = (lines, type) => lines.length === 0 ? 0 : (Math.max(...lines.map((each) => each.length)) + 1) * type * 0.56;
|
|
181
|
+
var figureHeight = (lines, type) => type * 0.45 * 2 + type * 1.15 * lines;
|
|
182
|
+
var bandOffset = (bands, band, room) => {
|
|
183
|
+
let at2 = room.arrow + room.gap;
|
|
184
|
+
for (let index = 0; index < band; index += 1) {
|
|
185
|
+
at2 += (bands[index] ?? 0) + room.gap * 2;
|
|
186
|
+
}
|
|
187
|
+
return at2;
|
|
188
|
+
};
|
|
189
|
+
var laneOffset = (bands, lane, room) => bandOffset(bands, lane, room) + (bands[lane] ?? 0) + room.gap;
|
|
190
|
+
var bandRoom = (bands, room) => bands.length === 0 ? 0 : bandOffset(bands, bands.length - 1, room) + (bands[bands.length - 1] ?? 0);
|
|
191
|
+
var linesOf = (model, format) => [
|
|
192
|
+
...model.lengths.map((each) => ({
|
|
193
|
+
code: each.code,
|
|
194
|
+
lines: [dimensionLabel(each.code), format(each.to - each.from)]
|
|
195
|
+
})),
|
|
196
|
+
...model.angles.map((each) => ({
|
|
197
|
+
code: each.code,
|
|
198
|
+
lines: [dimensionLabel(each.code), `${String(each.degrees)}\xB0`]
|
|
199
|
+
})),
|
|
200
|
+
...model.widths.map((each) => ({
|
|
201
|
+
code: each.code,
|
|
202
|
+
// Some labels carry the diameter sign already; none carries it twice.
|
|
203
|
+
lines: [
|
|
204
|
+
dimensionLabel(each.code).includes("\u2300") ? dimensionLabel(each.code) : `${dimensionLabel(each.code)} \u2300`,
|
|
205
|
+
format(each.radius * 2),
|
|
206
|
+
// The corner radius belongs to the tip's diameter, and reads under it
|
|
207
|
+
// rather than beside it.
|
|
208
|
+
...each.at === 0 && model.cornerRadius !== null ? [`RE ${format(model.cornerRadius)}`] : []
|
|
209
|
+
]
|
|
210
|
+
}))
|
|
211
|
+
];
|
|
212
|
+
var dimensionLayout = (model, format, fontSize, sides = "one", textAlongAxis = false) => {
|
|
213
|
+
const type = figureType(fontSize);
|
|
214
|
+
const said = new Map(linesOf(model, format).map((each) => [each.code, each.lines]));
|
|
215
|
+
const extents = (lines) => {
|
|
216
|
+
const wide = figureWidth(lines, type);
|
|
217
|
+
const deep = figureHeight(lines.length, type);
|
|
218
|
+
return textAlongAxis ? { across: deep, along: wide } : { across: wide, along: deep };
|
|
219
|
+
};
|
|
220
|
+
const across = [...model.widths, ...model.angles];
|
|
221
|
+
const figures = [
|
|
222
|
+
...across.map((each, index) => {
|
|
223
|
+
const side = sides === "both" && index % 2 === 1 ? "plus" : "minus";
|
|
224
|
+
const lines = said.get(each.code) ?? [];
|
|
225
|
+
return { code: each.code, side, band: 0, lane: null, lines, ...extents(lines) };
|
|
226
|
+
}),
|
|
227
|
+
...model.lengths.map((each) => {
|
|
228
|
+
const side = sides === "both" && each.lane % 2 === 1 ? "plus" : "minus";
|
|
229
|
+
const lane = sides === "both" ? Math.floor(each.lane / 2) : each.lane;
|
|
230
|
+
const lines = said.get(each.code) ?? [];
|
|
231
|
+
return { code: each.code, side, band: lane + 1, lane, lines, ...extents(lines) };
|
|
232
|
+
})
|
|
233
|
+
];
|
|
234
|
+
const bandsOn = (side) => {
|
|
235
|
+
const mine = figures.filter((each) => each.side === side);
|
|
236
|
+
const count = Math.max(0, ...mine.map((each) => each.band)) + 1;
|
|
237
|
+
return Array.from(
|
|
238
|
+
{ length: mine.length === 0 ? 0 : count },
|
|
239
|
+
(_, band) => Math.max(0, ...mine.filter((each) => each.band === band).map((each) => each.across))
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
return { figures, bands: { minus: bandsOn("minus"), plus: bandsOn("plus") } };
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// src/render/sheet.ts
|
|
246
|
+
var SHEETS = {
|
|
247
|
+
light: {
|
|
248
|
+
ground: "#ffffff",
|
|
249
|
+
ink: "#3f4650",
|
|
250
|
+
centre: "#15181c",
|
|
251
|
+
dimension: "#606a76",
|
|
252
|
+
body: "#c4c8ce",
|
|
253
|
+
flutes: "#e6bf59",
|
|
254
|
+
holder: "#9aa2ad",
|
|
255
|
+
connection: "#78818d"
|
|
256
|
+
},
|
|
257
|
+
dark: {
|
|
258
|
+
// A step above `zinc-900`, which is the card, and above the wash on it.
|
|
259
|
+
ground: "#22252b",
|
|
260
|
+
ink: "#c7cdd6",
|
|
261
|
+
centre: "#e8ebef",
|
|
262
|
+
dimension: "#8d97a4",
|
|
263
|
+
body: "#5b626c",
|
|
264
|
+
// The gold reads as gold on either ground, a shade deeper here so it does
|
|
265
|
+
// not glare against the greys around it.
|
|
266
|
+
flutes: "#c9a44b",
|
|
267
|
+
holder: "#474d57",
|
|
268
|
+
connection: "#3a4048"
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
var isConnection = (segment) => segment.part === "flange" || segment.part === "body" && segment.provenance === "assumed";
|
|
272
|
+
var sectionFill = (segment, sheet) => {
|
|
273
|
+
if (segment.part === "flutes" || segment.part === "tip") {
|
|
274
|
+
return sheet.flutes;
|
|
275
|
+
}
|
|
276
|
+
if (segment.part === "shank" || segment.part === "neck") {
|
|
277
|
+
return sheet.body;
|
|
278
|
+
}
|
|
279
|
+
return isConnection(segment) ? sheet.connection : sheet.holder;
|
|
280
|
+
};
|
|
281
|
+
var ASSUMED = {
|
|
282
|
+
tip: "tip angle",
|
|
283
|
+
nose: "nose length",
|
|
284
|
+
body: "body cone",
|
|
285
|
+
flange: "flange thickness"
|
|
286
|
+
};
|
|
287
|
+
var assumedNames = (segments) => [
|
|
288
|
+
...new Set(
|
|
289
|
+
segments.filter((segment) => segment.provenance === "assumed").map((segment) => ASSUMED[segment.part] ?? segment.part)
|
|
290
|
+
)
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
// src/render/tool-drawing.tsx
|
|
294
|
+
import { useEffect, useRef, useState } from "react";
|
|
295
|
+
|
|
296
|
+
// src/render/silhouette.ts
|
|
297
|
+
var at = (point, frame) => `${frame.toX(point.r, point.z).toFixed(2)},${frame.toY(point.r, point.z).toFixed(2)}`;
|
|
298
|
+
var silhouettePath = (segments, frame) => {
|
|
299
|
+
const up = segments.flatMap((segment) => segment.points);
|
|
300
|
+
if (up.length === 0) {
|
|
301
|
+
return "";
|
|
302
|
+
}
|
|
303
|
+
const down = [...segments].reverse().flatMap((segment) => [...segment.points].reverse());
|
|
304
|
+
const right = up.map((point) => at(point, frame)).join(" L");
|
|
305
|
+
const left = down.map((point) => at({ r: -point.r, z: point.z }, frame)).join(" L");
|
|
306
|
+
return `M${right} L${left} Z`;
|
|
307
|
+
};
|
|
308
|
+
var sectionPoints = (segment, frame) => {
|
|
309
|
+
const right = segment.points.map((point) => at(point, frame));
|
|
310
|
+
const left = [...segment.points].reverse().map((point) => at({ r: -point.r, z: point.z }, frame));
|
|
311
|
+
return [...right, ...left].join(" ");
|
|
312
|
+
};
|
|
313
|
+
var STEP = 0.01;
|
|
314
|
+
var joins = (segments) => {
|
|
315
|
+
const found = [];
|
|
316
|
+
for (let index = 0; index + 1 < segments.length; index += 1) {
|
|
317
|
+
const lower = segments[index];
|
|
318
|
+
const upper = segments[index + 1];
|
|
319
|
+
const below = lower.points[lower.points.length - 1];
|
|
320
|
+
const above = upper.points[0];
|
|
321
|
+
if (below === void 0 || above === void 0) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
found.push({
|
|
325
|
+
part: lower.part,
|
|
326
|
+
radius: Math.min(below.r, above.r),
|
|
327
|
+
z: below.z,
|
|
328
|
+
stepped: Math.abs(below.r - above.r) > STEP
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return found;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
// src/render/dimension-lines.tsx
|
|
335
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
336
|
+
var DimensionLines = ({
|
|
337
|
+
model,
|
|
338
|
+
layout,
|
|
339
|
+
frame,
|
|
340
|
+
outline,
|
|
341
|
+
room,
|
|
342
|
+
requested,
|
|
343
|
+
ink,
|
|
344
|
+
ground
|
|
345
|
+
}) => {
|
|
346
|
+
const { fontSize, scale } = frame;
|
|
347
|
+
const head = fontSize * 0.9;
|
|
348
|
+
const type = figureType(fontSize);
|
|
349
|
+
const lineHeight = type * 1.15;
|
|
350
|
+
const inset = type * 0.45;
|
|
351
|
+
const mm = (pixels) => pixels / scale;
|
|
352
|
+
const sign = (side) => side === "minus" ? -1 : 1;
|
|
353
|
+
const granted = (side) => {
|
|
354
|
+
const asked = side === "minus" ? requested.minus : requested.plus;
|
|
355
|
+
const got = side === "minus" ? frame.padding.minus : frame.padding.plus;
|
|
356
|
+
return asked > 0 ? got / asked : 1;
|
|
357
|
+
};
|
|
358
|
+
const at2 = (r, z) => `${frame.toX(r, z).toFixed(2)},${frame.toY(r, z).toFixed(2)}`;
|
|
359
|
+
const edgeAt = (side) => sign(side) * outline.radius;
|
|
360
|
+
const laneAt = (lane, side) => sign(side) * (outline.radius + mm(laneOffset(layout.bands[side], lane, room) * granted(side)));
|
|
361
|
+
const bandAt = (band, side) => sign(side) * (outline.radius + mm(bandOffset(layout.bands[side], band, room) * granted(side)));
|
|
362
|
+
const placeOf = new Map(layout.figures.map((each) => [each.code, each]));
|
|
363
|
+
const wants = (figure) => {
|
|
364
|
+
if (figure.lane === null) {
|
|
365
|
+
const angle = model.angles.find((each) => each.code === figure.code);
|
|
366
|
+
if (angle) {
|
|
367
|
+
return { r: angle.at.r * sign(figure.side), z: angle.at.z };
|
|
368
|
+
}
|
|
369
|
+
const width = model.widths.find((each) => each.code === figure.code);
|
|
370
|
+
return { r: (width?.radius ?? 0) * sign(figure.side), z: width?.at ?? 0 };
|
|
371
|
+
}
|
|
372
|
+
const length = model.lengths.find((each) => each.code === figure.code);
|
|
373
|
+
return {
|
|
374
|
+
r: laneAt(figure.lane, figure.side),
|
|
375
|
+
/**
|
|
376
|
+
* **At the top of its dimension, not the middle of it** (Paul,
|
|
377
|
+
* 2026-09-01). A figure halfway down a 50 mm dimension is level with
|
|
378
|
+
* nothing on the tool; at the top it is level with the arrow it belongs
|
|
379
|
+
* to.
|
|
380
|
+
*/
|
|
381
|
+
z: Math.max(length?.from ?? 0, length?.to ?? 0)
|
|
382
|
+
};
|
|
383
|
+
};
|
|
384
|
+
const boxOf = (figure) => {
|
|
385
|
+
const inboard = bandAt(figure.band, figure.side);
|
|
386
|
+
const across = mm(figure.across * granted(figure.side));
|
|
387
|
+
const along = mm(figure.along);
|
|
388
|
+
return {
|
|
389
|
+
// Outward from the band's inboard edge, on this flank.
|
|
390
|
+
from: inboard,
|
|
391
|
+
to: inboard + sign(figure.side) * across,
|
|
392
|
+
across,
|
|
393
|
+
along
|
|
394
|
+
};
|
|
395
|
+
};
|
|
396
|
+
const obstacles = model.lengths.flatMap((each) => {
|
|
397
|
+
const place = placeOf.get(each.code);
|
|
398
|
+
if (place === void 0 || place.lane === null) {
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
const lane = laneAt(place.lane, place.side);
|
|
402
|
+
const edge = edgeAt(place.side);
|
|
403
|
+
const thickness = mm(figureType(fontSize) * 0.7 * scale);
|
|
404
|
+
return [each.from, each.to].map((height, index) => ({
|
|
405
|
+
key: `line-${each.code}-${String(index)}`,
|
|
406
|
+
across: Math.min(lane, edge),
|
|
407
|
+
width: Math.abs(lane - edge),
|
|
408
|
+
along: height - thickness / 2,
|
|
409
|
+
height: thickness
|
|
410
|
+
}));
|
|
411
|
+
});
|
|
412
|
+
const placed = stackLabels(
|
|
413
|
+
layout.figures.map((figure) => {
|
|
414
|
+
const box = boxOf(figure);
|
|
415
|
+
const start = wants(figure);
|
|
416
|
+
return {
|
|
417
|
+
key: figure.code,
|
|
418
|
+
across: Math.min(box.from, box.to),
|
|
419
|
+
width: box.across,
|
|
420
|
+
along: start.z,
|
|
421
|
+
height: box.along
|
|
422
|
+
};
|
|
423
|
+
}),
|
|
424
|
+
mm(type * 0.5 * scale),
|
|
425
|
+
obstacles
|
|
426
|
+
);
|
|
427
|
+
return /* @__PURE__ */ jsxs("g", { "data-dimensions": true, children: [
|
|
428
|
+
model.lengths.map((each) => {
|
|
429
|
+
const place = placeOf.get(each.code);
|
|
430
|
+
if (place === void 0 || place.lane === null) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
const lane = laneAt(place.lane, place.side);
|
|
434
|
+
const edge = edgeAt(place.side);
|
|
435
|
+
const inside = Math.abs(each.to - each.from) >= head * 2.6;
|
|
436
|
+
const reach = head * 2.2;
|
|
437
|
+
return /* @__PURE__ */ jsxs("g", { "data-dimension": each.code, children: [
|
|
438
|
+
/* @__PURE__ */ jsx(
|
|
439
|
+
"line",
|
|
440
|
+
{
|
|
441
|
+
x1: frame.toX(lane, each.from),
|
|
442
|
+
y1: frame.toY(lane, each.from),
|
|
443
|
+
x2: frame.toX(edge, each.from),
|
|
444
|
+
y2: frame.toY(edge, each.from),
|
|
445
|
+
stroke: ink,
|
|
446
|
+
strokeOpacity: 0.4,
|
|
447
|
+
strokeWidth: fontSize * 0.05
|
|
448
|
+
}
|
|
449
|
+
),
|
|
450
|
+
/* @__PURE__ */ jsx(
|
|
451
|
+
"line",
|
|
452
|
+
{
|
|
453
|
+
x1: frame.toX(lane, each.to),
|
|
454
|
+
y1: frame.toY(lane, each.to),
|
|
455
|
+
x2: frame.toX(edge, each.to),
|
|
456
|
+
y2: frame.toY(edge, each.to),
|
|
457
|
+
stroke: ink,
|
|
458
|
+
strokeOpacity: 0.4,
|
|
459
|
+
strokeWidth: fontSize * 0.05
|
|
460
|
+
}
|
|
461
|
+
),
|
|
462
|
+
/* @__PURE__ */ jsx(
|
|
463
|
+
"line",
|
|
464
|
+
{
|
|
465
|
+
x1: frame.toX(lane, inside ? each.from : each.from - reach),
|
|
466
|
+
y1: frame.toY(lane, inside ? each.from : each.from - reach),
|
|
467
|
+
x2: frame.toX(lane, inside ? each.to : each.to + reach),
|
|
468
|
+
y2: frame.toY(lane, inside ? each.to : each.to + reach),
|
|
469
|
+
stroke: ink,
|
|
470
|
+
strokeWidth: fontSize * 0.07
|
|
471
|
+
}
|
|
472
|
+
),
|
|
473
|
+
/* @__PURE__ */ jsx(
|
|
474
|
+
"polygon",
|
|
475
|
+
{
|
|
476
|
+
points: arrowhead(lane, each.from, inside ? TOWARD_TIP : AWAY_FROM_TIP, head, frame),
|
|
477
|
+
fill: ink
|
|
478
|
+
}
|
|
479
|
+
),
|
|
480
|
+
/* @__PURE__ */ jsx(
|
|
481
|
+
"polygon",
|
|
482
|
+
{
|
|
483
|
+
points: arrowhead(lane, each.to, inside ? AWAY_FROM_TIP : TOWARD_TIP, head, frame),
|
|
484
|
+
fill: ink
|
|
485
|
+
}
|
|
486
|
+
)
|
|
487
|
+
] }, each.code);
|
|
488
|
+
}),
|
|
489
|
+
model.widths.map((each) => {
|
|
490
|
+
const stand = head * 2.4;
|
|
491
|
+
return /* @__PURE__ */ jsxs("g", { "data-dimension": each.code, children: [
|
|
492
|
+
/* @__PURE__ */ jsx(
|
|
493
|
+
"line",
|
|
494
|
+
{
|
|
495
|
+
x1: frame.toX(-each.radius - stand, each.at),
|
|
496
|
+
y1: frame.toY(-each.radius - stand, each.at),
|
|
497
|
+
x2: frame.toX(-each.radius, each.at),
|
|
498
|
+
y2: frame.toY(-each.radius, each.at),
|
|
499
|
+
stroke: ink,
|
|
500
|
+
strokeWidth: fontSize * 0.07
|
|
501
|
+
}
|
|
502
|
+
),
|
|
503
|
+
/* @__PURE__ */ jsx(
|
|
504
|
+
"polygon",
|
|
505
|
+
{
|
|
506
|
+
points: arrowhead(-each.radius, each.at, TOWARD_PLUS, head, frame),
|
|
507
|
+
fill: ink
|
|
508
|
+
}
|
|
509
|
+
),
|
|
510
|
+
/* @__PURE__ */ jsx(
|
|
511
|
+
"line",
|
|
512
|
+
{
|
|
513
|
+
x1: frame.toX(each.radius, each.at),
|
|
514
|
+
y1: frame.toY(each.radius, each.at),
|
|
515
|
+
x2: frame.toX(each.radius + stand, each.at),
|
|
516
|
+
y2: frame.toY(each.radius + stand, each.at),
|
|
517
|
+
stroke: ink,
|
|
518
|
+
strokeWidth: fontSize * 0.07
|
|
519
|
+
}
|
|
520
|
+
),
|
|
521
|
+
/* @__PURE__ */ jsx(
|
|
522
|
+
"polygon",
|
|
523
|
+
{
|
|
524
|
+
points: arrowhead(each.radius, each.at, TOWARD_MINUS, head, frame),
|
|
525
|
+
fill: ink
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
] }, each.code);
|
|
529
|
+
}),
|
|
530
|
+
layout.figures.map((figure) => {
|
|
531
|
+
const box = boxOf(figure);
|
|
532
|
+
const start = wants(figure);
|
|
533
|
+
const low = placed.get(figure.code) ?? start.z;
|
|
534
|
+
const one = { x: frame.toX(box.from, low), y: frame.toY(box.from, low) };
|
|
535
|
+
const two = {
|
|
536
|
+
x: frame.toX(box.to, low + box.along),
|
|
537
|
+
y: frame.toY(box.to, low + box.along)
|
|
538
|
+
};
|
|
539
|
+
const rect = {
|
|
540
|
+
x: Math.min(one.x, two.x),
|
|
541
|
+
y: Math.min(one.y, two.y),
|
|
542
|
+
width: Math.abs(one.x - two.x),
|
|
543
|
+
height: Math.abs(one.y - two.y)
|
|
544
|
+
};
|
|
545
|
+
const turn = box.from - sign(figure.side) * mm(type * 0.5 * scale);
|
|
546
|
+
const middleZ = low + box.along / 2;
|
|
547
|
+
const anchorX = frame.toX(box.from, middleZ);
|
|
548
|
+
const axisX = frame.toX(0, middleZ);
|
|
549
|
+
const apart = Math.abs(anchorX - axisX) > mm(1);
|
|
550
|
+
const textAnchor = apart ? anchorX < axisX ? "end" : "start" : "middle";
|
|
551
|
+
const textX = apart ? anchorX : rect.x + rect.width / 2;
|
|
552
|
+
return /* @__PURE__ */ jsxs("g", { "data-figure": figure.code, children: [
|
|
553
|
+
/* @__PURE__ */ jsx(
|
|
554
|
+
"polyline",
|
|
555
|
+
{
|
|
556
|
+
points: `${at2(start.r, start.z)} ${at2(turn, start.z)} ${at2(turn, middleZ)}`,
|
|
557
|
+
fill: "none",
|
|
558
|
+
stroke: ink,
|
|
559
|
+
strokeOpacity: 0.35,
|
|
560
|
+
strokeWidth: fontSize * 0.05
|
|
561
|
+
}
|
|
562
|
+
),
|
|
563
|
+
/* @__PURE__ */ jsx(
|
|
564
|
+
"rect",
|
|
565
|
+
{
|
|
566
|
+
x: rect.x,
|
|
567
|
+
y: rect.y,
|
|
568
|
+
width: rect.width,
|
|
569
|
+
height: rect.height,
|
|
570
|
+
fill: ground,
|
|
571
|
+
rx: type * 0.2
|
|
572
|
+
}
|
|
573
|
+
),
|
|
574
|
+
figure.lines.map((line, index) => /* @__PURE__ */ jsx(
|
|
575
|
+
"text",
|
|
576
|
+
{
|
|
577
|
+
x: textX,
|
|
578
|
+
y: rect.y + inset + type * 0.85 + lineHeight * index,
|
|
579
|
+
fontSize: type,
|
|
580
|
+
textAnchor,
|
|
581
|
+
fill: ink,
|
|
582
|
+
fontFamily: "ui-monospace, monospace",
|
|
583
|
+
children: line
|
|
584
|
+
},
|
|
585
|
+
line
|
|
586
|
+
))
|
|
587
|
+
] }, `figure-${figure.code}`);
|
|
588
|
+
})
|
|
589
|
+
] });
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
// src/render/tool-drawing.tsx
|
|
593
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
594
|
+
var STROKE = { silhouette: 0.09, edge: 0.09, chord: 0.06, centre: 0.05 };
|
|
595
|
+
var DEFAULT_PADDING2 = 16;
|
|
596
|
+
var STRUCK = "#f87171";
|
|
597
|
+
var ToolDrawing = ({
|
|
598
|
+
assembly,
|
|
599
|
+
theme = "dark",
|
|
600
|
+
caption,
|
|
601
|
+
dimensions = false,
|
|
602
|
+
dimensionSides = "one",
|
|
603
|
+
formatLength = formatMillimetres,
|
|
604
|
+
padding = DEFAULT_PADDING2,
|
|
605
|
+
collisions,
|
|
606
|
+
verdict,
|
|
607
|
+
children,
|
|
608
|
+
className
|
|
609
|
+
}) => {
|
|
610
|
+
const sheet = SHEETS[theme];
|
|
611
|
+
const element = useRef(null);
|
|
612
|
+
const [box, setBox] = useState({ width: 0, height: 0 });
|
|
613
|
+
useEffect(() => {
|
|
614
|
+
const svg = element.current;
|
|
615
|
+
if (!svg || typeof ResizeObserver === "undefined") {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
const watch = new ResizeObserver((entries) => {
|
|
619
|
+
const measured = entries[0]?.contentRect;
|
|
620
|
+
if (measured && measured.width > 0 && measured.height > 0) {
|
|
621
|
+
setBox({ width: measured.width, height: measured.height });
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
watch.observe(svg);
|
|
625
|
+
return () => {
|
|
626
|
+
watch.disconnect();
|
|
627
|
+
};
|
|
628
|
+
}, []);
|
|
629
|
+
const { tool, holder } = assembly;
|
|
630
|
+
const name = caption ?? tool.label ?? tool.form;
|
|
631
|
+
const outline = assemblyOutline(assembly);
|
|
632
|
+
if (outline === null) {
|
|
633
|
+
const missing = tool.geometry.DC === void 0 || tool.geometry.LCF === void 0;
|
|
634
|
+
return /* @__PURE__ */ jsx2(
|
|
635
|
+
"p",
|
|
636
|
+
{
|
|
637
|
+
"data-undrawable": tool.form,
|
|
638
|
+
className,
|
|
639
|
+
style: { background: sheet.ground, color: sheet.dimension, padding: "1rem", margin: 0 },
|
|
640
|
+
children: missing ? `${name} states no cutting diameter or flute length, so there is nothing to draw.` : `${name} is a ${tool.form}, and this drawing has no shape for that form \u2014 so it is not drawn, rather than drawn wrong.`
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
const typePx = typeSizeFor(box);
|
|
645
|
+
const model = dimensions ? dimensionsFor(assembly) : null;
|
|
646
|
+
const room = { arrow: typePx * 0.9 * 2.4, gap: typePx * 0.5 };
|
|
647
|
+
const layout = model === null ? null : dimensionLayout(
|
|
648
|
+
model,
|
|
649
|
+
formatLength,
|
|
650
|
+
typePx,
|
|
651
|
+
dimensionSides,
|
|
652
|
+
orientationFor(box) === "horizontal"
|
|
653
|
+
);
|
|
654
|
+
const asked = typeof padding === "number" ? { minus: padding, plus: padding, along: padding } : {
|
|
655
|
+
minus: padding.minus ?? DEFAULT_PADDING2,
|
|
656
|
+
plus: padding.plus ?? DEFAULT_PADDING2,
|
|
657
|
+
along: padding.along ?? DEFAULT_PADDING2
|
|
658
|
+
};
|
|
659
|
+
const chrome = layout === null ? asked : {
|
|
660
|
+
minus: asked.minus + bandRoom(layout.bands.minus, room) + room.gap,
|
|
661
|
+
plus: asked.plus + bandRoom(layout.bands.plus, room) + room.gap,
|
|
662
|
+
// Headroom for one figure's depth past each end: the stacker moves a
|
|
663
|
+
// clash away from the tip, so the outermost figure of a crowded tool
|
|
664
|
+
// ends up past the top of what it measures.
|
|
665
|
+
along: asked.along + Math.max(0, ...layout.figures.map((each) => each.along))
|
|
666
|
+
};
|
|
667
|
+
const frame = frameFor(outline, box, { padding: chrome });
|
|
668
|
+
const { fontSize } = frame;
|
|
669
|
+
const assumed = assumedNames(outline.segments);
|
|
670
|
+
const line = (r, z) => ({ x: frame.toX(r, z), y: frame.toY(r, z) });
|
|
671
|
+
const overhang = fontSize * 1.2;
|
|
672
|
+
const from = line(0, -overhang);
|
|
673
|
+
const to = line(0, outline.height + overhang);
|
|
674
|
+
return /* @__PURE__ */ jsxs2(
|
|
675
|
+
"figure",
|
|
676
|
+
{
|
|
677
|
+
className,
|
|
678
|
+
style: { display: "flex", flexDirection: "column", minHeight: 0, height: "100%", margin: 0 },
|
|
679
|
+
children: [
|
|
680
|
+
/* @__PURE__ */ jsxs2(
|
|
681
|
+
"figcaption",
|
|
682
|
+
{
|
|
683
|
+
style: {
|
|
684
|
+
display: "flex",
|
|
685
|
+
padding: "0.5rem 0.75rem",
|
|
686
|
+
fontSize: "0.75rem",
|
|
687
|
+
fontFamily: "ui-monospace, monospace",
|
|
688
|
+
color: sheet.dimension
|
|
689
|
+
},
|
|
690
|
+
children: [
|
|
691
|
+
name,
|
|
692
|
+
verdict ? /* @__PURE__ */ jsx2(
|
|
693
|
+
"span",
|
|
694
|
+
{
|
|
695
|
+
"data-verdict": verdict.clears ? "clears" : "collides",
|
|
696
|
+
style: { marginLeft: "auto", color: verdict.clears ? sheet.dimension : STRUCK },
|
|
697
|
+
children: verdict.clears ? "clears the part" : "collides with the part"
|
|
698
|
+
}
|
|
699
|
+
) : null
|
|
700
|
+
]
|
|
701
|
+
}
|
|
702
|
+
),
|
|
703
|
+
verdict?.note ? /* @__PURE__ */ jsx2(
|
|
704
|
+
"p",
|
|
705
|
+
{
|
|
706
|
+
"data-verdict-note": true,
|
|
707
|
+
style: {
|
|
708
|
+
padding: "0 0.75rem 0.25rem",
|
|
709
|
+
fontSize: "0.625rem",
|
|
710
|
+
color: sheet.dimension,
|
|
711
|
+
margin: 0
|
|
712
|
+
},
|
|
713
|
+
children: verdict.note
|
|
714
|
+
}
|
|
715
|
+
) : null,
|
|
716
|
+
/* @__PURE__ */ jsxs2(
|
|
717
|
+
"svg",
|
|
718
|
+
{
|
|
719
|
+
ref: element,
|
|
720
|
+
role: "img",
|
|
721
|
+
"aria-label": `${name}, drawn from its stated dimensions`,
|
|
722
|
+
viewBox: frame.viewBox,
|
|
723
|
+
style: { background: sheet.ground, flex: 1, minHeight: 0, borderRadius: "0.25rem" },
|
|
724
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
725
|
+
children: [
|
|
726
|
+
outline.segments.map((segment, index) => {
|
|
727
|
+
const struck = (collisions ?? []).some(
|
|
728
|
+
(each) => each.part === segment.part && each.height >= Math.min(...segment.points.map((point) => point.z)) - 1e-6 && each.height <= Math.max(...segment.points.map((point) => point.z)) + 1e-6
|
|
729
|
+
);
|
|
730
|
+
return /* @__PURE__ */ jsx2(
|
|
731
|
+
"polygon",
|
|
732
|
+
{
|
|
733
|
+
"data-part": segment.part,
|
|
734
|
+
"data-provenance": segment.provenance,
|
|
735
|
+
...struck ? { "data-struck": "true" } : {},
|
|
736
|
+
points: sectionPoints(segment, frame),
|
|
737
|
+
fill: struck ? STRUCK : sectionFill(segment, sheet),
|
|
738
|
+
fillOpacity: struck ? 0.75 : 1,
|
|
739
|
+
stroke: "none"
|
|
740
|
+
},
|
|
741
|
+
`${segment.part}-${String(index)}`
|
|
742
|
+
);
|
|
743
|
+
}),
|
|
744
|
+
joins(outline.segments).map((join, index) => {
|
|
745
|
+
const start = line(-join.radius, join.z);
|
|
746
|
+
const end = line(join.radius, join.z);
|
|
747
|
+
return /* @__PURE__ */ jsx2(
|
|
748
|
+
"line",
|
|
749
|
+
{
|
|
750
|
+
"data-join": join.part,
|
|
751
|
+
"data-stepped": join.stepped ? "true" : "false",
|
|
752
|
+
x1: start.x,
|
|
753
|
+
y1: start.y,
|
|
754
|
+
x2: end.x,
|
|
755
|
+
y2: end.y,
|
|
756
|
+
stroke: sheet.ink,
|
|
757
|
+
strokeOpacity: join.stepped ? 1 : 0.35,
|
|
758
|
+
strokeWidth: fontSize * (join.stepped ? STROKE.edge : STROKE.chord),
|
|
759
|
+
...join.stepped ? {} : {
|
|
760
|
+
strokeDasharray: `${(fontSize * 0.5).toFixed(2)} ${(fontSize * 0.4).toFixed(2)}`
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
`join-${join.part}-${String(index)}`
|
|
764
|
+
);
|
|
765
|
+
}),
|
|
766
|
+
/* @__PURE__ */ jsx2(
|
|
767
|
+
"path",
|
|
768
|
+
{
|
|
769
|
+
"data-silhouette": true,
|
|
770
|
+
d: silhouettePath(outline.segments, frame),
|
|
771
|
+
fill: "none",
|
|
772
|
+
stroke: sheet.ink,
|
|
773
|
+
strokeWidth: fontSize * STROKE.silhouette,
|
|
774
|
+
strokeLinejoin: "round"
|
|
775
|
+
}
|
|
776
|
+
),
|
|
777
|
+
/* @__PURE__ */ jsx2(DrawingProvider, { value: { frame, outline, sheet }, children }),
|
|
778
|
+
model !== null && layout !== null ? /* @__PURE__ */ jsx2(
|
|
779
|
+
DimensionLines,
|
|
780
|
+
{
|
|
781
|
+
model,
|
|
782
|
+
layout,
|
|
783
|
+
frame,
|
|
784
|
+
outline,
|
|
785
|
+
room,
|
|
786
|
+
requested: chrome,
|
|
787
|
+
ink: sheet.dimension,
|
|
788
|
+
ground: sheet.ground
|
|
789
|
+
}
|
|
790
|
+
) : null,
|
|
791
|
+
/* @__PURE__ */ jsx2(
|
|
792
|
+
"line",
|
|
793
|
+
{
|
|
794
|
+
"data-centreline": true,
|
|
795
|
+
x1: from.x,
|
|
796
|
+
y1: from.y,
|
|
797
|
+
x2: to.x,
|
|
798
|
+
y2: to.y,
|
|
799
|
+
stroke: sheet.centre,
|
|
800
|
+
strokeWidth: fontSize * STROKE.centre,
|
|
801
|
+
strokeDasharray: `${(fontSize * 1.6).toFixed(2)} ${(fontSize * 0.5).toFixed(2)} ${(fontSize * 0.3).toFixed(2)} ${(fontSize * 0.5).toFixed(2)}`
|
|
802
|
+
}
|
|
803
|
+
)
|
|
804
|
+
]
|
|
805
|
+
}
|
|
806
|
+
),
|
|
807
|
+
/* @__PURE__ */ jsxs2(
|
|
808
|
+
"p",
|
|
809
|
+
{
|
|
810
|
+
"data-provenance-note": true,
|
|
811
|
+
style: {
|
|
812
|
+
padding: "0 0.75rem 0.5rem",
|
|
813
|
+
fontSize: "0.625rem",
|
|
814
|
+
color: sheet.dimension,
|
|
815
|
+
margin: 0
|
|
816
|
+
},
|
|
817
|
+
children: [
|
|
818
|
+
"Drawn from stated dimensions",
|
|
819
|
+
assumed.length > 0 ? `; ${assumed.join(", ")} assumed` : "",
|
|
820
|
+
".",
|
|
821
|
+
holder !== null && holder.gaugeLength === null ? " The holder length is not stated." : ""
|
|
822
|
+
]
|
|
823
|
+
}
|
|
824
|
+
)
|
|
825
|
+
]
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
};
|
|
829
|
+
export {
|
|
830
|
+
SHEETS,
|
|
831
|
+
ToolDrawing,
|
|
832
|
+
bandOffset,
|
|
833
|
+
bandRoom,
|
|
834
|
+
dimensionLabel,
|
|
835
|
+
dimensionLayout,
|
|
836
|
+
dimensionsFor,
|
|
837
|
+
figureHeight,
|
|
838
|
+
figureType,
|
|
839
|
+
formatMillimetres,
|
|
840
|
+
frameFor,
|
|
841
|
+
laneOffset,
|
|
842
|
+
orientationFor,
|
|
843
|
+
stackLabels,
|
|
844
|
+
typeSizeFor,
|
|
845
|
+
useDrawingContext
|
|
846
|
+
};
|