brepjs 20.12.5 → 20.12.6
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/dist/2d/lib/offset.d.ts +1 -1
- package/dist/2d.cjs +6 -6
- package/dist/2d.js +6 -6
- package/dist/{adjacencyFns-B2Po1gPs.js → adjacencyFns--KmBIKYJ.js} +3 -3
- package/dist/{adjacencyFns-Cho9v3Q3.cjs → adjacencyFns-BFJfaUwX.cjs} +3 -3
- package/dist/{blueprint-Zmt5-07y.js → blueprint-BhIk9Pvm.js} +7 -8
- package/dist/{blueprint-ERmwk6zG.cjs → blueprint-D83DDDPb.cjs} +7 -8
- package/dist/{blueprintFns-B_sxtIo7.cjs → blueprintFns-C4TQn-x_.cjs} +2 -2
- package/dist/{blueprintFns-Dtm-XukP.js → blueprintFns-eULVgkq9.js} +2 -2
- package/dist/{blueprintSketcher-CwTAtrkY.js → blueprintSketcher-D_Faf0GO.js} +32 -14
- package/dist/{blueprintSketcher-CZyokLGJ.cjs → blueprintSketcher-Di376t-0.cjs} +32 -14
- package/dist/{boolean2D-CRuNKgXs.cjs → boolean2D-CNZFI_y8.cjs} +4 -4
- package/dist/{boolean2D-yUpS8onH.js → boolean2D-DqpCF72W.js} +4 -4
- package/dist/brepjs.cjs +30 -30
- package/dist/brepjs.js +30 -30
- package/dist/{cameraFns-DRok4UxZ.js → cameraFns-CrClIkmV.js} +3 -3
- package/dist/{cameraFns-DB1IyMmS.cjs → cameraFns-RqraCXIc.cjs} +3 -3
- package/dist/core.cjs +2 -2
- package/dist/core.js +2 -2
- package/dist/{cornerFinder-D5qnDbtZ.js → cornerFinder-BZuQkRp2.js} +1 -1
- package/dist/{cornerFinder-DfYBHHZG.cjs → cornerFinder-CdOe1Wrb.cjs} +1 -1
- package/dist/{curveFns-BGPSFUWB.js → curveFns-BP3srCfK.js} +1 -1
- package/dist/{curveFns-BKT4Sg8q.cjs → curveFns-CyIvQNYT.cjs} +1 -1
- package/dist/{drawFns-D8e5RiRe.cjs → drawFns-71cOhWD-.cjs} +18 -13
- package/dist/{drawFns-aGufr-pr.js → drawFns-DxDwJ5zZ.js} +18 -13
- package/dist/{faceFns-Cx7cwUMx.cjs → faceFns-B7MiDYj1.cjs} +2 -2
- package/dist/{faceFns-lE2OvBfq.js → faceFns-iVPbTxki.js} +2 -2
- package/dist/{healingFns-B94zSk1-.js → healingFns-BhP8zfp1.js} +5 -5
- package/dist/{healingFns-CcgyrL6h.cjs → healingFns-DWljGTNR.cjs} +5 -5
- package/dist/{helpers-CNgipe6l.js → helpers-D2hANLRu.js} +6 -6
- package/dist/{helpers-C4f2MV-r.cjs → helpers-DKEVm6Hu.cjs} +6 -6
- package/dist/{importFns-gB1X3ti2.js → importFns-DUzIkns7.js} +2 -2
- package/dist/{importFns-fIdcaMqt.cjs → importFns-KEPAgbjj.cjs} +2 -2
- package/dist/io.cjs +2 -2
- package/dist/io.js +2 -2
- package/dist/kernel/bspline2dFit.d.ts +42 -0
- package/dist/kernel/geometry2d.d.ts +45 -0
- package/dist/kernel/occt/kernel2dOps.d.ts +1 -1
- package/dist/kernel/occtWasm/kernel2dOps.d.ts +7 -4
- package/dist/kernel/occtWasm/occtWasmAdapter.cjs +1 -1
- package/dist/kernel/occtWasm/occtWasmAdapter.d.ts +2 -2
- package/dist/kernel/occtWasm/occtWasmAdapter.js +1 -1
- package/dist/kernel/occtWasm/occtWasmTypes.d.ts +2 -0
- package/dist/{loftFns-AHaYxJyS.cjs → loftFns-D5AvfCGG.cjs} +1 -1
- package/dist/{loftFns-mbyBvJ8Y.js → loftFns-D5NJpHLZ.js} +1 -1
- package/dist/{measureFns-CuabRDRL.cjs → measureFns-CZHNWkiA.cjs} +3 -3
- package/dist/{measureFns-C94G0fH8.js → measureFns-mxBdPiWa.js} +3 -3
- package/dist/measurement.cjs +2 -2
- package/dist/measurement.js +2 -2
- package/dist/{meshFns-GXF5gPZL.cjs → meshFns-Czlt-GBJ.cjs} +3 -3
- package/dist/{meshFns-jarDZKw1.js → meshFns-DmjMzNbH.js} +3 -3
- package/dist/{occtWasmAdapter-DlNmVNUn.js → occtWasmAdapter-B9d0Q0Vl.js} +1153 -257
- package/dist/{occtWasmAdapter-DeoeK2iJ.cjs → occtWasmAdapter-DVxLkjlJ.cjs} +1164 -256
- package/dist/operations.cjs +2 -2
- package/dist/operations.js +2 -2
- package/dist/{originTrackingFns-BxeUD2NM.js → originTrackingFns-CBnyKLm7.js} +2 -2
- package/dist/{originTrackingFns-C-yuMV3m.cjs → originTrackingFns-CHymOLLm.cjs} +2 -2
- package/dist/{primitiveFns-BVAkYHVf.cjs → primitiveFns-BVeBEaa4.cjs} +4 -4
- package/dist/{primitiveFns-CualoFAk.js → primitiveFns-DeNl60Rw.js} +4 -4
- package/dist/{principalBoxFns-DtI7tVuS.js → principalBoxFns-3mW_hA6z.js} +3 -3
- package/dist/{principalBoxFns-DHh5_HCw.cjs → principalBoxFns-CG6kG7vT.cjs} +3 -3
- package/dist/projection.cjs +1 -1
- package/dist/projection.js +1 -1
- package/dist/query.cjs +2 -2
- package/dist/query.js +2 -2
- package/dist/{refResolveFns-CaAw-ya3.cjs → refResolveFns-BAs4p9z7.cjs} +6 -6
- package/dist/{refResolveFns--SRyN2qa.js → refResolveFns-DHx1Y3GG.js} +6 -6
- package/dist/{shapeFns-CwjjUOr0.cjs → shapeFns-C0w8vvfb.cjs} +2 -2
- package/dist/{shapeFns-CdrDroOM.js → shapeFns-CgqKICF_.js} +2 -2
- package/dist/shapeRef.cjs +1 -1
- package/dist/shapeRef.js +1 -1
- package/dist/{shapeTypes-D938X2mg.cjs → shapeTypes-Dc7j-MYg.cjs} +10 -16
- package/dist/{shapeTypes-PNVEv6ga.js → shapeTypes-DwvQUp9M.js} +10 -16
- package/dist/sketching.cjs +3 -3
- package/dist/sketching.js +3 -3
- package/dist/{solidBuilders-Hft8Cr6a.js → solidBuilders-DT1gpVWM.js} +5 -5
- package/dist/{solidBuilders-CAkBQKWb.cjs → solidBuilders-iS5DDwg8.cjs} +5 -5
- package/dist/{surfaceBuilders-BKCiVf7T.cjs → surfaceBuilders-BvwSgD3C.cjs} +3 -3
- package/dist/{surfaceBuilders-CrO_BBKM.js → surfaceBuilders-CPLe0bbk.js} +3 -3
- package/dist/text.cjs +2 -2
- package/dist/text.js +2 -2
- package/dist/{textBlueprints-B8q2a-nN.js → textBlueprints-BU0Rhk54.js} +7 -7
- package/dist/{textBlueprints-C9HeYc5a.cjs → textBlueprints-CNPCPJix.cjs} +7 -7
- package/dist/{textMetrics-1fhCSAGF.cjs → textMetrics-C71g4TmO.cjs} +1 -1
- package/dist/{textMetrics-DJfTSy6w.js → textMetrics-Df9aLYK2.js} +1 -1
- package/dist/{threadFns-CVIEbOW3.cjs → threadFns-CzXEnnmo.cjs} +7 -7
- package/dist/{threadFns-f2KwibLR.js → threadFns-DESH7lcs.js} +7 -7
- package/dist/topology.cjs +9 -9
- package/dist/topology.js +9 -9
- package/dist/{topologyQueryFns-4WYqtSFF.cjs → topologyQueryFns-5Frx8Y-0.cjs} +1 -1
- package/dist/{topologyQueryFns-BZUY10TD.js → topologyQueryFns-LXV1nTaS.js} +1 -1
- package/dist/{validityTypes-tWSx_zAH.cjs → validityTypes-C6rROGTP.cjs} +1 -1
- package/dist/{validityTypes-DAXIqwZM.js → validityTypes-DM-Iwe8N.js} +1 -1
- package/package.json +1 -1
|
@@ -296,9 +296,393 @@ function quickHull(inputPoints, tolerance) {
|
|
|
296
296
|
points
|
|
297
297
|
};
|
|
298
298
|
}
|
|
299
|
+
// src/kernel/bspline2dFit.ts
|
|
300
|
+
/* v8 ignore file -- pure-math hot path for 2D curve ops; excluded from coverage for the
|
|
301
|
+
same reason as geometry2d.ts (instrumentation overhead times out 2D-heavy tests). */
|
|
302
|
+
function findSpan(U, p, n, u) {
|
|
303
|
+
if (u >= U[n + 1]) return n;
|
|
304
|
+
if (u <= U[p]) return p;
|
|
305
|
+
let low = p;
|
|
306
|
+
let high = n + 1;
|
|
307
|
+
let mid = low + high >> 1;
|
|
308
|
+
while (u < U[mid] || u >= U[mid + 1]) {
|
|
309
|
+
if (u < U[mid]) high = mid;
|
|
310
|
+
else low = mid;
|
|
311
|
+
mid = low + high >> 1;
|
|
312
|
+
}
|
|
313
|
+
return mid;
|
|
314
|
+
}
|
|
315
|
+
function basisFuns(U, span, u, p) {
|
|
316
|
+
const N = new Array(p + 1).fill(0);
|
|
317
|
+
const left = new Array(p + 1).fill(0);
|
|
318
|
+
const right = new Array(p + 1).fill(0);
|
|
319
|
+
N[0] = 1;
|
|
320
|
+
for (let j = 1; j <= p; j++) {
|
|
321
|
+
left[j] = u - U[span + 1 - j];
|
|
322
|
+
right[j] = U[span + j] - u;
|
|
323
|
+
let saved = 0;
|
|
324
|
+
for (let r = 0; r < j; r++) {
|
|
325
|
+
const denom = right[r + 1] + left[j - r];
|
|
326
|
+
const temp = denom === 0 ? 0 : N[r] / denom;
|
|
327
|
+
N[r] = saved + right[r + 1] * temp;
|
|
328
|
+
saved = left[j - r] * temp;
|
|
329
|
+
}
|
|
330
|
+
N[j] = saved;
|
|
331
|
+
}
|
|
332
|
+
return N;
|
|
333
|
+
}
|
|
334
|
+
function evaluateFull(U, poles, p, u) {
|
|
335
|
+
const span = findSpan(U, p, poles.length - 1, u);
|
|
336
|
+
const N = basisFuns(U, span, u, p);
|
|
337
|
+
let x = 0;
|
|
338
|
+
let y = 0;
|
|
339
|
+
for (let j = 0; j <= p; j++) {
|
|
340
|
+
const pole = poles[span - p + j];
|
|
341
|
+
x += N[j] * pole[0];
|
|
342
|
+
y += N[j] * pole[1];
|
|
343
|
+
}
|
|
344
|
+
return [x, y];
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Gaussian elimination without pivoting on a banded system. B-spline collocation
|
|
348
|
+
* matrices are totally positive, so elimination in natural order is stable.
|
|
349
|
+
*/
|
|
350
|
+
function solveBanded(rows, rhs) {
|
|
351
|
+
const n = rows.length;
|
|
352
|
+
let kl = 0;
|
|
353
|
+
let ku = 0;
|
|
354
|
+
rows.forEach((row, k) => {
|
|
355
|
+
kl = Math.max(kl, k - row.start);
|
|
356
|
+
ku = Math.max(ku, row.start + row.vals.length - 1 - k);
|
|
357
|
+
});
|
|
358
|
+
const width = kl + ku + 1;
|
|
359
|
+
const band = rows.map((row, k) => {
|
|
360
|
+
const r = new Float64Array(width);
|
|
361
|
+
row.vals.forEach((v, j) => {
|
|
362
|
+
r[row.start + j - k + kl] = v;
|
|
363
|
+
});
|
|
364
|
+
return r;
|
|
365
|
+
});
|
|
366
|
+
const bx = rhs.map((q) => q[0]);
|
|
367
|
+
const by = rhs.map((q) => q[1]);
|
|
368
|
+
const at = (r, c) => band[r][c - r + kl];
|
|
369
|
+
for (let i = 0; i < n; i++) {
|
|
370
|
+
const pivot = at(i, i);
|
|
371
|
+
if (Math.abs(pivot) < 1e-300) throw new Error("interpolateBSpline2d: singular system");
|
|
372
|
+
for (let r = i + 1; r <= Math.min(n - 1, i + kl); r++) {
|
|
373
|
+
const f = at(r, i) / pivot;
|
|
374
|
+
if (f === 0) continue;
|
|
375
|
+
const rowR = band[r];
|
|
376
|
+
for (let c = i; c <= Math.min(n - 1, i + ku); c++) rowR[c - r + kl] = rowR[c - r + kl] - f * at(i, c);
|
|
377
|
+
bx[r] = bx[r] - f * bx[i];
|
|
378
|
+
by[r] = by[r] - f * by[i];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const out = new Array(n);
|
|
382
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
383
|
+
let sx = bx[i];
|
|
384
|
+
let sy = by[i];
|
|
385
|
+
for (let c = i + 1; c <= Math.min(n - 1, i + ku); c++) {
|
|
386
|
+
const a = at(i, c);
|
|
387
|
+
sx -= a * out[c][0];
|
|
388
|
+
sy -= a * out[c][1];
|
|
389
|
+
}
|
|
390
|
+
const d = at(i, i);
|
|
391
|
+
out[i] = [sx / d, sy / d];
|
|
392
|
+
}
|
|
393
|
+
return out;
|
|
394
|
+
}
|
|
395
|
+
function chordLengthParams(points) {
|
|
396
|
+
const n = points.length - 1;
|
|
397
|
+
const d = [0];
|
|
398
|
+
for (let k = 1; k <= n; k++) {
|
|
399
|
+
const a = points[k - 1];
|
|
400
|
+
const b = points[k];
|
|
401
|
+
d.push(d[k - 1] + Math.hypot(b[0] - a[0], b[1] - a[1]));
|
|
402
|
+
}
|
|
403
|
+
const total = d[n];
|
|
404
|
+
if (total < 1e-300) return d.map((_, k) => k / n);
|
|
405
|
+
return d.map((v) => v / total);
|
|
406
|
+
}
|
|
407
|
+
function compressKnots(U) {
|
|
408
|
+
const knots = [];
|
|
409
|
+
const multiplicities = [];
|
|
410
|
+
for (const u of U) {
|
|
411
|
+
const last = knots.length - 1;
|
|
412
|
+
if (last >= 0 && u === knots[last]) multiplicities[last] = multiplicities[last] + 1;
|
|
413
|
+
else {
|
|
414
|
+
knots.push(u);
|
|
415
|
+
multiplicities.push(1);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
knots,
|
|
420
|
+
multiplicities
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Interpolate `points` with a clamped B-spline of `degree` (capped at
|
|
425
|
+
* `points.length - 1`). `params` are the increasing curve parameters assigned to
|
|
426
|
+
* the points, normalised to [0, 1]; chord length when omitted.
|
|
427
|
+
*/
|
|
428
|
+
function interpolateBSpline2d(points, degree = 3, params) {
|
|
429
|
+
const n = points.length - 1;
|
|
430
|
+
if (n < 1) throw new Error("interpolateBSpline2d: need at least 2 points");
|
|
431
|
+
const p = Math.max(1, Math.min(degree, n));
|
|
432
|
+
const ub = params ? [...params] : chordLengthParams(points);
|
|
433
|
+
const U = [];
|
|
434
|
+
for (let i = 0; i <= p; i++) U.push(0);
|
|
435
|
+
for (let j = 1; j <= n - p; j++) {
|
|
436
|
+
let sum = 0;
|
|
437
|
+
for (let i = j; i < j + p; i++) sum += ub[i];
|
|
438
|
+
U.push(sum / p);
|
|
439
|
+
}
|
|
440
|
+
for (let i = 0; i <= p; i++) U.push(1);
|
|
441
|
+
const poles = solveBanded(ub.map((u) => {
|
|
442
|
+
const span = findSpan(U, p, n, u);
|
|
443
|
+
return {
|
|
444
|
+
start: span - p,
|
|
445
|
+
vals: basisFuns(U, span, u, p)
|
|
446
|
+
};
|
|
447
|
+
}), points.map((q) => [q[0], q[1]]));
|
|
448
|
+
poles[0] = [points[0][0], points[0][1]];
|
|
449
|
+
poles[n] = [points[n][0], points[n][1]];
|
|
450
|
+
const { knots, multiplicities } = compressKnots(U);
|
|
451
|
+
return {
|
|
452
|
+
__bk2d: "bspline",
|
|
453
|
+
poles,
|
|
454
|
+
knots,
|
|
455
|
+
multiplicities,
|
|
456
|
+
degree: p,
|
|
457
|
+
isPeriodic: false
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function fitPiece(evaluate, s0, s1, tolerance, degree, minSegments, maxSegments) {
|
|
461
|
+
let segments = Math.min(maxSegments, Math.max(2, minSegments));
|
|
462
|
+
for (;;) {
|
|
463
|
+
const params = [];
|
|
464
|
+
const points = [];
|
|
465
|
+
for (let i = 0; i <= segments; i++) {
|
|
466
|
+
const u = i / segments;
|
|
467
|
+
params.push(u);
|
|
468
|
+
points.push(evaluate(s0 + (s1 - s0) * u));
|
|
469
|
+
}
|
|
470
|
+
const curve = interpolateBSpline2d(points, degree, params);
|
|
471
|
+
if (segments >= maxSegments) return curve;
|
|
472
|
+
const U = [];
|
|
473
|
+
curve.knots.forEach((k, i) => {
|
|
474
|
+
for (let m = 0; m < (curve.multiplicities[i] ?? 1); m++) U.push(k);
|
|
475
|
+
});
|
|
476
|
+
let maxErr = 0;
|
|
477
|
+
for (let i = 0; i < segments && maxErr <= tolerance; i++) {
|
|
478
|
+
const u = (i + .5) / segments;
|
|
479
|
+
const [fx, fy] = evaluateFull(U, curve.poles, curve.degree, u);
|
|
480
|
+
const [tx, ty] = evaluate(s0 + (s1 - s0) * u);
|
|
481
|
+
maxErr = Math.max(maxErr, Math.hypot(fx - tx, fy - ty));
|
|
482
|
+
}
|
|
483
|
+
if (maxErr <= tolerance) return curve;
|
|
484
|
+
segments = Math.min(maxSegments, segments * 2);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Concatenate clamped B-splines of one degree, each running over [0, 1], onto
|
|
489
|
+
* the consecutive intervals of `cuts`. Joints are C0 (knot multiplicity =
|
|
490
|
+
* degree); each piece starts on the pole the previous one ended on.
|
|
491
|
+
*/
|
|
492
|
+
function joinPieces(pieces, cuts) {
|
|
493
|
+
const p = pieces[0].degree;
|
|
494
|
+
const knots = [];
|
|
495
|
+
const multiplicities = [];
|
|
496
|
+
const poles = [];
|
|
497
|
+
pieces.forEach((piece, i) => {
|
|
498
|
+
const a = cuts[i];
|
|
499
|
+
const b = cuts[i + 1];
|
|
500
|
+
if (i === 0) {
|
|
501
|
+
knots.push(a);
|
|
502
|
+
multiplicities.push(p + 1);
|
|
503
|
+
poles.push(...piece.poles);
|
|
504
|
+
} else poles.push(...piece.poles.slice(1));
|
|
505
|
+
for (let j = 1; j < piece.knots.length - 1; j++) {
|
|
506
|
+
knots.push(a + piece.knots[j] * (b - a));
|
|
507
|
+
multiplicities.push(piece.multiplicities[j]);
|
|
508
|
+
}
|
|
509
|
+
knots.push(b);
|
|
510
|
+
multiplicities.push(i === pieces.length - 1 ? p + 1 : p);
|
|
511
|
+
});
|
|
512
|
+
return {
|
|
513
|
+
__bk2d: "bspline",
|
|
514
|
+
poles,
|
|
515
|
+
knots,
|
|
516
|
+
multiplicities,
|
|
517
|
+
degree: p,
|
|
518
|
+
isPeriodic: false
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Approximate the parametric curve `evaluate` over [s0, s1] by interpolating
|
|
523
|
+
* uniformly spaced samples, doubling the sample count until the interpolant is
|
|
524
|
+
* within `tolerance` of the curve at every mid-sample parameter (or the
|
|
525
|
+
* segment cap is reached). With `breaks`, each piece between them is fitted
|
|
526
|
+
* separately and the pieces are joined. The B-spline runs over [0, 1] in step
|
|
527
|
+
* with s.
|
|
528
|
+
*/
|
|
529
|
+
function fitBSpline2d(evaluate, s0, s1, tolerance, options = {}) {
|
|
530
|
+
const degree = options.degree ?? 3;
|
|
531
|
+
const maxSegments = Math.max(2, options.maxSegments ?? 1024);
|
|
532
|
+
const lo = Math.min(s0, s1);
|
|
533
|
+
const hi = Math.max(s0, s1);
|
|
534
|
+
const margin = 1e-12 * (hi - lo);
|
|
535
|
+
const inner = [...new Set(options.breaks ?? [])].filter((b) => b > lo + margin && b < hi - margin).sort((a, b) => s0 <= s1 ? a - b : b - a);
|
|
536
|
+
if (!inner.length) return fitPiece(evaluate, s0, s1, tolerance, degree, options.minSegments ?? 8, maxSegments);
|
|
537
|
+
const cuts = [
|
|
538
|
+
s0,
|
|
539
|
+
...inner,
|
|
540
|
+
s1
|
|
541
|
+
];
|
|
542
|
+
return joinPieces(cuts.slice(1).map((end, i) => fitPiece(evaluate, cuts[i], end, tolerance, degree, Math.max(degree, 2), maxSegments)), cuts.map((c) => (c - s0) / (s1 - s0)));
|
|
543
|
+
}
|
|
544
|
+
function fullKnotVector(c) {
|
|
545
|
+
const U = [];
|
|
546
|
+
c.knots.forEach((k, i) => {
|
|
547
|
+
for (let m = 0; m < (c.multiplicities[i] ?? 1); m++) U.push(k);
|
|
548
|
+
});
|
|
549
|
+
return U;
|
|
550
|
+
}
|
|
551
|
+
/** The B-spline form of a Bezier; a B-spline is returned as is. */
|
|
552
|
+
function asBSpline2d(c) {
|
|
553
|
+
if (c.__bk2d === "bspline") return c;
|
|
554
|
+
const degree = c.poles.length - 1;
|
|
555
|
+
return {
|
|
556
|
+
__bk2d: "bspline",
|
|
557
|
+
poles: c.poles.map(([x, y]) => [x, y]),
|
|
558
|
+
knots: [0, 1],
|
|
559
|
+
multiplicities: [degree + 1, degree + 1],
|
|
560
|
+
degree,
|
|
561
|
+
isPeriodic: false
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
/** Insert `u` once (Boehm, The NURBS Book A5.1 with r = 1). */
|
|
565
|
+
function insertKnot(U, P, p, u) {
|
|
566
|
+
const n = P.length - 1;
|
|
567
|
+
const k = findSpan(U, p, n, u);
|
|
568
|
+
const Q = [];
|
|
569
|
+
for (let i = 0; i <= n + 1; i++) if (i <= k - p) Q.push(P[i]);
|
|
570
|
+
else if (i > k) Q.push(P[i - 1]);
|
|
571
|
+
else {
|
|
572
|
+
const alpha = (u - U[i]) / (U[i + p] - U[i]);
|
|
573
|
+
const a = P[i];
|
|
574
|
+
const b = P[i - 1];
|
|
575
|
+
Q.push([alpha * a[0] + (1 - alpha) * b[0], alpha * a[1] + (1 - alpha) * b[1]]);
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
U: [
|
|
579
|
+
...U.slice(0, k + 1),
|
|
580
|
+
u,
|
|
581
|
+
...U.slice(k + 1)
|
|
582
|
+
],
|
|
583
|
+
P: Q
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* The part of a clamped B-spline over [lo, hi] (inside its knot range, lo < hi)
|
|
588
|
+
* as a clamped B-spline of its own, exact by knot insertion.
|
|
589
|
+
*/
|
|
590
|
+
function subBSpline2d(c, lo, hi) {
|
|
591
|
+
const p = c.degree;
|
|
592
|
+
let U = fullKnotVector(c);
|
|
593
|
+
let P = c.poles.map(([x, y]) => [x, y]);
|
|
594
|
+
const first = U[p];
|
|
595
|
+
const last = U[U.length - 1 - p];
|
|
596
|
+
const a = Math.min(last, Math.max(first, lo));
|
|
597
|
+
const b = Math.min(last, Math.max(first, hi));
|
|
598
|
+
for (const u of [a, b]) {
|
|
599
|
+
if (u <= first || u >= last) continue;
|
|
600
|
+
while (U.filter((k) => k === u).length < p) ({U, P} = insertKnot(U, P, p, u));
|
|
601
|
+
}
|
|
602
|
+
const kLo = a <= first ? p : U.lastIndexOf(a);
|
|
603
|
+
const kHi = b >= last ? U.length - 1 - p : U.indexOf(b);
|
|
604
|
+
const sub = [];
|
|
605
|
+
for (let i = 0; i <= p; i++) sub.push(a);
|
|
606
|
+
sub.push(...U.slice(kLo + 1, kHi));
|
|
607
|
+
for (let i = 0; i <= p; i++) sub.push(b);
|
|
608
|
+
const { knots, multiplicities } = compressKnots(sub);
|
|
609
|
+
return {
|
|
610
|
+
__bk2d: "bspline",
|
|
611
|
+
poles: P.slice(kLo - p, kHi),
|
|
612
|
+
knots,
|
|
613
|
+
multiplicities,
|
|
614
|
+
degree: p,
|
|
615
|
+
isPeriodic: false
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Bezier poles of every non-empty knot span (The NURBS Book A5.6). A malformed
|
|
620
|
+
* knot vector yields the whole control polygon as one segment, which still
|
|
621
|
+
* bounds the curve.
|
|
622
|
+
*/
|
|
623
|
+
function bezierSegments2d(c) {
|
|
624
|
+
const poles = c.poles;
|
|
625
|
+
const p = c.degree;
|
|
626
|
+
const U = fullKnotVector(c);
|
|
627
|
+
const m = poles.length - 1 + p + 1;
|
|
628
|
+
const clone = (pt) => [pt[0], pt[1]];
|
|
629
|
+
if (p < 1 || U.length !== m + 1) return [poles.map(clone)];
|
|
630
|
+
const segments = [];
|
|
631
|
+
let a = p;
|
|
632
|
+
let b = p + 1;
|
|
633
|
+
let segPoles = [];
|
|
634
|
+
for (let i = 0; i <= p; i++) segPoles[i] = clone(poles[i]);
|
|
635
|
+
let nextPoles = [];
|
|
636
|
+
while (b < m) {
|
|
637
|
+
const i0 = b;
|
|
638
|
+
while (b < m && U[b + 1] === U[b]) b++;
|
|
639
|
+
const mult = b - i0 + 1;
|
|
640
|
+
if (mult > p && b < m) return [poles.map(clone)];
|
|
641
|
+
if (mult < p) {
|
|
642
|
+
const numer = U[b] - U[a];
|
|
643
|
+
const alphas = [];
|
|
644
|
+
for (let j = p; j > mult; j--) alphas[j - mult - 1] = numer / (U[a + j] - U[a]);
|
|
645
|
+
const r = p - mult;
|
|
646
|
+
for (let j = 1; j <= r; j++) {
|
|
647
|
+
const save = r - j;
|
|
648
|
+
const s = mult + j;
|
|
649
|
+
for (let k = p; k >= s; k--) {
|
|
650
|
+
const alpha = alphas[k - s];
|
|
651
|
+
const cur = segPoles[k];
|
|
652
|
+
const prev = segPoles[k - 1];
|
|
653
|
+
segPoles[k] = [alpha * cur[0] + (1 - alpha) * prev[0], alpha * cur[1] + (1 - alpha) * prev[1]];
|
|
654
|
+
}
|
|
655
|
+
if (b < m) nextPoles[save] = clone(segPoles[p]);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
segments.push(segPoles);
|
|
659
|
+
if (b < m) {
|
|
660
|
+
for (let i = p - mult; i <= p; i++) nextPoles[i] = clone(poles[b - p + i]);
|
|
661
|
+
segPoles = nextPoles;
|
|
662
|
+
nextPoles = [];
|
|
663
|
+
a = b;
|
|
664
|
+
b++;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return segments;
|
|
668
|
+
}
|
|
299
669
|
// src/kernel/geometry2d.ts
|
|
300
670
|
/* v8 ignore file -- excluded from coverage reports; prevents V8 instrumentation
|
|
301
671
|
overhead that causes 2D-heavy tests to timeout under coverage mode. */
|
|
672
|
+
/**
|
|
673
|
+
* Pure-TypeScript 2D geometry implementation for Kernel2DCapability.
|
|
674
|
+
*
|
|
675
|
+
* All 2D curves are represented as plain objects with a `type` discriminant
|
|
676
|
+
* and `evaluate(t)` method. No WASM boundary crossing required.
|
|
677
|
+
*
|
|
678
|
+
* ADR-0006: Inline math here (distance, rotation, normalization, dot/cross
|
|
679
|
+
* products) operates on curve struct fields, not Point2D tuples. The canonical
|
|
680
|
+
* tuple-based functions live in src/utils/vec2d.ts. The struct-field patterns
|
|
681
|
+
* are not worth converting to tuple form — the temporary array allocations
|
|
682
|
+
* would degrade both readability and performance.
|
|
683
|
+
*
|
|
684
|
+
* @module
|
|
685
|
+
*/
|
|
302
686
|
function evaluateCurve2d$1(c, t) {
|
|
303
687
|
switch (c.__bk2d) {
|
|
304
688
|
case "line": return [c.ox + c.dx * t, c.oy + c.dy * t];
|
|
@@ -398,6 +782,79 @@ function curveTypeName(c) {
|
|
|
398
782
|
case "trimmed": return "TRIMMED_" + curveTypeName(c.basis);
|
|
399
783
|
}
|
|
400
784
|
}
|
|
785
|
+
/**
|
|
786
|
+
* Collapse nested trimmed wrappers into a single trimmed curve over a
|
|
787
|
+
* non-trimmed basis (or return the curve itself when it is not trimmed).
|
|
788
|
+
* Evaluation is unchanged: both forms map t in [0, 1] to the same basis point.
|
|
789
|
+
*/
|
|
790
|
+
function flattenTrimmed(c) {
|
|
791
|
+
if (c.__bk2d !== "trimmed") return c;
|
|
792
|
+
const inner = flattenTrimmed(c.basis);
|
|
793
|
+
if (inner.__bk2d !== "trimmed") return c;
|
|
794
|
+
const span = inner.tEnd - inner.tStart;
|
|
795
|
+
return {
|
|
796
|
+
__bk2d: "trimmed",
|
|
797
|
+
basis: inner.basis,
|
|
798
|
+
tStart: inner.tStart + c.tStart * span,
|
|
799
|
+
tEnd: inner.tStart + c.tEnd * span
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
/** Basis parameter of the reversed basis that lands on the point at `u` of the original. */
|
|
803
|
+
function reversedBasisParam(c, u) {
|
|
804
|
+
switch (c.__bk2d) {
|
|
805
|
+
case "circle":
|
|
806
|
+
case "ellipse": return -u;
|
|
807
|
+
default: {
|
|
808
|
+
const { first, last } = curveBounds(c);
|
|
809
|
+
return first + last - u;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* The same geometry traversed in the opposite direction (OCCT `Geom2d_Curve::Reversed`).
|
|
815
|
+
* Trimmed curves keep an increasing trim range: the basis is reversed and the
|
|
816
|
+
* range is remapped onto it.
|
|
817
|
+
*/
|
|
818
|
+
function reverseCurve2d$1(c) {
|
|
819
|
+
switch (c.__bk2d) {
|
|
820
|
+
case "line": return {
|
|
821
|
+
...c,
|
|
822
|
+
ox: c.ox + c.dx * c.len,
|
|
823
|
+
oy: c.oy + c.dy * c.len,
|
|
824
|
+
dx: -c.dx,
|
|
825
|
+
dy: -c.dy
|
|
826
|
+
};
|
|
827
|
+
case "circle":
|
|
828
|
+
case "ellipse": return {
|
|
829
|
+
...c,
|
|
830
|
+
sense: !c.sense
|
|
831
|
+
};
|
|
832
|
+
case "bezier": return {
|
|
833
|
+
...c,
|
|
834
|
+
poles: [...c.poles].reverse()
|
|
835
|
+
};
|
|
836
|
+
case "bspline": {
|
|
837
|
+
const lo = c.knots[0] ?? 0;
|
|
838
|
+
const hi = c.knots[c.knots.length - 1] ?? 1;
|
|
839
|
+
return {
|
|
840
|
+
...c,
|
|
841
|
+
poles: [...c.poles].reverse(),
|
|
842
|
+
knots: c.knots.map((k) => lo + hi - k).reverse(),
|
|
843
|
+
multiplicities: [...c.multiplicities].reverse()
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
case "trimmed": {
|
|
847
|
+
const flat = flattenTrimmed(c);
|
|
848
|
+
if (flat.__bk2d !== "trimmed") return reverseCurve2d$1(flat);
|
|
849
|
+
return {
|
|
850
|
+
__bk2d: "trimmed",
|
|
851
|
+
basis: reverseCurve2d$1(flat.basis),
|
|
852
|
+
tStart: reversedBasisParam(flat.basis, flat.tEnd),
|
|
853
|
+
tEnd: reversedBasisParam(flat.basis, flat.tStart)
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
401
858
|
function makeLine2d$1(x1, y1, x2, y2) {
|
|
402
859
|
const dx = x2 - x1;
|
|
403
860
|
const dy = y2 - y1;
|
|
@@ -630,7 +1087,8 @@ function scaleCurve2d$1(c, factor, cx, cy) {
|
|
|
630
1087
|
cx: ncx,
|
|
631
1088
|
cy: ncy,
|
|
632
1089
|
majorRadius: c.majorRadius * Math.abs(factor),
|
|
633
|
-
minorRadius: c.minorRadius * Math.abs(factor)
|
|
1090
|
+
minorRadius: c.minorRadius * Math.abs(factor),
|
|
1091
|
+
xDirAngle: factor < 0 ? c.xDirAngle + Math.PI : c.xDirAngle
|
|
634
1092
|
};
|
|
635
1093
|
}
|
|
636
1094
|
case "bezier": return {
|
|
@@ -641,10 +1099,31 @@ function scaleCurve2d$1(c, factor, cx, cy) {
|
|
|
641
1099
|
...c,
|
|
642
1100
|
poles: c.poles.map(([x, y]) => scalePoint(x, y))
|
|
643
1101
|
};
|
|
644
|
-
case "trimmed":
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
1102
|
+
case "trimmed": {
|
|
1103
|
+
const basis = scaleCurve2d$1(c.basis, factor, cx, cy);
|
|
1104
|
+
if (basis.__bk2d === "line") {
|
|
1105
|
+
const s = Math.abs(factor);
|
|
1106
|
+
return {
|
|
1107
|
+
...c,
|
|
1108
|
+
basis,
|
|
1109
|
+
tStart: c.tStart * s,
|
|
1110
|
+
tEnd: c.tEnd * s
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
if (basis.__bk2d === "circle" && factor < 0) {
|
|
1114
|
+
const shift = basis.sense ? Math.PI : -Math.PI;
|
|
1115
|
+
return {
|
|
1116
|
+
...c,
|
|
1117
|
+
basis,
|
|
1118
|
+
tStart: c.tStart + shift,
|
|
1119
|
+
tEnd: c.tEnd + shift
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
...c,
|
|
1124
|
+
basis
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
648
1127
|
}
|
|
649
1128
|
}
|
|
650
1129
|
function mirrorAtPoint(c, cx, cy) {
|
|
@@ -723,6 +1202,328 @@ function mirrorAcrossAxis(c, ox, oy, dx, dy) {
|
|
|
723
1202
|
}
|
|
724
1203
|
}
|
|
725
1204
|
}
|
|
1205
|
+
function applyAffine(m, x, y) {
|
|
1206
|
+
return [m.a * x + m.b * y + m.tx, m.c * x + m.d * y + m.ty];
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Image of the conic C + M0 * (cos psi, sin psi), psi = sense ? t : -t.
|
|
1210
|
+
* The closed-form 2x2 SVD M = R(phi) * diag(sx, sy) * R(theta) gives the new
|
|
1211
|
+
* frame (phi), radii (sx, |sy|) and the angular shift (theta); sy < 0 means the
|
|
1212
|
+
* map reflects, which flips the sense.
|
|
1213
|
+
*/
|
|
1214
|
+
function affineConic(cx, cy, m0, sense, m) {
|
|
1215
|
+
const [ncx, ncy] = applyAffine(m, cx, cy);
|
|
1216
|
+
const m00 = m.a * m0[0] + m.b * m0[2];
|
|
1217
|
+
const m01 = m.a * m0[1] + m.b * m0[3];
|
|
1218
|
+
const m10 = m.c * m0[0] + m.d * m0[2];
|
|
1219
|
+
const m11 = m.c * m0[1] + m.d * m0[3];
|
|
1220
|
+
const e = (m00 + m11) / 2;
|
|
1221
|
+
const f = (m00 - m11) / 2;
|
|
1222
|
+
const g = (m10 + m01) / 2;
|
|
1223
|
+
const h = (m10 - m01) / 2;
|
|
1224
|
+
const q = Math.hypot(e, h);
|
|
1225
|
+
const r = Math.hypot(f, g);
|
|
1226
|
+
const sx = q + r;
|
|
1227
|
+
const sy = q - r;
|
|
1228
|
+
const a1 = Math.atan2(g, f);
|
|
1229
|
+
const a2 = Math.atan2(h, e);
|
|
1230
|
+
const theta = (a2 - a1) / 2;
|
|
1231
|
+
const phi = (a2 + a1) / 2;
|
|
1232
|
+
const newSense = sy < 0 ? !sense : sense;
|
|
1233
|
+
const shift = sense ? theta : -theta;
|
|
1234
|
+
if (Math.min(q, r) <= 1e-12 * Math.max(q, r)) {
|
|
1235
|
+
const circleShift = shift + (newSense ? phi : -phi);
|
|
1236
|
+
return {
|
|
1237
|
+
curve: {
|
|
1238
|
+
__bk2d: "circle",
|
|
1239
|
+
cx: ncx,
|
|
1240
|
+
cy: ncy,
|
|
1241
|
+
radius: sx,
|
|
1242
|
+
sense: newSense
|
|
1243
|
+
},
|
|
1244
|
+
mapParam: (u) => u + circleShift
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
return {
|
|
1248
|
+
curve: {
|
|
1249
|
+
__bk2d: "ellipse",
|
|
1250
|
+
cx: ncx,
|
|
1251
|
+
cy: ncy,
|
|
1252
|
+
majorRadius: sx,
|
|
1253
|
+
minorRadius: Math.abs(sy),
|
|
1254
|
+
xDirAngle: phi,
|
|
1255
|
+
sense: newSense
|
|
1256
|
+
},
|
|
1257
|
+
mapParam: (u) => u + shift
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function affineBasis(c, m) {
|
|
1261
|
+
switch (c.__bk2d) {
|
|
1262
|
+
case "line": {
|
|
1263
|
+
const ldx = m.a * c.dx + m.b * c.dy;
|
|
1264
|
+
const ldy = m.c * c.dx + m.d * c.dy;
|
|
1265
|
+
const speed = Math.hypot(ldx, ldy);
|
|
1266
|
+
const [ox, oy] = applyAffine(m, c.ox, c.oy);
|
|
1267
|
+
if (speed < 1e-300) return {
|
|
1268
|
+
curve: {
|
|
1269
|
+
...c,
|
|
1270
|
+
ox,
|
|
1271
|
+
oy,
|
|
1272
|
+
len: 0
|
|
1273
|
+
},
|
|
1274
|
+
mapParam: () => 0
|
|
1275
|
+
};
|
|
1276
|
+
return {
|
|
1277
|
+
curve: {
|
|
1278
|
+
__bk2d: "line",
|
|
1279
|
+
ox,
|
|
1280
|
+
oy,
|
|
1281
|
+
dx: ldx / speed,
|
|
1282
|
+
dy: ldy / speed,
|
|
1283
|
+
len: c.len * speed
|
|
1284
|
+
},
|
|
1285
|
+
mapParam: (u) => u * speed
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
case "circle": return affineConic(c.cx, c.cy, [
|
|
1289
|
+
c.radius,
|
|
1290
|
+
0,
|
|
1291
|
+
0,
|
|
1292
|
+
c.radius
|
|
1293
|
+
], c.sense, m);
|
|
1294
|
+
case "ellipse": {
|
|
1295
|
+
const cos = Math.cos(c.xDirAngle);
|
|
1296
|
+
const sin = Math.sin(c.xDirAngle);
|
|
1297
|
+
const m0 = [
|
|
1298
|
+
c.majorRadius * cos,
|
|
1299
|
+
-c.minorRadius * sin,
|
|
1300
|
+
c.majorRadius * sin,
|
|
1301
|
+
c.minorRadius * cos
|
|
1302
|
+
];
|
|
1303
|
+
return affineConic(c.cx, c.cy, m0, c.sense, m);
|
|
1304
|
+
}
|
|
1305
|
+
case "bezier":
|
|
1306
|
+
case "bspline": return {
|
|
1307
|
+
curve: {
|
|
1308
|
+
...c,
|
|
1309
|
+
poles: c.poles.map(([x, y]) => applyAffine(m, x, y))
|
|
1310
|
+
},
|
|
1311
|
+
mapParam: (u) => u
|
|
1312
|
+
};
|
|
1313
|
+
case "trimmed": return {
|
|
1314
|
+
curve: affineCurve2d(c, m),
|
|
1315
|
+
mapParam: (u) => u
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Exact image of a curve under an affine map (OCCT `Geom2d_Curve::Transformed`
|
|
1321
|
+
* with a `gp_GTrsf2d`): lines stay lines, circles and ellipses become ellipses
|
|
1322
|
+
* (or circles), and pole-based curves map their poles.
|
|
1323
|
+
*/
|
|
1324
|
+
function affineCurve2d(c, m) {
|
|
1325
|
+
const flat = flattenTrimmed(c);
|
|
1326
|
+
if (flat.__bk2d !== "trimmed") return affineBasis(flat, m).curve;
|
|
1327
|
+
const { curve, mapParam } = affineBasis(flat.basis, m);
|
|
1328
|
+
return {
|
|
1329
|
+
__bk2d: "trimmed",
|
|
1330
|
+
basis: curve,
|
|
1331
|
+
tStart: mapParam(flat.tStart),
|
|
1332
|
+
tEnd: mapParam(flat.tEnd)
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
function unitTangent(c, t, first, last) {
|
|
1336
|
+
let [tx, ty] = tangentCurve2d(c, t);
|
|
1337
|
+
let len = Math.hypot(tx, ty);
|
|
1338
|
+
for (let k = 1; len < 1e-12 && k <= 6; k++) {
|
|
1339
|
+
const h = (last - first) * 10 ** (-9 + k);
|
|
1340
|
+
[tx, ty] = tangentCurve2d(c, t + h <= Math.max(first, last) ? t + h : t - h);
|
|
1341
|
+
len = Math.hypot(tx, ty);
|
|
1342
|
+
}
|
|
1343
|
+
return len < 1e-300 ? [1, 0] : [tx / len, ty / len];
|
|
1344
|
+
}
|
|
1345
|
+
function curveExtent(c) {
|
|
1346
|
+
const box = createBBox2d();
|
|
1347
|
+
addCurveToBBox(box, c, 0);
|
|
1348
|
+
return Math.hypot(box.xMax - box.xMin, box.yMax - box.yMin);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Parameters of `c`, in its own parameter range, at the interior knots of its
|
|
1352
|
+
* B-spline basis: where a fit of the curve (or of its offset) must sample.
|
|
1353
|
+
*/
|
|
1354
|
+
function curveKnotParams(c) {
|
|
1355
|
+
const flat = flattenTrimmed(c);
|
|
1356
|
+
const basis = flat.__bk2d === "trimmed" ? flat.basis : flat;
|
|
1357
|
+
if (basis.__bk2d !== "bspline") return [];
|
|
1358
|
+
const inner = basis.knots.slice(1, -1);
|
|
1359
|
+
if (flat.__bk2d !== "trimmed") return inner;
|
|
1360
|
+
const { tStart, tEnd } = flat;
|
|
1361
|
+
return inner.filter((k) => k > Math.min(tStart, tEnd) && k < Math.max(tStart, tEnd)).map((k) => (k - tStart) / (tEnd - tStart));
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Offset `distance` to the right of the direction of travel (OCCT
|
|
1365
|
+
* `Geom2d_OffsetCurve`: a counter-clockwise circle grows). Lines and circles
|
|
1366
|
+
* are offset exactly; other curves are fitted within `tolerance` (by default
|
|
1367
|
+
* relative to the curve's size).
|
|
1368
|
+
*/
|
|
1369
|
+
function offsetCurve2d$1(c, distance, tolerance) {
|
|
1370
|
+
const flat = flattenTrimmed(c);
|
|
1371
|
+
const trimmed = flat.__bk2d === "trimmed" ? flat : null;
|
|
1372
|
+
const basis = trimmed ? trimmed.basis : flat;
|
|
1373
|
+
const d = trimmed && trimmed.tEnd < trimmed.tStart ? -distance : distance;
|
|
1374
|
+
if (basis.__bk2d === "line") {
|
|
1375
|
+
const shifted = {
|
|
1376
|
+
...basis,
|
|
1377
|
+
ox: basis.ox + basis.dy * d,
|
|
1378
|
+
oy: basis.oy - basis.dx * d
|
|
1379
|
+
};
|
|
1380
|
+
return trimmed ? {
|
|
1381
|
+
...trimmed,
|
|
1382
|
+
basis: shifted
|
|
1383
|
+
} : shifted;
|
|
1384
|
+
}
|
|
1385
|
+
if (basis.__bk2d === "circle") {
|
|
1386
|
+
const radius = basis.radius + (basis.sense ? d : -d);
|
|
1387
|
+
if (Math.abs(radius) <= 1e-12 * Math.max(1, basis.radius)) throw new Error("offsetCurve2d: the offset collapses the circle to its center");
|
|
1388
|
+
const circle = {
|
|
1389
|
+
...basis,
|
|
1390
|
+
radius: Math.abs(radius)
|
|
1391
|
+
};
|
|
1392
|
+
if (!trimmed) return circle;
|
|
1393
|
+
if (radius > 0) return {
|
|
1394
|
+
...trimmed,
|
|
1395
|
+
basis: circle
|
|
1396
|
+
};
|
|
1397
|
+
const shift = basis.sense ? Math.PI : -Math.PI;
|
|
1398
|
+
return {
|
|
1399
|
+
...trimmed,
|
|
1400
|
+
basis: circle,
|
|
1401
|
+
tStart: trimmed.tStart + shift,
|
|
1402
|
+
tEnd: trimmed.tEnd + shift
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
const { first, last } = curveBounds(flat);
|
|
1406
|
+
return fitBSpline2d((t) => {
|
|
1407
|
+
const [px, py] = evaluateCurve2d$1(flat, t);
|
|
1408
|
+
const [ux, uy] = unitTangent(flat, t, first, last);
|
|
1409
|
+
return [px + uy * distance, py - ux * distance];
|
|
1410
|
+
}, first, last, tolerance ?? 1e-9 * Math.max(1, curveExtent(flat) + Math.abs(distance)), { breaks: curveKnotParams(flat) });
|
|
1411
|
+
}
|
|
1412
|
+
function sampleCurve(c, from, to, n) {
|
|
1413
|
+
const pts = [];
|
|
1414
|
+
for (let i = 0; i <= n; i++) pts.push(evaluateCurve2d$1(c, from + (to - from) * i / n));
|
|
1415
|
+
return pts;
|
|
1416
|
+
}
|
|
1417
|
+
/** Second derivative by central differences, kept inside [lo, hi] for clamped curves. */
|
|
1418
|
+
function secondDerivative(c, t, lo, hi) {
|
|
1419
|
+
const h = 1e-4 * Math.max(hi - lo, 1e-9);
|
|
1420
|
+
const mid = Math.min(hi - h, Math.max(lo + h, t));
|
|
1421
|
+
const [ax, ay] = evaluateCurve2d$1(c, mid - h);
|
|
1422
|
+
const [bx, by] = evaluateCurve2d$1(c, mid);
|
|
1423
|
+
const [cx, cy] = evaluateCurve2d$1(c, mid + h);
|
|
1424
|
+
return [(ax - 2 * bx + cx) / (h * h), (ay - 2 * by + cy) / (h * h)];
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Newton step for f(s, t) = |c1(s) - c2(t)|^2 / 2. The closest pair of two
|
|
1428
|
+
* disjoint curves has parallel tangents, so the curvature terms of the Hessian
|
|
1429
|
+
* are what make it converge; where the Hessian is not positive definite the
|
|
1430
|
+
* step falls back to Gauss-Newton, then to projecting c1(s) onto c2.
|
|
1431
|
+
*/
|
|
1432
|
+
function closestPairStep(c1, c2, s, t, box1, box2) {
|
|
1433
|
+
const [px, py] = evaluateCurve2d$1(c1, s);
|
|
1434
|
+
const [qx, qy] = evaluateCurve2d$1(c2, t);
|
|
1435
|
+
const dx = px - qx;
|
|
1436
|
+
const dy = py - qy;
|
|
1437
|
+
const t1 = tangentCurve2d(c1, s);
|
|
1438
|
+
const t2 = tangentCurve2d(c2, t);
|
|
1439
|
+
const g1 = t1[0] * dx + t1[1] * dy;
|
|
1440
|
+
const g2 = -(t2[0] * dx + t2[1] * dy);
|
|
1441
|
+
const a11 = t1[0] * t1[0] + t1[1] * t1[1];
|
|
1442
|
+
const a22 = t2[0] * t2[0] + t2[1] * t2[1];
|
|
1443
|
+
const a12 = -(t1[0] * t2[0] + t1[1] * t2[1]);
|
|
1444
|
+
const k1 = secondDerivative(c1, s, box1[0], box1[1]);
|
|
1445
|
+
const k2 = secondDerivative(c2, t, box2[0], box2[1]);
|
|
1446
|
+
const h11 = a11 + k1[0] * dx + k1[1] * dy;
|
|
1447
|
+
const h22 = a22 - (k2[0] * dx + k2[1] * dy);
|
|
1448
|
+
const detH = h11 * h22 - a12 * a12;
|
|
1449
|
+
if (h11 > 0 && detH > 1e-12 * Math.abs(h11 * h22)) return [(-g1 * h22 + g2 * a12) / detH, (-g2 * h11 + g1 * a12) / detH];
|
|
1450
|
+
const detGN = a11 * a22 - a12 * a12;
|
|
1451
|
+
if (detGN > 1e-12 * a11 * a22) return [(-g1 * a22 + g2 * a12) / detGN, (-g2 * a11 + g1 * a12) / detGN];
|
|
1452
|
+
if (a22 > 0) return [0, -g2 / a22];
|
|
1453
|
+
return a11 > 0 ? [-g1 / a11, 0] : [0, 0];
|
|
1454
|
+
}
|
|
1455
|
+
/** Refine a seed pair, halving each step until it lowers the distance; returns the squared distance. */
|
|
1456
|
+
function refineClosestPair(c1, c2, seed, box1, box2) {
|
|
1457
|
+
const clamp = (v, [lo, hi]) => Math.min(hi, Math.max(lo, v));
|
|
1458
|
+
const distSq = (a, b) => {
|
|
1459
|
+
const [px, py] = evaluateCurve2d$1(c1, a);
|
|
1460
|
+
const [qx, qy] = evaluateCurve2d$1(c2, b);
|
|
1461
|
+
return (px - qx) ** 2 + (py - qy) ** 2;
|
|
1462
|
+
};
|
|
1463
|
+
let [s, t] = seed;
|
|
1464
|
+
let f = distSq(s, t);
|
|
1465
|
+
for (let iter = 0; iter < 60 && f > 0; iter++) {
|
|
1466
|
+
const [ds, dt] = closestPairStep(c1, c2, s, t, box1, box2);
|
|
1467
|
+
let lambda = 1;
|
|
1468
|
+
let ns = s;
|
|
1469
|
+
let nt = t;
|
|
1470
|
+
let nf = f;
|
|
1471
|
+
for (; lambda > 1e-6 && nf >= f; lambda /= 2) {
|
|
1472
|
+
ns = clamp(s + lambda * ds, box1);
|
|
1473
|
+
nt = clamp(t + lambda * dt, box2);
|
|
1474
|
+
nf = distSq(ns, nt);
|
|
1475
|
+
}
|
|
1476
|
+
if (nf >= f || ns === s && nt === t) break;
|
|
1477
|
+
[s, t, f] = [
|
|
1478
|
+
ns,
|
|
1479
|
+
nt,
|
|
1480
|
+
nf
|
|
1481
|
+
];
|
|
1482
|
+
}
|
|
1483
|
+
return f;
|
|
1484
|
+
}
|
|
1485
|
+
/** Whether grid cell (i, j) is no larger than any of its eight neighbours. */
|
|
1486
|
+
function isGridMinimum(at, i, j, n) {
|
|
1487
|
+
const d = at(i, j);
|
|
1488
|
+
for (let ni = Math.max(0, i - 1); ni <= Math.min(n, i + 1); ni++) for (let nj = Math.max(0, j - 1); nj <= Math.min(n, j + 1); nj++) if (at(ni, nj) < d) return false;
|
|
1489
|
+
return true;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Minimum distance between c1 over [from1, to1] and c2 over [from2, to2]
|
|
1493
|
+
* (OCCT `Geom2dAPI_ExtremaCurveCurve`): every local minimum of a sampled
|
|
1494
|
+
* distance grid seeds a Gauss-Newton refinement.
|
|
1495
|
+
*/
|
|
1496
|
+
function distanceBetweenCurves2d$1(c1, c2, from1, to1, from2, to2) {
|
|
1497
|
+
const n = 32;
|
|
1498
|
+
const p1 = sampleCurve(c1, from1, to1, n);
|
|
1499
|
+
const p2 = sampleCurve(c2, from2, to2, n);
|
|
1500
|
+
const grid = /* @__PURE__ */ new Float64Array(1089);
|
|
1501
|
+
for (let i = 0; i <= n; i++) for (let j = 0; j <= n; j++) {
|
|
1502
|
+
const a = p1[i];
|
|
1503
|
+
const b = p2[j];
|
|
1504
|
+
grid[i * 33 + j] = (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;
|
|
1505
|
+
}
|
|
1506
|
+
const at = (i, j) => grid[i * 33 + j] ?? Infinity;
|
|
1507
|
+
const seeds = [];
|
|
1508
|
+
let best = Infinity;
|
|
1509
|
+
for (let i = 0; i <= n; i++) for (let j = 0; j <= n; j++) {
|
|
1510
|
+
const d = at(i, j);
|
|
1511
|
+
best = Math.min(best, d);
|
|
1512
|
+
if (isGridMinimum(at, i, j, n)) seeds.push({
|
|
1513
|
+
i,
|
|
1514
|
+
j,
|
|
1515
|
+
d
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
seeds.sort((x, y) => x.d - y.d);
|
|
1519
|
+
const box1 = [Math.min(from1, to1), Math.max(from1, to1)];
|
|
1520
|
+
const box2 = [Math.min(from2, to2), Math.max(from2, to2)];
|
|
1521
|
+
for (const { i, j } of seeds.slice(0, 8)) {
|
|
1522
|
+
const seed = [from1 + (to1 - from1) * i / n, from2 + (to2 - from2) * j / n];
|
|
1523
|
+
best = Math.min(best, refineClosestPair(c1, c2, seed, box1, box2));
|
|
1524
|
+
}
|
|
1525
|
+
return Math.sqrt(best);
|
|
1526
|
+
}
|
|
726
1527
|
/**
|
|
727
1528
|
* Compute intersection points (and overlapping segments) between two 2D curves.
|
|
728
1529
|
* Handles analytic cases (line-line, line-circle, circle-circle) and falls back
|
|
@@ -1046,61 +1847,136 @@ function createBBox2d() {
|
|
|
1046
1847
|
yMax: -Infinity
|
|
1047
1848
|
};
|
|
1048
1849
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1850
|
+
/** The first parameter congruent to `t` (mod 2*pi) inside [lo, hi], if any. */
|
|
1851
|
+
function periodicParamInRange(t, lo, hi) {
|
|
1852
|
+
const period = 2 * Math.PI;
|
|
1853
|
+
const tt = t + period * Math.ceil((lo - t) / period - 1e-12);
|
|
1854
|
+
return tt <= hi + 1e-12 ? tt : null;
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Basis parameters where a conic reaches its axis-aligned extremes. For
|
|
1858
|
+
* C + a*cos(psi)*U + b*sin(psi)*V (U at angle phi), dx/dpsi = 0 at
|
|
1859
|
+
* tan(psi) = -b*sin(phi) / (a*cos(phi)) and dy/dpsi = 0 at
|
|
1860
|
+
* tan(psi) = b*cos(phi) / (a*sin(phi)), each plus pi.
|
|
1861
|
+
*/
|
|
1862
|
+
function conicExtremeParams(c) {
|
|
1863
|
+
const [a, b, phi] = c.__bk2d === "circle" ? [
|
|
1864
|
+
c.radius,
|
|
1865
|
+
c.radius,
|
|
1866
|
+
0
|
|
1867
|
+
] : [
|
|
1868
|
+
c.majorRadius,
|
|
1869
|
+
c.minorRadius,
|
|
1870
|
+
c.xDirAngle
|
|
1871
|
+
];
|
|
1872
|
+
const psiX = Math.atan2(-b * Math.sin(phi), a * Math.cos(phi));
|
|
1873
|
+
const psiY = Math.atan2(b * Math.cos(phi), a * Math.sin(phi));
|
|
1874
|
+
return [
|
|
1875
|
+
psiX,
|
|
1876
|
+
psiX + Math.PI,
|
|
1877
|
+
psiY,
|
|
1878
|
+
psiY + Math.PI
|
|
1879
|
+
].map((psi) => c.sense ? psi : -psi);
|
|
1880
|
+
}
|
|
1881
|
+
function splitBezier1d(vals) {
|
|
1882
|
+
const work = [...vals];
|
|
1883
|
+
const n = work.length;
|
|
1884
|
+
const left = [];
|
|
1885
|
+
const right = [];
|
|
1886
|
+
left.push(work[0]);
|
|
1887
|
+
right.push(work[n - 1]);
|
|
1888
|
+
for (let r = 1; r < n; r++) {
|
|
1889
|
+
for (let i = 0; i < n - r; i++) work[i] = (work[i] + work[i + 1]) / 2;
|
|
1890
|
+
left.push(work[0]);
|
|
1891
|
+
right.push(work[n - 1 - r]);
|
|
1892
|
+
}
|
|
1893
|
+
return [left, right.reverse()];
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Widen `range` to the values a 1D Bezier takes. Its coefficients bound it
|
|
1897
|
+
* (convex hull), so a piece whose coefficients stay inside the range is done;
|
|
1898
|
+
* otherwise the piece is halved, adding the curve value at the split.
|
|
1899
|
+
*/
|
|
1900
|
+
function widenToBezier(vals, range, depth = 0) {
|
|
1901
|
+
const lo = Math.min(...vals);
|
|
1902
|
+
const hi = Math.max(...vals);
|
|
1903
|
+
const tol = 1e-12 * (1 + Math.abs(range.min) + Math.abs(range.max));
|
|
1904
|
+
if (lo >= range.min - tol && hi <= range.max + tol) return;
|
|
1905
|
+
if (depth >= 60) {
|
|
1906
|
+
range.min = Math.min(range.min, lo);
|
|
1907
|
+
range.max = Math.max(range.max, hi);
|
|
1092
1908
|
return;
|
|
1093
1909
|
}
|
|
1910
|
+
const [left, right] = splitBezier1d(vals);
|
|
1911
|
+
const mid = right[0] ?? lo;
|
|
1912
|
+
range.min = Math.min(range.min, mid);
|
|
1913
|
+
range.max = Math.max(range.max, mid);
|
|
1914
|
+
widenToBezier(left, range, depth + 1);
|
|
1915
|
+
widenToBezier(right, range, depth + 1);
|
|
1916
|
+
}
|
|
1917
|
+
/** Add the exact extent of a Bezier or B-spline over [lo, hi], span by span. */
|
|
1918
|
+
function addPoleCurveExtent(bbox, c, lo, hi) {
|
|
1919
|
+
const bs = asBSpline2d(c);
|
|
1920
|
+
const first = bs.knots[0] ?? 0;
|
|
1921
|
+
const last = bs.knots[bs.knots.length - 1] ?? 1;
|
|
1922
|
+
const a = Math.max(first, lo);
|
|
1923
|
+
const b = Math.min(last, hi);
|
|
1924
|
+
if (b <= a) return;
|
|
1925
|
+
const piece = a > first || b < last ? subBSpline2d(bs, a, b) : bs;
|
|
1926
|
+
const x = {
|
|
1927
|
+
min: bbox.xMin,
|
|
1928
|
+
max: bbox.xMax
|
|
1929
|
+
};
|
|
1930
|
+
const y = {
|
|
1931
|
+
min: bbox.yMin,
|
|
1932
|
+
max: bbox.yMax
|
|
1933
|
+
};
|
|
1934
|
+
for (const seg of bezierSegments2d(piece)) for (const [range, axis] of [[x, 0], [y, 1]]) {
|
|
1935
|
+
const vals = seg.map((pt) => pt[axis]);
|
|
1936
|
+
const ends = [vals[0] ?? range.min, vals[vals.length - 1] ?? range.min];
|
|
1937
|
+
range.min = Math.min(range.min, ...ends);
|
|
1938
|
+
range.max = Math.max(range.max, ...ends);
|
|
1939
|
+
widenToBezier(vals, range);
|
|
1940
|
+
}
|
|
1941
|
+
bbox.xMin = x.min;
|
|
1942
|
+
bbox.xMax = x.max;
|
|
1943
|
+
bbox.yMin = y.min;
|
|
1944
|
+
bbox.yMax = y.max;
|
|
1945
|
+
}
|
|
1946
|
+
function addCurveToBBox(bbox, c, _tol) {
|
|
1094
1947
|
const bounds = curveBounds(c);
|
|
1095
1948
|
if (!isFinite(bounds.first) || !isFinite(bounds.last)) return;
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1949
|
+
const addPoint = ([x, y]) => {
|
|
1950
|
+
bbox.xMin = Math.min(bbox.xMin, x);
|
|
1951
|
+
bbox.yMin = Math.min(bbox.yMin, y);
|
|
1952
|
+
bbox.xMax = Math.max(bbox.xMax, x);
|
|
1953
|
+
bbox.yMax = Math.max(bbox.yMax, y);
|
|
1954
|
+
};
|
|
1955
|
+
addPoint(evaluateCurve2d$1(c, bounds.first));
|
|
1956
|
+
addPoint(evaluateCurve2d$1(c, bounds.last));
|
|
1957
|
+
const flat = flattenTrimmed(c);
|
|
1958
|
+
const basis = flat.__bk2d === "trimmed" ? flat.basis : flat;
|
|
1959
|
+
const basisBounds = curveBounds(basis);
|
|
1960
|
+
const [from, to] = flat.__bk2d === "trimmed" ? [flat.tStart, flat.tEnd] : [basisBounds.first, basisBounds.last];
|
|
1961
|
+
const lo = Math.min(from, to);
|
|
1962
|
+
const hi = Math.max(from, to);
|
|
1963
|
+
const add = (t) => {
|
|
1964
|
+
addPoint(evaluateCurve2d$1(basis, t));
|
|
1965
|
+
};
|
|
1966
|
+
switch (basis.__bk2d) {
|
|
1967
|
+
case "line": return;
|
|
1968
|
+
case "circle":
|
|
1969
|
+
case "ellipse":
|
|
1970
|
+
for (const t of conicExtremeParams(basis)) {
|
|
1971
|
+
const tt = periodicParamInRange(t, lo, hi);
|
|
1972
|
+
if (tt !== null) add(tt);
|
|
1973
|
+
}
|
|
1974
|
+
return;
|
|
1975
|
+
case "bezier":
|
|
1976
|
+
case "bspline":
|
|
1977
|
+
addPoleCurveExtent(bbox, basis, lo, hi);
|
|
1978
|
+
return;
|
|
1979
|
+
case "trimmed": return;
|
|
1104
1980
|
}
|
|
1105
1981
|
}
|
|
1106
1982
|
function evaluateBezier(poles, t) {
|
|
@@ -4255,26 +5131,13 @@ function makeBezier2d(points) {
|
|
|
4255
5131
|
return c2dWrap(makeBezier2d$1(points));
|
|
4256
5132
|
}
|
|
4257
5133
|
function makeBSpline2d(points, options) {
|
|
4258
|
-
const
|
|
4259
|
-
const
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
knots.push(i / (nInternalKnots + 1));
|
|
4266
|
-
mults.push(1);
|
|
4267
|
-
}
|
|
4268
|
-
knots.push(1);
|
|
4269
|
-
mults.push(degree + 1);
|
|
4270
|
-
return c2dWrap({
|
|
4271
|
-
__bk2d: "bspline",
|
|
4272
|
-
poles,
|
|
4273
|
-
knots,
|
|
4274
|
-
multiplicities: mults,
|
|
4275
|
-
degree,
|
|
4276
|
-
isPeriodic: false
|
|
4277
|
-
});
|
|
5134
|
+
const pts = [];
|
|
5135
|
+
for (const p of points) {
|
|
5136
|
+
const prev = pts[pts.length - 1];
|
|
5137
|
+
if (!prev || Math.hypot(p[0] - prev[0], p[1] - prev[1]) > 1e-12) pts.push([p[0], p[1]]);
|
|
5138
|
+
}
|
|
5139
|
+
if (pts.length < 2) throw new Error("occt-wasm: makeBSpline2d needs at least 2 distinct points");
|
|
5140
|
+
return c2dWrap(interpolateBSpline2d(pts, options?.degMax ?? 3));
|
|
4278
5141
|
}
|
|
4279
5142
|
function evaluateCurve2d(curve, param) {
|
|
4280
5143
|
return evaluateCurve2d$1(c2d(curve), param);
|
|
@@ -4296,26 +5159,20 @@ function getCurve2dType(curve) {
|
|
|
4296
5159
|
function trimCurve2d(curve, start, end) {
|
|
4297
5160
|
return c2dWrap({
|
|
4298
5161
|
__bk2d: "trimmed",
|
|
4299
|
-
basis: c2d(curve),
|
|
5162
|
+
basis: structuredClone(c2d(curve)),
|
|
4300
5163
|
tStart: start,
|
|
4301
5164
|
tEnd: end
|
|
4302
5165
|
});
|
|
4303
5166
|
}
|
|
5167
|
+
function reverseCurve2d(curve) {
|
|
5168
|
+
const c = c2d(curve);
|
|
5169
|
+
Object.assign(c, reverseCurve2d$1(c));
|
|
5170
|
+
}
|
|
4304
5171
|
function copyCurve2d(curve) {
|
|
4305
5172
|
return c2dWrap(JSON.parse(JSON.stringify(c2d(curve))));
|
|
4306
5173
|
}
|
|
4307
5174
|
function offsetCurve2d(curve, offset) {
|
|
4308
|
-
|
|
4309
|
-
const bounds = curveBounds(c);
|
|
4310
|
-
const pts = [];
|
|
4311
|
-
for (let i = 0; i <= 20; i++) {
|
|
4312
|
-
const t = bounds.first + (bounds.last - bounds.first) * i / 20;
|
|
4313
|
-
const [px, py] = evaluateCurve2d$1(c, t);
|
|
4314
|
-
const [tx, ty] = tangentCurve2d(c, t);
|
|
4315
|
-
const len = Math.sqrt(tx * tx + ty * ty) || 1;
|
|
4316
|
-
pts.push([px - ty / len * offset, py + tx / len * offset]);
|
|
4317
|
-
}
|
|
4318
|
-
return c2dWrap(makeBezier2d$1(pts.length <= 25 ? pts : pts.filter((_, i) => i % 2 === 0 || i === pts.length - 1)));
|
|
5175
|
+
return c2dWrap(offsetCurve2d$1(c2d(curve), offset));
|
|
4319
5176
|
}
|
|
4320
5177
|
function translateCurve2d(curve, dx, dy) {
|
|
4321
5178
|
return c2dWrap(translateCurve2d$1(c2d(curve), dx, dy));
|
|
@@ -4335,8 +5192,7 @@ function mirrorCurve2dAcrossAxis(curve, originX, originY, dirX, dirY) {
|
|
|
4335
5192
|
/**
|
|
4336
5193
|
* Directional affinity matching OCCT `gp_GTrsf2d::SetAffinity`: scales the
|
|
4337
5194
|
* component perpendicular to the axis `(dx, dy)` through `(ox, oy)` by `ratio`,
|
|
4338
|
-
* leaving the parallel component fixed.
|
|
4339
|
-
* Bezier, since occt-wasm has no native Geom2d transform.
|
|
5195
|
+
* leaving the parallel component fixed.
|
|
4340
5196
|
*/
|
|
4341
5197
|
function affinityCurve2d(curve, ox, oy, dx, dy, ratio) {
|
|
4342
5198
|
const len = Math.sqrt(dx * dx + dy * dy);
|
|
@@ -4346,15 +5202,14 @@ function affinityCurve2d(curve, ox, oy, dx, dy, ratio) {
|
|
|
4346
5202
|
const a = 1 + k * px * px, b = k * px * py, d = k * py * px, e = 1 + k * py * py;
|
|
4347
5203
|
const tx = ox - a * ox - b * oy;
|
|
4348
5204
|
const ty = oy - d * ox - e * oy;
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
}
|
|
4357
|
-
return c2dWrap(makeBezier2d$1(pts));
|
|
5205
|
+
return c2dWrap(affineCurve2d(c2d(curve), {
|
|
5206
|
+
a,
|
|
5207
|
+
b,
|
|
5208
|
+
c: d,
|
|
5209
|
+
d: e,
|
|
5210
|
+
tx,
|
|
5211
|
+
ty
|
|
5212
|
+
}));
|
|
4358
5213
|
}
|
|
4359
5214
|
function affinityTransform2d(curve, ox, oy, dx, dy, ratio) {
|
|
4360
5215
|
return affinityCurve2d(curve, ox, oy, dx, dy, ratio);
|
|
@@ -4499,103 +5354,46 @@ function projectPointOnCurve2d(curve, x, y) {
|
|
|
4499
5354
|
return projectOnBasis(c, bounds.first, bounds.last);
|
|
4500
5355
|
}
|
|
4501
5356
|
function distanceBetweenCurves2d(c1, c2, p1Start, p1End, p2Start, p2End) {
|
|
4502
|
-
|
|
4503
|
-
let minDist = Infinity;
|
|
4504
|
-
for (let i = 0; i <= n; i++) {
|
|
4505
|
-
const t1 = p1Start + (p1End - p1Start) * (i / n);
|
|
4506
|
-
const [x1, y1] = evaluateCurve2d$1(c2d(c1), t1);
|
|
4507
|
-
for (let j = 0; j <= n; j++) {
|
|
4508
|
-
const t2 = p2Start + (p2End - p2Start) * (j / n);
|
|
4509
|
-
const [x2, y2] = evaluateCurve2d$1(c2d(c2), t2);
|
|
4510
|
-
const d = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
|
4511
|
-
if (d < minDist) minDist = d;
|
|
4512
|
-
}
|
|
4513
|
-
}
|
|
4514
|
-
return minDist;
|
|
5357
|
+
return distanceBetweenCurves2d$1(c2d(c1), c2d(c2), p1Start, p1End, p2Start, p2End);
|
|
4515
5358
|
}
|
|
4516
|
-
|
|
4517
|
-
|
|
5359
|
+
/**
|
|
5360
|
+
* The exact B-spline of a Bezier or B-spline basis over its trim range, traversed
|
|
5361
|
+
* from `from` to `to`; null for an empty range.
|
|
5362
|
+
*/
|
|
5363
|
+
function trimmedPoleCurve(basis, from, to) {
|
|
5364
|
+
if (from === to) return null;
|
|
5365
|
+
const sub = subBSpline2d(asBSpline2d(basis), Math.min(from, to), Math.max(from, to));
|
|
5366
|
+
if (from < to) return sub;
|
|
5367
|
+
const reversed = reverseCurve2d$1(sub);
|
|
5368
|
+
return reversed.__bk2d === "bspline" ? reversed : null;
|
|
5369
|
+
}
|
|
5370
|
+
/**
|
|
5371
|
+
* Degree <= 3 B-spline within `tolerance` of the curve (Geom2dConvert_ApproxCurve
|
|
5372
|
+
* with MaxDegree 3, as brepjs calls it); exact where the curve already is one.
|
|
5373
|
+
*/
|
|
5374
|
+
function approximateCurve2dAsBSpline(curve, tolerance, maxSegments) {
|
|
5375
|
+
const cu = flattenTrimmed(c2d(curve));
|
|
5376
|
+
const basis = cu.__bk2d === "trimmed" ? cu.basis : cu;
|
|
5377
|
+
const poleCurve = basis.__bk2d === "bspline" || basis.__bk2d === "bezier" ? basis : null;
|
|
5378
|
+
if (poleCurve && asBSpline2d(poleCurve).degree <= 3) {
|
|
5379
|
+
if (cu.__bk2d !== "trimmed") return copyCurve2d(asBSpline2d(poleCurve));
|
|
5380
|
+
const exact = trimmedPoleCurve(poleCurve, cu.tStart, cu.tEnd);
|
|
5381
|
+
if (exact) return c2dWrap(exact);
|
|
5382
|
+
}
|
|
5383
|
+
if (cu.__bk2d === "line") return c2dWrap(asBSpline2d(makeBezier2d$1([evaluateCurve2d$1(cu, 0), evaluateCurve2d$1(cu, cu.len)])));
|
|
4518
5384
|
const bounds = curveBounds(cu);
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
poles.push(evaluateCurve2d$1(cu, t));
|
|
4524
|
-
}
|
|
4525
|
-
const degree = Math.min(3, nPts - 1);
|
|
4526
|
-
const n = poles.length;
|
|
4527
|
-
const knots = [];
|
|
4528
|
-
const mults = [];
|
|
4529
|
-
const nInternalKnots = n - degree - 1;
|
|
4530
|
-
knots.push(0);
|
|
4531
|
-
mults.push(degree + 1);
|
|
4532
|
-
for (let i = 1; i <= nInternalKnots; i++) {
|
|
4533
|
-
knots.push(i / (nInternalKnots + 1));
|
|
4534
|
-
mults.push(1);
|
|
4535
|
-
}
|
|
4536
|
-
knots.push(1);
|
|
4537
|
-
mults.push(degree + 1);
|
|
4538
|
-
return c2dWrap({
|
|
4539
|
-
__bk2d: "bspline",
|
|
4540
|
-
poles,
|
|
4541
|
-
knots,
|
|
4542
|
-
multiplicities: mults,
|
|
4543
|
-
degree,
|
|
4544
|
-
isPeriodic: false
|
|
4545
|
-
});
|
|
5385
|
+
return c2dWrap(fitBSpline2d((t) => evaluateCurve2d$1(cu, t), bounds.first, bounds.last, tolerance, {
|
|
5386
|
+
maxSegments: Math.max(8, maxSegments),
|
|
5387
|
+
breaks: curveKnotParams(cu)
|
|
5388
|
+
}));
|
|
4546
5389
|
}
|
|
4547
5390
|
function decomposeBSpline2dToBeziers(curve) {
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
if (
|
|
4551
|
-
const
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
for (let i = 0; i < cu.knots.length; i++) {
|
|
4555
|
-
const knot = cu.knots[i] ?? 0;
|
|
4556
|
-
const mult = cu.multiplicities[i] ?? 0;
|
|
4557
|
-
for (let j = 0; j < mult; j++) U.push(knot);
|
|
4558
|
-
}
|
|
4559
|
-
const m = poles.length - 1 + p + 1;
|
|
4560
|
-
if (p < 1 || U.length !== m + 1) return [c2dWrap(makeBezier2d$1(poles))];
|
|
4561
|
-
const clone = (pt) => [pt[0], pt[1]];
|
|
4562
|
-
const segments = [];
|
|
4563
|
-
let a = p;
|
|
4564
|
-
let b = p + 1;
|
|
4565
|
-
let segPoles = [];
|
|
4566
|
-
for (let i = 0; i <= p; i++) segPoles[i] = clone(poles[i]);
|
|
4567
|
-
let nextPoles = [];
|
|
4568
|
-
while (b < m) {
|
|
4569
|
-
const i0 = b;
|
|
4570
|
-
while (b < m && U[b + 1] === U[b]) b++;
|
|
4571
|
-
const mult = b - i0 + 1;
|
|
4572
|
-
if (mult < p) {
|
|
4573
|
-
const numer = U[b] - U[a];
|
|
4574
|
-
const alphas = [];
|
|
4575
|
-
for (let j = p; j > mult; j--) alphas[j - mult - 1] = numer / (U[a + j] - U[a]);
|
|
4576
|
-
const r = p - mult;
|
|
4577
|
-
for (let j = 1; j <= r; j++) {
|
|
4578
|
-
const save = r - j;
|
|
4579
|
-
const s = mult + j;
|
|
4580
|
-
for (let k = p; k >= s; k--) {
|
|
4581
|
-
const alpha = alphas[k - s];
|
|
4582
|
-
const cur = segPoles[k];
|
|
4583
|
-
const prev = segPoles[k - 1];
|
|
4584
|
-
segPoles[k] = [alpha * cur[0] + (1 - alpha) * prev[0], alpha * cur[1] + (1 - alpha) * prev[1]];
|
|
4585
|
-
}
|
|
4586
|
-
if (b < m) nextPoles[save] = clone(segPoles[p]);
|
|
4587
|
-
}
|
|
4588
|
-
}
|
|
4589
|
-
segments.push(segPoles);
|
|
4590
|
-
if (b < m) {
|
|
4591
|
-
for (let i = p - mult; i <= p; i++) nextPoles[i] = clone(poles[b - p + i]);
|
|
4592
|
-
segPoles = nextPoles;
|
|
4593
|
-
nextPoles = [];
|
|
4594
|
-
a = b;
|
|
4595
|
-
b++;
|
|
4596
|
-
}
|
|
4597
|
-
}
|
|
4598
|
-
return segments.map((sp) => c2dWrap(makeBezier2d$1(sp)));
|
|
5391
|
+
const cu = flattenTrimmed(c2d(curve));
|
|
5392
|
+
const basis = cu.__bk2d === "trimmed" ? cu.basis : cu;
|
|
5393
|
+
if (basis.__bk2d !== "bspline") return [curve];
|
|
5394
|
+
const piece = cu.__bk2d === "trimmed" ? trimmedPoleCurve(basis, cu.tStart, cu.tEnd) : basis;
|
|
5395
|
+
if (!piece) return [curve];
|
|
5396
|
+
return bezierSegments2d(piece).map((poles) => c2dWrap(makeBezier2d$1(poles)));
|
|
4599
5397
|
}
|
|
4600
5398
|
function createBoundingBox2d() {
|
|
4601
5399
|
return createBBox2d();
|
|
@@ -4678,78 +5476,174 @@ function splitCurve2d(curve, params) {
|
|
|
4678
5476
|
}
|
|
4679
5477
|
return result;
|
|
4680
5478
|
}
|
|
4681
|
-
function
|
|
4682
|
-
const
|
|
4683
|
-
const [
|
|
4684
|
-
const [
|
|
4685
|
-
const [xx, xy, xz] = planeX;
|
|
5479
|
+
function planeFrame(origin, z, x) {
|
|
5480
|
+
const [ox, oy, oz] = origin;
|
|
5481
|
+
const [zx, zy, zz] = z;
|
|
5482
|
+
const [xx, xy, xz] = x;
|
|
4686
5483
|
const yx = zy * xz - zz * xy, yy = zz * xx - zx * xz, yz = zx * xy - zy * xx;
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
5484
|
+
return {
|
|
5485
|
+
point: (u, v) => [
|
|
5486
|
+
ox + u * xx + v * yx,
|
|
5487
|
+
oy + u * xy + v * yy,
|
|
5488
|
+
oz + u * xz + v * yz
|
|
5489
|
+
],
|
|
5490
|
+
dir: (u, v) => [
|
|
5491
|
+
u * xx + v * yx,
|
|
5492
|
+
u * xy + v * yy,
|
|
5493
|
+
u * xz + v * yz
|
|
5494
|
+
],
|
|
5495
|
+
normal: z
|
|
5496
|
+
};
|
|
5497
|
+
}
|
|
5498
|
+
/** Lift an ellipse (or an arc of it over `range`) as an exact OCCT ellipse edge. */
|
|
5499
|
+
function liftEllipse(k, frame, basis, range) {
|
|
5500
|
+
let e = basis;
|
|
5501
|
+
let r = range;
|
|
5502
|
+
if (r && r[1] < r[0]) {
|
|
5503
|
+
e = {
|
|
5504
|
+
...e,
|
|
5505
|
+
sense: !e.sense
|
|
5506
|
+
};
|
|
5507
|
+
r = [-r[0], -r[1]];
|
|
5508
|
+
}
|
|
5509
|
+
let major = e.majorRadius;
|
|
5510
|
+
let minor = e.minorRadius;
|
|
5511
|
+
let angle = e.xDirAngle;
|
|
5512
|
+
let shift = 0;
|
|
5513
|
+
if (major < minor) {
|
|
5514
|
+
[major, minor] = [minor, major];
|
|
5515
|
+
angle += Math.PI / 2;
|
|
5516
|
+
shift = e.sense ? -Math.PI / 2 : Math.PI / 2;
|
|
5517
|
+
}
|
|
5518
|
+
const n = frame.normal;
|
|
5519
|
+
const normal = e.sense ? n : [
|
|
5520
|
+
-n[0],
|
|
5521
|
+
-n[1],
|
|
5522
|
+
-n[2]
|
|
4691
5523
|
];
|
|
4692
|
-
const
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
const
|
|
4707
|
-
const
|
|
4708
|
-
|
|
4709
|
-
}
|
|
4710
|
-
if (trimmed.basis && trimmed.basis.__bk2d === "circle") {
|
|
4711
|
-
const bounds = curveBounds(cu);
|
|
4712
|
-
const [u1, v1] = evaluateCurve2d$1(cu, bounds.first);
|
|
4713
|
-
const [um, vm] = evaluateCurve2d$1(cu, (bounds.first + bounds.last) / 2);
|
|
4714
|
-
const [u2, v2] = evaluateCurve2d$1(cu, bounds.last);
|
|
4715
|
-
const p1 = lift(u1, v1);
|
|
4716
|
-
const pm = lift(um, vm);
|
|
4717
|
-
const p2 = lift(u2, v2);
|
|
4718
|
-
return handle("edge", k.makeArcEdge(p1[0], p1[1], p1[2], pm[0], pm[1], pm[2], p2[0], p2[1], p2[2]));
|
|
5524
|
+
const center = frame.point(e.cx, e.cy);
|
|
5525
|
+
const xDir = frame.dir(Math.cos(angle), Math.sin(angle));
|
|
5526
|
+
if (!r || r[1] - r[0] >= 2 * Math.PI - 1e-12) return makeEllipseEdge(k, center, normal, major, minor, xDir);
|
|
5527
|
+
return makeEllipseArc(k, center, normal, major, minor, r[0] + shift, r[1] + shift, xDir);
|
|
5528
|
+
}
|
|
5529
|
+
function splitBezier(poles, t) {
|
|
5530
|
+
const work = poles.map(([x, y]) => [x, y]);
|
|
5531
|
+
const n = work.length;
|
|
5532
|
+
const left = [];
|
|
5533
|
+
const right = [];
|
|
5534
|
+
left.push([...work[0]]);
|
|
5535
|
+
right.push([...work[n - 1]]);
|
|
5536
|
+
for (let r = 1; r < n; r++) {
|
|
5537
|
+
for (let i = 0; i < n - r; i++) {
|
|
5538
|
+
const a = work[i];
|
|
5539
|
+
const b = work[i + 1];
|
|
5540
|
+
work[i] = [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
4719
5541
|
}
|
|
5542
|
+
left.push([...work[0]]);
|
|
5543
|
+
right.push([...work[n - 1 - r]]);
|
|
4720
5544
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
5545
|
+
return [left, right.reverse()];
|
|
5546
|
+
}
|
|
5547
|
+
/** Poles of the Bezier restricted to [from, to] (either order), traversed from -> to. */
|
|
5548
|
+
function bezierSegmentPoles(poles, from, to) {
|
|
5549
|
+
const lo = Math.min(from, to);
|
|
5550
|
+
const hi = Math.max(from, to);
|
|
5551
|
+
let seg = poles;
|
|
5552
|
+
if (hi < 1) seg = splitBezier(seg, hi)[0];
|
|
5553
|
+
if (lo > 0) seg = splitBezier(seg, hi > 0 ? lo / hi : 0)[1];
|
|
5554
|
+
return from <= to ? seg : [...seg].reverse();
|
|
5555
|
+
}
|
|
5556
|
+
function liftBezier(k, Module, frame, poles) {
|
|
5557
|
+
const vec = new Module.VectorDouble();
|
|
5558
|
+
try {
|
|
5559
|
+
for (const [u, v] of poles) for (const x of frame.point(u, v)) vec.push_back(x);
|
|
5560
|
+
return handle("edge", k.makeBezierEdge(vec));
|
|
5561
|
+
} finally {
|
|
5562
|
+
vec.delete();
|
|
4735
5563
|
}
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
5564
|
+
}
|
|
5565
|
+
/** Lift a non-periodic B-spline exactly (an affine lift maps poles to poles). */
|
|
5566
|
+
function liftBSpline(k, Module, frame, c) {
|
|
5567
|
+
if (!k.makeBSplineEdge || c.isPeriodic) return null;
|
|
5568
|
+
const poles = new Module.VectorDouble();
|
|
5569
|
+
const weights = new Module.VectorDouble();
|
|
5570
|
+
const knots = new Module.VectorDouble();
|
|
5571
|
+
const mults = new Module.VectorInt();
|
|
5572
|
+
try {
|
|
5573
|
+
for (const [u, v] of c.poles) for (const x of frame.point(u, v)) poles.push_back(x);
|
|
5574
|
+
for (const knot of c.knots) knots.push_back(knot);
|
|
5575
|
+
for (const m of c.multiplicities) mults.push_back(m);
|
|
5576
|
+
return handle("edge", k.makeBSplineEdge(poles, weights, knots, mults, c.degree, false));
|
|
5577
|
+
} finally {
|
|
5578
|
+
poles.delete();
|
|
5579
|
+
weights.delete();
|
|
5580
|
+
knots.delete();
|
|
5581
|
+
mults.delete();
|
|
4744
5582
|
}
|
|
5583
|
+
}
|
|
5584
|
+
function liftFitted(k, Module, frame, cu) {
|
|
5585
|
+
const bounds = curveBounds(cu);
|
|
5586
|
+
const bbox = createBBox2d();
|
|
5587
|
+
addCurveToBBox(bbox, cu, 0);
|
|
5588
|
+
const extent = Math.hypot(bbox.xMax - bbox.xMin, bbox.yMax - bbox.yMin);
|
|
5589
|
+
const knots = curveKnotParams(cu);
|
|
5590
|
+
const fit = fitBSpline2d((t) => evaluateCurve2d$1(cu, t), bounds.first, bounds.last, 1e-9 * Math.max(1, extent), { breaks: knots });
|
|
5591
|
+
const exact = liftBSpline(k, Module, frame, fit);
|
|
5592
|
+
if (exact) return exact;
|
|
5593
|
+
const nSamples = Math.max(24, fit.poles.length);
|
|
5594
|
+
const span = bounds.last - bounds.first;
|
|
5595
|
+
const params = [];
|
|
5596
|
+
for (let i = 0; i <= nSamples; i++) params.push(bounds.first + span * i / nSamples);
|
|
5597
|
+
for (const t of knots) {
|
|
5598
|
+
const nearest = bounds.first + span * Math.round((t - bounds.first) / span * nSamples) / nSamples;
|
|
5599
|
+
if (Math.abs(t - nearest) > 1e-9 * span) params.push(t);
|
|
5600
|
+
}
|
|
5601
|
+
params.sort((a, b) => a - b);
|
|
5602
|
+
const distinct = params.filter((t, i) => i === 0 || t - (params[i - 1] ?? t) > 1e-9 * span);
|
|
4745
5603
|
const vec = new Module.VectorDouble();
|
|
4746
|
-
for (const p of pts) vec.push_back(p);
|
|
4747
5604
|
try {
|
|
5605
|
+
for (const t of distinct) {
|
|
5606
|
+
const [u, v] = evaluateCurve2d$1(cu, t);
|
|
5607
|
+
for (const x of frame.point(u, v)) vec.push_back(x);
|
|
5608
|
+
}
|
|
4748
5609
|
return handle("edge", k.interpolatePoints(vec, false));
|
|
4749
5610
|
} finally {
|
|
4750
5611
|
vec.delete();
|
|
4751
5612
|
}
|
|
4752
5613
|
}
|
|
5614
|
+
function liftCurve2dToPlane(k, Module, curve, planeOrigin, planeZ, planeX) {
|
|
5615
|
+
const cu = flattenTrimmed(c2d(curve));
|
|
5616
|
+
const frame = planeFrame(planeOrigin, planeZ, planeX);
|
|
5617
|
+
const trimmed = cu.__bk2d === "trimmed" ? cu : null;
|
|
5618
|
+
const basis = trimmed ? trimmed.basis : cu;
|
|
5619
|
+
const range = trimmed ? [trimmed.tStart, trimmed.tEnd] : null;
|
|
5620
|
+
const bounds = curveBounds(cu);
|
|
5621
|
+
switch (basis.__bk2d) {
|
|
5622
|
+
case "line": {
|
|
5623
|
+
const p1 = frame.point(...evaluateCurve2d$1(cu, bounds.first));
|
|
5624
|
+
const p2 = frame.point(...evaluateCurve2d$1(cu, bounds.last));
|
|
5625
|
+
return handle("edge", k.makeLineEdge(p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]));
|
|
5626
|
+
}
|
|
5627
|
+
case "circle": {
|
|
5628
|
+
if (!range || Math.abs(range[1] - range[0]) >= 2 * Math.PI - 1e-12) {
|
|
5629
|
+
const [pcx, pcy, pcz] = frame.point(basis.cx, basis.cy);
|
|
5630
|
+
const [zx, zy, zz] = planeZ;
|
|
5631
|
+
return handle("edge", k.makeCircleEdge(pcx, pcy, pcz, zx, zy, zz, basis.radius));
|
|
5632
|
+
}
|
|
5633
|
+
const p1 = frame.point(...evaluateCurve2d$1(cu, bounds.first));
|
|
5634
|
+
const pm = frame.point(...evaluateCurve2d$1(cu, (bounds.first + bounds.last) / 2));
|
|
5635
|
+
const p2 = frame.point(...evaluateCurve2d$1(cu, bounds.last));
|
|
5636
|
+
return handle("edge", k.makeArcEdge(p1[0], p1[1], p1[2], pm[0], pm[1], pm[2], p2[0], p2[1], p2[2]));
|
|
5637
|
+
}
|
|
5638
|
+
case "ellipse": return liftEllipse(k, frame, basis, range);
|
|
5639
|
+
case "bezier": return liftBezier(k, Module, frame, range ? bezierSegmentPoles(basis.poles, range[0], range[1]) : basis.poles);
|
|
5640
|
+
case "bspline": {
|
|
5641
|
+
const piece = range ? trimmedPoleCurve(basis, range[0], range[1]) : basis;
|
|
5642
|
+
return piece && liftBSpline(k, Module, frame, piece) || liftFitted(k, Module, frame, cu);
|
|
5643
|
+
}
|
|
5644
|
+
case "trimmed": return liftFitted(k, Module, frame, cu);
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
4753
5647
|
function buildEdgeOnSurface(k, Module, curve, surface) {
|
|
4754
5648
|
const cu = c2d(curve);
|
|
4755
5649
|
const bounds = curveBounds(cu);
|
|
@@ -5831,8 +6725,8 @@ var OcctWasmAdapter = class OcctWasmAdapter {
|
|
|
5831
6725
|
makeBezier2d(points) {
|
|
5832
6726
|
return makeBezier2d(points);
|
|
5833
6727
|
}
|
|
5834
|
-
makeBSpline2d(points,
|
|
5835
|
-
return makeBSpline2d(points);
|
|
6728
|
+
makeBSpline2d(points, options) {
|
|
6729
|
+
return makeBSpline2d(points, options);
|
|
5836
6730
|
}
|
|
5837
6731
|
evaluateCurve2d(curve, param) {
|
|
5838
6732
|
return evaluateCurve2d(curve, param);
|
|
@@ -5849,7 +6743,9 @@ var OcctWasmAdapter = class OcctWasmAdapter {
|
|
|
5849
6743
|
trimCurve2d(curve, start, end) {
|
|
5850
6744
|
return trimCurve2d(curve, start, end);
|
|
5851
6745
|
}
|
|
5852
|
-
reverseCurve2d(curve) {
|
|
6746
|
+
reverseCurve2d(curve) {
|
|
6747
|
+
reverseCurve2d(curve);
|
|
6748
|
+
}
|
|
5853
6749
|
copyCurve2d(curve) {
|
|
5854
6750
|
return copyCurve2d(curve);
|
|
5855
6751
|
}
|
|
@@ -5910,8 +6806,8 @@ var OcctWasmAdapter = class OcctWasmAdapter {
|
|
|
5910
6806
|
distanceBetweenCurves2d(c1, c2, p1Start, p1End, p2Start, p2End) {
|
|
5911
6807
|
return distanceBetweenCurves2d(c1, c2, p1Start, p1End, p2Start, p2End);
|
|
5912
6808
|
}
|
|
5913
|
-
approximateCurve2dAsBSpline(curve,
|
|
5914
|
-
return approximateCurve2dAsBSpline(curve, maxSegments);
|
|
6809
|
+
approximateCurve2dAsBSpline(curve, tolerance, _continuity, maxSegments) {
|
|
6810
|
+
return approximateCurve2dAsBSpline(curve, tolerance, maxSegments);
|
|
5915
6811
|
}
|
|
5916
6812
|
decomposeBSpline2dToBeziers(curve) {
|
|
5917
6813
|
return decomposeBSpline2dToBeziers(curve);
|
|
@@ -5983,4 +6879,4 @@ var OcctWasmAdapter = class OcctWasmAdapter {
|
|
|
5983
6879
|
return getNurbsCurveData(this.k, edge);
|
|
5984
6880
|
}
|
|
5985
6881
|
};
|
|
5986
|
-
export {
|
|
6882
|
+
export { UnsupportedKernelOperationError as A, reverseCurve2d$1 as C, tangentCurve2d as D, serializeCurve2d$1 as E, DEFAULT_CAPABILITIES as M, EXACT_BREP_CAPABILITIES as N, translateCurve2d$1 as O, OCCT_CAPABILITIES as P, offsetCurve2d$1 as S, scaleCurve2d$1 as T, makeCircle2d$1 as _, buildAsciiSTL as a, mirrorAcrossAxis as b, createBBox2d as c, deserializeCurve2d$1 as d, evaluateCurve2d$1 as f, makeBezier2d$1 as g, makeArc2dThreePoints$1 as h, DEFAULT_STL_TOLERANCE as i, isUnsupportedKernelOperationError as j, quickHull as k, curveBounds as l, makeArc2dTangent$1 as m, faultyEdgeIndexesOf as n, buildBinarySTL as o, intersectCurves2dFn as p, DEFAULT_STL_ANGULAR_TOLERANCE as r, addCurveToBBox as s, OcctWasmAdapter as t, curveTypeName as u, makeEllipse2d$1 as v, rotateCurve2d$1 as w, mirrorAtPoint as x, makeLine2d$1 as y };
|