@metamesh/modeling 0.7.0 → 0.8.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/CHANGELOG.md +24 -0
- package/README.md +76 -3
- package/dist/api/engine.d.ts +4 -0
- package/dist/api/geometry.d.ts +104 -19
- package/dist/api/index.d.ts +203 -18
- package/dist/api/text.d.ts +21 -7
- package/dist/engine/construction-operations.js +52 -10
- package/dist/engine/execution.js +55 -2
- package/dist/engine/export-3mf.js +17 -1
- package/dist/engine/finalization.js +27 -18
- package/dist/engine/inspection-operations.js +26 -22
- package/dist/engine/loft-operations.js +1 -1
- package/dist/engine/mechanical-operations.js +5 -8
- package/dist/engine/mesh-view-cache.js +23 -0
- package/dist/engine/native/adapter.js +2 -4
- package/dist/engine/native/distance.js +42 -0
- package/dist/engine/native/status.js +16 -0
- package/dist/engine/operation-environment.js +2 -1
- package/dist/engine/section-operations.js +3 -3
- package/dist/geometry/heightfield.js +101 -42
- package/dist/geometry/index.js +2 -0
- package/dist/geometry/loft-simple-mesh.js +2 -3
- package/dist/geometry/loft-triangulation.js +12 -9
- package/dist/geometry/loft.js +2 -6
- package/dist/geometry/mechanical/standards.js +2 -3
- package/dist/geometry/mechanical/threads.js +2 -3
- package/dist/geometry/mesh-centroid.js +36 -0
- package/dist/geometry/mesh-solid-intersections.js +123 -0
- package/dist/geometry/mesh-solid.js +284 -0
- package/dist/geometry/sweeps/sweep-closed.js +4 -4
- package/dist/geometry/sweeps/sweep-mesh.js +2 -6
- package/dist/geometry/sweeps/sweep-open.js +4 -4
- package/dist/geometry/sweeps/validation.js +1 -2
- package/dist/model/operations.js +10 -3
- package/dist/text/vector-text-layout.js +46 -3
- package/package.json +1 -1
|
@@ -22,13 +22,33 @@ function checkText(text) {
|
|
|
22
22
|
return lines;
|
|
23
23
|
}
|
|
24
24
|
function normalizeControls(options, staticWeight) {
|
|
25
|
+
const baseline = options.baseline;
|
|
26
|
+
if (baseline !== undefined) {
|
|
27
|
+
if (typeof baseline !== "object" || baseline === null || baseline.kind !== "arc")
|
|
28
|
+
fail("Invalid text baseline; use an arc or omit baseline for horizontal text.");
|
|
29
|
+
if (!Number.isFinite(baseline.radius) || baseline.radius <= 0 || baseline.radius > 10000)
|
|
30
|
+
fail("Arc baseline radius must be positive and at most 10000 mm.");
|
|
31
|
+
if (baseline.centerAngleDegrees !== undefined && !Number.isFinite(baseline.centerAngleDegrees))
|
|
32
|
+
fail("Arc baseline centerAngleDegrees must be finite.");
|
|
33
|
+
if (baseline.direction !== undefined &&
|
|
34
|
+
baseline.direction !== "clockwise" &&
|
|
35
|
+
baseline.direction !== "counterclockwise")
|
|
36
|
+
fail("Invalid arc baseline direction.");
|
|
37
|
+
}
|
|
25
38
|
const controls = {
|
|
26
39
|
fontSize: options.fontSize,
|
|
27
40
|
fontWeight: options.fontWeight ?? staticWeight,
|
|
28
41
|
letterSpacing: options.letterSpacing ?? 0,
|
|
29
|
-
align: options.align ?? "left",
|
|
42
|
+
align: options.align ?? (baseline === undefined ? "left" : "center"),
|
|
30
43
|
verticalAlign: options.verticalAlign ?? "baseline",
|
|
31
44
|
lineHeight: options.lineHeight,
|
|
45
|
+
baseline: baseline === undefined
|
|
46
|
+
? undefined
|
|
47
|
+
: {
|
|
48
|
+
radius: baseline.radius,
|
|
49
|
+
centerAngleDegrees: (((baseline.centerAngleDegrees ?? 90) % 360) + 360) % 360,
|
|
50
|
+
direction: baseline.direction ?? "clockwise",
|
|
51
|
+
},
|
|
32
52
|
};
|
|
33
53
|
if (!Number.isFinite(controls.fontSize) || controls.fontSize <= 0 || controls.fontSize > 10000)
|
|
34
54
|
fail("fontSize must be positive and at most 10000 mm.");
|
|
@@ -45,6 +65,8 @@ function normalizeControls(options, staticWeight) {
|
|
|
45
65
|
controls.verticalAlign !== "center" &&
|
|
46
66
|
controls.verticalAlign !== "bottom")
|
|
47
67
|
fail("Invalid vertical text alignment.");
|
|
68
|
+
if (controls.baseline !== undefined && controls.verticalAlign !== "baseline")
|
|
69
|
+
fail("Arc text requires baseline vertical alignment.");
|
|
48
70
|
if (controls.lineHeight !== undefined &&
|
|
49
71
|
(!Number.isFinite(controls.lineHeight) || controls.lineHeight < 0.5 || controls.lineHeight > 5))
|
|
50
72
|
fail("lineHeight must be between 0.5 and 5.");
|
|
@@ -189,6 +211,8 @@ function buildLayout(font, fontMetrics, lines, controls) {
|
|
|
189
211
|
let commands = 0;
|
|
190
212
|
for (let lineIndex = 0; lineIndex < shaped.length; lineIndex++) {
|
|
191
213
|
const { run, advanceWidth } = shaped[lineIndex];
|
|
214
|
+
if (controls.baseline !== undefined && advanceWidth / controls.baseline.radius >= Math.PI * 2)
|
|
215
|
+
fail("Arc text must span less than one full revolution. Increase radius or reduce text size or spacing.");
|
|
192
216
|
const lineX = controls.align === "center"
|
|
193
217
|
? -advanceWidth / 2
|
|
194
218
|
: controls.align === "right"
|
|
@@ -197,11 +221,16 @@ function buildLayout(font, fontMetrics, lines, controls) {
|
|
|
197
221
|
let x = 0;
|
|
198
222
|
let y = 0;
|
|
199
223
|
let spacingSteps = -1;
|
|
224
|
+
let groupCenter = 0;
|
|
200
225
|
for (let index = 0; index < run.glyphs.length; index++) {
|
|
201
226
|
const position = run.positions[index];
|
|
202
227
|
// Zero-advance marks retain their base glyph's tracking offset.
|
|
203
|
-
if (index === 0 || position.xAdvance !== 0)
|
|
228
|
+
if (index === 0 || position.xAdvance !== 0) {
|
|
204
229
|
spacingSteps++;
|
|
230
|
+
// Keep zero-advance marks on the same rigid frame as their advancing base glyph.
|
|
231
|
+
groupCenter =
|
|
232
|
+
(x + position.xAdvance / 2) * scale + spacingSteps * controls.letterSpacing + lineX;
|
|
233
|
+
}
|
|
205
234
|
const source = run.glyphs[index].path.commands;
|
|
206
235
|
commands += source.length;
|
|
207
236
|
if (commands > MAX_TEXT_COMMANDS)
|
|
@@ -209,9 +238,21 @@ function buildLayout(font, fontMetrics, lines, controls) {
|
|
|
209
238
|
if (source.length > 0) {
|
|
210
239
|
// Preserve full precision: fontkit's toSVG() rounds before scaling and reparsing it wastes work.
|
|
211
240
|
const path = new SVGPathData(source.map(convertCommand)).transform(SVGPathDataTransformer.MATRIX(scale, 0, 0, scale, (x + position.xOffset) * scale + spacingSteps * controls.letterSpacing + lineX, (y + position.yOffset) * scale - lineIndex * lineHeight));
|
|
212
|
-
|
|
241
|
+
let bounds = path.getBounds();
|
|
213
242
|
if (![bounds.minX, bounds.minY, bounds.maxX, bounds.maxY].every(Number.isFinite))
|
|
214
243
|
fail("The font returned invalid glyph outline bounds.");
|
|
244
|
+
if (controls.baseline !== undefined) {
|
|
245
|
+
const { radius, centerAngleDegrees, direction } = controls.baseline;
|
|
246
|
+
const clockwise = direction === "clockwise";
|
|
247
|
+
if (clockwise ? radius + bounds.minY <= 0 : radius - bounds.maxY <= 0)
|
|
248
|
+
fail("Arc glyph outlines cross the circle's center axis. Increase the radius or reduce text size.");
|
|
249
|
+
const angle = (centerAngleDegrees * Math.PI) / 180 + ((clockwise ? -1 : 1) * groupCenter) / radius;
|
|
250
|
+
const rotation = angle + (clockwise ? -Math.PI / 2 : Math.PI / 2);
|
|
251
|
+
const cosine = Math.cos(rotation);
|
|
252
|
+
const sine = Math.sin(rotation);
|
|
253
|
+
path.transform(SVGPathDataTransformer.MATRIX(cosine, sine, -sine, cosine, radius * Math.cos(angle) - cosine * groupCenter, radius * Math.sin(angle) - sine * groupCenter));
|
|
254
|
+
bounds = path.getBounds();
|
|
255
|
+
}
|
|
215
256
|
minX = Math.min(minX, bounds.minX);
|
|
216
257
|
minY = Math.min(minY, bounds.minY);
|
|
217
258
|
maxX = Math.max(maxX, bounds.maxX);
|
|
@@ -261,6 +302,8 @@ export function createVectorTextLayout(font, staticWeight) {
|
|
|
261
302
|
return (text, options) => {
|
|
262
303
|
const lines = checkText(text);
|
|
263
304
|
const controls = normalizeControls(options, staticWeight);
|
|
305
|
+
if (controls.baseline !== undefined && lines.length !== 1)
|
|
306
|
+
fail("Arc text supports exactly one line.");
|
|
264
307
|
const key = JSON.stringify([lines, controls]);
|
|
265
308
|
if (previous?.key === key)
|
|
266
309
|
return previous.layout;
|