brepjs 8.0.0 → 8.0.2

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 (124) hide show
  1. package/dist/2d/blueprints/booleanHelpers.d.ts +32 -0
  2. package/dist/2d/blueprints/booleanHelpers.d.ts.map +1 -0
  3. package/dist/2d/blueprints/booleanOperations.d.ts +5 -3
  4. package/dist/2d/blueprints/booleanOperations.d.ts.map +1 -1
  5. package/dist/2d/blueprints/intersectionSegments.d.ts +12 -0
  6. package/dist/2d/blueprints/intersectionSegments.d.ts.map +1 -0
  7. package/dist/2d/blueprints/segmentAssembly.d.ts +31 -0
  8. package/dist/2d/blueprints/segmentAssembly.d.ts.map +1 -0
  9. package/dist/2d.cjs +2 -2
  10. package/dist/2d.js +8 -8
  11. package/dist/{Blueprint-D3JfGJTz.js → Blueprint-B9fhnpFp.js} +117 -30
  12. package/dist/{Blueprint-CVctc41Z.cjs → Blueprint-VGbo3izk.cjs} +111 -24
  13. package/dist/{boolean2D-BdZATaHs.cjs → boolean2D-B1XrGVgx.cjs} +426 -345
  14. package/dist/{boolean2D-hOw5Qay5.js → boolean2D-_WiqPxWZ.js} +391 -310
  15. package/dist/{booleanFns-BBSVKhL2.cjs → booleanFns-BxW-N3rP.cjs} +12 -16
  16. package/dist/{booleanFns-CqehfzcK.js → booleanFns-CkccZ7UL.js} +14 -18
  17. package/dist/brepjs.cjs +133 -62
  18. package/dist/brepjs.js +290 -217
  19. package/dist/{cast-DQaUibmm.js → cast-C4Ff_1Qe.js} +2 -2
  20. package/dist/{cast-DkB0GKmQ.cjs → cast-DIiyxDLo.cjs} +2 -2
  21. package/dist/core/disposal.d.ts +1 -1
  22. package/dist/core/disposal.d.ts.map +1 -1
  23. package/dist/core.cjs +3 -3
  24. package/dist/core.js +3 -3
  25. package/dist/cornerFinder-BndBNtJE.cjs +58 -0
  26. package/dist/cornerFinder-DzGzfiqb.js +59 -0
  27. package/dist/curveBuilders-BUoFO1UG.cjs +196 -0
  28. package/dist/curveBuilders-CBlIWlbU.js +197 -0
  29. package/dist/{curveFns-BilyYL_s.cjs → curveFns-BrJDkaWi.cjs} +31 -44
  30. package/dist/{curveFns-CdVE4da7.js → curveFns-BshHA9Ys.js} +31 -44
  31. package/dist/{drawFns-921SkhDL.js → drawFns-Btmlh_Oz.js} +13 -14
  32. package/dist/{drawFns-CUyx50gi.cjs → drawFns-D2eDcf4k.cjs} +58 -59
  33. package/dist/{faceFns-DHu-2JpA.js → faceFns-DDzCECn3.js} +3 -3
  34. package/dist/{faceFns-BwK7FP7N.cjs → faceFns-NDRFeekj.cjs} +3 -3
  35. package/dist/helpers-Ck8GJ58k.cjs +203 -0
  36. package/dist/helpers-jku2V1DY.js +204 -0
  37. package/dist/io.cjs +4 -4
  38. package/dist/io.js +4 -4
  39. package/dist/kernel/occtAdapter.d.ts +1 -0
  40. package/dist/kernel/occtAdapter.d.ts.map +1 -1
  41. package/dist/kernel/sweepOps.d.ts +8 -0
  42. package/dist/kernel/sweepOps.d.ts.map +1 -1
  43. package/dist/kernel/types.d.ts +1 -0
  44. package/dist/kernel/types.d.ts.map +1 -1
  45. package/dist/loft-Bk9EM0gZ.js +373 -0
  46. package/dist/loft-DJXwxV_L.cjs +372 -0
  47. package/dist/{measurement-C5JGCuUP.js → measurement-DlXaTzKc.js} +3 -3
  48. package/dist/{measurement-fxm_pW7x.cjs → measurement-LcGh4wV0.cjs} +3 -3
  49. package/dist/measurement.cjs +1 -1
  50. package/dist/measurement.js +1 -1
  51. package/dist/{meshFns-AqAjTTVl.js → meshFns-Djzdn-CS.js} +1 -1
  52. package/dist/{meshFns-BhrZGi6w.cjs → meshFns-c8lDKfYy.cjs} +1 -1
  53. package/dist/{occtBoundary-du8_ex-p.cjs → occtBoundary-6kQSl3cF.cjs} +21 -0
  54. package/dist/{occtBoundary-CwegMzqc.js → occtBoundary-CqXvDhZY.js} +26 -5
  55. package/dist/operations/extrude.d.ts.map +1 -1
  56. package/dist/operations/extrudeFns.d.ts.map +1 -1
  57. package/dist/operations/extrudeUtils.d.ts +17 -0
  58. package/dist/operations/extrudeUtils.d.ts.map +1 -1
  59. package/dist/{operations-C1rWoba2.js → operations-CrQlFDHc.js} +30 -7
  60. package/dist/{operations-BP1wVDw0.cjs → operations-Do-WZGXc.cjs} +30 -7
  61. package/dist/operations.cjs +2 -2
  62. package/dist/operations.js +4 -4
  63. package/dist/query/cornerFinder.d.ts +48 -0
  64. package/dist/query/cornerFinder.d.ts.map +1 -0
  65. package/dist/query/directionUtils.d.ts +6 -0
  66. package/dist/query/directionUtils.d.ts.map +1 -0
  67. package/dist/query/edgeFinder.d.ts +15 -0
  68. package/dist/query/edgeFinder.d.ts.map +1 -0
  69. package/dist/query/faceFinder.d.ts +15 -0
  70. package/dist/query/faceFinder.d.ts.map +1 -0
  71. package/dist/query/finderCore.d.ts +35 -0
  72. package/dist/query/finderCore.d.ts.map +1 -0
  73. package/dist/query/finderFns.d.ts +21 -106
  74. package/dist/query/finderFns.d.ts.map +1 -1
  75. package/dist/query/shapeDistanceFilter.d.ts +11 -0
  76. package/dist/query/shapeDistanceFilter.d.ts.map +1 -0
  77. package/dist/query/vertexFinder.d.ts +16 -0
  78. package/dist/query/vertexFinder.d.ts.map +1 -0
  79. package/dist/query/wireFinder.d.ts +10 -0
  80. package/dist/query/wireFinder.d.ts.map +1 -0
  81. package/dist/query.cjs +42 -5
  82. package/dist/query.js +40 -2
  83. package/dist/{shapeFns-BrF97sKt.js → shapeFns-DQtpzndX.js} +17 -18
  84. package/dist/{shapeFns-BvOndshS.cjs → shapeFns-cN4qGpbO.cjs} +6 -7
  85. package/dist/{shapeTypes-DKhwEnUM.cjs → shapeTypes-BJ3Hmskg.cjs} +24 -20
  86. package/dist/{shapeTypes-BlSElW8z.js → shapeTypes-C9sUsmEW.js} +32 -28
  87. package/dist/sketching/Sketcher.d.ts.map +1 -1
  88. package/dist/sketching/Sketcher2d.d.ts +12 -4
  89. package/dist/sketching/Sketcher2d.d.ts.map +1 -1
  90. package/dist/sketching/ellipseUtils.d.ts +29 -0
  91. package/dist/sketching/ellipseUtils.d.ts.map +1 -0
  92. package/dist/sketching.cjs +2 -2
  93. package/dist/sketching.js +2 -2
  94. package/dist/topology/booleanFns.d.ts.map +1 -1
  95. package/dist/topology/curveBuilders.d.ts +75 -0
  96. package/dist/topology/curveBuilders.d.ts.map +1 -0
  97. package/dist/topology/curveFns.d.ts.map +1 -1
  98. package/dist/topology/primitiveFns.d.ts.map +1 -1
  99. package/dist/topology/shapeFns.d.ts.map +1 -1
  100. package/dist/topology/shapeHelpers.d.ts +6 -173
  101. package/dist/topology/shapeHelpers.d.ts.map +1 -1
  102. package/dist/topology/shapeUtils.d.ts +13 -0
  103. package/dist/topology/shapeUtils.d.ts.map +1 -0
  104. package/dist/topology/solidBuilders.d.ts +70 -0
  105. package/dist/topology/solidBuilders.d.ts.map +1 -0
  106. package/dist/topology/surfaceBuilders.d.ts +35 -0
  107. package/dist/topology/surfaceBuilders.d.ts.map +1 -0
  108. package/dist/topology/wrapperFns.d.ts +1 -0
  109. package/dist/topology/wrapperFns.d.ts.map +1 -1
  110. package/dist/{topology-tFzqSrGH.js → topology-CtfUZwLR.js} +8 -8
  111. package/dist/{topology-CIooytHH.cjs → topology-DXq8dLsi.cjs} +8 -8
  112. package/dist/topology.cjs +7 -7
  113. package/dist/topology.js +31 -31
  114. package/dist/{vectors-CBuaMeZv.js → vectors-BVgXsYWl.js} +1 -1
  115. package/dist/{vectors-ChWEZPwy.cjs → vectors-DK2hEKcI.cjs} +1 -1
  116. package/dist/vectors.cjs +2 -2
  117. package/dist/vectors.js +2 -2
  118. package/package.json +1 -1
  119. package/dist/loft-BzWFokmC.cjs +0 -178
  120. package/dist/loft-CtG5nMq5.js +0 -179
  121. package/dist/query-V6nV-VfL.js +0 -396
  122. package/dist/query-hMSmOWJP.cjs +0 -395
  123. package/dist/shapeHelpers-B2SXz1p4.cjs +0 -488
  124. package/dist/shapeHelpers-BcoZf2N9.js +0 -489
@@ -1,16 +1,16 @@
1
- import { a as createPlane } from "./vectors-CBuaMeZv.js";
2
- import { o as makeFace, u as makeNewFaceWithinFace, a as assembleWire, z as zip } from "./shapeHelpers-BcoZf2N9.js";
1
+ import { a as createPlane } from "./vectors-BVgXsYWl.js";
3
2
  import { l as ok, e as err, b as computationError, u as unwrap, g as isOk } from "./errors-wGhcJMpB.js";
4
- import { d as downcast } from "./cast-DQaUibmm.js";
5
- import { g as getKernel, a as toVec3 } from "./occtBoundary-CwegMzqc.js";
3
+ import { d as downcast } from "./cast-C4Ff_1Qe.js";
4
+ import { g as getKernel, a as toVec3 } from "./occtBoundary-CqXvDhZY.js";
6
5
  import { n as vecScale, j as vecNormalize, b as vecCross, D as DEG2RAD, R as RAD2DEG } from "./vecOps-ZDdZWbwT.js";
7
- import { r as revolution, c as complexExtrude, t as twistExtrude, b as basicFaceExtrusion, g as genericSweep, l as loft } from "./loft-CtG5nMq5.js";
8
- import { n as gcWithScope, q as createFace, s as createWire, o as localGC, t as createEdge } from "./shapeTypes-BlSElW8z.js";
9
- import { e as curveStartPoint, a as curveTangentAt, c as curveIsClosed } from "./curveFns-CdVE4da7.js";
10
- import { u as uvBounds, p as pointOnSurface, n as normalAt } from "./faceFns-DHu-2JpA.js";
6
+ import { r as revolution, k as complexExtrude, t as twistExtrude, i as basicFaceExtrusion, j as genericSweep, l as loft } from "./loft-Bk9EM0gZ.js";
7
+ import { p as gcWithScope, u as createFace, n as createWire, t as localGC, o as createEdge } from "./shapeTypes-C9sUsmEW.js";
8
+ import { e as curveStartPoint, a as curveTangentAt, c as curveIsClosed } from "./curveFns-BshHA9Ys.js";
9
+ import { C as Curve2D, i as make2dSegmentCurve, r as approximateAsBSpline, j as make2dArcFromCenter, s as isPoint2D, o as make2dCircle, t as make2dThreePointArc, e as BoundingBox2d, v as viewbox, u as asSVG, B as Blueprint, m as makeFace, d as makeNewFaceWithinFace, w as make2dEllipseArc, x as make2dTangentArc, y as make2dBezierCurve, f as axis2d, z as zip, A as removeDuplicatePoints } from "./Blueprint-B9fhnpFp.js";
10
+ import { u as uvBounds, p as pointOnSurface, n as normalAt } from "./faceFns-DDzCECn3.js";
11
11
  import { bug } from "./result.js";
12
- import { s as samePoint$1, n as normalize2d, a as subtract2d, b as add2d, d as crossProduct2d, h as scalarMultiply2d, p as polarToCartesian, i as distance2d, r as rotate2d, j as cartesianToPolar, k as polarAngle2d, P as PRECISION_INTERSECTION } from "./query-V6nV-VfL.js";
13
- import { C as Curve2D, c as make2dSegmentCurve, d as approximateAsBSpline, i as isPoint2D, e as make2dCircle, f as make2dThreePointArc, g as make2dArcFromCenter, a as BoundingBox2d, v as viewbox, h as asSVG, B as Blueprint, j as make2dTangentArc, k as make2dEllipseArc, l as make2dBezierCurve, b as axis2d, r as removeDuplicatePoints } from "./Blueprint-D3JfGJTz.js";
12
+ import { s as samePoint$1, n as normalize2d, h as subtract2d, i as add2d, j as crossProduct2d, k as scalarMultiply2d, b as polarToCartesian, r as rotate2d, l as cartesianToPolar, d as distance2d, p as polarAngle2d, m as PRECISION_INTERSECTION } from "./helpers-jku2V1DY.js";
13
+ import { a as assembleWire } from "./curveBuilders-CBlIWlbU.js";
14
14
  function* pointsIteration(intersector) {
15
15
  const nPoints = intersector.NbPoints();
16
16
  if (!nPoints) return;
@@ -1220,9 +1220,57 @@ function convertSvgEllipseParams([x1, y1], [x2, y2], rx, ry, phi, fA, fS) {
1220
1220
  };
1221
1221
  return outputObj;
1222
1222
  }
1223
+ function normalizeEllipseRadii(horizontalRadius, verticalRadius, rotation) {
1224
+ if (horizontalRadius < verticalRadius) {
1225
+ return {
1226
+ majorRadius: verticalRadius,
1227
+ minorRadius: horizontalRadius,
1228
+ rotationAngle: rotation + 90
1229
+ };
1230
+ }
1231
+ return {
1232
+ majorRadius: horizontalRadius,
1233
+ minorRadius: verticalRadius,
1234
+ rotationAngle: rotation
1235
+ };
1236
+ }
1237
+ function makeEllipseArcFromSvgParams(startUV, endUV, majorRadius, minorRadius, rotationAngleDeg, longAxis, sweep, convertToUV) {
1238
+ const radRotationAngle = rotationAngleDeg * DEG2RAD;
1239
+ const convertAxis = (ax) => distance2d(convertToUV(ax));
1240
+ const r1 = convertAxis(polarToCartesian(majorRadius, radRotationAngle));
1241
+ const r2 = convertAxis(polarToCartesian(minorRadius, radRotationAngle + Math.PI / 2));
1242
+ const xDir = normalize2d(convertToUV(rotate2d([1, 0], radRotationAngle)));
1243
+ const [, newRotationAngle] = cartesianToPolar(xDir);
1244
+ const { cx, cy, startAngle, endAngle, clockwise, rx, ry } = convertSvgEllipseParams(
1245
+ startUV,
1246
+ endUV,
1247
+ r1,
1248
+ r2,
1249
+ newRotationAngle,
1250
+ longAxis,
1251
+ sweep
1252
+ );
1253
+ const arc = make2dEllipseArc(
1254
+ rx,
1255
+ ry,
1256
+ clockwise ? startAngle : endAngle,
1257
+ clockwise ? endAngle : startAngle,
1258
+ [cx, cy],
1259
+ xDir
1260
+ );
1261
+ if (!clockwise) {
1262
+ arc.reverse();
1263
+ }
1264
+ return arc;
1265
+ }
1266
+ const cornerModeFns = {
1267
+ chamfer: chamferCurves,
1268
+ dogbone: dogboneFilletCurves,
1269
+ fillet: filletCurves
1270
+ };
1223
1271
  function buildCornerFunction(radius, mode) {
1224
1272
  if (typeof radius === "function") return radius;
1225
- const makeFn = mode === "chamfer" ? chamferCurves : mode === "dogbone" ? dogboneFilletCurves : filletCurves;
1273
+ const makeFn = cornerModeFns[mode];
1226
1274
  return (first, second) => makeFn(first, second, radius);
1227
1275
  }
1228
1276
  class BaseSketcher2d {
@@ -1236,12 +1284,37 @@ class BaseSketcher2d {
1236
1284
  this._nextCorner = null;
1237
1285
  this.pendingCurves = [];
1238
1286
  }
1287
+ // ── Coordinate conversion (overridden by FaceSketcher for UV mapping) ──
1239
1288
  _convertToUV([x, y]) {
1240
1289
  return [x, y];
1241
1290
  }
1242
1291
  _convertFromUV([u, v]) {
1243
1292
  return [u, v];
1244
1293
  }
1294
+ // ── Internal helpers ──
1295
+ /** Return the last curve in the pending list, or null if empty. */
1296
+ _lastCurve() {
1297
+ const len = this.pendingCurves.length;
1298
+ if (len === 0) return null;
1299
+ return this.pendingCurves[len - 1];
1300
+ }
1301
+ /** Require that a previous curve exists, returning it or throwing. */
1302
+ _requireLastCurve(caller, action) {
1303
+ const curve = this._lastCurve();
1304
+ if (!curve) bug(caller, `You need a previous curve to ${action}`);
1305
+ return curve;
1306
+ }
1307
+ /** Resolve a relative offset from the current pointer position. */
1308
+ _resolveRelative(xDist, yDist) {
1309
+ return [this.pointer[0] + xDist, this.pointer[1] + yDist];
1310
+ }
1311
+ /** Save a curve, advance the pointer to the given end point, and return `this`. */
1312
+ _saveCurveAndAdvance(curve, end) {
1313
+ this.saveCurve(curve);
1314
+ this.pointer = end;
1315
+ return this;
1316
+ }
1317
+ // ── Drawing state ──
1245
1318
  /**
1246
1319
  * Returns the current pen position as [x, y] coordinates
1247
1320
  *
@@ -1260,11 +1333,10 @@ class BaseSketcher2d {
1260
1333
  * @category Drawing State
1261
1334
  */
1262
1335
  get penAngle() {
1263
- if (this.pendingCurves.length === 0) return 0;
1264
- const lastCurve = this.pendingCurves[this.pendingCurves.length - 1];
1336
+ const lastCurve = this._lastCurve();
1337
+ if (!lastCurve) return 0;
1265
1338
  const [dx, dy] = lastCurve.tangentAt(1);
1266
- const angleInRadians = Math.atan2(dy, dx);
1267
- return angleInRadians * RAD2DEG;
1339
+ return Math.atan2(dy, dx) * RAD2DEG;
1268
1340
  }
1269
1341
  /** Move the pen to an absolute 2D position before drawing any curves. */
1270
1342
  movePointerTo(point) {
@@ -1285,16 +1357,15 @@ class BaseSketcher2d {
1285
1357
  this.pendingCurves.push(...this._nextCorner(previousCurve, curve));
1286
1358
  this._nextCorner = null;
1287
1359
  }
1360
+ // ── Line segments ──
1288
1361
  /** Draw a straight line to an absolute 2D point. */
1289
1362
  lineTo(point) {
1290
1363
  const curve = make2dSegmentCurve(this._convertToUV(this.pointer), this._convertToUV(point));
1291
- this.pointer = point;
1292
- this.saveCurve(curve);
1293
- return this;
1364
+ return this._saveCurveAndAdvance(curve, point);
1294
1365
  }
1295
1366
  /** Draw a straight line by relative horizontal and vertical distances. */
1296
1367
  line(xDist, yDist) {
1297
- return this.lineTo([this.pointer[0] + xDist, this.pointer[1] + yDist]);
1368
+ return this.lineTo(this._resolveRelative(xDist, yDist));
1298
1369
  }
1299
1370
  /** Draw a vertical line of the given signed distance. */
1300
1371
  vLine(distance) {
@@ -1314,41 +1385,35 @@ class BaseSketcher2d {
1314
1385
  }
1315
1386
  /** Draw a line to a point given in polar coordinates [r, theta] from the origin. */
1316
1387
  polarLineTo([r, theta]) {
1317
- const angleInRads = theta * DEG2RAD;
1318
- const point = polarToCartesian(r, angleInRads);
1319
- return this.lineTo(point);
1388
+ return this.lineTo(polarToCartesian(r, theta * DEG2RAD));
1320
1389
  }
1321
1390
  /** Draw a line in polar coordinates (distance and angle in degrees) from the current point. */
1322
1391
  polarLine(distance, angle) {
1323
- const angleInRads = angle * DEG2RAD;
1324
- const [x, y] = polarToCartesian(distance, angleInRads);
1392
+ const [x, y] = polarToCartesian(distance, angle * DEG2RAD);
1325
1393
  return this.line(x, y);
1326
1394
  }
1327
1395
  /** Draw a line tangent to the previous curve, extending by the given distance. */
1328
1396
  tangentLine(distance) {
1329
- const previousCurve = this.pendingCurves.length ? this.pendingCurves[this.pendingCurves.length - 1] : null;
1330
- if (!previousCurve)
1331
- bug("Sketcher2d.tangentLine", "You need a previous curve to sketch a tangent line");
1397
+ const previousCurve = this._requireLastCurve("Sketcher2d.tangentLine", "sketch a tangent line");
1332
1398
  const direction = normalize2d(this._convertFromUV(previousCurve.tangentAt(1)));
1333
1399
  return this.line(direction[0] * distance, direction[1] * distance);
1334
1400
  }
1401
+ // ── Three-point arcs ──
1335
1402
  /** Draw a circular arc passing through a mid-point to an absolute end point. */
1336
1403
  threePointsArcTo(end, midPoint) {
1337
- this.saveCurve(
1338
- make2dThreePointArc(
1339
- this._convertToUV(this.pointer),
1340
- this._convertToUV(midPoint),
1341
- this._convertToUV(end)
1342
- )
1404
+ const curve = make2dThreePointArc(
1405
+ this._convertToUV(this.pointer),
1406
+ this._convertToUV(midPoint),
1407
+ this._convertToUV(end)
1343
1408
  );
1344
- this.pointer = end;
1345
- return this;
1409
+ return this._saveCurveAndAdvance(curve, end);
1346
1410
  }
1347
1411
  /** Draw a circular arc through a via-point to an end point, both as relative distances. */
1348
1412
  threePointsArc(xDist, yDist, viaXDist, viaYDist) {
1349
1413
  const [x0, y0] = this.pointer;
1350
1414
  return this.threePointsArcTo([x0 + xDist, y0 + yDist], [x0 + viaXDist, y0 + viaYDist]);
1351
1415
  }
1416
+ // ── Sagitta arcs ──
1352
1417
  /** Draw a circular arc to an absolute end point, bulging by the given sagitta. */
1353
1418
  sagittaArcTo(end, sagitta) {
1354
1419
  const [x0, y0] = this.pointer;
@@ -1365,19 +1430,16 @@ class BaseSketcher2d {
1365
1430
  midX + sagDirX / sagDirLen * sagitta,
1366
1431
  midY + sagDirY / sagDirLen * sagitta
1367
1432
  ];
1368
- this.saveCurve(
1369
- make2dThreePointArc(
1370
- this._convertToUV(this.pointer),
1371
- this._convertToUV(sagPoint),
1372
- this._convertToUV(end)
1373
- )
1433
+ const curve = make2dThreePointArc(
1434
+ this._convertToUV(this.pointer),
1435
+ this._convertToUV(sagPoint),
1436
+ this._convertToUV(end)
1374
1437
  );
1375
- this.pointer = end;
1376
- return this;
1438
+ return this._saveCurveAndAdvance(curve, end);
1377
1439
  }
1378
1440
  /** Draw a circular arc to a relative end point, bulging by the given sagitta. */
1379
1441
  sagittaArc(xDist, yDist, sagitta) {
1380
- return this.sagittaArcTo([xDist + this.pointer[0], yDist + this.pointer[1]], sagitta);
1442
+ return this.sagittaArcTo(this._resolveRelative(xDist, yDist), sagitta);
1381
1443
  }
1382
1444
  /** Draw a vertical sagitta arc of the given distance and bulge. */
1383
1445
  vSagittaArc(distance, sagitta) {
@@ -1387,16 +1449,16 @@ class BaseSketcher2d {
1387
1449
  hSagittaArc(distance, sagitta) {
1388
1450
  return this.sagittaArc(distance, 0, sagitta);
1389
1451
  }
1452
+ // ── Bulge arcs ──
1390
1453
  /** Draw an arc to an absolute end point using a bulge factor (sagitta as fraction of half-chord). */
1391
1454
  bulgeArcTo(end, bulge) {
1392
1455
  if (!bulge) return this.lineTo(end);
1393
1456
  const halfChord = distance2d(this.pointer, end) / 2;
1394
- const bulgeAsSagitta = -bulge * halfChord;
1395
- return this.sagittaArcTo(end, bulgeAsSagitta);
1457
+ return this.sagittaArcTo(end, -bulge * halfChord);
1396
1458
  }
1397
1459
  /** Draw an arc to a relative end point using a bulge factor. */
1398
1460
  bulgeArc(xDist, yDist, bulge) {
1399
- return this.bulgeArcTo([xDist + this.pointer[0], yDist + this.pointer[1]], bulge);
1461
+ return this.bulgeArcTo(this._resolveRelative(xDist, yDist), bulge);
1400
1462
  }
1401
1463
  /** Draw a vertical bulge arc of the given distance and bulge factor. */
1402
1464
  vBulgeArc(distance, bulge) {
@@ -1406,71 +1468,45 @@ class BaseSketcher2d {
1406
1468
  hBulgeArc(distance, bulge) {
1407
1469
  return this.bulgeArc(distance, 0, bulge);
1408
1470
  }
1471
+ // ── Tangent arcs ──
1409
1472
  /** Draw a circular arc tangent to the previous curve, ending at an absolute point. */
1410
1473
  tangentArcTo(end) {
1411
- const previousCurve = this.pendingCurves.length ? this.pendingCurves[this.pendingCurves.length - 1] : null;
1412
- if (!previousCurve)
1413
- bug("Sketcher2d.tangentArc", "You need a previous curve to sketch a tangent arc");
1414
- this.saveCurve(
1415
- make2dTangentArc(
1416
- this._convertToUV(this.pointer),
1417
- previousCurve.tangentAt(1),
1418
- this._convertToUV(end)
1419
- )
1474
+ const previousCurve = this._requireLastCurve("Sketcher2d.tangentArc", "sketch a tangent arc");
1475
+ const curve = make2dTangentArc(
1476
+ this._convertToUV(this.pointer),
1477
+ previousCurve.tangentAt(1),
1478
+ this._convertToUV(end)
1420
1479
  );
1421
- this.pointer = end;
1422
- return this;
1480
+ return this._saveCurveAndAdvance(curve, end);
1423
1481
  }
1424
1482
  /** Draw a circular arc tangent to the previous curve, ending at a relative offset. */
1425
1483
  tangentArc(xDist, yDist) {
1426
- const [x0, y0] = this.pointer;
1427
- return this.tangentArcTo([xDist + x0, yDist + y0]);
1484
+ return this.tangentArcTo(this._resolveRelative(xDist, yDist));
1428
1485
  }
1486
+ // ── Ellipse arcs ──
1429
1487
  /** Draw an elliptical arc to an absolute end point (SVG-style parameters). */
1430
1488
  ellipseTo(end, horizontalRadius, verticalRadius, rotation = 0, longAxis = false, sweep = false) {
1431
- let rotationAngle = rotation;
1432
- let majorRadius = horizontalRadius;
1433
- let minorRadius = verticalRadius;
1434
- if (horizontalRadius < verticalRadius) {
1435
- rotationAngle = rotation + 90;
1436
- majorRadius = verticalRadius;
1437
- minorRadius = horizontalRadius;
1438
- }
1439
- const radRotationAngle = rotationAngle * DEG2RAD;
1440
- const convertAxis = (ax) => distance2d(this._convertToUV(ax));
1441
- const r1 = convertAxis(polarToCartesian(majorRadius, radRotationAngle));
1442
- const r2 = convertAxis(polarToCartesian(minorRadius, radRotationAngle + Math.PI / 2));
1443
- const xDir = normalize2d(this._convertToUV(rotate2d([1, 0], radRotationAngle)));
1444
- const [, newRotationAngle] = cartesianToPolar(xDir);
1445
- const { cx, cy, startAngle, endAngle, clockwise, rx, ry } = convertSvgEllipseParams(
1489
+ const { majorRadius, minorRadius, rotationAngle } = normalizeEllipseRadii(
1490
+ horizontalRadius,
1491
+ verticalRadius,
1492
+ rotation
1493
+ );
1494
+ const arc = makeEllipseArcFromSvgParams(
1446
1495
  this._convertToUV(this.pointer),
1447
1496
  this._convertToUV(end),
1448
- r1,
1449
- r2,
1450
- newRotationAngle,
1497
+ majorRadius,
1498
+ minorRadius,
1499
+ rotationAngle,
1451
1500
  longAxis,
1452
- sweep
1453
- );
1454
- const arc = make2dEllipseArc(
1455
- rx,
1456
- ry,
1457
- clockwise ? startAngle : endAngle,
1458
- clockwise ? endAngle : startAngle,
1459
- [cx, cy],
1460
- xDir
1501
+ sweep,
1502
+ (p) => this._convertToUV(p)
1461
1503
  );
1462
- if (!clockwise) {
1463
- arc.reverse();
1464
- }
1465
- this.saveCurve(arc);
1466
- this.pointer = end;
1467
- return this;
1504
+ return this._saveCurveAndAdvance(arc, end);
1468
1505
  }
1469
1506
  /** Draw an elliptical arc to a relative end point (SVG-style parameters). */
1470
1507
  ellipse(xDist, yDist, horizontalRadius, verticalRadius, rotation = 0, longAxis = false, sweep = false) {
1471
- const [x0, y0] = this.pointer;
1472
1508
  return this.ellipseTo(
1473
- [xDist + x0, yDist + y0],
1509
+ this._resolveRelative(xDist, yDist),
1474
1510
  horizontalRadius,
1475
1511
  verticalRadius,
1476
1512
  rotation,
@@ -1486,26 +1522,18 @@ class BaseSketcher2d {
1486
1522
  }
1487
1523
  /** Draw a half-ellipse arc to a relative end point with a given minor radius. */
1488
1524
  halfEllipse(xDist, yDist, minorRadius, sweep = false) {
1489
- const [x0, y0] = this.pointer;
1490
- return this.halfEllipseTo([x0 + xDist, y0 + yDist], minorRadius, sweep);
1525
+ return this.halfEllipseTo(this._resolveRelative(xDist, yDist), minorRadius, sweep);
1491
1526
  }
1527
+ // ── Bezier curves ──
1492
1528
  /** Draw a Bezier curve to an absolute end point through one or more control points. */
1493
1529
  bezierCurveTo(end, controlPoints) {
1494
- let cp;
1495
- if (controlPoints.length === 2 && !Array.isArray(controlPoints[0])) {
1496
- cp = [controlPoints];
1497
- } else {
1498
- cp = controlPoints;
1499
- }
1500
- this.saveCurve(
1501
- make2dBezierCurve(
1502
- this._convertToUV(this.pointer),
1503
- cp.map((point) => this._convertToUV(point)),
1504
- this._convertToUV(end)
1505
- )
1530
+ const cp = controlPoints.length === 2 && !Array.isArray(controlPoints[0]) ? [controlPoints] : controlPoints;
1531
+ const curve = make2dBezierCurve(
1532
+ this._convertToUV(this.pointer),
1533
+ cp.map((point) => this._convertToUV(point)),
1534
+ this._convertToUV(end)
1506
1535
  );
1507
- this.pointer = end;
1508
- return this;
1536
+ return this._saveCurveAndAdvance(curve, end);
1509
1537
  }
1510
1538
  /** Draw a quadratic Bezier curve to an absolute end point with a single control point. */
1511
1539
  quadraticBezierCurveTo(end, controlPoint) {
@@ -1515,10 +1543,11 @@ class BaseSketcher2d {
1515
1543
  cubicBezierCurveTo(end, startControlPoint, endControlPoint) {
1516
1544
  return this.bezierCurveTo(end, [startControlPoint, endControlPoint]);
1517
1545
  }
1546
+ // ── Smooth splines ──
1518
1547
  /** Draw a smooth cubic Bezier spline to an absolute end point, blending tangent with the previous curve. */
1519
1548
  smoothSplineTo(end, config) {
1520
1549
  const { endTangent, startTangent, startFactor, endFactor } = defaultsSplineOptions(config);
1521
- const previousCurve = this.pendingCurves.length ? this.pendingCurves[this.pendingCurves.length - 1] : null;
1550
+ const previousCurve = this._lastCurve();
1522
1551
  const defaultDistance = distance2d(this.pointer, end) * 0.25;
1523
1552
  let startPoleDirection;
1524
1553
  if (startTangent) {
@@ -1548,8 +1577,9 @@ class BaseSketcher2d {
1548
1577
  }
1549
1578
  /** Draw a smooth cubic Bezier spline to a relative end point, blending tangent with the previous curve. */
1550
1579
  smoothSpline(xDist, yDist, splineConfig) {
1551
- return this.smoothSplineTo([xDist + this.pointer[0], yDist + this.pointer[1]], splineConfig);
1580
+ return this.smoothSplineTo(this._resolveRelative(xDist, yDist), splineConfig);
1552
1581
  }
1582
+ // ── Corner treatments ──
1553
1583
  /**
1554
1584
  * Changes the corner between the previous and next segments.
1555
1585
  */
@@ -1567,6 +1597,7 @@ class BaseSketcher2d {
1567
1597
  bug("Sketcher2d._customCornerLastWithFirst", "Not enough curves to close and fillet");
1568
1598
  this.pendingCurves.push(...buildCornerFunction(radius, mode)(previousCurve, curve));
1569
1599
  }
1600
+ // ── Close / mirror helpers ──
1570
1601
  _closeSketch() {
1571
1602
  if (!samePoint$1(this.pointer, this.firstPoint)) {
1572
1603
  this.lineTo(this.firstPoint);
@@ -1587,9 +1618,9 @@ class BaseSketcher2d {
1587
1618
  (c) => new Curve2D(c.innerCurve.Mirrored_2(mirrorAxis))
1588
1619
  );
1589
1620
  mirroredCurves.reverse();
1590
- mirroredCurves.forEach((c) => {
1621
+ for (const c of mirroredCurves) {
1591
1622
  c.reverse();
1592
- });
1623
+ }
1593
1624
  this.pendingCurves.push(...mirroredCurves);
1594
1625
  this.pointer = this.firstPoint;
1595
1626
  }
@@ -1758,77 +1789,100 @@ const roundedRectangleBlueprint = (width, height, r = 0) => {
1758
1789
  return sk.close();
1759
1790
  };
1760
1791
  const samePoint = (x, y) => samePoint$1(x, y, PRECISION_INTERSECTION);
1761
- const curveMidPoint = (curve) => {
1792
+ function hashPoint(p) {
1793
+ return `${p[0].toFixed(9)},${p[1].toFixed(9)}`;
1794
+ }
1795
+ function hashSegment(first, last) {
1796
+ const h1 = hashPoint(first);
1797
+ const h2 = hashPoint(last);
1798
+ return h1 < h2 ? `${h1}|${h2}` : `${h2}|${h1}`;
1799
+ }
1800
+ function startOfSegment(s) {
1801
+ const first = s[0];
1802
+ if (first === void 0) {
1803
+ bug("startOfSegment", "empty segment");
1804
+ }
1805
+ return first.firstPoint;
1806
+ }
1807
+ function endOfSegment(s) {
1808
+ const last = s[s.length - 1];
1809
+ if (last === void 0) {
1810
+ bug("endOfSegment", "empty segment");
1811
+ }
1812
+ return last.lastPoint;
1813
+ }
1814
+ function reverseSegment(segment) {
1815
+ return [...segment].reverse().map((curve) => {
1816
+ const newCurve = curve.clone();
1817
+ newCurve.reverse();
1818
+ return newCurve;
1819
+ });
1820
+ }
1821
+ function reverseSegments(segments) {
1822
+ return [...segments].reverse().map(reverseSegment);
1823
+ }
1824
+ function curveMidPoint(curve) {
1762
1825
  const midParameter = (curve.lastParameter + curve.firstParameter) / 2;
1763
1826
  return curve.value(midParameter);
1764
- };
1765
- const rotateToStartAt = (curves, point) => {
1766
- const pointHash = hashPoint(point);
1767
- let startIndex = -1;
1827
+ }
1828
+ function findCurveIndexByStartPoint(curves, point) {
1829
+ const targetHash = hashPoint(point);
1768
1830
  for (let i = 0; i < curves.length; i++) {
1769
1831
  const curve = curves[i];
1770
- if (hashPoint(curve.firstPoint) === pointHash && samePoint(point, curve.firstPoint)) {
1771
- startIndex = i;
1772
- break;
1832
+ if (curve === void 0) continue;
1833
+ if (hashPoint(curve.firstPoint) === targetHash && samePoint(point, curve.firstPoint)) {
1834
+ return i;
1773
1835
  }
1774
1836
  }
1775
- if (startIndex <= 0) return curves;
1776
- return curves.slice(startIndex).concat(curves.slice(0, startIndex));
1777
- };
1778
- const rotateToStartAtSegment = (curves, segment) => {
1779
- const segFirstHash = hashPoint(segment.firstPoint);
1780
- const segLastHash = hashPoint(segment.lastPoint);
1781
- const onSegment = (curve) => {
1782
- return samePoint(segment.firstPoint, curve.firstPoint) && samePoint(segment.lastPoint, curve.lastPoint);
1783
- };
1784
- let startIndex = -1;
1837
+ return -1;
1838
+ }
1839
+ function findCurveIndexBySegment(curves, segFirstHash, segLastHash, matchesFn) {
1785
1840
  for (let i = 0; i < curves.length; i++) {
1786
1841
  const curve = curves[i];
1787
- if (hashPoint(curve.firstPoint) === segFirstHash && hashPoint(curve.lastPoint) === segLastHash && onSegment(curve)) {
1788
- startIndex = i;
1789
- break;
1842
+ if (curve === void 0) continue;
1843
+ if (hashPoint(curve.firstPoint) === segFirstHash && hashPoint(curve.lastPoint) === segLastHash && matchesFn(curve)) {
1844
+ return i;
1790
1845
  }
1791
1846
  }
1847
+ return -1;
1848
+ }
1849
+ function rotateArray(arr, startIndex) {
1850
+ if (startIndex <= 0) return arr;
1851
+ return arr.slice(startIndex).concat(arr.slice(0, startIndex));
1852
+ }
1853
+ function rotateToStartAt(curves, point) {
1854
+ const startIndex = findCurveIndexByStartPoint(curves, point);
1855
+ return rotateArray(curves, startIndex);
1856
+ }
1857
+ function rotateToStartAtSegment(curves, segment) {
1858
+ const segFirstHash = hashPoint(segment.firstPoint);
1859
+ const segLastHash = hashPoint(segment.lastPoint);
1860
+ const onSegment = (curve) => samePoint(segment.firstPoint, curve.firstPoint) && samePoint(segment.lastPoint, curve.lastPoint);
1861
+ let startIndex = findCurveIndexBySegment(curves, segFirstHash, segLastHash, onSegment);
1862
+ if (startIndex !== -1) {
1863
+ return rotateArray(curves, startIndex);
1864
+ }
1865
+ const reversed = reverseSegment(curves);
1866
+ startIndex = findCurveIndexBySegment(reversed, segFirstHash, segLastHash, onSegment);
1792
1867
  if (startIndex === -1) {
1793
- curves = reverseSegment(curves);
1794
- for (let i = 0; i < curves.length; i++) {
1795
- const curve = curves[i];
1796
- if (hashPoint(curve.firstPoint) === segFirstHash && hashPoint(curve.lastPoint) === segLastHash && onSegment(curve)) {
1797
- startIndex = i;
1798
- break;
1799
- }
1800
- }
1801
- if (startIndex === -1) {
1802
- bug("rotateToStartAtSegment", "Failed to rotate to segment start");
1803
- }
1868
+ bug("rotateToStartAtSegment", "failed to rotate to segment start");
1804
1869
  }
1805
- if (startIndex <= 0) return curves;
1806
- return curves.slice(startIndex).concat(curves.slice(0, startIndex));
1807
- };
1808
- const hashPoint = (p) => `${p[0].toFixed(9)},${p[1].toFixed(9)}`;
1809
- const hashSegment = (first, last) => {
1810
- const h1 = hashPoint(first);
1811
- const h2 = hashPoint(last);
1812
- return h1 < h2 ? `${h1}|${h2}` : `${h2}|${h1}`;
1813
- };
1870
+ return rotateArray(reversed, startIndex);
1871
+ }
1814
1872
  function* createSegmentOnPoints(curves, allIntersections, allCommonSegments) {
1815
1873
  const intersectionSet = new Set(allIntersections.map(hashPoint));
1816
1874
  const commonSegmentSet = new Set(
1817
1875
  allCommonSegments.map((seg) => hashSegment(seg.firstPoint, seg.lastPoint))
1818
1876
  );
1819
- const endsAtIntersection = (curve) => {
1820
- return intersectionSet.has(hashPoint(curve.lastPoint));
1821
- };
1822
- const isCommonSegment = (curve) => {
1823
- return commonSegmentSet.has(hashSegment(curve.firstPoint, curve.lastPoint));
1824
- };
1825
1877
  let currentCurves = [];
1826
1878
  for (const curve of curves) {
1827
- if (endsAtIntersection(curve)) {
1879
+ const endsAtIntersection = intersectionSet.has(hashPoint(curve.lastPoint));
1880
+ const isCommon = commonSegmentSet.has(hashSegment(curve.firstPoint, curve.lastPoint));
1881
+ if (endsAtIntersection) {
1828
1882
  currentCurves.push(curve);
1829
1883
  yield currentCurves;
1830
1884
  currentCurves = [];
1831
- } else if (isCommonSegment(curve)) {
1885
+ } else if (isCommon) {
1832
1886
  if (currentCurves.length) {
1833
1887
  yield currentCurves;
1834
1888
  currentCurves = [];
@@ -1842,73 +1896,85 @@ function* createSegmentOnPoints(curves, allIntersections, allCommonSegments) {
1842
1896
  yield currentCurves;
1843
1897
  }
1844
1898
  }
1845
- const startOfSegment = (s) => {
1846
- return s[0].firstPoint;
1847
- };
1848
- const endOfSegment = (s) => {
1849
- return s[s.length - 1].lastPoint;
1850
- };
1851
- const reverseSegment = (segment) => {
1852
- return [...segment].reverse().map((curve) => {
1853
- const newCurve = curve.clone();
1854
- newCurve.reverse();
1855
- return newCurve;
1856
- });
1857
- };
1858
- const reverseSegments = (s) => {
1859
- return [...s].reverse().map(reverseSegment);
1860
- };
1861
- function removeNonCrossingPoint(allIntersections, segmentedCurve, blueprintToCheck) {
1899
+ function removeNonCrossingPoints(allIntersections, segmentedCurve, blueprintToCheck) {
1862
1900
  return allIntersections.filter((intersection) => {
1863
- const segmentsOfIntersection = segmentedCurve.filter((s) => {
1864
- return samePoint(s.firstPoint, intersection) || samePoint(s.lastPoint, intersection);
1865
- });
1866
- if (segmentsOfIntersection.length % 2) {
1867
- bug("removeNonCrossingPoint", "Odd number of segments at intersection point (expected even)");
1901
+ const touching = segmentedCurve.filter(
1902
+ (s) => samePoint(s.firstPoint, intersection) || samePoint(s.lastPoint, intersection)
1903
+ );
1904
+ if (touching.length % 2) {
1905
+ bug(
1906
+ "removeNonCrossingPoints",
1907
+ "Odd number of segments at intersection point (expected even)"
1908
+ );
1868
1909
  }
1869
- const isInside = segmentsOfIntersection.map((segment) => {
1870
- return blueprintToCheck.isInside(curveMidPoint(segment));
1871
- });
1872
- const segmentsOnTheSameSide = isInside.every((i) => i) || !isInside.some((i) => i);
1873
- return !segmentsOnTheSameSide;
1910
+ const insideFlags = touching.map(
1911
+ (segment) => blueprintToCheck.isInside(curveMidPoint(segment))
1912
+ );
1913
+ const allSameSide = insideFlags.every(Boolean) || insideFlags.every((f) => !f);
1914
+ return !allSameSide;
1874
1915
  });
1875
1916
  }
1876
- function blueprintsIntersectionSegments(first, second) {
1877
- let allIntersections = [];
1917
+ function findAllIntersections(first, second) {
1918
+ const allIntersections = [];
1878
1919
  const allCommonSegments = [];
1879
- const firstCurvePoints = new Array(first.curves.length).fill(0).map(() => []);
1880
- const secondCurvePoints = new Array(second.curves.length).fill(0).map(() => []);
1920
+ const firstCurvePoints = first.curves.map(() => []);
1921
+ const secondCurvePoints = second.curves.map(() => []);
1881
1922
  first.curves.forEach((thisCurve, firstIndex) => {
1882
1923
  second.curves.forEach((otherCurve, secondIndex) => {
1883
- const { intersections, commonSegments, commonSegmentsPoints: commonSegmentsPoints2 } = unwrap(
1924
+ const { intersections, commonSegments, commonSegmentsPoints } = unwrap(
1884
1925
  intersectCurves(thisCurve, otherCurve, PRECISION_INTERSECTION / 100)
1885
1926
  );
1886
1927
  allIntersections.push(...intersections);
1887
- firstCurvePoints[firstIndex].push(...intersections);
1888
- secondCurvePoints[secondIndex].push(...intersections);
1928
+ firstCurvePoints[firstIndex]?.push(...intersections);
1929
+ secondCurvePoints[secondIndex]?.push(...intersections);
1889
1930
  allCommonSegments.push(...commonSegments);
1890
- allIntersections.push(...commonSegmentsPoints2);
1891
- firstCurvePoints[firstIndex].push(...commonSegmentsPoints2);
1892
- secondCurvePoints[secondIndex].push(...commonSegmentsPoints2);
1931
+ allIntersections.push(...commonSegmentsPoints);
1932
+ firstCurvePoints[firstIndex]?.push(...commonSegmentsPoints);
1933
+ secondCurvePoints[secondIndex]?.push(...commonSegmentsPoints);
1893
1934
  });
1894
1935
  });
1895
- allIntersections = removeDuplicatePoints(allIntersections, PRECISION_INTERSECTION);
1896
- if (!allIntersections.length || allIntersections.length === 1) return null;
1897
- const cutCurve = ([curve, intersections]) => {
1898
- if (!intersections.length) return [curve];
1899
- return curve.splitAt(intersections, PRECISION_INTERSECTION / 100);
1936
+ return {
1937
+ allIntersections: removeDuplicatePoints(allIntersections, PRECISION_INTERSECTION),
1938
+ allCommonSegments,
1939
+ firstCurvePoints,
1940
+ secondCurvePoints
1900
1941
  };
1901
- let firstCurveSegments = zip([first.curves, firstCurvePoints]).flatMap(cutCurve);
1902
- let secondCurveSegments = zip([second.curves, secondCurvePoints]).flatMap(cutCurve);
1942
+ }
1943
+ function splitCurvesAtIntersections(curves, curvePoints) {
1944
+ return zip([curves, curvePoints]).flatMap(
1945
+ ([curve, intersections]) => {
1946
+ if (intersections.length === 0) return [curve];
1947
+ return curve.splitAt(intersections, PRECISION_INTERSECTION / 100);
1948
+ }
1949
+ );
1950
+ }
1951
+ function isCommonSegmentMatch(commonSegmentsPoints, segmentStart, segmentEnd) {
1952
+ return commonSegmentsPoints.some(([startPoint, endPoint]) => {
1953
+ if (startPoint === void 0 || endPoint === void 0) return false;
1954
+ return samePoint(startPoint, segmentStart) && samePoint(endPoint, segmentEnd) || samePoint(startPoint, segmentEnd) && samePoint(startPoint, segmentStart);
1955
+ });
1956
+ }
1957
+ function blueprintsIntersectionSegments(first, second) {
1958
+ const {
1959
+ allIntersections: rawIntersections,
1960
+ allCommonSegments,
1961
+ firstCurvePoints,
1962
+ secondCurvePoints
1963
+ } = findAllIntersections(first, second);
1964
+ if (rawIntersections.length <= 1) return null;
1965
+ let firstCurveSegments = splitCurvesAtIntersections(first.curves, firstCurvePoints);
1966
+ let secondCurveSegments = splitCurvesAtIntersections(second.curves, secondCurvePoints);
1903
1967
  const commonSegmentsPoints = allCommonSegments.map((c) => [c.firstPoint, c.lastPoint]);
1904
- allIntersections = removeNonCrossingPoint(allIntersections, firstCurveSegments, second);
1905
- if (!allIntersections.length && !allCommonSegments.length) return null;
1906
- if (!allCommonSegments.length) {
1968
+ const allIntersections = removeNonCrossingPoints(rawIntersections, firstCurveSegments, second);
1969
+ if (allIntersections.length === 0 && allCommonSegments.length === 0) return null;
1970
+ if (allCommonSegments.length === 0) {
1907
1971
  const startAt = allIntersections[0];
1972
+ if (startAt === void 0) return null;
1908
1973
  firstCurveSegments = rotateToStartAt(firstCurveSegments, startAt);
1909
1974
  secondCurveSegments = rotateToStartAt(secondCurveSegments, startAt);
1910
1975
  } else {
1911
1976
  const startSegment = allCommonSegments[0];
1977
+ if (startSegment === void 0) return null;
1912
1978
  firstCurveSegments = rotateToStartAtSegment(firstCurveSegments, startSegment);
1913
1979
  secondCurveSegments = rotateToStartAtSegment(secondCurveSegments, startSegment);
1914
1980
  }
@@ -1918,130 +1984,145 @@ function blueprintsIntersectionSegments(first, second) {
1918
1984
  let secondIntersectedSegments = Array.from(
1919
1985
  createSegmentOnPoints(secondCurveSegments, allIntersections, allCommonSegments)
1920
1986
  );
1921
- if (!samePoint(
1922
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1923
- endOfSegment(secondIntersectedSegments[0]),
1924
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1925
- endOfSegment(firstIntersectedSegments[0])
1926
- ) || // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1927
- allCommonSegments.length > 0 && secondIntersectedSegments[0].length !== 1) {
1928
- secondIntersectedSegments = reverseSegments(secondIntersectedSegments);
1987
+ const firstSeg = firstIntersectedSegments[0];
1988
+ const secondSeg = secondIntersectedSegments[0];
1989
+ if (firstSeg !== void 0 && secondSeg !== void 0) {
1990
+ const endpointsMismatch = !samePoint(endOfSegment(secondSeg), endOfSegment(firstSeg));
1991
+ const commonSegmentLengthMismatch = allCommonSegments.length > 0 && secondSeg.length !== 1;
1992
+ if (endpointsMismatch || commonSegmentLengthMismatch) {
1993
+ secondIntersectedSegments = reverseSegments(secondIntersectedSegments);
1994
+ }
1929
1995
  }
1930
1996
  return zip([firstIntersectedSegments, secondIntersectedSegments]).map(
1931
1997
  ([first2, second2]) => {
1932
- const firstSegment = first2;
1933
- const secondSegment = second2;
1934
- const currentStart = startOfSegment(firstSegment);
1935
- const currentEnd = endOfSegment(firstSegment);
1936
- if (commonSegmentsPoints.find(([startPoint, endPoint]) => {
1937
- return (
1938
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1939
- samePoint(startPoint, currentStart) && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1940
- samePoint(endPoint, currentEnd) || // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1941
- samePoint(startPoint, currentEnd) && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1942
- samePoint(startPoint, currentStart)
1943
- );
1944
- })) {
1945
- return [firstSegment, "same"];
1998
+ if (first2 === void 0 || second2 === void 0) {
1999
+ bug("blueprintsIntersectionSegments", "Mismatched segment counts between blueprints");
1946
2000
  }
1947
- return [firstSegment, secondSegment];
2001
+ const currentStart = startOfSegment(first2);
2002
+ const currentEnd = endOfSegment(first2);
2003
+ if (isCommonSegmentMatch(commonSegmentsPoints, currentStart, currentEnd)) {
2004
+ return [first2, "same"];
2005
+ }
2006
+ return [first2, second2];
1948
2007
  }
1949
2008
  );
1950
2009
  }
1951
- const splitPaths = (curves) => {
2010
+ function splitPaths(curves) {
1952
2011
  const startPoints = curves.map((c) => c.firstPoint);
1953
- let endPoints = curves.map((c) => c.lastPoint);
1954
- endPoints = endPoints.slice(-1).concat(endPoints.slice(0, -1));
2012
+ const shiftedEndPoints = curves.map((c) => c.lastPoint);
2013
+ const endPoints = shiftedEndPoints.slice(-1).concat(shiftedEndPoints.slice(0, -1));
1955
2014
  const discontinuities = zip([startPoints, endPoints]).map(([startPoint, endPoint], index) => {
1956
- if (!samePoint(startPoint, endPoint)) {
1957
- return index;
1958
- }
1959
- return null;
2015
+ if (startPoint === void 0 || endPoint === void 0) return null;
2016
+ return samePoint(startPoint, endPoint) ? null : index;
1960
2017
  }).filter((f) => f !== null);
1961
- if (!discontinuities.length) return [curves];
2018
+ if (discontinuities.length === 0) return [curves];
1962
2019
  const paths = zip([discontinuities.slice(0, -1), discontinuities.slice(1)]).map(
1963
- ([start, end]) => {
1964
- return curves.slice(start, end);
1965
- }
2020
+ ([start, end]) => curves.slice(start, end)
1966
2021
  );
1967
2022
  let lastPath = curves.slice(discontinuities[discontinuities.length - 1]);
1968
- if (discontinuities[0] !== 0) {
1969
- lastPath = lastPath.concat(curves.slice(0, discontinuities[0]));
2023
+ const firstDiscontinuity = discontinuities[0];
2024
+ if (firstDiscontinuity !== void 0 && firstDiscontinuity !== 0) {
2025
+ lastPath = lastPath.concat(curves.slice(0, firstDiscontinuity));
1970
2026
  }
1971
2027
  paths.push(lastPath);
1972
2028
  return paths;
1973
- };
1974
- function booleanOperation(first, second, {
1975
- firstInside,
1976
- secondInside
1977
- }) {
1978
- const segments = blueprintsIntersectionSegments(first, second);
1979
- if (!segments) {
1980
- const firstBlueprintPoint = curveMidPoint(first.curves[0]);
1981
- const firstCurveInSecond = second.isInside(firstBlueprintPoint);
1982
- const secondBlueprintPoint = curveMidPoint(second.curves[0]);
1983
- const secondCurveInFirst = first.isInside(secondBlueprintPoint);
1984
- return {
1985
- identical: false,
1986
- firstCurveInSecond,
1987
- secondCurveInFirst
1988
- };
2029
+ }
2030
+ function handleSameSegment(firstSegment, segmentsIn, lastWasSame) {
2031
+ if (segmentsIn === 1) {
2032
+ return { curves: [...firstSegment], segmentsIn: 1, lastWasSame: null };
1989
2033
  }
1990
- if (segments.every(([, secondSegment]) => secondSegment === "same")) {
1991
- return { identical: true };
2034
+ if (segmentsIn === 2 || segmentsIn === 0) {
2035
+ return { curves: [], segmentsIn: null, lastWasSame: null };
1992
2036
  }
1993
- let lastWasSame = null;
1994
- let segmentsIn = null;
1995
- const s = segments.flatMap(([firstSegment, secondSegment]) => {
1996
- let segments2 = [];
1997
- let segmentsOut = 0;
1998
- if (secondSegment === "same") {
1999
- if (segmentsIn === 1) {
2000
- segmentsIn = 1;
2001
- return [...firstSegment];
2002
- }
2003
- if (segmentsIn === 2 || segmentsIn === 0) {
2004
- segmentsIn = null;
2005
- return [];
2006
- }
2007
- if (segmentsIn === null) {
2008
- if (!lastWasSame) lastWasSame = firstSegment;
2009
- else lastWasSame = [...lastWasSame, ...firstSegment];
2010
- return [];
2011
- }
2012
- return [];
2013
- }
2014
- const firstSegmentPoint = curveMidPoint(firstSegment[0]);
2015
- const firstSegmentInSecondShape = second.isInside(firstSegmentPoint);
2016
- if (firstInside === "keep" && firstSegmentInSecondShape || firstInside === "remove" && !firstSegmentInSecondShape) {
2037
+ if (segmentsIn === null) {
2038
+ const accumulated = lastWasSame ? [...lastWasSame, ...firstSegment] : firstSegment;
2039
+ return { curves: [], segmentsIn: null, lastWasSame: accumulated };
2040
+ }
2041
+ return { curves: [], segmentsIn, lastWasSame };
2042
+ }
2043
+ function selectSegments(firstSegment, secondSegment, first, second, config, segmentsIn, lastWasSame) {
2044
+ let segments = [];
2045
+ let segmentsOut = 0;
2046
+ const firstCurve = firstSegment[0];
2047
+ if (firstCurve !== void 0) {
2048
+ const firstSegmentPoint = curveMidPoint(firstCurve);
2049
+ const firstInSecond = second.isInside(firstSegmentPoint);
2050
+ if (config.firstInside === "keep" && firstInSecond || config.firstInside === "remove" && !firstInSecond) {
2017
2051
  segmentsOut += 1;
2018
- segments2.push(...firstSegment);
2052
+ segments.push(...firstSegment);
2019
2053
  }
2020
- const secondSegmentPoint = curveMidPoint(secondSegment[0]);
2021
- const secondSegmentInFirstShape = first.isInside(secondSegmentPoint);
2022
- if (secondInside === "keep" && secondSegmentInFirstShape || secondInside === "remove" && !secondSegmentInFirstShape) {
2054
+ }
2055
+ const secondCurve = secondSegment[0];
2056
+ if (secondCurve !== void 0) {
2057
+ const secondSegmentPoint = curveMidPoint(secondCurve);
2058
+ const secondInFirst = first.isInside(secondSegmentPoint);
2059
+ if (config.secondInside === "keep" && secondInFirst || config.secondInside === "remove" && !secondInFirst) {
2023
2060
  let segmentsToAdd = secondSegment;
2024
2061
  if (segmentsOut === 1) {
2025
2062
  segmentsToAdd = reverseSegment(secondSegment);
2026
2063
  }
2027
2064
  segmentsOut += 1;
2028
- segments2.push(...segmentsToAdd);
2029
- }
2030
- if (segmentsIn === null && segmentsOut === 1 && lastWasSame) {
2031
- segments2 = [...lastWasSame, ...segments2];
2032
- }
2033
- if (segmentsOut === 1) {
2034
- segmentsIn = segmentsOut;
2035
- lastWasSame = null;
2065
+ segments.push(...segmentsToAdd);
2036
2066
  }
2037
- return segments2;
2067
+ }
2068
+ if (segmentsIn === null && segmentsOut === 1 && lastWasSame !== null) {
2069
+ segments = [...lastWasSame, ...segments];
2070
+ }
2071
+ const newSegmentsIn = segmentsOut === 1 ? segmentsOut : segmentsIn;
2072
+ const newLastWasSame = segmentsOut === 1 ? null : lastWasSame;
2073
+ return { curves: segments, segmentsIn: newSegmentsIn, lastWasSame: newLastWasSame };
2074
+ }
2075
+ function booleanOperation(first, second, config) {
2076
+ const segments = blueprintsIntersectionSegments(first, second);
2077
+ if (segments === null) {
2078
+ return buildNoIntersectionResult(first, second);
2079
+ }
2080
+ if (segments.every(([, secondSegment]) => secondSegment === "same")) {
2081
+ return { identical: true };
2082
+ }
2083
+ let segmentsIn = null;
2084
+ let lastWasSame = null;
2085
+ const assembledCurves = segments.flatMap(([firstSegment, secondSegment]) => {
2086
+ if (secondSegment === "same") {
2087
+ const result2 = handleSameSegment(firstSegment, segmentsIn, lastWasSame);
2088
+ segmentsIn = result2.segmentsIn;
2089
+ lastWasSame = result2.lastWasSame;
2090
+ return result2.curves;
2091
+ }
2092
+ const result = selectSegments(
2093
+ firstSegment,
2094
+ secondSegment,
2095
+ first,
2096
+ second,
2097
+ config,
2098
+ segmentsIn,
2099
+ lastWasSame
2100
+ );
2101
+ segmentsIn = result.segmentsIn;
2102
+ lastWasSame = result.lastWasSame;
2103
+ return result.curves;
2038
2104
  });
2039
- const paths = splitPaths(s).filter((b) => b.length).map((b) => new Blueprint(b));
2105
+ const paths = splitPaths(assembledCurves).filter((b) => b.length > 0).map((b) => new Blueprint(b));
2040
2106
  if (paths.length === 0) return null;
2041
- if (paths.length === 1) return paths[0];
2107
+ if (paths.length === 1) {
2108
+ const single = paths[0];
2109
+ if (single === void 0) return null;
2110
+ return single;
2111
+ }
2042
2112
  return organiseBlueprints(paths);
2043
2113
  }
2044
- const fuseBlueprints = (first, second) => {
2114
+ function buildNoIntersectionResult(first, second) {
2115
+ const firstCurve = first.curves[0];
2116
+ const secondCurve = second.curves[0];
2117
+ const firstCurveInSecond = firstCurve !== void 0 && second.isInside(curveMidPoint(firstCurve));
2118
+ const secondCurveInFirst = secondCurve !== void 0 && first.isInside(curveMidPoint(secondCurve));
2119
+ return {
2120
+ identical: false,
2121
+ firstCurveInSecond,
2122
+ secondCurveInFirst
2123
+ };
2124
+ }
2125
+ function fuseBlueprints(first, second) {
2045
2126
  const result = booleanOperation(first, second, {
2046
2127
  firstInside: "remove",
2047
2128
  secondInside: "remove"
@@ -2057,8 +2138,8 @@ const fuseBlueprints = (first, second) => {
2057
2138
  return first.clone();
2058
2139
  }
2059
2140
  return new Blueprints([first, second]);
2060
- };
2061
- const cutBlueprints = (first, second) => {
2141
+ }
2142
+ function cutBlueprints(first, second) {
2062
2143
  const result = booleanOperation(first, second, {
2063
2144
  firstInside: "remove",
2064
2145
  secondInside: "keep"
@@ -2074,8 +2155,8 @@ const cutBlueprints = (first, second) => {
2074
2155
  return new Blueprints([new CompoundBlueprint([first, second])]);
2075
2156
  }
2076
2157
  return first.clone();
2077
- };
2078
- const intersectBlueprints = (first, second) => {
2158
+ }
2159
+ function intersectBlueprints(first, second) {
2079
2160
  const result = booleanOperation(first, second, {
2080
2161
  firstInside: "keep",
2081
2162
  secondInside: "keep"
@@ -2091,7 +2172,7 @@ const intersectBlueprints = (first, second) => {
2091
2172
  return second.clone();
2092
2173
  }
2093
2174
  return null;
2094
- };
2175
+ }
2095
2176
  const genericIntersects = (first, second) => {
2096
2177
  if (first instanceof Blueprint && second instanceof Blueprint) {
2097
2178
  let allIntersections = [];