brepjs 20.12.4 → 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.
Files changed (102) hide show
  1. package/dist/2d/lib/offset.d.ts +1 -1
  2. package/dist/2d.cjs +6 -6
  3. package/dist/2d.js +6 -6
  4. package/dist/{adjacencyFns-54UTqtan.js → adjacencyFns--KmBIKYJ.js} +3 -3
  5. package/dist/{adjacencyFns-C_Vb1AeW.cjs → adjacencyFns-BFJfaUwX.cjs} +3 -3
  6. package/dist/{blueprint-DFpjcrlm.js → blueprint-BhIk9Pvm.js} +7 -8
  7. package/dist/{blueprint-D0VEeTzJ.cjs → blueprint-D83DDDPb.cjs} +7 -8
  8. package/dist/{blueprintFns-Douf5ZdX.cjs → blueprintFns-C4TQn-x_.cjs} +2 -2
  9. package/dist/{blueprintFns-Dye1jezD.js → blueprintFns-eULVgkq9.js} +2 -2
  10. package/dist/{blueprintSketcher-Cr0Lr1Dq.js → blueprintSketcher-D_Faf0GO.js} +32 -14
  11. package/dist/{blueprintSketcher-W9yON-UC.cjs → blueprintSketcher-Di376t-0.cjs} +32 -14
  12. package/dist/{boolean2D-C9VghXHG.cjs → boolean2D-CNZFI_y8.cjs} +4 -4
  13. package/dist/{boolean2D-CCV3R6lN.js → boolean2D-DqpCF72W.js} +4 -4
  14. package/dist/brepjs.cjs +128 -100
  15. package/dist/brepjs.js +128 -100
  16. package/dist/{cameraFns-DZOI_ewG.js → cameraFns-CrClIkmV.js} +3 -3
  17. package/dist/{cameraFns-x3L6dtA8.cjs → cameraFns-RqraCXIc.cjs} +3 -3
  18. package/dist/core.cjs +2 -2
  19. package/dist/core.js +2 -2
  20. package/dist/{cornerFinder-BY10jciz.js → cornerFinder-BZuQkRp2.js} +1 -1
  21. package/dist/{cornerFinder-B8PqO4um.cjs → cornerFinder-CdOe1Wrb.cjs} +1 -1
  22. package/dist/{curveFns-B4308HNM.js → curveFns-BP3srCfK.js} +1 -1
  23. package/dist/{curveFns-8lUhNquZ.cjs → curveFns-CyIvQNYT.cjs} +1 -1
  24. package/dist/{drawFns-C7zAvera.cjs → drawFns-71cOhWD-.cjs} +29 -16
  25. package/dist/{drawFns-DjnU3fEd.js → drawFns-DxDwJ5zZ.js} +29 -16
  26. package/dist/{faceFns-BjmlYcc9.cjs → faceFns-B7MiDYj1.cjs} +2 -2
  27. package/dist/{faceFns-7eHqNM0P.js → faceFns-iVPbTxki.js} +2 -2
  28. package/dist/{healingFns-BW7mZ_SW.js → healingFns-BhP8zfp1.js} +5 -5
  29. package/dist/{healingFns-CYLIcIWv.cjs → healingFns-DWljGTNR.cjs} +5 -5
  30. package/dist/{helpers-BQ6XMFcm.js → helpers-D2hANLRu.js} +14 -10
  31. package/dist/{helpers-De0Z_6iV.cjs → helpers-DKEVm6Hu.cjs} +14 -10
  32. package/dist/{importFns-BCL4-H8e.js → importFns-DUzIkns7.js} +2 -2
  33. package/dist/{importFns-3IMBHQrQ.cjs → importFns-KEPAgbjj.cjs} +2 -2
  34. package/dist/index.d.ts +2 -2
  35. package/dist/io.cjs +2 -2
  36. package/dist/io.js +2 -2
  37. package/dist/kernel/bspline2dFit.d.ts +42 -0
  38. package/dist/kernel/geometry2d.d.ts +45 -0
  39. package/dist/kernel/occt/kernel2dOps.d.ts +1 -1
  40. package/dist/kernel/occtWasm/constructionOps.d.ts +1 -1
  41. package/dist/kernel/occtWasm/kernel2dOps.d.ts +7 -4
  42. package/dist/kernel/occtWasm/occtWasmAdapter.cjs +1 -1
  43. package/dist/kernel/occtWasm/occtWasmAdapter.d.ts +3 -3
  44. package/dist/kernel/occtWasm/occtWasmAdapter.js +1 -1
  45. package/dist/kernel/occtWasm/occtWasmTypes.d.ts +7 -0
  46. package/dist/{loftFns-DlG5oR0X.cjs → loftFns-D5AvfCGG.cjs} +17 -2
  47. package/dist/{loftFns-H4ZOpRAk.js → loftFns-D5NJpHLZ.js} +17 -2
  48. package/dist/{measureFns-Ce01LVwq.cjs → measureFns-CZHNWkiA.cjs} +102 -35
  49. package/dist/{measureFns-1IfNe629.js → measureFns-mxBdPiWa.js} +102 -35
  50. package/dist/measurement/measureFns.d.ts +3 -2
  51. package/dist/measurement.cjs +2 -2
  52. package/dist/measurement.js +2 -2
  53. package/dist/{meshFns-nEvfwNRF.cjs → meshFns-Czlt-GBJ.cjs} +3 -3
  54. package/dist/{meshFns-DaRZxa7B.js → meshFns-DmjMzNbH.js} +3 -3
  55. package/dist/{occtWasmAdapter-ipKUdNLN.js → occtWasmAdapter-B9d0Q0Vl.js} +1160 -261
  56. package/dist/{occtWasmAdapter-CRd9RFEu.cjs → occtWasmAdapter-DVxLkjlJ.cjs} +1171 -260
  57. package/dist/operations/patternFns.d.ts +6 -1
  58. package/dist/operations.cjs +2 -2
  59. package/dist/operations.js +2 -2
  60. package/dist/{originTrackingFns-zrksCJTa.js → originTrackingFns-CBnyKLm7.js} +2 -2
  61. package/dist/{originTrackingFns-9gyCMFFQ.cjs → originTrackingFns-CHymOLLm.cjs} +2 -2
  62. package/dist/{primitiveFns-BRXZ7jnP.cjs → primitiveFns-BVeBEaa4.cjs} +7 -14
  63. package/dist/{primitiveFns-ClQP6zMQ.js → primitiveFns-DeNl60Rw.js} +7 -14
  64. package/dist/{principalBoxFns-DsfWOoXg.js → principalBoxFns-3mW_hA6z.js} +3 -3
  65. package/dist/{principalBoxFns-tIv-crMB.cjs → principalBoxFns-CG6kG7vT.cjs} +3 -3
  66. package/dist/projection.cjs +1 -1
  67. package/dist/projection.js +1 -1
  68. package/dist/query/finderCore.d.ts +7 -7
  69. package/dist/query.cjs +2 -2
  70. package/dist/query.js +2 -2
  71. package/dist/{refResolveFns-BZdUlv9f.cjs → refResolveFns-BAs4p9z7.cjs} +6 -6
  72. package/dist/{refResolveFns-BG3JGs--.js → refResolveFns-DHx1Y3GG.js} +6 -6
  73. package/dist/{shapeFns-BbXqiNqY.cjs → shapeFns-C0w8vvfb.cjs} +2 -2
  74. package/dist/{shapeFns-BCgegapL.js → shapeFns-CgqKICF_.js} +2 -2
  75. package/dist/shapeRef.cjs +1 -1
  76. package/dist/shapeRef.js +1 -1
  77. package/dist/{shapeTypes-DJT3SnGK.cjs → shapeTypes-Dc7j-MYg.cjs} +10 -16
  78. package/dist/{shapeTypes-DAz6aAnJ.js → shapeTypes-DwvQUp9M.js} +10 -16
  79. package/dist/sketching/sketches.d.ts +2 -0
  80. package/dist/sketching.cjs +3 -3
  81. package/dist/sketching.js +3 -3
  82. package/dist/{solidBuilders-8xytLt8-.js → solidBuilders-DT1gpVWM.js} +5 -5
  83. package/dist/{solidBuilders-BWbqhbyv.cjs → solidBuilders-iS5DDwg8.cjs} +5 -5
  84. package/dist/{surfaceBuilders-CwG_RPdZ.cjs → surfaceBuilders-BvwSgD3C.cjs} +3 -3
  85. package/dist/{surfaceBuilders-BO96DMCH.js → surfaceBuilders-CPLe0bbk.js} +3 -3
  86. package/dist/text.cjs +2 -2
  87. package/dist/text.js +2 -2
  88. package/dist/{textBlueprints-Dhe8_Uvc.js → textBlueprints-BU0Rhk54.js} +19 -11
  89. package/dist/{textBlueprints-DHfYmLtP.cjs → textBlueprints-CNPCPJix.cjs} +19 -11
  90. package/dist/{textMetrics-CwYDH9js.cjs → textMetrics-C71g4TmO.cjs} +1 -1
  91. package/dist/{textMetrics-Bbf-Dllw.js → textMetrics-Df9aLYK2.js} +1 -1
  92. package/dist/{threadFns-B23RqugN.cjs → threadFns-CzXEnnmo.cjs} +37 -29
  93. package/dist/{threadFns-Cn7_bmux.js → threadFns-DESH7lcs.js} +36 -28
  94. package/dist/topology/apiTypes.d.ts +13 -7
  95. package/dist/topology.cjs +9 -9
  96. package/dist/topology.d.ts +1 -1
  97. package/dist/topology.js +9 -9
  98. package/dist/{topologyQueryFns-xh11vEQw.cjs → topologyQueryFns-5Frx8Y-0.cjs} +1 -1
  99. package/dist/{topologyQueryFns-BfYdGOJc.js → topologyQueryFns-LXV1nTaS.js} +1 -1
  100. package/dist/{validityTypes-CXkecqMw.cjs → validityTypes-C6rROGTP.cjs} +1 -1
  101. package/dist/{validityTypes-BfOuvfyp.js → validityTypes-DM-Iwe8N.js} +1 -1
  102. package/package.json +2 -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": return {
645
- ...c,
646
- basis: scaleCurve2d$1(c.basis, factor, cx, cy)
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
- function addCurveToBBox(bbox, c, _tol) {
1050
- const basis = c.__bk2d === "trimmed" ? c.basis : c;
1051
- if (basis.__bk2d === "line") {
1052
- const bounds = curveBounds(c);
1053
- if (!isFinite(bounds.first) || !isFinite(bounds.last)) return;
1054
- const [x0, y0] = evaluateCurve2d$1(c, bounds.first);
1055
- const [x1, y1] = evaluateCurve2d$1(c, bounds.last);
1056
- bbox.xMin = Math.min(bbox.xMin, x0, x1);
1057
- bbox.yMin = Math.min(bbox.yMin, y0, y1);
1058
- bbox.xMax = Math.max(bbox.xMax, x0, x1);
1059
- bbox.yMax = Math.max(bbox.yMax, y0, y1);
1060
- return;
1061
- }
1062
- if (basis.__bk2d === "circle") {
1063
- const bounds = curveBounds(c);
1064
- if (!isFinite(bounds.first) || !isFinite(bounds.last)) return;
1065
- const { cx, cy, radius, sense } = basis;
1066
- const [x0, y0] = evaluateCurve2d$1(c, bounds.first);
1067
- const [x1, y1] = evaluateCurve2d$1(c, bounds.last);
1068
- let xMin = Math.min(x0, x1), xMax = Math.max(x0, x1);
1069
- let yMin = Math.min(y0, y1), yMax = Math.max(y0, y1);
1070
- const tStart = c.__bk2d === "trimmed" ? c.tStart : bounds.first;
1071
- const tEnd = c.__bk2d === "trimmed" ? c.tEnd : bounds.last;
1072
- for (let k = 0; k < 4; k++) {
1073
- const angle = k * Math.PI / 2;
1074
- const t = sense ? angle : -angle;
1075
- for (let wrap = 0; wrap <= 1; wrap++) {
1076
- const tt = t + wrap * 2 * Math.PI;
1077
- if (tt >= tStart - 1e-10 && tt <= tEnd + 1e-10) {
1078
- const ex = cx + radius * Math.cos(angle);
1079
- const ey = cy + radius * Math.sin(angle);
1080
- xMin = Math.min(xMin, ex);
1081
- xMax = Math.max(xMax, ex);
1082
- yMin = Math.min(yMin, ey);
1083
- yMax = Math.max(yMax, ey);
1084
- break;
1085
- }
1086
- }
1087
- }
1088
- bbox.xMin = Math.min(bbox.xMin, xMin);
1089
- bbox.yMin = Math.min(bbox.yMin, yMin);
1090
- bbox.xMax = Math.max(bbox.xMax, xMax);
1091
- bbox.yMax = Math.max(bbox.yMax, yMax);
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 nSamples = 20;
1097
- const dt = (bounds.last - bounds.first) / nSamples;
1098
- for (let i = 0; i <= nSamples; i++) {
1099
- const [x, y] = evaluateCurve2d$1(c, bounds.first + i * dt);
1100
- if (x < bbox.xMin) bbox.xMin = x;
1101
- if (y < bbox.yMin) bbox.yMin = y;
1102
- if (x > bbox.xMax) bbox.xMax = x;
1103
- if (y > bbox.yMax) bbox.yMax = y;
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) {
@@ -3894,14 +4770,17 @@ function makeTangentArc(k, startPoint, startTangent, endPoint) {
3894
4770
  const [x2, y2, z2] = endPoint;
3895
4771
  return handle("edge", k.makeTangentArc(x1, y1, z1, tx, ty, tz, x2, y2, z2));
3896
4772
  }
3897
- function makeHelixWire(k, pitch, height, radius, center, direction) {
4773
+ function makeHelixWire(k, pitch, height, radius, center, direction, leftHanded = false) {
3898
4774
  const px = center ? center[0] : 0;
3899
4775
  const py = center ? center[1] : 0;
3900
4776
  const pz = center ? center[2] : 0;
3901
4777
  const dx = direction ? direction[0] : 0;
3902
4778
  const dy = direction ? direction[1] : 0;
3903
4779
  const dz = direction ? direction[2] : 1;
3904
- const wireId = k.makeHelixWire(px, py, pz, dx, dy, dz, pitch, height, radius);
4780
+ let wireId;
4781
+ if (typeof k.makeHelixWireHanded === "function") wireId = k.makeHelixWireHanded(px, py, pz, dx, dy, dz, pitch, height, radius, leftHanded);
4782
+ else if (leftHanded) throw new Error("occt-wasm: a left-handed helix needs occt-wasm 4.3.0 or newer");
4783
+ else wireId = k.makeHelixWire(px, py, pz, dx, dy, dz, pitch, height, radius);
3905
4784
  k.buildCurves3d(wireId);
3906
4785
  return handle("wire", wireId);
3907
4786
  }
@@ -4252,26 +5131,13 @@ function makeBezier2d(points) {
4252
5131
  return c2dWrap(makeBezier2d$1(points));
4253
5132
  }
4254
5133
  function makeBSpline2d(points, options) {
4255
- const poles = points;
4256
- const n = poles.length;
4257
- const degree = Math.max(1, Math.min(options?.degMax ?? 3, n - 1));
4258
- const knots = [0];
4259
- const mults = [degree + 1];
4260
- const nInternalKnots = n - degree - 1;
4261
- for (let i = 1; i <= nInternalKnots; i++) {
4262
- knots.push(i / (nInternalKnots + 1));
4263
- mults.push(1);
4264
- }
4265
- knots.push(1);
4266
- mults.push(degree + 1);
4267
- return c2dWrap({
4268
- __bk2d: "bspline",
4269
- poles,
4270
- knots,
4271
- multiplicities: mults,
4272
- degree,
4273
- isPeriodic: false
4274
- });
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));
4275
5141
  }
4276
5142
  function evaluateCurve2d(curve, param) {
4277
5143
  return evaluateCurve2d$1(c2d(curve), param);
@@ -4293,26 +5159,20 @@ function getCurve2dType(curve) {
4293
5159
  function trimCurve2d(curve, start, end) {
4294
5160
  return c2dWrap({
4295
5161
  __bk2d: "trimmed",
4296
- basis: c2d(curve),
5162
+ basis: structuredClone(c2d(curve)),
4297
5163
  tStart: start,
4298
5164
  tEnd: end
4299
5165
  });
4300
5166
  }
5167
+ function reverseCurve2d(curve) {
5168
+ const c = c2d(curve);
5169
+ Object.assign(c, reverseCurve2d$1(c));
5170
+ }
4301
5171
  function copyCurve2d(curve) {
4302
5172
  return c2dWrap(JSON.parse(JSON.stringify(c2d(curve))));
4303
5173
  }
4304
5174
  function offsetCurve2d(curve, offset) {
4305
- const c = c2d(curve);
4306
- const bounds = curveBounds(c);
4307
- const pts = [];
4308
- for (let i = 0; i <= 20; i++) {
4309
- const t = bounds.first + (bounds.last - bounds.first) * i / 20;
4310
- const [px, py] = evaluateCurve2d$1(c, t);
4311
- const [tx, ty] = tangentCurve2d(c, t);
4312
- const len = Math.sqrt(tx * tx + ty * ty) || 1;
4313
- pts.push([px - ty / len * offset, py + tx / len * offset]);
4314
- }
4315
- 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));
4316
5176
  }
4317
5177
  function translateCurve2d(curve, dx, dy) {
4318
5178
  return c2dWrap(translateCurve2d$1(c2d(curve), dx, dy));
@@ -4332,8 +5192,7 @@ function mirrorCurve2dAcrossAxis(curve, originX, originY, dirX, dirY) {
4332
5192
  /**
4333
5193
  * Directional affinity matching OCCT `gp_GTrsf2d::SetAffinity`: scales the
4334
5194
  * component perpendicular to the axis `(dx, dy)` through `(ox, oy)` by `ratio`,
4335
- * leaving the parallel component fixed. Applied by sampling and refitting as a
4336
- * Bezier, since occt-wasm has no native Geom2d transform.
5195
+ * leaving the parallel component fixed.
4337
5196
  */
4338
5197
  function affinityCurve2d(curve, ox, oy, dx, dy, ratio) {
4339
5198
  const len = Math.sqrt(dx * dx + dy * dy);
@@ -4343,15 +5202,14 @@ function affinityCurve2d(curve, ox, oy, dx, dy, ratio) {
4343
5202
  const a = 1 + k * px * px, b = k * px * py, d = k * py * px, e = 1 + k * py * py;
4344
5203
  const tx = ox - a * ox - b * oy;
4345
5204
  const ty = oy - d * ox - e * oy;
4346
- const c = c2d(curve);
4347
- const bounds = curveBounds(c);
4348
- const N = 20;
4349
- const pts = [];
4350
- for (let i = 0; i <= N; i++) {
4351
- const [qx, qy] = evaluateCurve2d$1(c, bounds.first + (bounds.last - bounds.first) * i / N);
4352
- pts.push([a * qx + b * qy + tx, d * qx + e * qy + ty]);
4353
- }
4354
- 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
+ }));
4355
5213
  }
4356
5214
  function affinityTransform2d(curve, ox, oy, dx, dy, ratio) {
4357
5215
  return affinityCurve2d(curve, ox, oy, dx, dy, ratio);
@@ -4496,103 +5354,46 @@ function projectPointOnCurve2d(curve, x, y) {
4496
5354
  return projectOnBasis(c, bounds.first, bounds.last);
4497
5355
  }
4498
5356
  function distanceBetweenCurves2d(c1, c2, p1Start, p1End, p2Start, p2End) {
4499
- const n = 20;
4500
- let minDist = Infinity;
4501
- for (let i = 0; i <= n; i++) {
4502
- const t1 = p1Start + (p1End - p1Start) * (i / n);
4503
- const [x1, y1] = evaluateCurve2d$1(c2d(c1), t1);
4504
- for (let j = 0; j <= n; j++) {
4505
- const t2 = p2Start + (p2End - p2Start) * (j / n);
4506
- const [x2, y2] = evaluateCurve2d$1(c2d(c2), t2);
4507
- const d = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
4508
- if (d < minDist) minDist = d;
4509
- }
4510
- }
4511
- return minDist;
5357
+ return distanceBetweenCurves2d$1(c2d(c1), c2d(c2), p1Start, p1End, p2Start, p2End);
4512
5358
  }
4513
- function approximateCurve2dAsBSpline(curve, maxSegments) {
4514
- const cu = c2d(curve);
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)])));
4515
5384
  const bounds = curveBounds(cu);
4516
- const nPts = Math.min(Math.max(maxSegments + 1, 10), 100);
4517
- const poles = [];
4518
- for (let i = 0; i < nPts; i++) {
4519
- const t = bounds.first + (bounds.last - bounds.first) * i / (nPts - 1);
4520
- poles.push(evaluateCurve2d$1(cu, t));
4521
- }
4522
- const degree = Math.min(3, nPts - 1);
4523
- const n = poles.length;
4524
- const knots = [];
4525
- const mults = [];
4526
- const nInternalKnots = n - degree - 1;
4527
- knots.push(0);
4528
- mults.push(degree + 1);
4529
- for (let i = 1; i <= nInternalKnots; i++) {
4530
- knots.push(i / (nInternalKnots + 1));
4531
- mults.push(1);
4532
- }
4533
- knots.push(1);
4534
- mults.push(degree + 1);
4535
- return c2dWrap({
4536
- __bk2d: "bspline",
4537
- poles,
4538
- knots,
4539
- multiplicities: mults,
4540
- degree,
4541
- isPeriodic: false
4542
- });
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
+ }));
4543
5389
  }
4544
5390
  function decomposeBSpline2dToBeziers(curve) {
4545
- let cu = c2d(curve);
4546
- while (cu.__bk2d === "trimmed") cu = cu.basis;
4547
- if (cu.__bk2d !== "bspline") return [curve];
4548
- const poles = cu.poles;
4549
- const p = cu.degree;
4550
- const U = [];
4551
- for (let i = 0; i < cu.knots.length; i++) {
4552
- const knot = cu.knots[i] ?? 0;
4553
- const mult = cu.multiplicities[i] ?? 0;
4554
- for (let j = 0; j < mult; j++) U.push(knot);
4555
- }
4556
- const m = poles.length - 1 + p + 1;
4557
- if (p < 1 || U.length !== m + 1) return [c2dWrap(makeBezier2d$1(poles))];
4558
- const clone = (pt) => [pt[0], pt[1]];
4559
- const segments = [];
4560
- let a = p;
4561
- let b = p + 1;
4562
- let segPoles = [];
4563
- for (let i = 0; i <= p; i++) segPoles[i] = clone(poles[i]);
4564
- let nextPoles = [];
4565
- while (b < m) {
4566
- const i0 = b;
4567
- while (b < m && U[b + 1] === U[b]) b++;
4568
- const mult = b - i0 + 1;
4569
- if (mult < p) {
4570
- const numer = U[b] - U[a];
4571
- const alphas = [];
4572
- for (let j = p; j > mult; j--) alphas[j - mult - 1] = numer / (U[a + j] - U[a]);
4573
- const r = p - mult;
4574
- for (let j = 1; j <= r; j++) {
4575
- const save = r - j;
4576
- const s = mult + j;
4577
- for (let k = p; k >= s; k--) {
4578
- const alpha = alphas[k - s];
4579
- const cur = segPoles[k];
4580
- const prev = segPoles[k - 1];
4581
- segPoles[k] = [alpha * cur[0] + (1 - alpha) * prev[0], alpha * cur[1] + (1 - alpha) * prev[1]];
4582
- }
4583
- if (b < m) nextPoles[save] = clone(segPoles[p]);
4584
- }
4585
- }
4586
- segments.push(segPoles);
4587
- if (b < m) {
4588
- for (let i = p - mult; i <= p; i++) nextPoles[i] = clone(poles[b - p + i]);
4589
- segPoles = nextPoles;
4590
- nextPoles = [];
4591
- a = b;
4592
- b++;
4593
- }
4594
- }
4595
- 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)));
4596
5397
  }
4597
5398
  function createBoundingBox2d() {
4598
5399
  return createBBox2d();
@@ -4675,78 +5476,174 @@ function splitCurve2d(curve, params) {
4675
5476
  }
4676
5477
  return result;
4677
5478
  }
4678
- function liftCurve2dToPlane(k, Module, curve, planeOrigin, planeZ, planeX) {
4679
- const cu = c2d(curve);
4680
- const [ox, oy, oz] = planeOrigin;
4681
- const [zx, zy, zz] = planeZ;
4682
- 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;
4683
5483
  const yx = zy * xz - zz * xy, yy = zz * xx - zx * xz, yz = zx * xy - zy * xx;
4684
- const lift = (u, v) => [
4685
- ox + u * xx + v * yx,
4686
- oy + u * xy + v * yy,
4687
- oz + u * xz + v * yz
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]
4688
5523
  ];
4689
- const bk2dType = cu.__bk2d;
4690
- if (bk2dType === "line") {
4691
- const bounds = curveBounds(cu);
4692
- const [u1, v1] = evaluateCurve2d$1(cu, bounds.first);
4693
- const [u2, v2] = evaluateCurve2d$1(cu, bounds.last);
4694
- const p1 = lift(u1, v1);
4695
- const p2 = lift(u2, v2);
4696
- return handle("edge", k.makeLineEdge(p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]));
4697
- }
4698
- if (bk2dType === "trimmed") {
4699
- const trimmed = cu;
4700
- if (trimmed.basis && trimmed.basis.__bk2d === "line") {
4701
- const [u1, v1] = evaluateCurve2d$1(cu, 0);
4702
- const [u2, v2] = evaluateCurve2d$1(cu, 1);
4703
- const p1 = lift(u1, v1);
4704
- const p2 = lift(u2, v2);
4705
- return handle("edge", k.makeLineEdge(p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]));
4706
- }
4707
- if (trimmed.basis && trimmed.basis.__bk2d === "circle") {
4708
- const bounds = curveBounds(cu);
4709
- const [u1, v1] = evaluateCurve2d$1(cu, bounds.first);
4710
- const [um, vm] = evaluateCurve2d$1(cu, (bounds.first + bounds.last) / 2);
4711
- const [u2, v2] = evaluateCurve2d$1(cu, bounds.last);
4712
- const p1 = lift(u1, v1);
4713
- const pm = lift(um, vm);
4714
- const p2 = lift(u2, v2);
4715
- 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];
4716
5541
  }
5542
+ left.push([...work[0]]);
5543
+ right.push([...work[n - 1 - r]]);
4717
5544
  }
4718
- if (bk2dType === "circle") {
4719
- const circleData = cu;
4720
- if (circleData.cx !== void 0 && circleData.radius !== void 0) {
4721
- const [pcx, pcy, pcz] = lift(circleData.cx, circleData.cy);
4722
- return handle("edge", k.makeCircleEdge(pcx, pcy, pcz, zx, zy, zz, circleData.radius));
4723
- }
4724
- const bounds = curveBounds(cu);
4725
- const [u1, v1] = evaluateCurve2d$1(cu, bounds.first);
4726
- const [um, vm] = evaluateCurve2d$1(cu, (bounds.first + bounds.last) / 2);
4727
- const [u2, v2] = evaluateCurve2d$1(cu, bounds.last);
4728
- const p1 = lift(u1, v1);
4729
- const pm = lift(um, vm);
4730
- const p2 = lift(u2, v2);
4731
- return handle("edge", k.makeArcEdge(p1[0], p1[1], p1[2], pm[0], pm[1], pm[2], p2[0], p2[1], p2[2]));
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();
4732
5563
  }
4733
- const bounds = curveBounds(cu);
4734
- const nSamples = 24;
4735
- const dt = (bounds.last - bounds.first) / nSamples;
4736
- const pts = [];
4737
- for (let i = 0; i <= nSamples; i++) {
4738
- const [u, v] = evaluateCurve2d$1(cu, bounds.first + i * dt);
4739
- const [px, py, pz] = lift(u, v);
4740
- pts.push(px, py, pz);
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();
4741
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);
4742
5603
  const vec = new Module.VectorDouble();
4743
- for (const p of pts) vec.push_back(p);
4744
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
+ }
4745
5609
  return handle("edge", k.interpolatePoints(vec, false));
4746
5610
  } finally {
4747
5611
  vec.delete();
4748
5612
  }
4749
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
+ }
4750
5647
  function buildEdgeOnSurface(k, Module, curve, surface) {
4751
5648
  const cu = c2d(curve);
4752
5649
  const bounds = curveBounds(cu);
@@ -5288,8 +6185,8 @@ var OcctWasmAdapter = class OcctWasmAdapter {
5288
6185
  makeTangentArc(startPoint, startTangent, endPoint) {
5289
6186
  return makeTangentArc(this.k, startPoint, startTangent, endPoint);
5290
6187
  }
5291
- makeHelixWire(pitch, height, radius, center, direction, _leftHanded) {
5292
- return makeHelixWire(this.k, pitch, height, radius, center, direction);
6188
+ makeHelixWire(pitch, height, radius, center, direction, leftHanded) {
6189
+ return makeHelixWire(this.k, pitch, height, radius, center, direction, leftHanded);
5293
6190
  }
5294
6191
  makeWireFromMixed(items) {
5295
6192
  return makeWireFromMixed(this.k, this.Module, items);
@@ -5828,8 +6725,8 @@ var OcctWasmAdapter = class OcctWasmAdapter {
5828
6725
  makeBezier2d(points) {
5829
6726
  return makeBezier2d(points);
5830
6727
  }
5831
- makeBSpline2d(points, _options) {
5832
- return makeBSpline2d(points);
6728
+ makeBSpline2d(points, options) {
6729
+ return makeBSpline2d(points, options);
5833
6730
  }
5834
6731
  evaluateCurve2d(curve, param) {
5835
6732
  return evaluateCurve2d(curve, param);
@@ -5846,7 +6743,9 @@ var OcctWasmAdapter = class OcctWasmAdapter {
5846
6743
  trimCurve2d(curve, start, end) {
5847
6744
  return trimCurve2d(curve, start, end);
5848
6745
  }
5849
- reverseCurve2d(curve) {}
6746
+ reverseCurve2d(curve) {
6747
+ reverseCurve2d(curve);
6748
+ }
5850
6749
  copyCurve2d(curve) {
5851
6750
  return copyCurve2d(curve);
5852
6751
  }
@@ -5907,8 +6806,8 @@ var OcctWasmAdapter = class OcctWasmAdapter {
5907
6806
  distanceBetweenCurves2d(c1, c2, p1Start, p1End, p2Start, p2End) {
5908
6807
  return distanceBetweenCurves2d(c1, c2, p1Start, p1End, p2Start, p2End);
5909
6808
  }
5910
- approximateCurve2dAsBSpline(curve, _tolerance, _continuity, maxSegments) {
5911
- return approximateCurve2dAsBSpline(curve, maxSegments);
6809
+ approximateCurve2dAsBSpline(curve, tolerance, _continuity, maxSegments) {
6810
+ return approximateCurve2dAsBSpline(curve, tolerance, maxSegments);
5912
6811
  }
5913
6812
  decomposeBSpline2dToBeziers(curve) {
5914
6813
  return decomposeBSpline2dToBeziers(curve);
@@ -5980,4 +6879,4 @@ var OcctWasmAdapter = class OcctWasmAdapter {
5980
6879
  return getNurbsCurveData(this.k, edge);
5981
6880
  }
5982
6881
  };
5983
- export { DEFAULT_CAPABILITIES as A, scaleCurve2d$1 as C, quickHull as D, translateCurve2d$1 as E, OCCT_CAPABILITIES as M, UnsupportedKernelOperationError as O, rotateCurve2d$1 as S, tangentCurve2d 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, EXACT_BREP_CAPABILITIES as j, isUnsupportedKernelOperationError 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, serializeCurve2d$1 as w, mirrorAtPoint as x, makeLine2d$1 as y };
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 };