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.
- package/dist/2d/lib/offset.d.ts +1 -1
- package/dist/2d.cjs +6 -6
- package/dist/2d.js +6 -6
- package/dist/{adjacencyFns-54UTqtan.js → adjacencyFns--KmBIKYJ.js} +3 -3
- package/dist/{adjacencyFns-C_Vb1AeW.cjs → adjacencyFns-BFJfaUwX.cjs} +3 -3
- package/dist/{blueprint-DFpjcrlm.js → blueprint-BhIk9Pvm.js} +7 -8
- package/dist/{blueprint-D0VEeTzJ.cjs → blueprint-D83DDDPb.cjs} +7 -8
- package/dist/{blueprintFns-Douf5ZdX.cjs → blueprintFns-C4TQn-x_.cjs} +2 -2
- package/dist/{blueprintFns-Dye1jezD.js → blueprintFns-eULVgkq9.js} +2 -2
- package/dist/{blueprintSketcher-Cr0Lr1Dq.js → blueprintSketcher-D_Faf0GO.js} +32 -14
- package/dist/{blueprintSketcher-W9yON-UC.cjs → blueprintSketcher-Di376t-0.cjs} +32 -14
- package/dist/{boolean2D-C9VghXHG.cjs → boolean2D-CNZFI_y8.cjs} +4 -4
- package/dist/{boolean2D-CCV3R6lN.js → boolean2D-DqpCF72W.js} +4 -4
- package/dist/brepjs.cjs +128 -100
- package/dist/brepjs.js +128 -100
- package/dist/{cameraFns-DZOI_ewG.js → cameraFns-CrClIkmV.js} +3 -3
- package/dist/{cameraFns-x3L6dtA8.cjs → cameraFns-RqraCXIc.cjs} +3 -3
- package/dist/core.cjs +2 -2
- package/dist/core.js +2 -2
- package/dist/{cornerFinder-BY10jciz.js → cornerFinder-BZuQkRp2.js} +1 -1
- package/dist/{cornerFinder-B8PqO4um.cjs → cornerFinder-CdOe1Wrb.cjs} +1 -1
- package/dist/{curveFns-B4308HNM.js → curveFns-BP3srCfK.js} +1 -1
- package/dist/{curveFns-8lUhNquZ.cjs → curveFns-CyIvQNYT.cjs} +1 -1
- package/dist/{drawFns-C7zAvera.cjs → drawFns-71cOhWD-.cjs} +29 -16
- package/dist/{drawFns-DjnU3fEd.js → drawFns-DxDwJ5zZ.js} +29 -16
- package/dist/{faceFns-BjmlYcc9.cjs → faceFns-B7MiDYj1.cjs} +2 -2
- package/dist/{faceFns-7eHqNM0P.js → faceFns-iVPbTxki.js} +2 -2
- package/dist/{healingFns-BW7mZ_SW.js → healingFns-BhP8zfp1.js} +5 -5
- package/dist/{healingFns-CYLIcIWv.cjs → healingFns-DWljGTNR.cjs} +5 -5
- package/dist/{helpers-BQ6XMFcm.js → helpers-D2hANLRu.js} +14 -10
- package/dist/{helpers-De0Z_6iV.cjs → helpers-DKEVm6Hu.cjs} +14 -10
- package/dist/{importFns-BCL4-H8e.js → importFns-DUzIkns7.js} +2 -2
- package/dist/{importFns-3IMBHQrQ.cjs → importFns-KEPAgbjj.cjs} +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/io.cjs +2 -2
- package/dist/io.js +2 -2
- package/dist/kernel/bspline2dFit.d.ts +42 -0
- package/dist/kernel/geometry2d.d.ts +45 -0
- package/dist/kernel/occt/kernel2dOps.d.ts +1 -1
- package/dist/kernel/occtWasm/constructionOps.d.ts +1 -1
- package/dist/kernel/occtWasm/kernel2dOps.d.ts +7 -4
- package/dist/kernel/occtWasm/occtWasmAdapter.cjs +1 -1
- package/dist/kernel/occtWasm/occtWasmAdapter.d.ts +3 -3
- package/dist/kernel/occtWasm/occtWasmAdapter.js +1 -1
- package/dist/kernel/occtWasm/occtWasmTypes.d.ts +7 -0
- package/dist/{loftFns-DlG5oR0X.cjs → loftFns-D5AvfCGG.cjs} +17 -2
- package/dist/{loftFns-H4ZOpRAk.js → loftFns-D5NJpHLZ.js} +17 -2
- package/dist/{measureFns-Ce01LVwq.cjs → measureFns-CZHNWkiA.cjs} +102 -35
- package/dist/{measureFns-1IfNe629.js → measureFns-mxBdPiWa.js} +102 -35
- package/dist/measurement/measureFns.d.ts +3 -2
- package/dist/measurement.cjs +2 -2
- package/dist/measurement.js +2 -2
- package/dist/{meshFns-nEvfwNRF.cjs → meshFns-Czlt-GBJ.cjs} +3 -3
- package/dist/{meshFns-DaRZxa7B.js → meshFns-DmjMzNbH.js} +3 -3
- package/dist/{occtWasmAdapter-ipKUdNLN.js → occtWasmAdapter-B9d0Q0Vl.js} +1160 -261
- package/dist/{occtWasmAdapter-CRd9RFEu.cjs → occtWasmAdapter-DVxLkjlJ.cjs} +1171 -260
- package/dist/operations/patternFns.d.ts +6 -1
- package/dist/operations.cjs +2 -2
- package/dist/operations.js +2 -2
- package/dist/{originTrackingFns-zrksCJTa.js → originTrackingFns-CBnyKLm7.js} +2 -2
- package/dist/{originTrackingFns-9gyCMFFQ.cjs → originTrackingFns-CHymOLLm.cjs} +2 -2
- package/dist/{primitiveFns-BRXZ7jnP.cjs → primitiveFns-BVeBEaa4.cjs} +7 -14
- package/dist/{primitiveFns-ClQP6zMQ.js → primitiveFns-DeNl60Rw.js} +7 -14
- package/dist/{principalBoxFns-DsfWOoXg.js → principalBoxFns-3mW_hA6z.js} +3 -3
- package/dist/{principalBoxFns-tIv-crMB.cjs → principalBoxFns-CG6kG7vT.cjs} +3 -3
- package/dist/projection.cjs +1 -1
- package/dist/projection.js +1 -1
- package/dist/query/finderCore.d.ts +7 -7
- package/dist/query.cjs +2 -2
- package/dist/query.js +2 -2
- package/dist/{refResolveFns-BZdUlv9f.cjs → refResolveFns-BAs4p9z7.cjs} +6 -6
- package/dist/{refResolveFns-BG3JGs--.js → refResolveFns-DHx1Y3GG.js} +6 -6
- package/dist/{shapeFns-BbXqiNqY.cjs → shapeFns-C0w8vvfb.cjs} +2 -2
- package/dist/{shapeFns-BCgegapL.js → shapeFns-CgqKICF_.js} +2 -2
- package/dist/shapeRef.cjs +1 -1
- package/dist/shapeRef.js +1 -1
- package/dist/{shapeTypes-DJT3SnGK.cjs → shapeTypes-Dc7j-MYg.cjs} +10 -16
- package/dist/{shapeTypes-DAz6aAnJ.js → shapeTypes-DwvQUp9M.js} +10 -16
- package/dist/sketching/sketches.d.ts +2 -0
- package/dist/sketching.cjs +3 -3
- package/dist/sketching.js +3 -3
- package/dist/{solidBuilders-8xytLt8-.js → solidBuilders-DT1gpVWM.js} +5 -5
- package/dist/{solidBuilders-BWbqhbyv.cjs → solidBuilders-iS5DDwg8.cjs} +5 -5
- package/dist/{surfaceBuilders-CwG_RPdZ.cjs → surfaceBuilders-BvwSgD3C.cjs} +3 -3
- package/dist/{surfaceBuilders-BO96DMCH.js → surfaceBuilders-CPLe0bbk.js} +3 -3
- package/dist/text.cjs +2 -2
- package/dist/text.js +2 -2
- package/dist/{textBlueprints-Dhe8_Uvc.js → textBlueprints-BU0Rhk54.js} +19 -11
- package/dist/{textBlueprints-DHfYmLtP.cjs → textBlueprints-CNPCPJix.cjs} +19 -11
- package/dist/{textMetrics-CwYDH9js.cjs → textMetrics-C71g4TmO.cjs} +1 -1
- package/dist/{textMetrics-Bbf-Dllw.js → textMetrics-Df9aLYK2.js} +1 -1
- package/dist/{threadFns-B23RqugN.cjs → threadFns-CzXEnnmo.cjs} +37 -29
- package/dist/{threadFns-Cn7_bmux.js → threadFns-DESH7lcs.js} +36 -28
- package/dist/topology/apiTypes.d.ts +13 -7
- package/dist/topology.cjs +9 -9
- package/dist/topology.d.ts +1 -1
- package/dist/topology.js +9 -9
- package/dist/{topologyQueryFns-xh11vEQw.cjs → topologyQueryFns-5Frx8Y-0.cjs} +1 -1
- package/dist/{topologyQueryFns-BfYdGOJc.js → topologyQueryFns-LXV1nTaS.js} +1 -1
- package/dist/{validityTypes-CXkecqMw.cjs → validityTypes-C6rROGTP.cjs} +1 -1
- package/dist/{validityTypes-BfOuvfyp.js → validityTypes-DM-Iwe8N.js} +1 -1
- 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":
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
1102
|
+
case "trimmed": {
|
|
1103
|
+
const basis = scaleCurve2d$1(c.basis, factor, cx, cy);
|
|
1104
|
+
if (basis.__bk2d === "line") {
|
|
1105
|
+
const s = Math.abs(factor);
|
|
1106
|
+
return {
|
|
1107
|
+
...c,
|
|
1108
|
+
basis,
|
|
1109
|
+
tStart: c.tStart * s,
|
|
1110
|
+
tEnd: c.tEnd * s
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
if (basis.__bk2d === "circle" && factor < 0) {
|
|
1114
|
+
const shift = basis.sense ? Math.PI : -Math.PI;
|
|
1115
|
+
return {
|
|
1116
|
+
...c,
|
|
1117
|
+
basis,
|
|
1118
|
+
tStart: c.tStart + shift,
|
|
1119
|
+
tEnd: c.tEnd + shift
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
...c,
|
|
1124
|
+
basis
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
648
1127
|
}
|
|
649
1128
|
}
|
|
650
1129
|
function mirrorAtPoint(c, cx, cy) {
|
|
@@ -723,6 +1202,328 @@ function mirrorAcrossAxis(c, ox, oy, dx, dy) {
|
|
|
723
1202
|
}
|
|
724
1203
|
}
|
|
725
1204
|
}
|
|
1205
|
+
function applyAffine(m, x, y) {
|
|
1206
|
+
return [m.a * x + m.b * y + m.tx, m.c * x + m.d * y + m.ty];
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Image of the conic C + M0 * (cos psi, sin psi), psi = sense ? t : -t.
|
|
1210
|
+
* The closed-form 2x2 SVD M = R(phi) * diag(sx, sy) * R(theta) gives the new
|
|
1211
|
+
* frame (phi), radii (sx, |sy|) and the angular shift (theta); sy < 0 means the
|
|
1212
|
+
* map reflects, which flips the sense.
|
|
1213
|
+
*/
|
|
1214
|
+
function affineConic(cx, cy, m0, sense, m) {
|
|
1215
|
+
const [ncx, ncy] = applyAffine(m, cx, cy);
|
|
1216
|
+
const m00 = m.a * m0[0] + m.b * m0[2];
|
|
1217
|
+
const m01 = m.a * m0[1] + m.b * m0[3];
|
|
1218
|
+
const m10 = m.c * m0[0] + m.d * m0[2];
|
|
1219
|
+
const m11 = m.c * m0[1] + m.d * m0[3];
|
|
1220
|
+
const e = (m00 + m11) / 2;
|
|
1221
|
+
const f = (m00 - m11) / 2;
|
|
1222
|
+
const g = (m10 + m01) / 2;
|
|
1223
|
+
const h = (m10 - m01) / 2;
|
|
1224
|
+
const q = Math.hypot(e, h);
|
|
1225
|
+
const r = Math.hypot(f, g);
|
|
1226
|
+
const sx = q + r;
|
|
1227
|
+
const sy = q - r;
|
|
1228
|
+
const a1 = Math.atan2(g, f);
|
|
1229
|
+
const a2 = Math.atan2(h, e);
|
|
1230
|
+
const theta = (a2 - a1) / 2;
|
|
1231
|
+
const phi = (a2 + a1) / 2;
|
|
1232
|
+
const newSense = sy < 0 ? !sense : sense;
|
|
1233
|
+
const shift = sense ? theta : -theta;
|
|
1234
|
+
if (Math.min(q, r) <= 1e-12 * Math.max(q, r)) {
|
|
1235
|
+
const circleShift = shift + (newSense ? phi : -phi);
|
|
1236
|
+
return {
|
|
1237
|
+
curve: {
|
|
1238
|
+
__bk2d: "circle",
|
|
1239
|
+
cx: ncx,
|
|
1240
|
+
cy: ncy,
|
|
1241
|
+
radius: sx,
|
|
1242
|
+
sense: newSense
|
|
1243
|
+
},
|
|
1244
|
+
mapParam: (u) => u + circleShift
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
return {
|
|
1248
|
+
curve: {
|
|
1249
|
+
__bk2d: "ellipse",
|
|
1250
|
+
cx: ncx,
|
|
1251
|
+
cy: ncy,
|
|
1252
|
+
majorRadius: sx,
|
|
1253
|
+
minorRadius: Math.abs(sy),
|
|
1254
|
+
xDirAngle: phi,
|
|
1255
|
+
sense: newSense
|
|
1256
|
+
},
|
|
1257
|
+
mapParam: (u) => u + shift
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function affineBasis(c, m) {
|
|
1261
|
+
switch (c.__bk2d) {
|
|
1262
|
+
case "line": {
|
|
1263
|
+
const ldx = m.a * c.dx + m.b * c.dy;
|
|
1264
|
+
const ldy = m.c * c.dx + m.d * c.dy;
|
|
1265
|
+
const speed = Math.hypot(ldx, ldy);
|
|
1266
|
+
const [ox, oy] = applyAffine(m, c.ox, c.oy);
|
|
1267
|
+
if (speed < 1e-300) return {
|
|
1268
|
+
curve: {
|
|
1269
|
+
...c,
|
|
1270
|
+
ox,
|
|
1271
|
+
oy,
|
|
1272
|
+
len: 0
|
|
1273
|
+
},
|
|
1274
|
+
mapParam: () => 0
|
|
1275
|
+
};
|
|
1276
|
+
return {
|
|
1277
|
+
curve: {
|
|
1278
|
+
__bk2d: "line",
|
|
1279
|
+
ox,
|
|
1280
|
+
oy,
|
|
1281
|
+
dx: ldx / speed,
|
|
1282
|
+
dy: ldy / speed,
|
|
1283
|
+
len: c.len * speed
|
|
1284
|
+
},
|
|
1285
|
+
mapParam: (u) => u * speed
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
case "circle": return affineConic(c.cx, c.cy, [
|
|
1289
|
+
c.radius,
|
|
1290
|
+
0,
|
|
1291
|
+
0,
|
|
1292
|
+
c.radius
|
|
1293
|
+
], c.sense, m);
|
|
1294
|
+
case "ellipse": {
|
|
1295
|
+
const cos = Math.cos(c.xDirAngle);
|
|
1296
|
+
const sin = Math.sin(c.xDirAngle);
|
|
1297
|
+
const m0 = [
|
|
1298
|
+
c.majorRadius * cos,
|
|
1299
|
+
-c.minorRadius * sin,
|
|
1300
|
+
c.majorRadius * sin,
|
|
1301
|
+
c.minorRadius * cos
|
|
1302
|
+
];
|
|
1303
|
+
return affineConic(c.cx, c.cy, m0, c.sense, m);
|
|
1304
|
+
}
|
|
1305
|
+
case "bezier":
|
|
1306
|
+
case "bspline": return {
|
|
1307
|
+
curve: {
|
|
1308
|
+
...c,
|
|
1309
|
+
poles: c.poles.map(([x, y]) => applyAffine(m, x, y))
|
|
1310
|
+
},
|
|
1311
|
+
mapParam: (u) => u
|
|
1312
|
+
};
|
|
1313
|
+
case "trimmed": return {
|
|
1314
|
+
curve: affineCurve2d(c, m),
|
|
1315
|
+
mapParam: (u) => u
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Exact image of a curve under an affine map (OCCT `Geom2d_Curve::Transformed`
|
|
1321
|
+
* with a `gp_GTrsf2d`): lines stay lines, circles and ellipses become ellipses
|
|
1322
|
+
* (or circles), and pole-based curves map their poles.
|
|
1323
|
+
*/
|
|
1324
|
+
function affineCurve2d(c, m) {
|
|
1325
|
+
const flat = flattenTrimmed(c);
|
|
1326
|
+
if (flat.__bk2d !== "trimmed") return affineBasis(flat, m).curve;
|
|
1327
|
+
const { curve, mapParam } = affineBasis(flat.basis, m);
|
|
1328
|
+
return {
|
|
1329
|
+
__bk2d: "trimmed",
|
|
1330
|
+
basis: curve,
|
|
1331
|
+
tStart: mapParam(flat.tStart),
|
|
1332
|
+
tEnd: mapParam(flat.tEnd)
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
function unitTangent(c, t, first, last) {
|
|
1336
|
+
let [tx, ty] = tangentCurve2d(c, t);
|
|
1337
|
+
let len = Math.hypot(tx, ty);
|
|
1338
|
+
for (let k = 1; len < 1e-12 && k <= 6; k++) {
|
|
1339
|
+
const h = (last - first) * 10 ** (-9 + k);
|
|
1340
|
+
[tx, ty] = tangentCurve2d(c, t + h <= Math.max(first, last) ? t + h : t - h);
|
|
1341
|
+
len = Math.hypot(tx, ty);
|
|
1342
|
+
}
|
|
1343
|
+
return len < 1e-300 ? [1, 0] : [tx / len, ty / len];
|
|
1344
|
+
}
|
|
1345
|
+
function curveExtent(c) {
|
|
1346
|
+
const box = createBBox2d();
|
|
1347
|
+
addCurveToBBox(box, c, 0);
|
|
1348
|
+
return Math.hypot(box.xMax - box.xMin, box.yMax - box.yMin);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Parameters of `c`, in its own parameter range, at the interior knots of its
|
|
1352
|
+
* B-spline basis: where a fit of the curve (or of its offset) must sample.
|
|
1353
|
+
*/
|
|
1354
|
+
function curveKnotParams(c) {
|
|
1355
|
+
const flat = flattenTrimmed(c);
|
|
1356
|
+
const basis = flat.__bk2d === "trimmed" ? flat.basis : flat;
|
|
1357
|
+
if (basis.__bk2d !== "bspline") return [];
|
|
1358
|
+
const inner = basis.knots.slice(1, -1);
|
|
1359
|
+
if (flat.__bk2d !== "trimmed") return inner;
|
|
1360
|
+
const { tStart, tEnd } = flat;
|
|
1361
|
+
return inner.filter((k) => k > Math.min(tStart, tEnd) && k < Math.max(tStart, tEnd)).map((k) => (k - tStart) / (tEnd - tStart));
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Offset `distance` to the right of the direction of travel (OCCT
|
|
1365
|
+
* `Geom2d_OffsetCurve`: a counter-clockwise circle grows). Lines and circles
|
|
1366
|
+
* are offset exactly; other curves are fitted within `tolerance` (by default
|
|
1367
|
+
* relative to the curve's size).
|
|
1368
|
+
*/
|
|
1369
|
+
function offsetCurve2d$1(c, distance, tolerance) {
|
|
1370
|
+
const flat = flattenTrimmed(c);
|
|
1371
|
+
const trimmed = flat.__bk2d === "trimmed" ? flat : null;
|
|
1372
|
+
const basis = trimmed ? trimmed.basis : flat;
|
|
1373
|
+
const d = trimmed && trimmed.tEnd < trimmed.tStart ? -distance : distance;
|
|
1374
|
+
if (basis.__bk2d === "line") {
|
|
1375
|
+
const shifted = {
|
|
1376
|
+
...basis,
|
|
1377
|
+
ox: basis.ox + basis.dy * d,
|
|
1378
|
+
oy: basis.oy - basis.dx * d
|
|
1379
|
+
};
|
|
1380
|
+
return trimmed ? {
|
|
1381
|
+
...trimmed,
|
|
1382
|
+
basis: shifted
|
|
1383
|
+
} : shifted;
|
|
1384
|
+
}
|
|
1385
|
+
if (basis.__bk2d === "circle") {
|
|
1386
|
+
const radius = basis.radius + (basis.sense ? d : -d);
|
|
1387
|
+
if (Math.abs(radius) <= 1e-12 * Math.max(1, basis.radius)) throw new Error("offsetCurve2d: the offset collapses the circle to its center");
|
|
1388
|
+
const circle = {
|
|
1389
|
+
...basis,
|
|
1390
|
+
radius: Math.abs(radius)
|
|
1391
|
+
};
|
|
1392
|
+
if (!trimmed) return circle;
|
|
1393
|
+
if (radius > 0) return {
|
|
1394
|
+
...trimmed,
|
|
1395
|
+
basis: circle
|
|
1396
|
+
};
|
|
1397
|
+
const shift = basis.sense ? Math.PI : -Math.PI;
|
|
1398
|
+
return {
|
|
1399
|
+
...trimmed,
|
|
1400
|
+
basis: circle,
|
|
1401
|
+
tStart: trimmed.tStart + shift,
|
|
1402
|
+
tEnd: trimmed.tEnd + shift
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
const { first, last } = curveBounds(flat);
|
|
1406
|
+
return fitBSpline2d((t) => {
|
|
1407
|
+
const [px, py] = evaluateCurve2d$1(flat, t);
|
|
1408
|
+
const [ux, uy] = unitTangent(flat, t, first, last);
|
|
1409
|
+
return [px + uy * distance, py - ux * distance];
|
|
1410
|
+
}, first, last, tolerance ?? 1e-9 * Math.max(1, curveExtent(flat) + Math.abs(distance)), { breaks: curveKnotParams(flat) });
|
|
1411
|
+
}
|
|
1412
|
+
function sampleCurve(c, from, to, n) {
|
|
1413
|
+
const pts = [];
|
|
1414
|
+
for (let i = 0; i <= n; i++) pts.push(evaluateCurve2d$1(c, from + (to - from) * i / n));
|
|
1415
|
+
return pts;
|
|
1416
|
+
}
|
|
1417
|
+
/** Second derivative by central differences, kept inside [lo, hi] for clamped curves. */
|
|
1418
|
+
function secondDerivative(c, t, lo, hi) {
|
|
1419
|
+
const h = 1e-4 * Math.max(hi - lo, 1e-9);
|
|
1420
|
+
const mid = Math.min(hi - h, Math.max(lo + h, t));
|
|
1421
|
+
const [ax, ay] = evaluateCurve2d$1(c, mid - h);
|
|
1422
|
+
const [bx, by] = evaluateCurve2d$1(c, mid);
|
|
1423
|
+
const [cx, cy] = evaluateCurve2d$1(c, mid + h);
|
|
1424
|
+
return [(ax - 2 * bx + cx) / (h * h), (ay - 2 * by + cy) / (h * h)];
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Newton step for f(s, t) = |c1(s) - c2(t)|^2 / 2. The closest pair of two
|
|
1428
|
+
* disjoint curves has parallel tangents, so the curvature terms of the Hessian
|
|
1429
|
+
* are what make it converge; where the Hessian is not positive definite the
|
|
1430
|
+
* step falls back to Gauss-Newton, then to projecting c1(s) onto c2.
|
|
1431
|
+
*/
|
|
1432
|
+
function closestPairStep(c1, c2, s, t, box1, box2) {
|
|
1433
|
+
const [px, py] = evaluateCurve2d$1(c1, s);
|
|
1434
|
+
const [qx, qy] = evaluateCurve2d$1(c2, t);
|
|
1435
|
+
const dx = px - qx;
|
|
1436
|
+
const dy = py - qy;
|
|
1437
|
+
const t1 = tangentCurve2d(c1, s);
|
|
1438
|
+
const t2 = tangentCurve2d(c2, t);
|
|
1439
|
+
const g1 = t1[0] * dx + t1[1] * dy;
|
|
1440
|
+
const g2 = -(t2[0] * dx + t2[1] * dy);
|
|
1441
|
+
const a11 = t1[0] * t1[0] + t1[1] * t1[1];
|
|
1442
|
+
const a22 = t2[0] * t2[0] + t2[1] * t2[1];
|
|
1443
|
+
const a12 = -(t1[0] * t2[0] + t1[1] * t2[1]);
|
|
1444
|
+
const k1 = secondDerivative(c1, s, box1[0], box1[1]);
|
|
1445
|
+
const k2 = secondDerivative(c2, t, box2[0], box2[1]);
|
|
1446
|
+
const h11 = a11 + k1[0] * dx + k1[1] * dy;
|
|
1447
|
+
const h22 = a22 - (k2[0] * dx + k2[1] * dy);
|
|
1448
|
+
const detH = h11 * h22 - a12 * a12;
|
|
1449
|
+
if (h11 > 0 && detH > 1e-12 * Math.abs(h11 * h22)) return [(-g1 * h22 + g2 * a12) / detH, (-g2 * h11 + g1 * a12) / detH];
|
|
1450
|
+
const detGN = a11 * a22 - a12 * a12;
|
|
1451
|
+
if (detGN > 1e-12 * a11 * a22) return [(-g1 * a22 + g2 * a12) / detGN, (-g2 * a11 + g1 * a12) / detGN];
|
|
1452
|
+
if (a22 > 0) return [0, -g2 / a22];
|
|
1453
|
+
return a11 > 0 ? [-g1 / a11, 0] : [0, 0];
|
|
1454
|
+
}
|
|
1455
|
+
/** Refine a seed pair, halving each step until it lowers the distance; returns the squared distance. */
|
|
1456
|
+
function refineClosestPair(c1, c2, seed, box1, box2) {
|
|
1457
|
+
const clamp = (v, [lo, hi]) => Math.min(hi, Math.max(lo, v));
|
|
1458
|
+
const distSq = (a, b) => {
|
|
1459
|
+
const [px, py] = evaluateCurve2d$1(c1, a);
|
|
1460
|
+
const [qx, qy] = evaluateCurve2d$1(c2, b);
|
|
1461
|
+
return (px - qx) ** 2 + (py - qy) ** 2;
|
|
1462
|
+
};
|
|
1463
|
+
let [s, t] = seed;
|
|
1464
|
+
let f = distSq(s, t);
|
|
1465
|
+
for (let iter = 0; iter < 60 && f > 0; iter++) {
|
|
1466
|
+
const [ds, dt] = closestPairStep(c1, c2, s, t, box1, box2);
|
|
1467
|
+
let lambda = 1;
|
|
1468
|
+
let ns = s;
|
|
1469
|
+
let nt = t;
|
|
1470
|
+
let nf = f;
|
|
1471
|
+
for (; lambda > 1e-6 && nf >= f; lambda /= 2) {
|
|
1472
|
+
ns = clamp(s + lambda * ds, box1);
|
|
1473
|
+
nt = clamp(t + lambda * dt, box2);
|
|
1474
|
+
nf = distSq(ns, nt);
|
|
1475
|
+
}
|
|
1476
|
+
if (nf >= f || ns === s && nt === t) break;
|
|
1477
|
+
[s, t, f] = [
|
|
1478
|
+
ns,
|
|
1479
|
+
nt,
|
|
1480
|
+
nf
|
|
1481
|
+
];
|
|
1482
|
+
}
|
|
1483
|
+
return f;
|
|
1484
|
+
}
|
|
1485
|
+
/** Whether grid cell (i, j) is no larger than any of its eight neighbours. */
|
|
1486
|
+
function isGridMinimum(at, i, j, n) {
|
|
1487
|
+
const d = at(i, j);
|
|
1488
|
+
for (let ni = Math.max(0, i - 1); ni <= Math.min(n, i + 1); ni++) for (let nj = Math.max(0, j - 1); nj <= Math.min(n, j + 1); nj++) if (at(ni, nj) < d) return false;
|
|
1489
|
+
return true;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Minimum distance between c1 over [from1, to1] and c2 over [from2, to2]
|
|
1493
|
+
* (OCCT `Geom2dAPI_ExtremaCurveCurve`): every local minimum of a sampled
|
|
1494
|
+
* distance grid seeds a Gauss-Newton refinement.
|
|
1495
|
+
*/
|
|
1496
|
+
function distanceBetweenCurves2d$1(c1, c2, from1, to1, from2, to2) {
|
|
1497
|
+
const n = 32;
|
|
1498
|
+
const p1 = sampleCurve(c1, from1, to1, n);
|
|
1499
|
+
const p2 = sampleCurve(c2, from2, to2, n);
|
|
1500
|
+
const grid = /* @__PURE__ */ new Float64Array(1089);
|
|
1501
|
+
for (let i = 0; i <= n; i++) for (let j = 0; j <= n; j++) {
|
|
1502
|
+
const a = p1[i];
|
|
1503
|
+
const b = p2[j];
|
|
1504
|
+
grid[i * 33 + j] = (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;
|
|
1505
|
+
}
|
|
1506
|
+
const at = (i, j) => grid[i * 33 + j] ?? Infinity;
|
|
1507
|
+
const seeds = [];
|
|
1508
|
+
let best = Infinity;
|
|
1509
|
+
for (let i = 0; i <= n; i++) for (let j = 0; j <= n; j++) {
|
|
1510
|
+
const d = at(i, j);
|
|
1511
|
+
best = Math.min(best, d);
|
|
1512
|
+
if (isGridMinimum(at, i, j, n)) seeds.push({
|
|
1513
|
+
i,
|
|
1514
|
+
j,
|
|
1515
|
+
d
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
seeds.sort((x, y) => x.d - y.d);
|
|
1519
|
+
const box1 = [Math.min(from1, to1), Math.max(from1, to1)];
|
|
1520
|
+
const box2 = [Math.min(from2, to2), Math.max(from2, to2)];
|
|
1521
|
+
for (const { i, j } of seeds.slice(0, 8)) {
|
|
1522
|
+
const seed = [from1 + (to1 - from1) * i / n, from2 + (to2 - from2) * j / n];
|
|
1523
|
+
best = Math.min(best, refineClosestPair(c1, c2, seed, box1, box2));
|
|
1524
|
+
}
|
|
1525
|
+
return Math.sqrt(best);
|
|
1526
|
+
}
|
|
726
1527
|
/**
|
|
727
1528
|
* Compute intersection points (and overlapping segments) between two 2D curves.
|
|
728
1529
|
* Handles analytic cases (line-line, line-circle, circle-circle) and falls back
|
|
@@ -1046,61 +1847,136 @@ function createBBox2d() {
|
|
|
1046
1847
|
yMax: -Infinity
|
|
1047
1848
|
};
|
|
1048
1849
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1850
|
+
/** The first parameter congruent to `t` (mod 2*pi) inside [lo, hi], if any. */
|
|
1851
|
+
function periodicParamInRange(t, lo, hi) {
|
|
1852
|
+
const period = 2 * Math.PI;
|
|
1853
|
+
const tt = t + period * Math.ceil((lo - t) / period - 1e-12);
|
|
1854
|
+
return tt <= hi + 1e-12 ? tt : null;
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Basis parameters where a conic reaches its axis-aligned extremes. For
|
|
1858
|
+
* C + a*cos(psi)*U + b*sin(psi)*V (U at angle phi), dx/dpsi = 0 at
|
|
1859
|
+
* tan(psi) = -b*sin(phi) / (a*cos(phi)) and dy/dpsi = 0 at
|
|
1860
|
+
* tan(psi) = b*cos(phi) / (a*sin(phi)), each plus pi.
|
|
1861
|
+
*/
|
|
1862
|
+
function conicExtremeParams(c) {
|
|
1863
|
+
const [a, b, phi] = c.__bk2d === "circle" ? [
|
|
1864
|
+
c.radius,
|
|
1865
|
+
c.radius,
|
|
1866
|
+
0
|
|
1867
|
+
] : [
|
|
1868
|
+
c.majorRadius,
|
|
1869
|
+
c.minorRadius,
|
|
1870
|
+
c.xDirAngle
|
|
1871
|
+
];
|
|
1872
|
+
const psiX = Math.atan2(-b * Math.sin(phi), a * Math.cos(phi));
|
|
1873
|
+
const psiY = Math.atan2(b * Math.cos(phi), a * Math.sin(phi));
|
|
1874
|
+
return [
|
|
1875
|
+
psiX,
|
|
1876
|
+
psiX + Math.PI,
|
|
1877
|
+
psiY,
|
|
1878
|
+
psiY + Math.PI
|
|
1879
|
+
].map((psi) => c.sense ? psi : -psi);
|
|
1880
|
+
}
|
|
1881
|
+
function splitBezier1d(vals) {
|
|
1882
|
+
const work = [...vals];
|
|
1883
|
+
const n = work.length;
|
|
1884
|
+
const left = [];
|
|
1885
|
+
const right = [];
|
|
1886
|
+
left.push(work[0]);
|
|
1887
|
+
right.push(work[n - 1]);
|
|
1888
|
+
for (let r = 1; r < n; r++) {
|
|
1889
|
+
for (let i = 0; i < n - r; i++) work[i] = (work[i] + work[i + 1]) / 2;
|
|
1890
|
+
left.push(work[0]);
|
|
1891
|
+
right.push(work[n - 1 - r]);
|
|
1892
|
+
}
|
|
1893
|
+
return [left, right.reverse()];
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Widen `range` to the values a 1D Bezier takes. Its coefficients bound it
|
|
1897
|
+
* (convex hull), so a piece whose coefficients stay inside the range is done;
|
|
1898
|
+
* otherwise the piece is halved, adding the curve value at the split.
|
|
1899
|
+
*/
|
|
1900
|
+
function widenToBezier(vals, range, depth = 0) {
|
|
1901
|
+
const lo = Math.min(...vals);
|
|
1902
|
+
const hi = Math.max(...vals);
|
|
1903
|
+
const tol = 1e-12 * (1 + Math.abs(range.min) + Math.abs(range.max));
|
|
1904
|
+
if (lo >= range.min - tol && hi <= range.max + tol) return;
|
|
1905
|
+
if (depth >= 60) {
|
|
1906
|
+
range.min = Math.min(range.min, lo);
|
|
1907
|
+
range.max = Math.max(range.max, hi);
|
|
1092
1908
|
return;
|
|
1093
1909
|
}
|
|
1910
|
+
const [left, right] = splitBezier1d(vals);
|
|
1911
|
+
const mid = right[0] ?? lo;
|
|
1912
|
+
range.min = Math.min(range.min, mid);
|
|
1913
|
+
range.max = Math.max(range.max, mid);
|
|
1914
|
+
widenToBezier(left, range, depth + 1);
|
|
1915
|
+
widenToBezier(right, range, depth + 1);
|
|
1916
|
+
}
|
|
1917
|
+
/** Add the exact extent of a Bezier or B-spline over [lo, hi], span by span. */
|
|
1918
|
+
function addPoleCurveExtent(bbox, c, lo, hi) {
|
|
1919
|
+
const bs = asBSpline2d(c);
|
|
1920
|
+
const first = bs.knots[0] ?? 0;
|
|
1921
|
+
const last = bs.knots[bs.knots.length - 1] ?? 1;
|
|
1922
|
+
const a = Math.max(first, lo);
|
|
1923
|
+
const b = Math.min(last, hi);
|
|
1924
|
+
if (b <= a) return;
|
|
1925
|
+
const piece = a > first || b < last ? subBSpline2d(bs, a, b) : bs;
|
|
1926
|
+
const x = {
|
|
1927
|
+
min: bbox.xMin,
|
|
1928
|
+
max: bbox.xMax
|
|
1929
|
+
};
|
|
1930
|
+
const y = {
|
|
1931
|
+
min: bbox.yMin,
|
|
1932
|
+
max: bbox.yMax
|
|
1933
|
+
};
|
|
1934
|
+
for (const seg of bezierSegments2d(piece)) for (const [range, axis] of [[x, 0], [y, 1]]) {
|
|
1935
|
+
const vals = seg.map((pt) => pt[axis]);
|
|
1936
|
+
const ends = [vals[0] ?? range.min, vals[vals.length - 1] ?? range.min];
|
|
1937
|
+
range.min = Math.min(range.min, ...ends);
|
|
1938
|
+
range.max = Math.max(range.max, ...ends);
|
|
1939
|
+
widenToBezier(vals, range);
|
|
1940
|
+
}
|
|
1941
|
+
bbox.xMin = x.min;
|
|
1942
|
+
bbox.xMax = x.max;
|
|
1943
|
+
bbox.yMin = y.min;
|
|
1944
|
+
bbox.yMax = y.max;
|
|
1945
|
+
}
|
|
1946
|
+
function addCurveToBBox(bbox, c, _tol) {
|
|
1094
1947
|
const bounds = curveBounds(c);
|
|
1095
1948
|
if (!isFinite(bounds.first) || !isFinite(bounds.last)) return;
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1949
|
+
const addPoint = ([x, y]) => {
|
|
1950
|
+
bbox.xMin = Math.min(bbox.xMin, x);
|
|
1951
|
+
bbox.yMin = Math.min(bbox.yMin, y);
|
|
1952
|
+
bbox.xMax = Math.max(bbox.xMax, x);
|
|
1953
|
+
bbox.yMax = Math.max(bbox.yMax, y);
|
|
1954
|
+
};
|
|
1955
|
+
addPoint(evaluateCurve2d$1(c, bounds.first));
|
|
1956
|
+
addPoint(evaluateCurve2d$1(c, bounds.last));
|
|
1957
|
+
const flat = flattenTrimmed(c);
|
|
1958
|
+
const basis = flat.__bk2d === "trimmed" ? flat.basis : flat;
|
|
1959
|
+
const basisBounds = curveBounds(basis);
|
|
1960
|
+
const [from, to] = flat.__bk2d === "trimmed" ? [flat.tStart, flat.tEnd] : [basisBounds.first, basisBounds.last];
|
|
1961
|
+
const lo = Math.min(from, to);
|
|
1962
|
+
const hi = Math.max(from, to);
|
|
1963
|
+
const add = (t) => {
|
|
1964
|
+
addPoint(evaluateCurve2d$1(basis, t));
|
|
1965
|
+
};
|
|
1966
|
+
switch (basis.__bk2d) {
|
|
1967
|
+
case "line": return;
|
|
1968
|
+
case "circle":
|
|
1969
|
+
case "ellipse":
|
|
1970
|
+
for (const t of conicExtremeParams(basis)) {
|
|
1971
|
+
const tt = periodicParamInRange(t, lo, hi);
|
|
1972
|
+
if (tt !== null) add(tt);
|
|
1973
|
+
}
|
|
1974
|
+
return;
|
|
1975
|
+
case "bezier":
|
|
1976
|
+
case "bspline":
|
|
1977
|
+
addPoleCurveExtent(bbox, basis, lo, hi);
|
|
1978
|
+
return;
|
|
1979
|
+
case "trimmed": return;
|
|
1104
1980
|
}
|
|
1105
1981
|
}
|
|
1106
1982
|
function evaluateBezier(poles, t) {
|
|
@@ -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
|
-
|
|
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
|
|
4256
|
-
const
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4514
|
-
|
|
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
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
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
|
-
|
|
4546
|
-
|
|
4547
|
-
if (
|
|
4548
|
-
const
|
|
4549
|
-
|
|
4550
|
-
|
|
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
|
|
4679
|
-
const
|
|
4680
|
-
const [
|
|
4681
|
-
const [
|
|
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
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
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
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
const
|
|
4704
|
-
const
|
|
4705
|
-
|
|
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
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
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
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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);
|
|
@@ -6136,12 +7035,24 @@ Object.defineProperty(exports, "mirrorAtPoint", {
|
|
|
6136
7035
|
return mirrorAtPoint;
|
|
6137
7036
|
}
|
|
6138
7037
|
});
|
|
7038
|
+
Object.defineProperty(exports, "offsetCurve2d", {
|
|
7039
|
+
enumerable: true,
|
|
7040
|
+
get: function() {
|
|
7041
|
+
return offsetCurve2d$1;
|
|
7042
|
+
}
|
|
7043
|
+
});
|
|
6139
7044
|
Object.defineProperty(exports, "quickHull", {
|
|
6140
7045
|
enumerable: true,
|
|
6141
7046
|
get: function() {
|
|
6142
7047
|
return quickHull;
|
|
6143
7048
|
}
|
|
6144
7049
|
});
|
|
7050
|
+
Object.defineProperty(exports, "reverseCurve2d", {
|
|
7051
|
+
enumerable: true,
|
|
7052
|
+
get: function() {
|
|
7053
|
+
return reverseCurve2d$1;
|
|
7054
|
+
}
|
|
7055
|
+
});
|
|
6145
7056
|
Object.defineProperty(exports, "rotateCurve2d", {
|
|
6146
7057
|
enumerable: true,
|
|
6147
7058
|
get: function() {
|