@tscircuit/capacity-autorouter 0.0.51 → 0.0.53

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/index.js CHANGED
@@ -948,7 +948,11 @@ var getTunedTotalCapacity1 = (nodeOrWidth, maxCapacityFactor = 1) => {
948
948
  const obstacleMargin = 0.2;
949
949
  const width = "width" in nodeOrWidth ? nodeOrWidth.width : nodeOrWidth;
950
950
  const viaLengthAcross = width / (VIA_DIAMETER / 2 + obstacleMargin);
951
- return (viaLengthAcross / 2) ** 1.1 * maxCapacityFactor;
951
+ const tunedTotalCapacity = (viaLengthAcross / 2) ** 1.1 * maxCapacityFactor;
952
+ if (nodeOrWidth.availableZ?.length === 1 && tunedTotalCapacity > 1) {
953
+ return 1;
954
+ }
955
+ return tunedTotalCapacity;
952
956
  };
953
957
  var calculateOptimalCapacityDepth = (initialWidth, targetMinCapacity = 0.5, maxDepth = 16) => {
954
958
  let depth = 0;
@@ -969,6 +973,7 @@ var ObstacleSpatialHashIndex = class {
969
973
  constructor(obstacles) {
970
974
  this.obstacles = obstacles;
971
975
  this.buckets = /* @__PURE__ */ new Map();
976
+ let bucketEntriesCount = 0;
972
977
  for (let i = 0; i < obstacles.length; i++) {
973
978
  const obstacle = obstacles[i];
974
979
  const nodeMinX = obstacle.center.x - obstacle.width / 2;
@@ -983,6 +988,7 @@ var ObstacleSpatialHashIndex = class {
983
988
  this.buckets.set(bucketKey, [[obstacle, i]]);
984
989
  } else {
985
990
  bucket.push([obstacle, i]);
991
+ bucketEntriesCount++;
986
992
  }
987
993
  }
988
994
  }
@@ -3136,6 +3142,645 @@ var findCircleLineIntersections = (circle, line) => {
3136
3142
  return points;
3137
3143
  };
3138
3144
 
3145
+ // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/computeDumbbellPaths.ts
3146
+ function computeDumbbellPaths({
3147
+ A,
3148
+ B,
3149
+ C,
3150
+ D,
3151
+ E,
3152
+ F,
3153
+ radius,
3154
+ margin,
3155
+ subdivisions = 0
3156
+ }) {
3157
+ const midpoint = (p1, p2) => ({
3158
+ x: (p1.x + p2.x) / 2,
3159
+ y: (p1.y + p2.y) / 2
3160
+ });
3161
+ const calculatePoints = (a, b, r) => {
3162
+ const dx = b.x - a.x;
3163
+ const dy = b.y - a.y;
3164
+ const len = Math.sqrt(dx * dx + dy * dy);
3165
+ const ux = dx / len;
3166
+ const uy = dy / len;
3167
+ const px = -uy;
3168
+ const py = ux;
3169
+ return {
3170
+ midpoint: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 },
3171
+ A_Opp: { x: a.x - ux * r, y: a.y - uy * r },
3172
+ A_Right: { x: a.x + px * r, y: a.y + py * r },
3173
+ A_Left: { x: a.x - px * r, y: a.y - py * r },
3174
+ B_Opp: { x: b.x + ux * r, y: b.y + uy * r },
3175
+ B_Right: { x: b.x + px * r, y: b.y + py * r },
3176
+ B_Left: { x: b.x - px * r, y: b.y - py * r }
3177
+ };
3178
+ };
3179
+ const isPointOnSegment = (point, segment) => {
3180
+ const d1 = distance(point, segment.start);
3181
+ const d2 = distance(point, segment.end);
3182
+ const segmentLength = distance(segment.start, segment.end);
3183
+ const tolerance = 1e-4;
3184
+ return Math.abs(d1 + d2 - segmentLength) < tolerance;
3185
+ };
3186
+ const intersect = (l1, l2) => {
3187
+ const { start: p1, end: p2 } = l1;
3188
+ const { start: p3, end: p4 } = l2;
3189
+ if (isPointOnSegment(p1, l2) || isPointOnSegment(p2, l2) || isPointOnSegment(p3, l1) || isPointOnSegment(p4, l1)) {
3190
+ return true;
3191
+ }
3192
+ const d1x = p2.x - p1.x;
3193
+ const d1y = p2.y - p1.y;
3194
+ const d2x = p4.x - p3.x;
3195
+ const d2y = p4.y - p3.y;
3196
+ const det = d1x * d2y - d1y * d2x;
3197
+ if (Math.abs(det) < 1e-4) return false;
3198
+ const dx = p3.x - p1.x;
3199
+ const dy = p3.y - p1.y;
3200
+ const t = (dx * d2y - dy * d2x) / det;
3201
+ const u = (dx * d1y - dy * d1x) / det;
3202
+ return t > 0 && t < 1 && u > 0 && u < 1;
3203
+ };
3204
+ const doPathsIntersect = (path1, path2) => {
3205
+ const segments1 = [];
3206
+ for (let i = 0; i < path1.length - 1; i++) {
3207
+ segments1.push({ start: path1[i], end: path1[i + 1] });
3208
+ }
3209
+ const segments2 = [];
3210
+ for (let i = 0; i < path2.length - 1; i++) {
3211
+ segments2.push({ start: path2[i], end: path2[i + 1] });
3212
+ }
3213
+ for (const seg1 of segments1) {
3214
+ for (const seg2 of segments2) {
3215
+ if (intersect(seg1, seg2)) {
3216
+ return true;
3217
+ }
3218
+ }
3219
+ }
3220
+ return false;
3221
+ };
3222
+ const pathLength = (points) => {
3223
+ let len = 0;
3224
+ for (let i = 1; i < points.length; i++) {
3225
+ const dx = points[i].x - points[i - 1].x;
3226
+ const dy = points[i].y - points[i - 1].y;
3227
+ len += Math.sqrt(dx * dx + dy * dy);
3228
+ }
3229
+ return len;
3230
+ };
3231
+ const closestPointOnSegment = (segment, circleCenter) => {
3232
+ const { start, end } = segment;
3233
+ const dx = end.x - start.x;
3234
+ const dy = end.y - start.y;
3235
+ const segmentLengthSquared = dx * dx + dy * dy;
3236
+ if (segmentLengthSquared === 0) return { ...start, t: 0 };
3237
+ const t = Math.max(
3238
+ 0,
3239
+ Math.min(
3240
+ 1,
3241
+ ((circleCenter.x - start.x) * dx + (circleCenter.y - start.y) * dy) / segmentLengthSquared
3242
+ )
3243
+ );
3244
+ return {
3245
+ x: start.x + t * dx,
3246
+ y: start.y + t * dy,
3247
+ t
3248
+ // Keep track of the parameter for later use
3249
+ };
3250
+ };
3251
+ const getSubdivisionPoint = (segment, circleCenter, r) => {
3252
+ const closestPoint = closestPointOnSegment(segment, circleCenter);
3253
+ const dist = distance(closestPoint, circleCenter);
3254
+ if (dist >= r) return closestPoint;
3255
+ const dirX = closestPoint.x - circleCenter.x;
3256
+ const dirY = closestPoint.y - circleCenter.y;
3257
+ const norm = Math.sqrt(dirX * dirX + dirY * dirY);
3258
+ if (norm === 0) {
3259
+ const segDirX = segment.end.x - segment.start.x;
3260
+ const segDirY = segment.end.y - segment.start.y;
3261
+ const segNorm = Math.sqrt(segDirX * segDirX + segDirY * segDirY);
3262
+ return {
3263
+ x: circleCenter.x + r * segDirX / segNorm,
3264
+ y: circleCenter.y + r * segDirY / segNorm,
3265
+ t: closestPoint.t,
3266
+ isSpecial: true,
3267
+ specialType: circleCenter === A ? "A" : "B"
3268
+ };
3269
+ }
3270
+ return {
3271
+ x: circleCenter.x + r * dirX / norm,
3272
+ y: circleCenter.y + r * dirY / norm,
3273
+ t: closestPoint.t,
3274
+ isSpecial: true,
3275
+ specialType: circleCenter === A ? "A" : "B"
3276
+ };
3277
+ };
3278
+ const subdivideOptimalPath = (path, numSubdivisions) => {
3279
+ if (path.length < 2) return path;
3280
+ const result = [path[0]];
3281
+ for (let i = 0; i < path.length - 1; i++) {
3282
+ const segment = { start: path[i], end: path[i + 1] };
3283
+ const segmentMidpoint = {
3284
+ x: (segment.start.x + segment.end.x) / 2,
3285
+ y: (segment.start.y + segment.end.y) / 2
3286
+ };
3287
+ const midpointDistToA = distance(segmentMidpoint, A);
3288
+ const midpointDistToB = distance(segmentMidpoint, B);
3289
+ const shouldSubdivide = (midpointDistToA <= radius || midpointDistToB <= radius) && Math.abs(midpointDistToA - midpointDistToB) > 1e-4;
3290
+ if (shouldSubdivide) {
3291
+ const closestPointA = closestPointOnSegment(segment, A);
3292
+ const closestPointB = closestPointOnSegment(segment, B);
3293
+ const distToA = distance(closestPointA, A);
3294
+ const distToB = distance(closestPointB, B);
3295
+ const needsRadiusPointA = distToA < radius;
3296
+ const needsRadiusPointB = distToB < radius;
3297
+ const adjustedPointA = needsRadiusPointA ? getSubdivisionPoint(segment, A, radius) : null;
3298
+ const adjustedPointB = needsRadiusPointB ? getSubdivisionPoint(segment, B, radius) : null;
3299
+ let subdivisionPoints = [];
3300
+ const segmentLength = distance(segment.start, segment.end);
3301
+ if (segmentLength > radius / 2 && numSubdivisions > 0) {
3302
+ for (let j = 1; j <= numSubdivisions; j++) {
3303
+ const t = j / (numSubdivisions + 1);
3304
+ const subPoint = {
3305
+ x: segment.start.x + t * (segment.end.x - segment.start.x),
3306
+ y: segment.start.y + t * (segment.end.y - segment.start.y),
3307
+ t,
3308
+ isSpecial: false
3309
+ };
3310
+ const subDistToA = distance(subPoint, A);
3311
+ const subDistToB = distance(subPoint, B);
3312
+ if (subDistToA < radius || subDistToB < radius) {
3313
+ continue;
3314
+ }
3315
+ if (adjustedPointA && Math.abs(subPoint.t - adjustedPointA.t) < 0.1) {
3316
+ continue;
3317
+ }
3318
+ if (adjustedPointB && Math.abs(subPoint.t - adjustedPointB.t) < 0.1) {
3319
+ continue;
3320
+ }
3321
+ subdivisionPoints.push(subPoint);
3322
+ }
3323
+ }
3324
+ if (adjustedPointA) {
3325
+ subdivisionPoints.push(adjustedPointA);
3326
+ }
3327
+ if (adjustedPointB) {
3328
+ subdivisionPoints.push(adjustedPointB);
3329
+ }
3330
+ subdivisionPoints.sort((a, b) => a.t - b.t);
3331
+ if (subdivisionPoints.length > 1) {
3332
+ const filteredPoints = [subdivisionPoints[0]];
3333
+ for (let j = 1; j < subdivisionPoints.length; j++) {
3334
+ const prev = filteredPoints[filteredPoints.length - 1];
3335
+ const curr = subdivisionPoints[j];
3336
+ if (distance(prev, curr) > radius / 10) {
3337
+ filteredPoints.push(curr);
3338
+ }
3339
+ }
3340
+ subdivisionPoints = filteredPoints;
3341
+ }
3342
+ subdivisionPoints.forEach((p) => result.push(p));
3343
+ }
3344
+ result.push(path[i + 1]);
3345
+ }
3346
+ if (result.length > 1) {
3347
+ const filteredResult = [result[0]];
3348
+ for (let i = 1; i < result.length; i++) {
3349
+ const prev = filteredResult[filteredResult.length - 1];
3350
+ const curr = result[i];
3351
+ if (distance(prev, curr) > radius / 10) {
3352
+ filteredResult.push(curr);
3353
+ }
3354
+ }
3355
+ return filteredResult;
3356
+ }
3357
+ return result;
3358
+ };
3359
+ const innerPoints = calculatePoints(A, B, radius);
3360
+ const outerPoints = calculatePoints(A, B, radius + margin);
3361
+ const getPaths = () => [
3362
+ // Path 1: C→B_Left→B_Opp→B_Right→Mid→A_Left→A_Opp→A_Right→D
3363
+ [
3364
+ C,
3365
+ innerPoints.B_Left,
3366
+ innerPoints.B_Opp,
3367
+ innerPoints.B_Right,
3368
+ midpoint(
3369
+ innerPoints.midpoint,
3370
+ midpoint(innerPoints.B_Right, innerPoints.A_Right)
3371
+ ),
3372
+ midpoint(
3373
+ innerPoints.midpoint,
3374
+ midpoint(innerPoints.A_Left, innerPoints.B_Left)
3375
+ ),
3376
+ innerPoints.A_Left,
3377
+ innerPoints.A_Opp,
3378
+ innerPoints.A_Right,
3379
+ D
3380
+ ],
3381
+ // Path 2: C→B_Right→B_Opp→B_Left→Mid→A_Right→A_Opp→A_Left→D
3382
+ [
3383
+ C,
3384
+ innerPoints.B_Right,
3385
+ innerPoints.B_Opp,
3386
+ innerPoints.B_Left,
3387
+ midpoint(
3388
+ innerPoints.midpoint,
3389
+ midpoint(innerPoints.A_Left, innerPoints.B_Left)
3390
+ ),
3391
+ midpoint(
3392
+ innerPoints.midpoint,
3393
+ midpoint(innerPoints.A_Right, innerPoints.B_Right)
3394
+ ),
3395
+ innerPoints.A_Right,
3396
+ innerPoints.A_Opp,
3397
+ innerPoints.A_Left,
3398
+ D
3399
+ ],
3400
+ // Path 3: D→B_Left→B_Opp→B_Right→Mid→A_Left→A_Opp→A_Right→C
3401
+ [
3402
+ D,
3403
+ innerPoints.B_Left,
3404
+ innerPoints.B_Opp,
3405
+ innerPoints.B_Right,
3406
+ midpoint(
3407
+ innerPoints.midpoint,
3408
+ midpoint(innerPoints.A_Right, innerPoints.B_Right)
3409
+ ),
3410
+ midpoint(
3411
+ innerPoints.midpoint,
3412
+ midpoint(innerPoints.A_Left, innerPoints.B_Left)
3413
+ ),
3414
+ innerPoints.A_Left,
3415
+ innerPoints.A_Opp,
3416
+ innerPoints.A_Right,
3417
+ C
3418
+ ],
3419
+ // Path 4: D→B_Right→B_Opp→B_Left→Mid→A_Right→A_Opp→A_Left→C
3420
+ [
3421
+ D,
3422
+ innerPoints.B_Right,
3423
+ innerPoints.B_Opp,
3424
+ innerPoints.B_Left,
3425
+ midpoint(
3426
+ innerPoints.midpoint,
3427
+ midpoint(innerPoints.A_Left, innerPoints.B_Left)
3428
+ ),
3429
+ midpoint(
3430
+ innerPoints.midpoint,
3431
+ midpoint(innerPoints.A_Right, innerPoints.B_Right)
3432
+ ),
3433
+ innerPoints.A_Right,
3434
+ innerPoints.A_Opp,
3435
+ innerPoints.A_Left,
3436
+ C
3437
+ ]
3438
+ ];
3439
+ const getJLines = () => {
3440
+ const mid_AR_BR = midpoint(innerPoints.A_Right, innerPoints.B_Right);
3441
+ const mid_AL_BL = midpoint(innerPoints.B_Left, innerPoints.A_Left);
3442
+ return [
3443
+ /*───────────────────────── Shortest (straight) ─────────────────────────*/
3444
+ { startsAt: "E", goesTo: "B", points: [E, B] },
3445
+ { startsAt: "E", goesTo: "A", points: [E, A] },
3446
+ { startsAt: "F", goesTo: "B", points: [F, B] },
3447
+ { startsAt: "F", goesTo: "A", points: [F, A] },
3448
+ /*───────────────────────── One‑bend variants ───────────────────────────*/
3449
+ // …via the (AR ↔ BR) right‑side midpoint
3450
+ { startsAt: "E", goesTo: "B", points: [E, mid_AR_BR, B] },
3451
+ { startsAt: "E", goesTo: "A", points: [E, mid_AR_BR, A] },
3452
+ { startsAt: "F", goesTo: "B", points: [F, mid_AR_BR, B] },
3453
+ { startsAt: "F", goesTo: "A", points: [F, mid_AR_BR, A] },
3454
+ // …via the (AL ↔ BL) left‑side midpoint
3455
+ { startsAt: "E", goesTo: "B", points: [E, mid_AL_BL, B] },
3456
+ { startsAt: "E", goesTo: "A", points: [E, mid_AL_BL, A] },
3457
+ { startsAt: "F", goesTo: "B", points: [F, mid_AL_BL, B] },
3458
+ { startsAt: "F", goesTo: "A", points: [F, mid_AL_BL, A] },
3459
+ /*───────────────────────── Medium (one outer waypoint) ─────────────────*/
3460
+ // right‑side outer arc
3461
+ {
3462
+ startsAt: "E",
3463
+ goesTo: "B",
3464
+ points: [E, outerPoints.A_Right, mid_AR_BR, B]
3465
+ },
3466
+ {
3467
+ startsAt: "F",
3468
+ goesTo: "B",
3469
+ points: [F, outerPoints.B_Right, mid_AR_BR, B]
3470
+ },
3471
+ // left‑side outer arc
3472
+ {
3473
+ startsAt: "E",
3474
+ goesTo: "A",
3475
+ points: [E, outerPoints.B_Left, mid_AL_BL, A]
3476
+ },
3477
+ {
3478
+ startsAt: "F",
3479
+ goesTo: "A",
3480
+ points: [F, outerPoints.A_Left, mid_AL_BL, A]
3481
+ },
3482
+ // criss‑cross outer arc
3483
+ {
3484
+ startsAt: "E",
3485
+ goesTo: "B",
3486
+ points: [E, outerPoints.A_Left, mid_AL_BL, B]
3487
+ },
3488
+ {
3489
+ startsAt: "E",
3490
+ goesTo: "A",
3491
+ points: [E, outerPoints.B_Right, mid_AR_BR, A]
3492
+ },
3493
+ /*───────────────────────── Long (two outer waypoints) ──────────────────*/
3494
+ {
3495
+ startsAt: "E",
3496
+ goesTo: "B",
3497
+ points: [E, outerPoints.A_Opp, outerPoints.A_Right, mid_AR_BR, B]
3498
+ },
3499
+ {
3500
+ startsAt: "E",
3501
+ goesTo: "A",
3502
+ points: [E, outerPoints.B_Opp, outerPoints.B_Left, mid_AL_BL, A]
3503
+ },
3504
+ {
3505
+ startsAt: "F",
3506
+ goesTo: "B",
3507
+ points: [F, outerPoints.A_Opp, outerPoints.A_Left, mid_AL_BL, B]
3508
+ },
3509
+ {
3510
+ startsAt: "F",
3511
+ goesTo: "A",
3512
+ points: [F, outerPoints.B_Opp, outerPoints.B_Right, mid_AR_BR, A]
3513
+ },
3514
+ {
3515
+ startsAt: "F",
3516
+ goesTo: "A",
3517
+ points: [F, outerPoints.B_Opp, outerPoints.B_Left, mid_AL_BL, A]
3518
+ },
3519
+ {
3520
+ startsAt: "E",
3521
+ goesTo: "B",
3522
+ points: [E, outerPoints.A_Opp, outerPoints.A_Left, mid_AL_BL, B]
3523
+ },
3524
+ {
3525
+ startsAt: "E",
3526
+ goesTo: "A",
3527
+ points: [E, outerPoints.B_Opp, outerPoints.B_Right, mid_AR_BR, A]
3528
+ },
3529
+ /*───────────────────────── Longest (three outer waypoints) ─────────────*/
3530
+ {
3531
+ startsAt: "E",
3532
+ goesTo: "B",
3533
+ points: [
3534
+ E,
3535
+ outerPoints.A_Left,
3536
+ outerPoints.A_Opp,
3537
+ outerPoints.A_Right,
3538
+ mid_AR_BR,
3539
+ B
3540
+ ]
3541
+ },
3542
+ {
3543
+ startsAt: "E",
3544
+ goesTo: "A",
3545
+ points: [
3546
+ E,
3547
+ outerPoints.B_Right,
3548
+ outerPoints.B_Opp,
3549
+ outerPoints.B_Left,
3550
+ mid_AL_BL,
3551
+ A
3552
+ ]
3553
+ },
3554
+ {
3555
+ startsAt: "F",
3556
+ goesTo: "B",
3557
+ points: [
3558
+ F,
3559
+ outerPoints.A_Right,
3560
+ outerPoints.A_Opp,
3561
+ outerPoints.A_Left,
3562
+ mid_AL_BL,
3563
+ B
3564
+ ]
3565
+ },
3566
+ {
3567
+ startsAt: "F",
3568
+ goesTo: "A",
3569
+ points: [
3570
+ F,
3571
+ outerPoints.B_Left,
3572
+ outerPoints.B_Opp,
3573
+ outerPoints.B_Right,
3574
+ mid_AR_BR,
3575
+ A
3576
+ ]
3577
+ },
3578
+ {
3579
+ startsAt: "F",
3580
+ goesTo: "A",
3581
+ points: [
3582
+ F,
3583
+ outerPoints.B_Right,
3584
+ outerPoints.B_Opp,
3585
+ outerPoints.B_Left,
3586
+ mid_AL_BL,
3587
+ A
3588
+ ]
3589
+ },
3590
+ {
3591
+ startsAt: "E",
3592
+ goesTo: "B",
3593
+ points: [
3594
+ E,
3595
+ outerPoints.A_Right,
3596
+ outerPoints.A_Opp,
3597
+ outerPoints.A_Left,
3598
+ mid_AL_BL,
3599
+ B
3600
+ ]
3601
+ },
3602
+ {
3603
+ startsAt: "E",
3604
+ goesTo: "A",
3605
+ points: [
3606
+ E,
3607
+ outerPoints.B_Left,
3608
+ outerPoints.B_Opp,
3609
+ outerPoints.B_Right,
3610
+ mid_AR_BR,
3611
+ A
3612
+ ]
3613
+ }
3614
+ ].map((l, index) => ({ ...l, index }));
3615
+ };
3616
+ const subdivideJLinePath = (jLine, oppositePoint, r, m, numSubdivisions) => {
3617
+ const path = jLine.points;
3618
+ if (path.length < 2) return path;
3619
+ const minDistThreshold = r + m;
3620
+ const result = [path[0]];
3621
+ for (let i = 0; i < path.length - 1; i++) {
3622
+ const segment = { start: path[i], end: path[i + 1] };
3623
+ const distToOpposite = pointToSegmentDistance(
3624
+ oppositePoint,
3625
+ segment.start,
3626
+ segment.end
3627
+ );
3628
+ if (distToOpposite < minDistThreshold) {
3629
+ const closestPt = closestPointOnSegment(segment, oppositePoint);
3630
+ const dirX = closestPt.x - oppositePoint.x;
3631
+ const dirY = closestPt.y - oppositePoint.y;
3632
+ const norm = Math.sqrt(dirX * dirX + dirY * dirY);
3633
+ let adjustedPoint = null;
3634
+ if (norm > 1e-6) {
3635
+ adjustedPoint = {
3636
+ x: oppositePoint.x + minDistThreshold * dirX / norm,
3637
+ y: oppositePoint.y + minDistThreshold * dirY / norm
3638
+ // We might need 't' if combining with regular subdivisions,
3639
+ // but for now, just the adjusted point is needed.
3640
+ // t: closestPt.t
3641
+ };
3642
+ } else {
3643
+ const segDirX = segment.end.x - segment.start.x;
3644
+ const segDirY = segment.end.y - segment.start.y;
3645
+ const segNorm = Math.sqrt(segDirX * segDirX + segDirY * segDirY);
3646
+ if (segNorm > 1e-6) {
3647
+ adjustedPoint = {
3648
+ x: oppositePoint.x + minDistThreshold * segDirX / segNorm,
3649
+ y: oppositePoint.y + minDistThreshold * segDirY / segNorm
3650
+ };
3651
+ } else {
3652
+ }
3653
+ }
3654
+ if (adjustedPoint) {
3655
+ if (distance(segment.start, adjustedPoint) > radius / 10) {
3656
+ result.push(adjustedPoint);
3657
+ }
3658
+ }
3659
+ }
3660
+ const lastPointInResult = result[result.length - 1];
3661
+ if (distance(lastPointInResult, segment.end) > radius / 10) {
3662
+ result.push(segment.end);
3663
+ }
3664
+ }
3665
+ if (result.length > 1) {
3666
+ const filteredResult = [result[0]];
3667
+ for (let i = 1; i < result.length; i++) {
3668
+ if (distance(filteredResult[filteredResult.length - 1], result[i]) > radius / 10) {
3669
+ filteredResult.push(result[i]);
3670
+ }
3671
+ }
3672
+ return filteredResult;
3673
+ }
3674
+ return result;
3675
+ };
3676
+ const findOptimalPath = () => {
3677
+ const paths = getPaths();
3678
+ const validPaths = [];
3679
+ for (let i = 0; i < paths.length; i++) {
3680
+ const path2 = paths[i];
3681
+ const firstSeg = { start: path2[0], end: path2[1] };
3682
+ const lastSeg = {
3683
+ start: path2[path2.length - 2],
3684
+ end: path2[path2.length - 1]
3685
+ };
3686
+ const midSeg = { start: path2[3], end: path2[4] };
3687
+ if (!intersect(firstSeg, lastSeg) && !intersect(firstSeg, midSeg) && !intersect(lastSeg, midSeg)) {
3688
+ validPaths.push({
3689
+ index: i + 1,
3690
+ path: path2,
3691
+ length: pathLength(path2)
3692
+ });
3693
+ }
3694
+ }
3695
+ if (validPaths.length === 0) {
3696
+ return { index: 0, path: [] };
3697
+ }
3698
+ const optimalPath2 = validPaths.sort((a, b) => a.length - b.length)[0];
3699
+ const path = [...optimalPath2.path];
3700
+ const firstPoint = path[0];
3701
+ const dist3 = distance(firstPoint, path[2]);
3702
+ const dist4 = distance(firstPoint, path[3]);
3703
+ const closerIdx = dist3 < dist4 ? 2 : 3;
3704
+ if (dist3 < distance(firstPoint, path[1]) || dist4 < distance(firstPoint, path[1])) {
3705
+ path.splice(1, closerIdx - 1);
3706
+ }
3707
+ const lastPoint = path[path.length - 1];
3708
+ const distM3 = distance(lastPoint, path[path.length - 3]);
3709
+ const distM4 = distance(lastPoint, path[path.length - 4]);
3710
+ const closerLastIdx = distM3 < distM4 ? path.length - 3 : path.length - 4;
3711
+ if (distM3 < distance(lastPoint, path[path.length - 2]) || distM4 < distance(lastPoint, path[path.length - 2])) {
3712
+ path.splice(closerLastIdx + 1, path.length - closerLastIdx - 2);
3713
+ }
3714
+ return {
3715
+ index: optimalPath2.index,
3716
+ path,
3717
+ startsAt: path[0] === C ? "C" : "D",
3718
+ goesTo: path[path.length - 1] === C ? "C" : "D"
3719
+ };
3720
+ };
3721
+ const optimalPath = findOptimalPath();
3722
+ const subdivided = subdivisions > 0 ? subdivideOptimalPath(optimalPath.path, subdivisions) : optimalPath.path;
3723
+ const findJPair = () => {
3724
+ if (optimalPath.path.length === 0) return null;
3725
+ const jLines = getJLines();
3726
+ const minDistFromAB = radius + margin / 2;
3727
+ const eLines = jLines.filter((line) => line.startsAt === "E");
3728
+ const fLines = jLines.filter((line) => line.startsAt === "F");
3729
+ const nonIntersectingELines = [];
3730
+ const nonIntersectingFLines = [];
3731
+ for (const jLine of eLines) {
3732
+ if (doPathsIntersect(jLine.points, optimalPath.path)) continue;
3733
+ nonIntersectingELines.push(jLine);
3734
+ break;
3735
+ }
3736
+ for (const jLine of fLines) {
3737
+ if (doPathsIntersect(jLine.points, optimalPath.path)) continue;
3738
+ nonIntersectingFLines.push(jLine);
3739
+ break;
3740
+ }
3741
+ if (nonIntersectingELines.length === 0 || nonIntersectingFLines.length === 0) {
3742
+ return null;
3743
+ }
3744
+ return {
3745
+ line1: nonIntersectingELines[0],
3746
+ line2: nonIntersectingFLines[0]
3747
+ };
3748
+ };
3749
+ let jPair = findJPair();
3750
+ if (jPair) {
3751
+ const oppositePoint1 = jPair.line1.goesTo === "A" ? B : A;
3752
+ const oppositePoint2 = jPair.line2.goesTo === "A" ? B : A;
3753
+ const subdividedPoints1 = subdivideJLinePath(
3754
+ jPair.line1,
3755
+ oppositePoint1,
3756
+ radius,
3757
+ margin,
3758
+ subdivisions
3759
+ // Use same subdivision count for consistency? Or 0? Let's use 0 for now.
3760
+ );
3761
+ const subdividedPoints2 = subdivideJLinePath(
3762
+ jPair.line2,
3763
+ oppositePoint2,
3764
+ radius,
3765
+ margin,
3766
+ subdivisions
3767
+ // Use same subdivision count for consistency? Or 0? Let's use 0 for now.
3768
+ );
3769
+ jPair = {
3770
+ line1: { ...jPair.line1, points: subdividedPoints1 },
3771
+ line2: { ...jPair.line2, points: subdividedPoints2 }
3772
+ };
3773
+ }
3774
+ return {
3775
+ jPair,
3776
+ optimalPath: {
3777
+ startsAt: optimalPath.startsAt,
3778
+ goesTo: optimalPath.goesTo,
3779
+ points: subdivided
3780
+ }
3781
+ };
3782
+ }
3783
+
3139
3784
  // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/TwoCrossingRoutesHighDensitySolver.ts
3140
3785
  var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3141
3786
  // Input parameters
@@ -3147,6 +3792,7 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3147
3792
  obstacleMargin;
3148
3793
  layerCount = 2;
3149
3794
  debugViaPositions;
3795
+ escapeLayer = 1;
3150
3796
  // Solution state
3151
3797
  solvedRoutes = [];
3152
3798
  // Bounds
@@ -3181,6 +3827,11 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3181
3827
  this.failed = true;
3182
3828
  return;
3183
3829
  }
3830
+ if (routeA.startPort.z === 0) {
3831
+ this.escapeLayer = 1;
3832
+ } else {
3833
+ this.escapeLayer = 0;
3834
+ }
3184
3835
  }
3185
3836
  /**
3186
3837
  * Extract routes that need to be connected from the node data
@@ -3350,78 +4001,168 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3350
4001
  via2
3351
4002
  };
3352
4003
  }
3353
- /**
3354
- * Create a route with properly placed vias
3355
- */
3356
- createRoute(start, end, via1, via2, connectionName) {
3357
- const middleZ = start.z === 0 ? 1 : 0;
3358
- const route = [
3359
- { x: start.x, y: start.y, z: start.z ?? 0 },
3360
- { x: via1.x, y: via1.y, z: start.z ?? 0 },
3361
- { x: via1.x, y: via1.y, z: middleZ },
3362
- // Via transition to layer 1
3363
- { x: via2.x, y: via2.y, z: middleZ },
3364
- // Stay on layer 1
3365
- { x: via2.x, y: via2.y, z: end.z ?? 0 },
3366
- // Via transition back
3367
- { x: end.x, y: end.y, z: end.z ?? 0 }
3368
- ];
3369
- return {
3370
- connectionName,
3371
- route,
3372
- traceThickness: this.traceThickness,
3373
- viaDiameter: this.viaDiameter,
3374
- vias: [via1, via2]
3375
- };
3376
- }
3377
4004
  /**
3378
4005
  * Try to solve with routeA going over and routeB staying on layer 0
3379
4006
  */
3380
- trySolveAOverB(routeA, routeB) {
3381
- const viaPositions = this.calculateViaPositions(routeA, routeB);
4007
+ trySolveAOverB(routeA, routeB, swapVias = false) {
4008
+ const viaPositions = swapVias ? this.calculateViaPositions(routeA, routeB) : this.calculateViaPositions(routeB, routeA);
3382
4009
  if (viaPositions) {
3383
4010
  this.debugViaPositions.push(viaPositions);
3384
4011
  } else {
3385
4012
  return false;
3386
4013
  }
3387
- const { via1, via2 } = this.optimizeViaPositions(viaPositions);
3388
- const routeASolution = this.createRoute(
3389
- routeA.startPort,
3390
- routeA.endPort,
3391
- via1,
3392
- via2,
3393
- routeA.connectionName
3394
- );
3395
- const midSegmentStart = { x: via1.x, y: via1.y, z: 1 };
3396
- const midSegmentEnd = { x: via2.x, y: via2.y, z: 1 };
3397
- const orthogonalPoints = this.calculateShortestOrthogonalRoutePoints(
3398
- routeB.startPort,
3399
- routeB.endPort,
3400
- midSegmentStart,
3401
- midSegmentEnd,
3402
- routeA.startPort,
3403
- routeA.endPort
3404
- ) ?? this.calculateConservativeOrthogonalRoutePoints(
3405
- routeB.startPort,
3406
- routeB.endPort,
3407
- midSegmentStart,
3408
- midSegmentEnd,
3409
- routeA.startPort,
3410
- routeA.endPort
4014
+ const { via1, via2 } = this.pushViasFromEndpoints(
4015
+ this.moveViasAsCloseAsPossible(viaPositions)
3411
4016
  );
4017
+ this.debugViaPositions.push({ via1, via2 });
4018
+ const NOT_CIRCULAR_PENALTY_TC = 1.5;
4019
+ const { jPair, optimalPath } = computeDumbbellPaths({
4020
+ A: via1,
4021
+ B: via2,
4022
+ C: routeA.startPort,
4023
+ D: routeA.endPort,
4024
+ E: routeB.startPort,
4025
+ F: routeB.endPort,
4026
+ // NOTE: Should be traceThickness /2, but we don't currently subdivide
4027
+ // enough to make a round enough circle, so we have to add additional margin
4028
+ radius: this.viaDiameter / 2 + this.obstacleMargin + this.traceThickness / 2 * NOT_CIRCULAR_PENALTY_TC,
4029
+ margin: this.obstacleMargin * 2 + this.traceThickness / 2 * NOT_CIRCULAR_PENALTY_TC,
4030
+ subdivisions: 1
4031
+ });
4032
+ if (!jPair) return false;
4033
+ const routeASolution = {
4034
+ connectionName: routeA.connectionName,
4035
+ route: optimalPath.points.map((p) => ({
4036
+ x: p.x,
4037
+ y: p.y,
4038
+ z: routeA.startPort.z ?? 0
4039
+ })),
4040
+ traceThickness: this.traceThickness,
4041
+ viaDiameter: this.viaDiameter,
4042
+ vias: []
4043
+ };
4044
+ jPair.line2.points.reverse();
3412
4045
  const routeBSolution = {
3413
4046
  connectionName: routeB.connectionName,
3414
- route: orthogonalPoints,
4047
+ route: [
4048
+ ...jPair.line1.points.map((p) => ({
4049
+ x: p.x,
4050
+ y: p.y,
4051
+ z: routeB.startPort.z ?? 0
4052
+ })),
4053
+ {
4054
+ ...jPair.line1.points[jPair.line1.points.length - 1],
4055
+ z: this.escapeLayer
4056
+ },
4057
+ { ...jPair.line2.points[0], z: this.escapeLayer },
4058
+ ...jPair.line2.points.map((p) => ({
4059
+ x: p.x,
4060
+ y: p.y,
4061
+ z: routeB.startPort.z ?? 0
4062
+ }))
4063
+ ],
3415
4064
  traceThickness: this.traceThickness,
3416
4065
  viaDiameter: this.viaDiameter,
3417
- vias: []
4066
+ vias: [via1, via2]
3418
4067
  };
3419
4068
  this.solvedRoutes.push(routeASolution, routeBSolution);
3420
4069
  return true;
3421
4070
  }
3422
- optimizeViaPositions(viaPositions) {
4071
+ pushViasFromEndpoints(viaPositions) {
4072
+ const currentVia1 = { ...viaPositions.via1 };
4073
+ const currentVia2 = { ...viaPositions.via2 };
4074
+ const endpoints = [
4075
+ this.routes[0].startPort,
4076
+ this.routes[0].endPort,
4077
+ this.routes[1].startPort,
4078
+ this.routes[1].endPort
4079
+ ];
4080
+ const optimalDistBtwViaCenters = this.getMinDistanceBetweenViaCenters();
4081
+ const minDistanceBtwViaAndEndpoint = this.viaDiameter / 2 + this.traceThickness * 2 + this.obstacleMargin * 2;
4082
+ const MAX_ITERS = 10;
4083
+ const PUSH_DECAY = 0.9;
4084
+ for (let iter = 0; iter < MAX_ITERS; iter++) {
4085
+ let via1Moved = false;
4086
+ let via2Moved = false;
4087
+ const pushDecayFactor = PUSH_DECAY ** iter;
4088
+ for (const endpoint of endpoints) {
4089
+ const dist1 = distance(currentVia1, endpoint);
4090
+ if (dist1 < minDistanceBtwViaAndEndpoint) {
4091
+ const overlap = minDistanceBtwViaAndEndpoint - dist1;
4092
+ const pushAmount = overlap * pushDecayFactor;
4093
+ const dx = currentVia1.x - endpoint.x;
4094
+ const dy = currentVia1.y - endpoint.y;
4095
+ const norm = Math.sqrt(dx * dx + dy * dy);
4096
+ if (norm > 1e-6) {
4097
+ currentVia1.x += dx / norm * pushAmount;
4098
+ currentVia1.y += dy / norm * pushAmount;
4099
+ via1Moved = true;
4100
+ }
4101
+ }
4102
+ const dist2 = distance(currentVia2, endpoint);
4103
+ if (dist2 < minDistanceBtwViaAndEndpoint) {
4104
+ const overlap = minDistanceBtwViaAndEndpoint - dist2;
4105
+ const pushAmount = overlap * pushDecayFactor;
4106
+ const dx = currentVia2.x - endpoint.x;
4107
+ const dy = currentVia2.y - endpoint.y;
4108
+ const norm = Math.sqrt(dx * dx + dy * dy);
4109
+ if (norm > 1e-6) {
4110
+ currentVia2.x += dx / norm * pushAmount;
4111
+ currentVia2.y += dy / norm * pushAmount;
4112
+ via2Moved = true;
4113
+ }
4114
+ }
4115
+ }
4116
+ const distBetweenVias = distance(currentVia1, currentVia2);
4117
+ if (distBetweenVias < optimalDistBtwViaCenters) {
4118
+ const overlap = optimalDistBtwViaCenters - distBetweenVias;
4119
+ const pushAmount = overlap / 2;
4120
+ const dx = currentVia2.x - currentVia1.x;
4121
+ const dy = currentVia2.y - currentVia1.y;
4122
+ const norm = Math.sqrt(dx * dx + dy * dy);
4123
+ if (norm > 1e-6) {
4124
+ currentVia1.x -= dx / norm * pushAmount;
4125
+ currentVia1.y -= dy / norm * pushAmount;
4126
+ currentVia2.x += dx / norm * pushAmount;
4127
+ currentVia2.y += dy / norm * pushAmount;
4128
+ via1Moved = true;
4129
+ via2Moved = true;
4130
+ } else {
4131
+ currentVia1.x -= pushAmount;
4132
+ currentVia2.x += pushAmount;
4133
+ via1Moved = true;
4134
+ via2Moved = true;
4135
+ }
4136
+ }
4137
+ if (!via1Moved && !via2Moved) {
4138
+ break;
4139
+ }
4140
+ }
4141
+ const finalDist = distance(currentVia1, currentVia2);
4142
+ if (finalDist < optimalDistBtwViaCenters) {
4143
+ const overlap = optimalDistBtwViaCenters - finalDist;
4144
+ const pushAmount = overlap / 2;
4145
+ const dx = currentVia2.x - currentVia1.x;
4146
+ const dy = currentVia2.y - currentVia1.y;
4147
+ const norm = Math.sqrt(dx * dx + dy * dy);
4148
+ if (norm > 1e-6) {
4149
+ currentVia1.x -= dx / norm * pushAmount;
4150
+ currentVia1.y -= dy / norm * pushAmount;
4151
+ currentVia2.x += dx / norm * pushAmount;
4152
+ currentVia2.y += dy / norm * pushAmount;
4153
+ } else {
4154
+ currentVia1.x -= pushAmount;
4155
+ currentVia2.x += pushAmount;
4156
+ }
4157
+ }
4158
+ return { via1: currentVia1, via2: currentVia2 };
4159
+ }
4160
+ getMinDistanceBetweenViaCenters() {
4161
+ return this.viaDiameter + this.traceThickness + this.obstacleMargin * 2;
4162
+ }
4163
+ moveViasAsCloseAsPossible(viaPositions) {
3423
4164
  const { via1, via2 } = viaPositions;
3424
- const minRequiredDistance = (this.viaDiameter + this.traceThickness + this.obstacleMargin) * 2;
4165
+ const minRequiredDistance = this.getMinDistanceBetweenViaCenters();
3425
4166
  const currentDistance = distance(via1, via2);
3426
4167
  if (currentDistance <= minRequiredDistance) {
3427
4168
  return viaPositions;
@@ -3447,129 +4188,47 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3447
4188
  via2: newVia2
3448
4189
  };
3449
4190
  }
3450
- /**
3451
- * Calculate the orthogonal route points for the second route
3452
- */
3453
- calculateConservativeOrthogonalRoutePoints(start, end, via1, via2, otherRouteStart, otherRouteEnd) {
3454
- const outerBox = {
3455
- width: this.bounds.maxX - this.bounds.minX,
3456
- height: this.bounds.maxY - this.bounds.minY,
3457
- x: this.bounds.minX,
3458
- y: this.bounds.minY
3459
- };
3460
- const innerEdgeBox = {
3461
- width: outerBox.width - 2 * this.obstacleMargin - this.traceThickness,
3462
- height: outerBox.height - 2 * this.obstacleMargin - this.traceThickness,
3463
- x: outerBox.x + this.obstacleMargin + this.traceThickness / 2,
3464
- y: outerBox.y + this.obstacleMargin + this.traceThickness / 2
3465
- };
3466
- const midSegmentDX = via2.x - via1.x;
3467
- const midSegmentDY = via2.y - via1.y;
3468
- const orthDX = -midSegmentDY;
3469
- const orthDY = midSegmentDX;
3470
- const orthLength = Math.sqrt(orthDX * orthDX + orthDY * orthDY);
3471
- const normOrthDX = orthDX / orthLength;
3472
- const normOrthDY = orthDY / orthLength;
3473
- const midpointX = (via1.x + via2.x) / 2;
3474
- const midpointY = (via1.y + via2.y) / 2;
3475
- const calculateIntersections = () => {
3476
- const intersections2 = [];
3477
- const leftT = (innerEdgeBox.x - midpointX) / normOrthDX;
3478
- const leftY = midpointY + leftT * normOrthDY;
3479
- if (leftY >= innerEdgeBox.y && leftY <= innerEdgeBox.y + innerEdgeBox.height) {
3480
- intersections2.push({ x: innerEdgeBox.x, y: leftY });
3481
- }
3482
- const rightT = (innerEdgeBox.x + innerEdgeBox.width - midpointX) / normOrthDX;
3483
- const rightY = midpointY + rightT * normOrthDY;
3484
- if (rightY >= innerEdgeBox.y && rightY <= innerEdgeBox.y + innerEdgeBox.height) {
3485
- intersections2.push({
3486
- x: innerEdgeBox.x + innerEdgeBox.width,
3487
- y: rightY
3488
- });
3489
- }
3490
- const topT = (innerEdgeBox.y - midpointY) / normOrthDY;
3491
- const topX = midpointX + topT * normOrthDX;
3492
- if (topX >= innerEdgeBox.x && topX <= innerEdgeBox.x + innerEdgeBox.width) {
3493
- intersections2.push({ x: topX, y: innerEdgeBox.y });
3494
- }
3495
- const bottomT = (innerEdgeBox.y + innerEdgeBox.height - midpointY) / normOrthDY;
3496
- const bottomX = midpointX + bottomT * normOrthDX;
3497
- if (bottomX >= innerEdgeBox.x && bottomX <= innerEdgeBox.x + innerEdgeBox.width) {
3498
- intersections2.push({
3499
- x: bottomX,
3500
- y: innerEdgeBox.y + innerEdgeBox.height
3501
- });
3502
- }
3503
- return intersections2;
4191
+ handleRoutesDontCross() {
4192
+ const [routeA, routeB] = this.routes;
4193
+ const routeASolution = {
4194
+ connectionName: routeA.connectionName,
4195
+ route: [
4196
+ {
4197
+ x: routeA.startPort.x,
4198
+ y: routeA.startPort.y,
4199
+ z: routeA.startPort.z ?? 0
4200
+ },
4201
+ {
4202
+ x: routeA.endPort.x,
4203
+ y: routeA.endPort.y,
4204
+ z: routeA.endPort.z ?? 0
4205
+ }
4206
+ ],
4207
+ traceThickness: this.traceThickness,
4208
+ viaDiameter: this.viaDiameter,
4209
+ vias: []
3504
4210
  };
3505
- const intersections = calculateIntersections();
3506
- if (intersections.length < 2) {
3507
- return [
3508
- { x: start.x, y: start.y, z: start.z ?? 0 },
3509
- { x: end.x, y: end.y, z: end.z ?? 0 }
3510
- ];
3511
- }
3512
- const sortedIntersections = [...intersections].sort((a, b) => {
3513
- const distA = distance(a, start);
3514
- const distB = distance(b, start);
3515
- return distA - distB;
3516
- });
3517
- let middlePoint1 = sortedIntersections[0];
3518
- let middlePoint2 = sortedIntersections[intersections.length - 1];
3519
- if (doSegmentsIntersect(start, middlePoint1, otherRouteStart, via1) || doSegmentsIntersect(end, middlePoint2, otherRouteEnd, via2)) {
3520
- ;
3521
- [middlePoint1, middlePoint2] = [middlePoint2, middlePoint1];
3522
- }
3523
- return [
3524
- { x: start.x, y: start.y, z: start.z ?? 0 },
3525
- { x: middlePoint1.x, y: middlePoint1.y, z: start.z ?? 0 },
3526
- { x: middlePoint2.x, y: middlePoint2.y, z: start.z ?? 0 },
3527
- { x: end.x, y: end.y, z: end.z ?? 0 }
3528
- ];
3529
- }
3530
- calculateShortestOrthogonalRoutePoints(start, end, via1, via2, otherRouteStart, otherRouteEnd) {
3531
- const midSegmentCenter = {
3532
- x: (via1.x + via2.x) / 2,
3533
- y: (via1.y + via2.y) / 2
4211
+ const routeBSolution = {
4212
+ connectionName: routeB.connectionName,
4213
+ route: [
4214
+ {
4215
+ x: routeB.startPort.x,
4216
+ y: routeB.startPort.y,
4217
+ z: routeB.startPort.z ?? 0
4218
+ },
4219
+ {
4220
+ x: routeB.endPort.x,
4221
+ y: routeB.endPort.y,
4222
+ z: routeB.endPort.z ?? 0
4223
+ }
4224
+ ],
4225
+ traceThickness: this.traceThickness,
4226
+ viaDiameter: this.viaDiameter,
4227
+ vias: []
3534
4228
  };
3535
- const midSegmentDirection = {
3536
- x: via2.x - via1.x,
3537
- y: via2.y - via1.y
3538
- };
3539
- const midSegmentLength = distance(via1, via2);
3540
- const normOrthDX = midSegmentDirection.y / midSegmentLength;
3541
- const normOrthDY = midSegmentDirection.x / midSegmentLength;
3542
- let orthogonalPoint1 = {
3543
- x: midSegmentCenter.x + midSegmentLength / 2 * normOrthDY,
3544
- y: midSegmentCenter.y - midSegmentLength / 2 * normOrthDX
3545
- };
3546
- let orthogonalPoint2 = {
3547
- x: midSegmentCenter.x - midSegmentLength / 2 * normOrthDY,
3548
- y: midSegmentCenter.y + midSegmentLength / 2 * normOrthDX
3549
- };
3550
- if (distance(orthogonalPoint2, start) < distance(orthogonalPoint1, start)) {
3551
- ;
3552
- [orthogonalPoint1, orthogonalPoint2] = [
3553
- orthogonalPoint2,
3554
- orthogonalPoint1
3555
- ];
3556
- }
3557
- if (doSegmentsIntersect(start, orthogonalPoint1, otherRouteStart, via1) || doSegmentsIntersect(end, orthogonalPoint2, otherRouteEnd, via2)) {
3558
- ;
3559
- [orthogonalPoint1, orthogonalPoint2] = [
3560
- orthogonalPoint2,
3561
- orthogonalPoint1
3562
- ];
3563
- }
3564
- if (doSegmentsIntersect(start, orthogonalPoint2, otherRouteStart, via1) || doSegmentsIntersect(end, orthogonalPoint1, otherRouteEnd, via2)) {
3565
- return null;
3566
- }
3567
- return [
3568
- { x: start.x, y: start.y, z: start.z ?? 0 },
3569
- { x: orthogonalPoint1.x, y: orthogonalPoint1.y, z: start.z ?? 0 },
3570
- { x: orthogonalPoint2.x, y: orthogonalPoint2.y, z: start.z ?? 0 },
3571
- { x: end.x, y: end.y, z: end.z ?? 0 }
3572
- ];
4229
+ this.solvedRoutes.push(routeASolution, routeBSolution);
4230
+ this.solved = true;
4231
+ return;
3573
4232
  }
3574
4233
  /**
3575
4234
  * Main step method that attempts to solve the two crossing routes
@@ -3581,44 +4240,7 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3581
4240
  }
3582
4241
  const [routeA, routeB] = this.routes;
3583
4242
  if (!this.doRoutesCross(routeA, routeB)) {
3584
- const routeASolution = {
3585
- connectionName: routeA.connectionName,
3586
- route: [
3587
- {
3588
- x: routeA.startPort.x,
3589
- y: routeA.startPort.y,
3590
- z: routeA.startPort.z ?? 0
3591
- },
3592
- {
3593
- x: routeA.endPort.x,
3594
- y: routeA.endPort.y,
3595
- z: routeA.endPort.z ?? 0
3596
- }
3597
- ],
3598
- traceThickness: this.traceThickness,
3599
- viaDiameter: this.viaDiameter,
3600
- vias: []
3601
- };
3602
- const routeBSolution = {
3603
- connectionName: routeB.connectionName,
3604
- route: [
3605
- {
3606
- x: routeB.startPort.x,
3607
- y: routeB.startPort.y,
3608
- z: routeB.startPort.z ?? 0
3609
- },
3610
- {
3611
- x: routeB.endPort.x,
3612
- y: routeB.endPort.y,
3613
- z: routeB.endPort.z ?? 0
3614
- }
3615
- ],
3616
- traceThickness: this.traceThickness,
3617
- viaDiameter: this.viaDiameter,
3618
- vias: []
3619
- };
3620
- this.solvedRoutes.push(routeASolution, routeBSolution);
3621
- this.solved = true;
4243
+ this.handleRoutesDontCross();
3622
4244
  return;
3623
4245
  }
3624
4246
  if (this.trySolveAOverB(routeA, routeB)) {
@@ -3629,6 +4251,14 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3629
4251
  this.solved = true;
3630
4252
  return;
3631
4253
  }
4254
+ if (this.trySolveAOverB(routeA, routeB, true)) {
4255
+ this.solved = true;
4256
+ return;
4257
+ }
4258
+ if (this.trySolveAOverB(routeB, routeA, true)) {
4259
+ this.solved = true;
4260
+ return;
4261
+ }
3632
4262
  this.failed = true;
3633
4263
  }
3634
4264
  /**
@@ -3649,44 +4279,49 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3649
4279
  width: this.bounds.maxX - this.bounds.minX,
3650
4280
  height: this.bounds.maxY - this.bounds.minY,
3651
4281
  stroke: "rgba(0, 0, 0, 0.5)",
3652
- fill: "rgba(240, 240, 240, 0.1)",
3653
- label: "PCB Bounds"
4282
+ fill: "rgba(240, 240, 240, 0.1)"
3654
4283
  });
3655
- for (const route of this.routes) {
4284
+ for (const [routeName, route] of [
4285
+ ["Route A", this.routes[0]],
4286
+ ["Route B", this.routes[1]]
4287
+ ]) {
3656
4288
  graphics.points.push({
3657
4289
  x: route.startPort.x,
3658
4290
  y: route.startPort.y,
3659
- label: `${route.connectionName} start`,
4291
+ label: `${routeName}
4292
+ ${route.connectionName} start`,
3660
4293
  color: "orange"
3661
4294
  });
3662
4295
  graphics.points.push({
3663
4296
  x: route.endPort.x,
3664
4297
  y: route.endPort.y,
3665
- label: `${route.connectionName} end`,
4298
+ label: `${routeName}
4299
+ ${route.connectionName} end`,
3666
4300
  color: "orange"
3667
4301
  });
3668
4302
  graphics.lines.push({
3669
4303
  points: [route.startPort, route.endPort],
3670
4304
  strokeColor: "rgba(255, 0, 0, 0.5)",
3671
- label: `${route.connectionName} direct`
4305
+ label: `${routeName}
4306
+ ${route.connectionName} direct`
3672
4307
  });
3673
4308
  }
3674
4309
  for (let i = 0; i < this.debugViaPositions.length; i++) {
3675
4310
  const { via1, via2 } = this.debugViaPositions[i];
3676
- const colors = ["rgba(255, 165, 0, 0.7)", "rgba(128, 0, 128, 0.7)"];
4311
+ const colors = ["rgba(255, 165, 0, 0.3)", "rgba(128, 0, 128, 0.3)"];
3677
4312
  const color = colors[i % colors.length];
3678
4313
  graphics.circles.push({
3679
4314
  center: via1,
3680
4315
  radius: this.viaDiameter / 2,
3681
4316
  fill: color,
3682
- stroke: "rgba(0, 0, 0, 0.5)",
4317
+ stroke: "rgba(0, 0, 0, 0.3)",
3683
4318
  label: `Computed Via A (attempt ${i + 1})`
3684
4319
  });
3685
4320
  graphics.circles.push({
3686
4321
  center: via2,
3687
4322
  radius: this.viaDiameter / 2,
3688
4323
  fill: color,
3689
- stroke: "rgba(0, 0, 0, 0.5)",
4324
+ stroke: "rgba(0, 0, 0, 0.3)",
3690
4325
  label: `Computed Via B (attempt ${i + 1})`
3691
4326
  });
3692
4327
  const safetyMargin = this.viaDiameter / 2 + this.obstacleMargin;
@@ -3695,14 +4330,14 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3695
4330
  radius: safetyMargin,
3696
4331
  stroke: color,
3697
4332
  fill: "rgba(0, 0, 0, 0)",
3698
- label: "Safety Margin"
4333
+ label: `Debug Via 1 Safety Margin (attempt ${i + 1})`
3699
4334
  });
3700
4335
  graphics.circles.push({
3701
4336
  center: via2,
3702
4337
  radius: safetyMargin,
3703
4338
  stroke: color,
3704
4339
  fill: "rgba(0, 0, 0, 0)",
3705
- label: "Safety Margin"
4340
+ label: `Debug Via 2 Safety Margin (attempt ${i + 1})`
3706
4341
  });
3707
4342
  graphics.lines.push({
3708
4343
  points: [
@@ -3729,6 +4364,13 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3729
4364
  strokeWidth: route.traceThickness,
3730
4365
  label: `${route.connectionName} z=${pointA.z}`
3731
4366
  });
4367
+ if (pointA._label) {
4368
+ graphics.points.push({
4369
+ x: pointA.x,
4370
+ y: pointA.y,
4371
+ label: pointA._label
4372
+ });
4373
+ }
3732
4374
  }
3733
4375
  for (const via of route.vias) {
3734
4376
  graphics.circles.push({
@@ -3743,7 +4385,7 @@ var TwoCrossingRoutesHighDensitySolver = class extends BaseSolver {
3743
4385
  radius: this.viaDiameter / 2 + this.obstacleMargin,
3744
4386
  fill: "rgba(0, 0, 255, 0.3)",
3745
4387
  stroke: "black",
3746
- label: "Via Margin"
4388
+ label: "Solved Via Margin"
3747
4389
  });
3748
4390
  }
3749
4391
  }
@@ -4030,20 +4672,17 @@ function distance2(p1, p2) {
4030
4672
 
4031
4673
  // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/calculateSideTraversal.ts
4032
4674
  var EPSILON2 = 1e-9;
4033
- function calculateSegmentTraversal(startPoint, endPoint, bounds) {
4675
+ function calculateSegmentTraversal(startPoint, endPoint, bounds, turnDirection = "cw") {
4034
4676
  const startAngle = pointToAngle(startPoint, bounds);
4035
- let endAngle = pointToAngle(endPoint, bounds);
4036
- if (endAngle < startAngle) {
4037
- endAngle += 2 * Math.PI;
4038
- }
4677
+ const endAngle = pointToAngle(endPoint, bounds);
4039
4678
  if (Math.abs(endAngle - startAngle) < EPSILON2) {
4040
4679
  return { left: 0, top: 0, right: 0, bottom: 0 };
4041
4680
  }
4042
- return calculateSidePercentages(startAngle, endAngle, bounds);
4681
+ return calculateSidePercentages(startAngle, endAngle, bounds, turnDirection);
4043
4682
  }
4044
- function calculateTraversalPercentages(A, B, C, bounds) {
4045
- const percentagesAB = calculateSegmentTraversal(A, B, bounds);
4046
- const percentagesBC = calculateSegmentTraversal(B, C, bounds);
4683
+ function calculateTraversalPercentages(A, B, C, bounds, turnDirection) {
4684
+ const percentagesAB = calculateSegmentTraversal(A, B, bounds, turnDirection);
4685
+ const percentagesBC = calculateSegmentTraversal(B, C, bounds, turnDirection);
4047
4686
  const totalPercentages = {
4048
4687
  left: Math.min(1, percentagesAB.left + percentagesBC.left),
4049
4688
  top: Math.min(1, percentagesAB.top + percentagesBC.top),
@@ -4080,7 +4719,7 @@ function pointToAngle(point, bounds) {
4080
4719
  distance6 = Math.max(0, Math.min(perimeter, distance6));
4081
4720
  return perimeter > EPSILON2 ? distance6 / perimeter * (2 * Math.PI) : 0;
4082
4721
  }
4083
- function calculateSidePercentages(startAngle, endAngle, bounds) {
4722
+ function calculateSidePercentages(startAngle, endAngle, bounds, turnDirection) {
4084
4723
  const width = bounds.maxX - bounds.minX;
4085
4724
  const height = bounds.maxY - bounds.minY;
4086
4725
  if (width < EPSILON2 && height < EPSILON2)
@@ -4104,17 +4743,54 @@ function calculateSidePercentages(startAngle, endAngle, bounds) {
4104
4743
  // Ends at 2PI
4105
4744
  ];
4106
4745
  const result = { left: 0, top: 0, right: 0, bottom: 0 };
4107
- const totalAngleTraversal = endAngle - startAngle;
4108
- if (totalAngleTraversal < EPSILON2) return result;
4746
+ const calculateTraversalOverlap = (sStart, sEnd, tStart, tEnd, wrapsAround) => {
4747
+ const effectiveSEnd = sEnd > 2 * Math.PI - EPSILON2 ? 2 * Math.PI : sEnd;
4748
+ if (effectiveSEnd <= sStart + EPSILON2) return 0;
4749
+ if (!wrapsAround) {
4750
+ const overlapStart = Math.max(sStart, tStart);
4751
+ const overlapEnd = Math.min(effectiveSEnd, tEnd);
4752
+ return Math.max(0, overlapEnd - overlapStart);
4753
+ } else {
4754
+ const overlap1Start = Math.max(sStart, tStart);
4755
+ const overlap1End = Math.min(effectiveSEnd, 2 * Math.PI);
4756
+ const overlap1 = Math.max(0, overlap1End - overlap1Start);
4757
+ const overlap2Start = Math.max(sStart, 0);
4758
+ const overlap2End = Math.min(effectiveSEnd, tEnd);
4759
+ const overlap2 = Math.max(0, overlap2End - overlap2Start);
4760
+ return overlap1 + overlap2;
4761
+ }
4762
+ };
4109
4763
  for (const side of sides) {
4110
4764
  const sideAngleRange = side.end - side.start;
4111
4765
  if (sideAngleRange < EPSILON2 || side.length < EPSILON2) continue;
4112
- const overlapStart = Math.max(startAngle, side.start);
4113
- const overlapEnd = Math.min(endAngle, side.end);
4114
- if (overlapStart < overlapEnd - EPSILON2) {
4115
- const traversedAngleOnSide = overlapEnd - overlapStart;
4766
+ let traversedAngleOnSide = 0;
4767
+ if (turnDirection === "cw") {
4768
+ const wraps = startAngle > endAngle + EPSILON2;
4769
+ traversedAngleOnSide = calculateTraversalOverlap(
4770
+ side.start,
4771
+ side.end,
4772
+ startAngle,
4773
+ endAngle,
4774
+ wraps
4775
+ );
4776
+ } else {
4777
+ const wraps = endAngle > startAngle + EPSILON2;
4778
+ traversedAngleOnSide = calculateTraversalOverlap(
4779
+ side.start,
4780
+ side.end,
4781
+ endAngle,
4782
+ // Start of equivalent CW traversal
4783
+ startAngle,
4784
+ // End of equivalent CW traversal
4785
+ wraps
4786
+ );
4787
+ }
4788
+ if (traversedAngleOnSide > EPSILON2) {
4116
4789
  const percentage = traversedAngleOnSide / sideAngleRange;
4117
- result[side.name] += Math.max(0, percentage);
4790
+ result[side.name] += Math.max(
4791
+ 0,
4792
+ Number.isFinite(percentage) ? percentage : 0
4793
+ );
4118
4794
  }
4119
4795
  }
4120
4796
  for (const key in result) {
@@ -4126,6 +4802,28 @@ function calculateSidePercentages(startAngle, endAngle, bounds) {
4126
4802
  return result;
4127
4803
  }
4128
4804
 
4805
+ // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/computeTurnDirection.ts
4806
+ function triangleDirection({
4807
+ angleA,
4808
+ angleB,
4809
+ angleC
4810
+ }) {
4811
+ const Ax = Math.cos(angleA);
4812
+ const Ay = Math.sin(angleA);
4813
+ const Bx = Math.cos(angleB);
4814
+ const By = Math.sin(angleB);
4815
+ const Cx = Math.cos(angleC);
4816
+ const Cy = Math.sin(angleC);
4817
+ const signedArea = (Bx - Ax) * (Cy - Ay) - (By - Ay) * (Cx - Ax);
4818
+ return signedArea < 0 ? "ccw" : "cw";
4819
+ }
4820
+ function computeTurnDirection(A, B, C, bounds) {
4821
+ const angleA = pointToAngle(A, bounds);
4822
+ const angleB = pointToAngle(B, bounds);
4823
+ const angleC = pointToAngle(C, bounds);
4824
+ return triangleDirection({ angleA, angleB, angleC });
4825
+ }
4826
+
4129
4827
  // lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/SingleTransitionCrossingRouteSolver.ts
4130
4828
  var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
4131
4829
  // Input parameters
@@ -4213,7 +4911,14 @@ var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
4213
4911
  const A = flatRoute.A;
4214
4912
  const B = ntrP1;
4215
4913
  const C = flatRoute.B;
4216
- const sideTraversal = calculateTraversalPercentages(A, B, C, this.bounds);
4914
+ const turnDirection = computeTurnDirection(A, B, C, this.bounds);
4915
+ const sideTraversal = calculateTraversalPercentages(
4916
+ A,
4917
+ B,
4918
+ C,
4919
+ this.bounds,
4920
+ turnDirection
4921
+ );
4217
4922
  const viaBounds = {
4218
4923
  minX: this.bounds.minX + (sideTraversal.left > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace),
4219
4924
  minY: this.bounds.minY + (sideTraversal.bottom > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace),
@@ -4291,23 +4996,29 @@ var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
4291
4996
  otherRouteStart.z !== flatStart.z ? otherRouteStart : otherRouteEnd,
4292
4997
  this.traceThickness
4293
4998
  );
4294
- const p1 = findPointToGetAroundCircle(flatStart, p2, {
4295
- center: { x: via.x, y: via.y },
4296
- radius: minDistFromViaToTrace
4297
- }).E;
4298
- const p3 = findPointToGetAroundCircle(p2, flatEnd, {
4999
+ const viaCircle = {
4299
5000
  center: { x: via.x, y: via.y },
4300
5001
  radius: minDistFromViaToTrace
4301
- }).E;
4302
- const p1IsNeeded = pointToSegmentDistance(via, flatStart, p2) < minDistFromViaToTrace;
4303
- const p3IsNeeded = pointToSegmentDistance(via, p2, flatEnd) < minDistFromViaToTrace;
5002
+ };
5003
+ const p1 = findPointToGetAroundCircle(flatStart, p2, viaCircle).E;
5004
+ const p3 = findPointToGetAroundCircle(p2, flatEnd, viaCircle).E;
5005
+ const p0_5 = findPointToGetAroundCircle(flatStart, p1, viaCircle).E;
5006
+ const p1_5 = findPointToGetAroundCircle(p1, p2, viaCircle).E;
5007
+ const p2_5 = findPointToGetAroundCircle(p2, p3, viaCircle).E;
5008
+ const p3_5 = findPointToGetAroundCircle(p3, flatEnd, viaCircle).E;
5009
+ const p2_better = findPointToGetAroundCircle(p1_5, p2_5, viaCircle).E;
4304
5010
  return {
4305
5011
  connectionName: flatRouteConnectionName,
4306
5012
  route: [
4307
5013
  { x: flatStart.x, y: flatStart.y, z: flatStart.z ?? 0 },
4308
- ...p1IsNeeded ? [{ x: p1.x, y: p1.y, z: flatStart.z ?? 0 }] : [],
4309
- { x: p2.x, y: p2.y, z: flatStart.z ?? 0 },
4310
- ...p3IsNeeded ? [{ x: p3.x, y: p3.y, z: flatStart.z ?? 0 }] : [],
5014
+ { x: p0_5.x, y: p0_5.y, z: flatStart.z ?? 0 },
5015
+ { x: p1.x, y: p1.y, z: flatStart.z ?? 0 },
5016
+ { x: p1_5.x, y: p1_5.y, z: flatStart.z ?? 0 },
5017
+ // { x: p2.x, y: p2.y, z: flatStart.z ?? 0 },
5018
+ { x: p2_better.x, y: p2_better.y, z: flatStart.z ?? 0 },
5019
+ { x: p2_5.x, y: p2_5.y, z: flatStart.z ?? 0 },
5020
+ { x: p3.x, y: p3.y, z: flatStart.z ?? 0 },
5021
+ { x: p3_5.x, y: p3_5.y, z: flatStart.z ?? 0 },
4311
5022
  { x: flatEnd.x, y: flatEnd.y, z: flatEnd.z ?? 0 }
4312
5023
  ],
4313
5024
  traceThickness: this.traceThickness,
@@ -5213,24 +5924,40 @@ var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
5213
5924
  remainingHdRoutes;
5214
5925
  start;
5215
5926
  end;
5927
+ colorMap;
5216
5928
  constructor(opts) {
5217
5929
  super();
5218
5930
  this.remainingHdRoutes = [...opts.hdRoutes];
5931
+ this.colorMap = opts.colorMap ?? {};
5932
+ const firstRoute = this.remainingHdRoutes[0];
5933
+ const firstRouteToStartDist = Math.min(
5934
+ distance(firstRoute.route[0], opts.start),
5935
+ distance(firstRoute.route[firstRoute.route.length - 1], opts.start)
5936
+ );
5937
+ const firstRouteToEndDist = Math.min(
5938
+ distance(firstRoute.route[0], opts.end),
5939
+ distance(firstRoute.route[firstRoute.route.length - 1], opts.end)
5940
+ );
5941
+ if (firstRouteToStartDist < firstRouteToEndDist) {
5942
+ this.start = opts.start;
5943
+ this.end = opts.end;
5944
+ } else {
5945
+ this.start = opts.end;
5946
+ this.end = opts.start;
5947
+ }
5219
5948
  this.mergedHdRoute = {
5220
- connectionName: opts.connectionName ?? opts.hdRoutes[0].connectionName,
5949
+ connectionName: opts.connectionName ?? firstRoute.connectionName,
5221
5950
  route: [
5222
5951
  {
5223
- x: opts.start.x,
5224
- y: opts.start.y,
5225
- z: opts.start.z
5952
+ x: this.start.x,
5953
+ y: this.start.y,
5954
+ z: this.start.z
5226
5955
  }
5227
5956
  ],
5228
5957
  vias: [],
5229
5958
  viaDiameter: opts.hdRoutes?.[0]?.viaDiameter ?? 0.6,
5230
5959
  traceThickness: opts.hdRoutes?.[0]?.traceThickness ?? 0.15
5231
5960
  };
5232
- this.start = opts.start;
5233
- this.end = opts.end;
5234
5961
  }
5235
5962
  _step() {
5236
5963
  if (this.remainingHdRoutes.length === 0) {
@@ -5316,31 +6043,29 @@ var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
5316
6043
  });
5317
6044
  }
5318
6045
  }
5319
- const colorList = Array.from(
5320
- { length: this.remainingHdRoutes.length },
5321
- (_, i) => `hsl(${i * 360 / this.remainingHdRoutes.length}, 100%, 50%)`
5322
- );
5323
6046
  for (const [i, hdRoute] of this.remainingHdRoutes.entries()) {
6047
+ const routeColor = this.colorMap[hdRoute.connectionName] ?? "gray";
5324
6048
  if (hdRoute.route.length > 1) {
5325
6049
  graphics.lines?.push({
5326
6050
  points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
5327
- strokeColor: colorList[i]
6051
+ strokeColor: routeColor
5328
6052
  });
5329
6053
  }
5330
6054
  for (let pi = 0; pi < hdRoute.route.length; pi++) {
5331
6055
  const point = hdRoute.route[pi];
5332
6056
  graphics.points?.push({
5333
6057
  x: point.x + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
6058
+ // Keep slight offset for visibility
5334
6059
  y: point.y + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
5335
- color: colorList[i],
5336
- label: `Route ${i} ${point === hdRoute.route[0] ? "First" : point === hdRoute.route[hdRoute.route.length - 1] ? "Last" : ""}`
6060
+ color: routeColor,
6061
+ label: `Route ${hdRoute.connectionName} ${point === hdRoute.route[0] ? "First" : point === hdRoute.route[hdRoute.route.length - 1] ? "Last" : ""}`
5337
6062
  });
5338
6063
  }
5339
6064
  for (const via of hdRoute.vias) {
5340
6065
  graphics.circles?.push({
5341
6066
  center: { x: via.x, y: via.y },
5342
6067
  radius: hdRoute.viaDiameter / 2,
5343
- fill: colorList[i]
6068
+ fill: routeColor
5344
6069
  });
5345
6070
  }
5346
6071
  }
@@ -5353,8 +6078,10 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5353
6078
  unsolvedRoutes;
5354
6079
  activeSolver = null;
5355
6080
  mergedHdRoutes = [];
6081
+ colorMap = {};
5356
6082
  constructor(opts) {
5357
6083
  super();
6084
+ this.colorMap = opts.colorMap ?? {};
5358
6085
  this.unsolvedRoutes = opts.connections.map((c) => ({
5359
6086
  connectionName: c.name,
5360
6087
  hdRoutes: opts.hdRoutes.filter((r) => r.connectionName === c.name),
@@ -5390,7 +6117,8 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5390
6117
  connectionName: unsolvedRoute.connectionName,
5391
6118
  hdRoutes: unsolvedRoute.hdRoutes,
5392
6119
  start: unsolvedRoute.start,
5393
- end: unsolvedRoute.end
6120
+ end: unsolvedRoute.end,
6121
+ colorMap: this.colorMap
5394
6122
  });
5395
6123
  }
5396
6124
  visualize() {
@@ -5416,22 +6144,26 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5416
6144
  }
5417
6145
  }
5418
6146
  for (const [i, mergedRoute] of this.mergedHdRoutes.entries()) {
5419
- const solvedColor = `hsl(120, 100%, ${40 + i * 10 % 40}%)`;
5420
- if (mergedRoute.route.length > 1) {
6147
+ const solvedColor = this.colorMap[mergedRoute.connectionName] ?? `hsl(120, 100%, ${40 + i * 10 % 40}%)`;
6148
+ for (let j = 0; j < mergedRoute.route.length - 1; j++) {
6149
+ const p1 = mergedRoute.route[j];
6150
+ const p2 = mergedRoute.route[j + 1];
6151
+ const segmentColor = p1.z !== 0 ? safeTransparentize(solvedColor, 0.5) : solvedColor;
5421
6152
  graphics.lines?.push({
5422
- points: mergedRoute.route.map((point) => ({
5423
- x: point.x,
5424
- y: point.y
5425
- })),
5426
- strokeColor: solvedColor,
6153
+ points: [
6154
+ { x: p1.x, y: p1.y },
6155
+ { x: p2.x, y: p2.y }
6156
+ ],
6157
+ strokeColor: segmentColor,
5427
6158
  strokeWidth: mergedRoute.traceThickness
5428
6159
  });
5429
6160
  }
5430
6161
  for (const point of mergedRoute.route) {
6162
+ const pointColor = point.z !== 0 ? safeTransparentize(solvedColor, 0.5) : solvedColor;
5431
6163
  graphics.points?.push({
5432
6164
  x: point.x,
5433
6165
  y: point.y,
5434
- color: solvedColor
6166
+ color: pointColor
5435
6167
  });
5436
6168
  }
5437
6169
  for (const via of mergedRoute.vias) {
@@ -5439,25 +6171,23 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5439
6171
  center: { x: via.x, y: via.y },
5440
6172
  radius: mergedRoute.viaDiameter / 2,
5441
6173
  fill: solvedColor
6174
+ // Keep vias solid color for visibility
5442
6175
  });
5443
6176
  }
5444
6177
  }
5445
- const colorList = Array.from(
5446
- { length: this.unsolvedRoutes.length },
5447
- (_, i) => `hsl(${i * 360 / this.unsolvedRoutes.length}, 100%, 50%)`
5448
- );
5449
- for (const [i, unsolvedRoute] of this.unsolvedRoutes.entries()) {
6178
+ for (const unsolvedRoute of this.unsolvedRoutes) {
6179
+ const routeColor = this.colorMap[unsolvedRoute.connectionName] ?? "gray";
5450
6180
  graphics.points?.push(
5451
6181
  {
5452
6182
  x: unsolvedRoute.start.x,
5453
6183
  y: unsolvedRoute.start.y,
5454
- color: colorList[i],
6184
+ color: routeColor,
5455
6185
  label: `${unsolvedRoute.connectionName} Start`
5456
6186
  },
5457
6187
  {
5458
6188
  x: unsolvedRoute.end.x,
5459
6189
  y: unsolvedRoute.end.y,
5460
- color: colorList[i],
6190
+ color: routeColor,
5461
6191
  label: `${unsolvedRoute.connectionName} End`
5462
6192
  }
5463
6193
  );
@@ -5466,14 +6196,15 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5466
6196
  { x: unsolvedRoute.start.x, y: unsolvedRoute.start.y },
5467
6197
  { x: unsolvedRoute.end.x, y: unsolvedRoute.end.y }
5468
6198
  ],
5469
- strokeColor: colorList[i],
6199
+ strokeColor: routeColor,
5470
6200
  strokeDash: "2 2"
5471
6201
  });
5472
6202
  for (const hdRoute of unsolvedRoute.hdRoutes) {
5473
6203
  if (hdRoute.route.length > 1) {
5474
6204
  graphics.lines?.push({
5475
6205
  points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
5476
- strokeColor: safeTransparentize(colorList[i], 0.5),
6206
+ strokeColor: safeTransparentize(routeColor, 0.5),
6207
+ // Use routeColor
5477
6208
  strokeDash: "10 5"
5478
6209
  });
5479
6210
  }
@@ -5481,7 +6212,8 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5481
6212
  graphics.circles?.push({
5482
6213
  center: { x: via.x, y: via.y },
5483
6214
  radius: hdRoute.viaDiameter / 2,
5484
- fill: colorList[i]
6215
+ fill: routeColor
6216
+ // Use routeColor
5485
6217
  });
5486
6218
  }
5487
6219
  }
@@ -6783,853 +7515,467 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
6783
7515
  }
6784
7516
  };
6785
7517
 
6786
- // lib/utils/createRectFromCapacityNode.ts
6787
- var createRectFromCapacityNode = (node, opts = {}) => {
6788
- const lowestZ = Math.min(...node.availableZ);
6789
- return {
6790
- center: !opts.rectMargin || opts.zOffset ? {
6791
- x: node.center.x + lowestZ * node.width * (opts.zOffset ?? 0.05),
6792
- y: node.center.y - lowestZ * node.width * (opts.zOffset ?? 0.05)
6793
- } : node.center,
6794
- width: opts.rectMargin ? node.width - opts.rectMargin * 2 : Math.max(node.width - 0.5, node.width * 0.8),
6795
- height: opts.rectMargin ? node.height - opts.rectMargin * 2 : Math.max(node.height - 0.5, node.height * 0.8),
6796
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
6797
- "0,1": "rgba(0,0,0,0.1)",
6798
- "0": "rgba(0,200,200, 0.1)",
6799
- "1": "rgba(0,0,200, 0.1)"
6800
- }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
6801
- layer: `z${node.availableZ.join(",")}`,
6802
- label: [
6803
- node.capacityMeshNodeId,
6804
- `availableZ: ${node.availableZ.join(",")}`,
6805
- `${node._containsTarget ? "containsTarget" : ""}`,
6806
- `${node._containsObstacle ? "containsObstacle" : ""}`
6807
- ].filter(Boolean).join("\n")
6808
- };
6809
- };
6810
-
6811
- // lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
6812
- var CapacityPathingSolver = class extends BaseSolver {
6813
- connectionsWithNodes;
6814
- usedNodeCapacityMap;
6815
- simpleRouteJson;
6816
- nodes;
6817
- edges;
6818
- GREEDY_MULTIPLIER = 1.1;
6819
- nodeMap;
6820
- nodeEdgeMap;
6821
- connectionNameToGoalNodeIds;
6822
- colorMap;
6823
- maxDepthOfNodes;
6824
- activeCandidateStraightLineDistance;
6825
- debug_lastNodeCostMap;
6826
- hyperParameters;
6827
- constructor({
6828
- simpleRouteJson,
6829
- nodes,
6830
- edges,
6831
- colorMap,
6832
- MAX_ITERATIONS = 1e6,
6833
- hyperParameters = {}
6834
- }) {
7518
+ // lib/solvers/StrawSolver/StrawSolver.ts
7519
+ var StrawSolver = class extends BaseSolver {
7520
+ multiLayerNodes;
7521
+ strawNodes;
7522
+ skippedNodes;
7523
+ unprocessedNodes;
7524
+ strawSize;
7525
+ nodeIdCounter;
7526
+ constructor(params) {
6835
7527
  super();
6836
- this.MAX_ITERATIONS = MAX_ITERATIONS;
6837
- this.simpleRouteJson = simpleRouteJson;
6838
- this.nodes = nodes;
6839
- this.edges = edges;
6840
- this.colorMap = colorMap ?? {};
6841
- const { connectionsWithNodes, connectionNameToGoalNodeIds } = this.getConnectionsWithNodes();
6842
- this.connectionsWithNodes = connectionsWithNodes;
6843
- this.connectionNameToGoalNodeIds = connectionNameToGoalNodeIds;
6844
- this.hyperParameters = hyperParameters;
6845
- this.usedNodeCapacityMap = new Map(
6846
- this.nodes.map((node) => [node.capacityMeshNodeId, 0])
6847
- );
6848
- this.nodeMap = new Map(
6849
- this.nodes.map((node) => [node.capacityMeshNodeId, node])
6850
- );
6851
- this.nodeEdgeMap = getNodeEdgeMap(this.edges);
6852
- this.maxDepthOfNodes = Math.max(
6853
- ...this.nodes.map((node) => node._depth ?? 0)
6854
- );
6855
- this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
6856
- }
6857
- getTotalCapacity(node) {
6858
- const depth = node._depth ?? 0;
6859
- return (this.maxDepthOfNodes - depth + 1) ** 2;
6860
- }
6861
- getConnectionsWithNodes() {
6862
- const connectionsWithNodes = [];
6863
- const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
6864
- const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
6865
- for (const connection of this.simpleRouteJson.connections) {
6866
- const nodesForConnection = [];
6867
- for (const point of connection.pointsToConnect) {
6868
- let closestNode = this.nodes[0];
6869
- let minDistance = Number.MAX_VALUE;
6870
- for (const node of nodesWithTargets) {
6871
- const distance6 = Math.sqrt(
6872
- (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
6873
- );
6874
- if (distance6 < minDistance) {
6875
- minDistance = distance6;
6876
- closestNode = node;
6877
- }
6878
- }
6879
- nodesForConnection.push(closestNode);
6880
- }
6881
- if (nodesForConnection.length < 2) {
6882
- throw new Error(
6883
- `Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
6884
- );
7528
+ this.MAX_ITERATIONS = 1e5;
7529
+ this.strawSize = params.strawSize ?? 0.5;
7530
+ this.multiLayerNodes = [];
7531
+ this.strawNodes = [];
7532
+ this.skippedNodes = [];
7533
+ this.nodeIdCounter = 0;
7534
+ this.unprocessedNodes = [];
7535
+ for (const node of params.nodes) {
7536
+ if (node.availableZ.length === 1) {
7537
+ this.unprocessedNodes.push(node);
7538
+ } else {
7539
+ this.multiLayerNodes.push(node);
6885
7540
  }
6886
- connectionNameToGoalNodeIds.set(
6887
- connection.name,
6888
- nodesForConnection.map((n) => n.capacityMeshNodeId)
6889
- );
6890
- connectionsWithNodes.push({
6891
- connection,
6892
- nodes: nodesForConnection,
6893
- pathFound: false,
6894
- straightLineDistance: distance(
6895
- nodesForConnection[0].center,
6896
- nodesForConnection[nodesForConnection.length - 1].center
6897
- )
6898
- });
6899
7541
  }
6900
- connectionsWithNodes.sort(
6901
- (a, b) => a.straightLineDistance - b.straightLineDistance
6902
- );
6903
- return { connectionsWithNodes, connectionNameToGoalNodeIds };
6904
- }
6905
- currentConnectionIndex = 0;
6906
- candidates;
6907
- visitedNodes;
6908
- computeG(prevCandidate, node, endGoal) {
6909
- return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
6910
- }
6911
- computeH(prevCandidate, node, endGoal) {
6912
- return this.getDistanceBetweenNodes(node, endGoal);
6913
7542
  }
6914
- getBacktrackedPath(candidate) {
6915
- const path = [];
6916
- let currentCandidate = candidate;
6917
- while (currentCandidate) {
6918
- path.push(currentCandidate.node);
6919
- currentCandidate = currentCandidate.prevCandidate;
7543
+ getCapacityOfMultiLayerNodesWithinBounds(bounds) {
7544
+ let totalCapacity = 0;
7545
+ for (const node of this.multiLayerNodes) {
7546
+ const nodeMinX = node.center.x - node.width / 2;
7547
+ const nodeMaxX = node.center.x + node.width / 2;
7548
+ const nodeMinY = node.center.y - node.height / 2;
7549
+ const nodeMaxY = node.center.y + node.height / 2;
7550
+ const overlapMinX = Math.max(bounds.minX, nodeMinX);
7551
+ const overlapMaxX = Math.min(bounds.maxX, nodeMaxX);
7552
+ const overlapMinY = Math.max(bounds.minY, nodeMinY);
7553
+ const overlapMaxY = Math.min(bounds.maxY, nodeMaxY);
7554
+ if (overlapMinX < overlapMaxX && overlapMinY < overlapMaxY) {
7555
+ const overlapWidth = overlapMaxX - overlapMinX;
7556
+ const overlapHeight = overlapMaxY - overlapMinY;
7557
+ const overlapArea = overlapWidth * overlapHeight;
7558
+ const nodeArea = node.width * node.height;
7559
+ const proportion = overlapArea / nodeArea;
7560
+ totalCapacity += getTunedTotalCapacity1(node) * proportion;
7561
+ }
6920
7562
  }
6921
- return path;
6922
- }
6923
- getNeighboringNodes(node) {
6924
- return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
6925
- (edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
6926
- ).map((n) => this.nodeMap.get(n));
7563
+ return totalCapacity;
6927
7564
  }
6928
- getCapacityPaths() {
6929
- const capacityPaths = [];
6930
- for (const connection of this.connectionsWithNodes) {
6931
- const path = connection.path;
6932
- if (path) {
6933
- capacityPaths.push({
6934
- capacityPathId: connection.connection.name,
6935
- connectionName: connection.connection.name,
6936
- nodeIds: path.map((node) => node.capacityMeshNodeId)
7565
+ getSurroundingCapacities(node) {
7566
+ const searchDistance = Math.min(node.width, node.height);
7567
+ const leftSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7568
+ minX: node.center.x - node.width / 2 - searchDistance,
7569
+ maxX: node.center.x - node.width / 2,
7570
+ minY: node.center.y - node.height / 2,
7571
+ maxY: node.center.y + node.height / 2
7572
+ });
7573
+ const rightSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7574
+ minX: node.center.x + node.width / 2,
7575
+ maxX: node.center.x + node.width / 2 + searchDistance,
7576
+ minY: node.center.y - node.height / 2,
7577
+ maxY: node.center.y + node.height / 2
7578
+ });
7579
+ const topSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7580
+ minX: node.center.x - node.width / 2,
7581
+ maxX: node.center.x + node.width / 2,
7582
+ minY: node.center.y - node.height / 2 - searchDistance,
7583
+ maxY: node.center.y - node.height / 2
7584
+ });
7585
+ const bottomSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7586
+ minX: node.center.x - node.width / 2,
7587
+ maxX: node.center.x + node.width / 2,
7588
+ minY: node.center.y + node.height / 2,
7589
+ maxY: node.center.y + node.height / 2 + searchDistance
7590
+ });
7591
+ return {
7592
+ leftSurroundingCapacity,
7593
+ rightSurroundingCapacity,
7594
+ topSurroundingCapacity,
7595
+ bottomSurroundingCapacity
7596
+ };
7597
+ }
7598
+ /**
7599
+ * Creates straw nodes from a single-layer node based on surrounding capacities
7600
+ */
7601
+ createStrawsForNode(node) {
7602
+ const result = [];
7603
+ const {
7604
+ leftSurroundingCapacity,
7605
+ rightSurroundingCapacity,
7606
+ topSurroundingCapacity,
7607
+ bottomSurroundingCapacity
7608
+ } = this.getSurroundingCapacities(node);
7609
+ const horizontalCapacity = leftSurroundingCapacity + rightSurroundingCapacity;
7610
+ const verticalCapacity = topSurroundingCapacity + bottomSurroundingCapacity;
7611
+ const layerPrefersFactor = 1;
7612
+ const effectiveHorizontalCapacity = horizontalCapacity * layerPrefersFactor;
7613
+ if (effectiveHorizontalCapacity > verticalCapacity) {
7614
+ const numStraws = Math.floor(node.height / this.strawSize);
7615
+ const strawHeight = node.height / numStraws;
7616
+ for (let i = 0; i < numStraws; i++) {
7617
+ const strawCenterY = node.center.y - node.height / 2 + i * strawHeight + strawHeight / 2;
7618
+ result.push({
7619
+ capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
7620
+ center: { x: node.center.x, y: strawCenterY },
7621
+ width: node.width,
7622
+ height: strawHeight,
7623
+ layer: node.layer,
7624
+ availableZ: [...node.availableZ],
7625
+ _depth: node._depth,
7626
+ _strawNode: true,
7627
+ _strawParentCapacityMeshNodeId: node.capacityMeshNodeId
7628
+ });
7629
+ }
7630
+ } else {
7631
+ const numStraws = Math.floor(node.width / this.strawSize);
7632
+ const strawWidth = node.width / numStraws;
7633
+ for (let i = 0; i < numStraws; i++) {
7634
+ const strawCenterX = node.center.x - node.width / 2 + i * strawWidth + strawWidth / 2;
7635
+ result.push({
7636
+ capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
7637
+ center: { x: strawCenterX, y: node.center.y },
7638
+ width: strawWidth,
7639
+ height: node.height,
7640
+ layer: node.layer,
7641
+ availableZ: [...node.availableZ],
7642
+ _depth: node._depth,
7643
+ _strawNode: true,
7644
+ _strawParentCapacityMeshNodeId: node.capacityMeshNodeId
6937
7645
  });
6938
7646
  }
6939
7647
  }
6940
- return capacityPaths;
6941
- }
6942
- doesNodeHaveCapacityForTrace(node, prevNode) {
6943
- const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
6944
- const totalCapacity = this.getTotalCapacity(node);
6945
- if (node.availableZ.length === 1 && !node._containsTarget && usedCapacity > 0)
6946
- return false;
6947
- let additionalCapacityRequirement = 0;
6948
- if (node.availableZ.length > 1 && prevNode.availableZ.length === 1) {
6949
- additionalCapacityRequirement += 0.5;
6950
- }
6951
- return usedCapacity + additionalCapacityRequirement < totalCapacity;
6952
- }
6953
- canTravelThroughObstacle(node, connectionName) {
6954
- const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
6955
- return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
6956
- }
6957
- getDistanceBetweenNodes(A, B) {
6958
- return Math.sqrt(
6959
- (A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
6960
- );
6961
- }
6962
- reduceCapacityAlongPath(nextConnection) {
6963
- for (const node of nextConnection.path ?? []) {
6964
- this.usedNodeCapacityMap.set(
6965
- node.capacityMeshNodeId,
6966
- this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
6967
- );
6968
- }
7648
+ return result;
6969
7649
  }
6970
- isConnectedToEndGoal(node, endGoal) {
6971
- return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
7650
+ getResultNodes() {
7651
+ return [...this.multiLayerNodes, ...this.strawNodes, ...this.skippedNodes];
6972
7652
  }
6973
7653
  _step() {
6974
- const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
6975
- if (!nextConnection) {
7654
+ const rootNode = this.unprocessedNodes.pop();
7655
+ if (!rootNode) {
6976
7656
  this.solved = true;
6977
7657
  return;
6978
7658
  }
6979
- const [start, end] = nextConnection.nodes;
6980
- if (!this.candidates) {
6981
- this.candidates = [{ prevCandidate: null, node: start, f: 0, g: 0, h: 0 }];
6982
- this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
6983
- this.visitedNodes = /* @__PURE__ */ new Set([start.capacityMeshNodeId]);
6984
- this.activeCandidateStraightLineDistance = distance(
6985
- start.center,
6986
- end.center
6987
- );
6988
- }
6989
- this.candidates.sort((a, b) => a.f - b.f);
6990
- const currentCandidate = this.candidates.shift();
6991
- if (!currentCandidate) {
6992
- console.error(
6993
- `Ran out of candidates on connection ${nextConnection.connection.name}`
6994
- );
6995
- this.currentConnectionIndex++;
6996
- this.candidates = null;
6997
- this.visitedNodes = null;
6998
- this.failed = true;
7659
+ if (rootNode.width < this.strawSize && rootNode.height < this.strawSize) {
7660
+ this.skippedNodes.push(rootNode);
6999
7661
  return;
7000
7662
  }
7001
- if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
7002
- nextConnection.path = this.getBacktrackedPath({
7003
- prevCandidate: currentCandidate,
7004
- node: end,
7005
- f: 0,
7006
- g: 0,
7007
- h: 0
7008
- });
7009
- this.reduceCapacityAlongPath(nextConnection);
7010
- this.currentConnectionIndex++;
7011
- this.candidates = null;
7012
- this.visitedNodes = null;
7663
+ if (rootNode._containsTarget) {
7664
+ this.skippedNodes.push(rootNode);
7013
7665
  return;
7014
7666
  }
7015
- const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
7016
- for (const neighborNode of neighborNodes) {
7017
- if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
7018
- continue;
7019
- }
7020
- if (!this.doesNodeHaveCapacityForTrace(neighborNode, currentCandidate.node)) {
7021
- continue;
7022
- }
7023
- const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
7024
- if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
7025
- continue;
7026
- }
7027
- const g = this.computeG(currentCandidate, neighborNode, end);
7028
- const h = this.computeH(currentCandidate, neighborNode, end);
7029
- const f = g + h * this.GREEDY_MULTIPLIER;
7030
- this.debug_lastNodeCostMap.set(neighborNode.capacityMeshNodeId, {
7031
- f,
7032
- g,
7033
- h
7034
- });
7035
- const newCandidate = {
7036
- prevCandidate: currentCandidate,
7037
- node: neighborNode,
7038
- f,
7039
- g,
7040
- h
7041
- };
7042
- this.candidates.push(newCandidate);
7043
- }
7044
- this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
7667
+ const strawNodes = this.createStrawsForNode(rootNode);
7668
+ this.strawNodes.push(...strawNodes);
7045
7669
  }
7046
7670
  visualize() {
7047
7671
  const graphics = {
7672
+ rects: [],
7048
7673
  lines: [],
7049
7674
  points: [],
7050
- rects: [],
7051
- circles: []
7675
+ circles: [],
7676
+ title: "Straw Solver"
7052
7677
  };
7053
- if (this.connectionsWithNodes) {
7054
- for (let i = 0; i < this.connectionsWithNodes.length; i++) {
7055
- const conn = this.connectionsWithNodes[i];
7056
- if (conn.path && conn.path.length > 0) {
7057
- const pathPoints = conn.path.map(
7058
- ({ center: { x, y }, width, availableZ }) => ({
7059
- // slight offset to allow viewing overlapping paths
7060
- x: x + (i % 10 + i % 19) * (5e-3 * width),
7061
- y: y + (i % 10 + i % 19) * (5e-3 * width),
7062
- availableZ
7063
- })
7064
- );
7065
- graphics.lines.push({
7066
- points: pathPoints,
7067
- strokeColor: this.colorMap[conn.connection.name]
7068
- });
7069
- for (let u = 0; u < pathPoints.length; u++) {
7070
- const point = pathPoints[u];
7071
- graphics.points.push({
7072
- x: point.x,
7073
- y: point.y,
7074
- label: [
7075
- `conn: ${conn.connection.name}`,
7076
- `node: ${conn.path[u].capacityMeshNodeId}`,
7077
- `z: ${point.availableZ.join(",")}`
7078
- ].join("\n")
7079
- });
7080
- }
7081
- }
7082
- }
7083
- }
7084
- for (const node of this.nodes) {
7085
- const nodeCosts = this.debug_lastNodeCostMap.get(node.capacityMeshNodeId);
7678
+ for (const node of this.unprocessedNodes) {
7086
7679
  graphics.rects.push({
7087
- ...createRectFromCapacityNode(node, {
7088
- rectMargin: 0.025,
7089
- zOffset: 0.01
7090
- }),
7091
- label: [
7092
- `${node.capacityMeshNodeId}`,
7093
- `${this.usedNodeCapacityMap.get(node.capacityMeshNodeId)}/${this.getTotalCapacity(node).toFixed(2)}`,
7094
- `${node.width.toFixed(2)}x${node.height.toFixed(2)}`,
7095
- `g: ${nodeCosts?.g !== void 0 ? nodeCosts.g.toFixed(2) : "?"}`,
7096
- `h: ${nodeCosts?.h !== void 0 ? nodeCosts.h.toFixed(2) : "?"}`,
7097
- `f: ${nodeCosts?.f !== void 0 ? nodeCosts.f.toFixed(2) : "?"}`,
7098
- `z: ${node.availableZ.join(", ")}`
7099
- ].join("\n")
7680
+ center: node.center,
7681
+ width: node.width,
7682
+ height: node.height,
7683
+ fill: "rgba(200, 200, 200, 0.5)",
7684
+ stroke: "rgba(0, 0, 0, 0.5)",
7685
+ label: `${node.capacityMeshNodeId}
7686
+ Unprocessed
7687
+ ${node.width}x${node.height}`
7100
7688
  });
7101
7689
  }
7102
- if (this.connectionsWithNodes) {
7103
- for (const conn of this.connectionsWithNodes) {
7104
- if (conn.connection?.pointsToConnect) {
7105
- for (const point of conn.connection.pointsToConnect) {
7106
- graphics.points.push({
7107
- x: point.x,
7108
- y: point.y,
7109
- label: [`pointsToConnect ${conn.connection.name}`].join("\n")
7110
- });
7111
- }
7112
- }
7113
- }
7114
- }
7115
- const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
7116
- if (nextConnection) {
7117
- const [start, end] = nextConnection.connection.pointsToConnect;
7118
- graphics.lines.push({
7119
- points: [
7120
- { x: start.x, y: start.y },
7121
- { x: end.x, y: end.y }
7122
- ],
7123
- strokeColor: "red",
7124
- strokeDash: "10 5"
7690
+ for (const node of this.strawNodes) {
7691
+ const color = node.availableZ[0] === 0 ? "rgba(0, 150, 255, 0.5)" : "rgba(255, 100, 0, 0.5)";
7692
+ graphics.rects.push({
7693
+ center: node.center,
7694
+ width: node.width,
7695
+ height: node.height,
7696
+ fill: color,
7697
+ stroke: "rgba(0, 0, 0, 0.5)",
7698
+ label: `${node.capacityMeshNodeId}
7699
+ Layer: ${node.availableZ[0]}
7700
+ ${node.width}x${node.height}`,
7701
+ layer: `z${node.availableZ.join(",")}`
7125
7702
  });
7126
7703
  }
7127
- if (this.candidates) {
7128
- const topCandidates = this.candidates.slice(0, 5);
7129
- const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
7130
- topCandidates.forEach((candidate, index) => {
7131
- const opacity = 0.5 * (1 - index / 5);
7132
- const backtrackedPath = this.getBacktrackedPath(candidate);
7133
- graphics.lines.push({
7134
- points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
7135
- strokeColor: safeTransparentize(
7136
- this.colorMap[connectionName] ?? "red",
7137
- 1 - opacity
7138
- )
7139
- });
7704
+ for (const node of this.multiLayerNodes) {
7705
+ graphics.rects.push({
7706
+ center: node.center,
7707
+ width: node.width * 0.9,
7708
+ height: node.height * 0.9,
7709
+ fill: "rgba(100, 255, 100, 0.5)",
7710
+ stroke: "rgba(0, 0, 0, 0.5)",
7711
+ layer: `z${node.availableZ.join(",")}`,
7712
+ label: `${node.capacityMeshNodeId}
7713
+ Layers: ${node.availableZ.join(",")}
7714
+ ${node.width}x${node.height}`
7140
7715
  });
7141
7716
  }
7142
7717
  return graphics;
7143
7718
  }
7144
7719
  };
7145
7720
 
7146
- // lib/solvers/CapacityPathingSolver/CapacityPathingSolver5.ts
7147
- var CapacityPathingSolver5 = class extends CapacityPathingSolver {
7148
- NEGATIVE_CAPACITY_PENALTY_FACTOR = 1;
7149
- REDUCED_CAPACITY_PENALTY_FACTOR = 1;
7150
- constructor(...args) {
7151
- super(...args);
7152
- this.GREEDY_MULTIPLIER = 2.5;
7153
- }
7154
- get maxCapacityFactor() {
7155
- return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
7156
- }
7157
- getTotalCapacity(node) {
7158
- return getTunedTotalCapacity1(node, this.maxCapacityFactor);
7159
- }
7160
- /**
7161
- * Penalty you pay for using this node
7162
- */
7163
- getNodeCapacityPenalty(node) {
7164
- const MAX_PENALTY = node.width + node.height;
7165
- const MIN_PENALTY = 0.05;
7166
- const START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW = 2;
7167
- const totalCapacity = this.getTotalCapacity(node);
7168
- const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
7169
- const remainingCapacity = totalCapacity - usedCapacity;
7170
- if (remainingCapacity > START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW) {
7171
- return MIN_PENALTY;
7721
+ // lib/utils/areNodesBordering.ts
7722
+ function areNodesBordering(node1, node2) {
7723
+ const n1Left = node1.center.x - node1.width / 2;
7724
+ const n1Right = node1.center.x + node1.width / 2;
7725
+ const n1Top = node1.center.y - node1.height / 2;
7726
+ const n1Bottom = node1.center.y + node1.height / 2;
7727
+ const n2Left = node2.center.x - node2.width / 2;
7728
+ const n2Right = node2.center.x + node2.width / 2;
7729
+ const n2Top = node2.center.y - node2.height / 2;
7730
+ const n2Bottom = node2.center.y + node2.height / 2;
7731
+ const epsilon = 1e-3;
7732
+ const shareVerticalBorder = (Math.abs(n1Right - n2Left) < epsilon || Math.abs(n1Left - n2Right) < epsilon) && Math.min(n1Bottom, n2Bottom) - Math.max(n1Top, n2Top) >= epsilon;
7733
+ const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
7734
+ return shareVerticalBorder || shareHorizontalBorder;
7735
+ }
7736
+
7737
+ // lib/utils/createRectFromCapacityNode.ts
7738
+ var createRectFromCapacityNode = (node, opts = {}) => {
7739
+ const lowestZ = Math.min(...node.availableZ);
7740
+ return {
7741
+ center: !opts.rectMargin || opts.zOffset ? {
7742
+ x: node.center.x + lowestZ * node.width * (opts.zOffset ?? 0.05),
7743
+ y: node.center.y - lowestZ * node.width * (opts.zOffset ?? 0.05)
7744
+ } : node.center,
7745
+ width: opts.rectMargin ? node.width - opts.rectMargin * 2 : Math.max(node.width - 0.5, node.width * 0.8),
7746
+ height: opts.rectMargin ? node.height - opts.rectMargin * 2 : Math.max(node.height - 0.5, node.height * 0.8),
7747
+ fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
7748
+ "0,1": "rgba(0,0,0,0.1)",
7749
+ "0": "rgba(0,200,200, 0.1)",
7750
+ "1": "rgba(0,0,200, 0.1)"
7751
+ }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
7752
+ layer: `z${node.availableZ.join(",")}`,
7753
+ label: [
7754
+ node.capacityMeshNodeId,
7755
+ `availableZ: ${node.availableZ.join(",")}`,
7756
+ `${node._containsTarget ? "containsTarget" : ""}`,
7757
+ `${node._containsObstacle ? "containsObstacle" : ""}`
7758
+ ].filter(Boolean).join("\n")
7759
+ };
7760
+ };
7761
+
7762
+ // lib/data-structures/CapacityNodeTree.ts
7763
+ var CapacityNodeTree = class {
7764
+ constructor(nodes) {
7765
+ this.nodes = nodes;
7766
+ this.buckets = /* @__PURE__ */ new Map();
7767
+ for (const node of nodes) {
7768
+ const nodeMinX = node.center.x - node.width / 2;
7769
+ const nodeMinY = node.center.y - node.height / 2;
7770
+ const nodeMaxX = node.center.x + node.width / 2;
7771
+ const nodeMaxY = node.center.y + node.height / 2;
7772
+ for (let x = nodeMinX; x <= nodeMaxX; x += this.CELL_SIZE) {
7773
+ for (let y = nodeMinY; y <= nodeMaxY; y += this.CELL_SIZE) {
7774
+ const bucketKey = this.getBucketKey(x, y);
7775
+ const bucket = this.buckets.get(bucketKey);
7776
+ if (!bucket) {
7777
+ this.buckets.set(bucketKey, [node]);
7778
+ } else {
7779
+ bucket.push(node);
7780
+ }
7781
+ }
7782
+ }
7172
7783
  }
7173
- const penalty = (MAX_PENALTY - MIN_PENALTY) * Math.max(
7174
- 1,
7175
- (START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW - remainingCapacity) / (MAX_PENALTY - MIN_PENALTY)
7176
- ) + MIN_PENALTY;
7177
- return penalty;
7178
- }
7179
- /**
7180
- * We're rewarding travel into big nodes.
7181
- *
7182
- * To minimize shortest path, you'd want to comment this out.
7183
- */
7184
- getDistanceBetweenNodes(A, B) {
7185
- const dx = A.center.x - B.center.x;
7186
- const dy = A.center.y - B.center.y;
7187
- return Math.sqrt(dx ** 2 + dy ** 2);
7188
7784
  }
7189
- computeG(prevCandidate, node, endGoal) {
7190
- return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node) + this.getNodeCapacityPenalty(node);
7785
+ buckets;
7786
+ CELL_SIZE = 0.4;
7787
+ getBucketKey(x, y) {
7788
+ return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
7191
7789
  }
7192
- computeH(prevCandidate, node, endGoal) {
7193
- return this.getDistanceBetweenNodes(node, endGoal) + this.getNodeCapacityPenalty(node);
7790
+ getNodesInArea(centerX, centerY, width, height) {
7791
+ const nodes = [];
7792
+ const alreadyAddedNodes = /* @__PURE__ */ new Set();
7793
+ const minX = centerX - width / 2;
7794
+ const minY = centerY - height / 2;
7795
+ const maxX = centerX + width / 2;
7796
+ const maxY = centerY + height / 2;
7797
+ for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
7798
+ for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
7799
+ const bucketKey = this.getBucketKey(x, y);
7800
+ const bucket = this.buckets.get(bucketKey) || [];
7801
+ for (const node of bucket) {
7802
+ if (alreadyAddedNodes.has(node.capacityMeshNodeId)) continue;
7803
+ alreadyAddedNodes.add(node.capacityMeshNodeId);
7804
+ nodes.push(node);
7805
+ }
7806
+ }
7807
+ }
7808
+ return nodes;
7194
7809
  }
7195
7810
  };
7196
7811
 
7197
- // lib/solvers/StrawSolver/StrawSolver.ts
7198
- var StrawSolver = class extends BaseSolver {
7199
- multiLayerNodes;
7200
- strawNodes;
7201
- skippedNodes;
7202
- unprocessedNodes;
7203
- strawSize;
7204
- nodeIdCounter;
7205
- constructor(params) {
7812
+ // lib/solvers/SingleLayerNodeMerger/SingleLayerNodeMergerSolver.ts
7813
+ var EPSILON3 = 5e-3;
7814
+ var SingleLayerNodeMergerSolver = class extends BaseSolver {
7815
+ nodeMap;
7816
+ currentBatchNodeIds;
7817
+ absorbedNodeIds;
7818
+ nextBatchNodeIds;
7819
+ batchHadModifications;
7820
+ hasComputedAdjacentNodeIds = false;
7821
+ newNodes;
7822
+ constructor(nodes) {
7206
7823
  super();
7824
+ this.nodeMap = /* @__PURE__ */ new Map();
7207
7825
  this.MAX_ITERATIONS = 1e5;
7208
- this.strawSize = params.strawSize ?? 0.5;
7209
- this.multiLayerNodes = [];
7210
- this.strawNodes = [];
7211
- this.skippedNodes = [];
7212
- this.nodeIdCounter = 0;
7213
- this.unprocessedNodes = [];
7214
- for (const node of params.nodes) {
7215
- if (node.availableZ.length === 1) {
7216
- this.unprocessedNodes.push(node);
7826
+ for (const node of nodes) {
7827
+ this.nodeMap.set(node.capacityMeshNodeId, node);
7828
+ }
7829
+ this.newNodes = [];
7830
+ this.absorbedNodeIds = /* @__PURE__ */ new Set();
7831
+ const unprocessedNodesWithArea = [];
7832
+ for (const node of nodes) {
7833
+ if (node.availableZ.length > 1) {
7834
+ this.newNodes.push(node);
7835
+ this.absorbedNodeIds.add(node.capacityMeshNodeId);
7217
7836
  } else {
7218
- this.multiLayerNodes.push(node);
7837
+ unprocessedNodesWithArea.push([node, node.width * node.height]);
7219
7838
  }
7220
7839
  }
7221
- }
7222
- getCapacityOfMultiLayerNodesWithinBounds(bounds) {
7223
- let totalCapacity = 0;
7224
- for (const node of this.multiLayerNodes) {
7225
- const nodeMinX = node.center.x - node.width / 2;
7226
- const nodeMaxX = node.center.x + node.width / 2;
7227
- const nodeMinY = node.center.y - node.height / 2;
7228
- const nodeMaxY = node.center.y + node.height / 2;
7229
- const overlapMinX = Math.max(bounds.minX, nodeMinX);
7230
- const overlapMaxX = Math.min(bounds.maxX, nodeMaxX);
7231
- const overlapMinY = Math.max(bounds.minY, nodeMinY);
7232
- const overlapMaxY = Math.min(bounds.maxY, nodeMaxY);
7233
- if (overlapMinX < overlapMaxX && overlapMinY < overlapMaxY) {
7234
- const overlapWidth = overlapMaxX - overlapMinX;
7235
- const overlapHeight = overlapMaxY - overlapMinY;
7236
- const overlapArea = overlapWidth * overlapHeight;
7237
- const nodeArea = node.width * node.height;
7238
- const proportion = overlapArea / nodeArea;
7239
- totalCapacity += getTunedTotalCapacity1(node) * proportion;
7240
- }
7840
+ unprocessedNodesWithArea.sort((a, b) => a[1] - b[1]);
7841
+ for (const [node, area] of unprocessedNodesWithArea) {
7842
+ const unprocessedNode = {
7843
+ ...node,
7844
+ center: { ...node.center }
7845
+ };
7846
+ this.nodeMap.set(node.capacityMeshNodeId, unprocessedNode);
7241
7847
  }
7242
- return totalCapacity;
7243
- }
7244
- getSurroundingCapacities(node) {
7245
- const searchDistance = Math.min(node.width, node.height);
7246
- const leftSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7247
- minX: node.center.x - node.width / 2 - searchDistance,
7248
- maxX: node.center.x - node.width / 2,
7249
- minY: node.center.y - node.height / 2,
7250
- maxY: node.center.y + node.height / 2
7251
- });
7252
- const rightSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7253
- minX: node.center.x + node.width / 2,
7254
- maxX: node.center.x + node.width / 2 + searchDistance,
7255
- minY: node.center.y - node.height / 2,
7256
- maxY: node.center.y + node.height / 2
7257
- });
7258
- const topSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7259
- minX: node.center.x - node.width / 2,
7260
- maxX: node.center.x + node.width / 2,
7261
- minY: node.center.y - node.height / 2 - searchDistance,
7262
- maxY: node.center.y - node.height / 2
7263
- });
7264
- const bottomSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7265
- minX: node.center.x - node.width / 2,
7266
- maxX: node.center.x + node.width / 2,
7267
- minY: node.center.y + node.height / 2,
7268
- maxY: node.center.y + node.height / 2 + searchDistance
7269
- });
7270
- return {
7271
- leftSurroundingCapacity,
7272
- rightSurroundingCapacity,
7273
- topSurroundingCapacity,
7274
- bottomSurroundingCapacity
7275
- };
7848
+ this.currentBatchNodeIds = unprocessedNodesWithArea.map(
7849
+ ([node]) => node.capacityMeshNodeId
7850
+ );
7851
+ this.nextBatchNodeIds = [];
7852
+ this.batchHadModifications = false;
7276
7853
  }
7277
- /**
7278
- * Creates straw nodes from a single-layer node based on surrounding capacities
7279
- */
7280
- createStrawsForNode(node) {
7281
- const result = [];
7282
- const {
7283
- leftSurroundingCapacity,
7284
- rightSurroundingCapacity,
7285
- topSurroundingCapacity,
7286
- bottomSurroundingCapacity
7287
- } = this.getSurroundingCapacities(node);
7288
- const horizontalCapacity = leftSurroundingCapacity + rightSurroundingCapacity;
7289
- const verticalCapacity = topSurroundingCapacity + bottomSurroundingCapacity;
7290
- const layerPrefersFactor = 1;
7291
- const effectiveHorizontalCapacity = horizontalCapacity * layerPrefersFactor;
7292
- if (effectiveHorizontalCapacity > verticalCapacity) {
7293
- const numStraws = Math.floor(node.height / this.strawSize);
7294
- const strawHeight = node.height / numStraws;
7295
- for (let i = 0; i < numStraws; i++) {
7296
- const strawCenterY = node.center.y - node.height / 2 + i * strawHeight + strawHeight / 2;
7297
- result.push({
7298
- capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
7299
- center: { x: node.center.x, y: strawCenterY },
7300
- width: node.width,
7301
- height: strawHeight,
7302
- layer: node.layer,
7303
- availableZ: [...node.availableZ],
7304
- _depth: node._depth,
7305
- _strawNode: true,
7306
- _strawParentCapacityMeshNodeId: node.capacityMeshNodeId
7307
- });
7308
- }
7309
- } else {
7310
- const numStraws = Math.floor(node.width / this.strawSize);
7311
- const strawWidth = node.width / numStraws;
7312
- for (let i = 0; i < numStraws; i++) {
7313
- const strawCenterX = node.center.x - node.width / 2 + i * strawWidth + strawWidth / 2;
7314
- result.push({
7315
- capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
7316
- center: { x: strawCenterX, y: node.center.y },
7317
- width: strawWidth,
7318
- height: node.height,
7319
- layer: node.layer,
7320
- availableZ: [...node.availableZ],
7321
- _depth: node._depth,
7322
- _strawNode: true,
7323
- _strawParentCapacityMeshNodeId: node.capacityMeshNodeId
7324
- });
7854
+ computeAdjacentNodeIdsForFirstBatch(nodes) {
7855
+ const nodeTrees = [
7856
+ new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 0)),
7857
+ new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 1))
7858
+ ];
7859
+ for (const node of nodes) {
7860
+ const adjacentNodes = [];
7861
+ const z = node.availableZ[0];
7862
+ const nodesInArea = nodeTrees[z].getNodesInArea(
7863
+ node.center.x,
7864
+ node.center.y,
7865
+ node.width * 4,
7866
+ node.height * 4
7867
+ );
7868
+ for (const unprocessedNode of nodesInArea) {
7869
+ if (unprocessedNode._containsTarget && unprocessedNode._targetConnectionName !== node._targetConnectionName)
7870
+ continue;
7871
+ if (unprocessedNode.capacityMeshNodeId === node.capacityMeshNodeId)
7872
+ continue;
7873
+ if (!areNodesBordering(node, unprocessedNode)) continue;
7874
+ adjacentNodes.push(unprocessedNode);
7325
7875
  }
7876
+ node._adjacentNodeIds = adjacentNodes.map((n) => n.capacityMeshNodeId);
7326
7877
  }
7327
- return result;
7328
7878
  }
7329
- getResultNodes() {
7330
- return [...this.multiLayerNodes, ...this.strawNodes, ...this.skippedNodes];
7879
+ // getAdjacentSameLayerUnprocessedNodes1(rootNode: CapacityMeshNode) {
7880
+ // const adjacentNodes: CapacityMeshNode[] = []
7881
+ // for (const unprocessedNodeId of this.currentBatchNodeIds) {
7882
+ // const unprocessedNode = this.nodeMap.get(unprocessedNodeId)!
7883
+ // if (!areNodesBordering(rootNode, unprocessedNode)) continue
7884
+ // if (unprocessedNode.availableZ[0] !== rootNode.availableZ[0]) continue
7885
+ // if (
7886
+ // unprocessedNode._containsTarget &&
7887
+ // unprocessedNode._targetConnectionName !== rootNode._targetConnectionName
7888
+ // )
7889
+ // continue
7890
+ // if (this.absorbedNodeIds.has(unprocessedNodeId)) continue
7891
+ // adjacentNodes.push(unprocessedNode)
7892
+ // }
7893
+ // return adjacentNodes
7894
+ // }
7895
+ getAdjacentSameLayerUnprocessedNodes(rootNode) {
7896
+ return this.getAdjacentSameLayerUnprocessedNodes2(rootNode);
7897
+ }
7898
+ getAdjacentSameLayerUnprocessedNodes2(rootNode) {
7899
+ const adjacentNodes = [];
7900
+ const unprocessedAdjNodes = Array.from(
7901
+ new Set(
7902
+ (rootNode._adjacentNodeIds ?? []).map((a) => this.nodeMap.get(a))
7903
+ )
7904
+ );
7905
+ unprocessedAdjNodes.sort((a, b) => a.width * a.height - b.width * b.height);
7906
+ for (const unprocessedNode of unprocessedAdjNodes) {
7907
+ if (this.absorbedNodeIds.has(unprocessedNode.capacityMeshNodeId)) continue;
7908
+ adjacentNodes.push(unprocessedNode);
7909
+ }
7910
+ return adjacentNodes;
7331
7911
  }
7332
7912
  _step() {
7333
- const rootNode = this.unprocessedNodes.pop();
7334
- if (!rootNode) {
7335
- this.solved = true;
7336
- return;
7913
+ if (!this.hasComputedAdjacentNodeIds) {
7914
+ this.computeAdjacentNodeIdsForFirstBatch(
7915
+ this.currentBatchNodeIds.map((id) => this.nodeMap.get(id))
7916
+ );
7917
+ this.hasComputedAdjacentNodeIds = true;
7337
7918
  }
7338
- if (rootNode.width < this.strawSize && rootNode.height < this.strawSize) {
7339
- this.skippedNodes.push(rootNode);
7919
+ let rootNodeId = this.currentBatchNodeIds.pop();
7920
+ while (rootNodeId && this.absorbedNodeIds.has(rootNodeId)) {
7921
+ rootNodeId = this.currentBatchNodeIds.pop();
7922
+ }
7923
+ if (!rootNodeId) {
7924
+ if (this.batchHadModifications) {
7925
+ this.currentBatchNodeIds = this.nextBatchNodeIds.sort((a, b) => {
7926
+ const A = this.nodeMap.get(a);
7927
+ const B = this.nodeMap.get(b);
7928
+ return A.width * A.height - B.width * B.height;
7929
+ });
7930
+ this.nextBatchNodeIds = [];
7931
+ this.batchHadModifications = false;
7932
+ return;
7933
+ }
7934
+ this.solved = true;
7935
+ this.newNodes.push(
7936
+ ...this.nextBatchNodeIds.map((id) => this.nodeMap.get(id))
7937
+ );
7340
7938
  return;
7341
7939
  }
7342
- if (rootNode._containsTarget) {
7343
- this.skippedNodes.push(rootNode);
7940
+ const rootNode = this.nodeMap.get(rootNodeId);
7941
+ let rootNodeHasGrown = false;
7942
+ const adjacentNodes = this.getAdjacentSameLayerUnprocessedNodes(rootNode);
7943
+ if (adjacentNodes.length === 0) {
7944
+ this.nextBatchNodeIds.push(rootNodeId);
7344
7945
  return;
7345
7946
  }
7346
- const strawNodes = this.createStrawsForNode(rootNode);
7347
- this.strawNodes.push(...strawNodes);
7348
- }
7349
- visualize() {
7350
- const graphics = {
7351
- rects: [],
7352
- lines: [],
7353
- points: [],
7354
- circles: [],
7355
- title: "Straw Solver"
7947
+ const absorbAdjacentNodeIds = (nodesToAbsorb) => {
7948
+ for (const adjNode of nodesToAbsorb) {
7949
+ this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
7950
+ }
7951
+ rootNode._adjacentNodeIds = Array.from(
7952
+ new Set(
7953
+ [
7954
+ ...rootNode._adjacentNodeIds ?? [],
7955
+ ...nodesToAbsorb.flatMap((n) => n._adjacentNodeIds ?? [])
7956
+ ].filter((id) => !this.absorbedNodeIds.has(id))
7957
+ )
7958
+ );
7356
7959
  };
7357
- for (const node of this.unprocessedNodes) {
7358
- graphics.rects.push({
7359
- center: node.center,
7360
- width: node.width,
7361
- height: node.height,
7362
- fill: "rgba(200, 200, 200, 0.5)",
7363
- stroke: "rgba(0, 0, 0, 0.5)",
7364
- label: `${node.capacityMeshNodeId}
7365
- Unprocessed
7366
- ${node.width}x${node.height}`
7367
- });
7368
- }
7369
- for (const node of this.strawNodes) {
7370
- const color = node.availableZ[0] === 0 ? "rgba(0, 150, 255, 0.5)" : "rgba(255, 100, 0, 0.5)";
7371
- graphics.rects.push({
7372
- center: node.center,
7373
- width: node.width,
7374
- height: node.height,
7375
- fill: color,
7376
- stroke: "rgba(0, 0, 0, 0.5)",
7377
- label: `${node.capacityMeshNodeId}
7378
- Layer: ${node.availableZ[0]}
7379
- ${node.width}x${node.height}`,
7380
- layer: `z${node.availableZ.join(",")}`
7381
- });
7382
- }
7383
- for (const node of this.multiLayerNodes) {
7384
- graphics.rects.push({
7385
- center: node.center,
7386
- width: node.width * 0.9,
7387
- height: node.height * 0.9,
7388
- fill: "rgba(100, 255, 100, 0.5)",
7389
- stroke: "rgba(0, 0, 0, 0.5)",
7390
- layer: `z${node.availableZ.join(",")}`,
7391
- label: `${node.capacityMeshNodeId}
7392
- Layers: ${node.availableZ.join(",")}
7393
- ${node.width}x${node.height}`
7394
- });
7395
- }
7396
- return graphics;
7397
- }
7398
- };
7399
-
7400
- // lib/utils/areNodesBordering.ts
7401
- function areNodesBordering(node1, node2) {
7402
- const n1Left = node1.center.x - node1.width / 2;
7403
- const n1Right = node1.center.x + node1.width / 2;
7404
- const n1Top = node1.center.y - node1.height / 2;
7405
- const n1Bottom = node1.center.y + node1.height / 2;
7406
- const n2Left = node2.center.x - node2.width / 2;
7407
- const n2Right = node2.center.x + node2.width / 2;
7408
- const n2Top = node2.center.y - node2.height / 2;
7409
- const n2Bottom = node2.center.y + node2.height / 2;
7410
- const epsilon = 1e-3;
7411
- const shareVerticalBorder = (Math.abs(n1Right - n2Left) < epsilon || Math.abs(n1Left - n2Right) < epsilon) && Math.min(n1Bottom, n2Bottom) - Math.max(n1Top, n2Top) >= epsilon;
7412
- const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
7413
- return shareVerticalBorder || shareHorizontalBorder;
7414
- }
7415
-
7416
- // lib/data-structures/CapacityNodeTree.ts
7417
- var CapacityNodeTree = class {
7418
- constructor(nodes) {
7419
- this.nodes = nodes;
7420
- this.buckets = /* @__PURE__ */ new Map();
7421
- for (const node of nodes) {
7422
- const nodeMinX = node.center.x - node.width / 2;
7423
- const nodeMinY = node.center.y - node.height / 2;
7424
- const nodeMaxX = node.center.x + node.width / 2;
7425
- const nodeMaxY = node.center.y + node.height / 2;
7426
- for (let x = nodeMinX; x <= nodeMaxX; x += this.CELL_SIZE) {
7427
- for (let y = nodeMinY; y <= nodeMaxY; y += this.CELL_SIZE) {
7428
- const bucketKey = this.getBucketKey(x, y);
7429
- const bucket = this.buckets.get(bucketKey);
7430
- if (!bucket) {
7431
- this.buckets.set(bucketKey, [node]);
7432
- } else {
7433
- bucket.push(node);
7434
- }
7435
- }
7436
- }
7437
- }
7438
- }
7439
- buckets;
7440
- CELL_SIZE = 0.4;
7441
- getBucketKey(x, y) {
7442
- return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
7443
- }
7444
- getNodesInArea(centerX, centerY, width, height) {
7445
- const nodes = [];
7446
- const alreadyAddedNodes = /* @__PURE__ */ new Set();
7447
- const minX = centerX - width / 2;
7448
- const minY = centerY - height / 2;
7449
- const maxX = centerX + width / 2;
7450
- const maxY = centerY + height / 2;
7451
- for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
7452
- for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
7453
- const bucketKey = this.getBucketKey(x, y);
7454
- const bucket = this.buckets.get(bucketKey) || [];
7455
- for (const node of bucket) {
7456
- if (alreadyAddedNodes.has(node.capacityMeshNodeId)) continue;
7457
- alreadyAddedNodes.add(node.capacityMeshNodeId);
7458
- nodes.push(node);
7459
- }
7460
- }
7461
- }
7462
- return nodes;
7463
- }
7464
- };
7465
-
7466
- // lib/solvers/SingleLayerNodeMerger/SingleLayerNodeMergerSolver.ts
7467
- var EPSILON3 = 5e-3;
7468
- var SingleLayerNodeMergerSolver = class extends BaseSolver {
7469
- nodeMap;
7470
- currentBatchNodeIds;
7471
- absorbedNodeIds;
7472
- nextBatchNodeIds;
7473
- batchHadModifications;
7474
- hasComputedAdjacentNodeIds = false;
7475
- newNodes;
7476
- constructor(nodes) {
7477
- super();
7478
- this.nodeMap = /* @__PURE__ */ new Map();
7479
- this.MAX_ITERATIONS = 1e5;
7480
- for (const node of nodes) {
7481
- this.nodeMap.set(node.capacityMeshNodeId, node);
7482
- }
7483
- this.newNodes = [];
7484
- this.absorbedNodeIds = /* @__PURE__ */ new Set();
7485
- const unprocessedNodesWithArea = [];
7486
- for (const node of nodes) {
7487
- if (node.availableZ.length > 1) {
7488
- this.newNodes.push(node);
7489
- this.absorbedNodeIds.add(node.capacityMeshNodeId);
7490
- } else {
7491
- unprocessedNodesWithArea.push([node, node.width * node.height]);
7492
- }
7493
- }
7494
- unprocessedNodesWithArea.sort((a, b) => a[1] - b[1]);
7495
- for (const [node, area] of unprocessedNodesWithArea) {
7496
- const unprocessedNode = {
7497
- ...node,
7498
- center: { ...node.center }
7499
- };
7500
- this.nodeMap.set(node.capacityMeshNodeId, unprocessedNode);
7501
- }
7502
- this.currentBatchNodeIds = unprocessedNodesWithArea.map(
7503
- ([node]) => node.capacityMeshNodeId
7504
- );
7505
- this.nextBatchNodeIds = [];
7506
- this.batchHadModifications = false;
7507
- }
7508
- computeAdjacentNodeIdsForFirstBatch(nodes) {
7509
- const nodeTrees = [
7510
- new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 0)),
7511
- new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 1))
7512
- ];
7513
- for (const node of nodes) {
7514
- const adjacentNodes = [];
7515
- const z = node.availableZ[0];
7516
- const nodesInArea = nodeTrees[z].getNodesInArea(
7517
- node.center.x,
7518
- node.center.y,
7519
- node.width * 4,
7520
- node.height * 4
7521
- );
7522
- for (const unprocessedNode of nodesInArea) {
7523
- if (unprocessedNode._containsTarget && unprocessedNode._targetConnectionName !== node._targetConnectionName)
7524
- continue;
7525
- if (unprocessedNode.capacityMeshNodeId === node.capacityMeshNodeId)
7526
- continue;
7527
- if (!areNodesBordering(node, unprocessedNode)) continue;
7528
- adjacentNodes.push(unprocessedNode);
7529
- }
7530
- node._adjacentNodeIds = adjacentNodes.map((n) => n.capacityMeshNodeId);
7531
- }
7532
- }
7533
- // getAdjacentSameLayerUnprocessedNodes1(rootNode: CapacityMeshNode) {
7534
- // const adjacentNodes: CapacityMeshNode[] = []
7535
- // for (const unprocessedNodeId of this.currentBatchNodeIds) {
7536
- // const unprocessedNode = this.nodeMap.get(unprocessedNodeId)!
7537
- // if (!areNodesBordering(rootNode, unprocessedNode)) continue
7538
- // if (unprocessedNode.availableZ[0] !== rootNode.availableZ[0]) continue
7539
- // if (
7540
- // unprocessedNode._containsTarget &&
7541
- // unprocessedNode._targetConnectionName !== rootNode._targetConnectionName
7542
- // )
7543
- // continue
7544
- // if (this.absorbedNodeIds.has(unprocessedNodeId)) continue
7545
- // adjacentNodes.push(unprocessedNode)
7546
- // }
7547
- // return adjacentNodes
7548
- // }
7549
- getAdjacentSameLayerUnprocessedNodes(rootNode) {
7550
- return this.getAdjacentSameLayerUnprocessedNodes2(rootNode);
7551
- }
7552
- getAdjacentSameLayerUnprocessedNodes2(rootNode) {
7553
- const adjacentNodes = [];
7554
- const unprocessedAdjNodes = Array.from(
7555
- new Set(
7556
- (rootNode._adjacentNodeIds ?? []).map((a) => this.nodeMap.get(a))
7557
- )
7558
- );
7559
- unprocessedAdjNodes.sort((a, b) => a.width * a.height - b.width * b.height);
7560
- for (const unprocessedNode of unprocessedAdjNodes) {
7561
- if (this.absorbedNodeIds.has(unprocessedNode.capacityMeshNodeId)) continue;
7562
- adjacentNodes.push(unprocessedNode);
7563
- }
7564
- return adjacentNodes;
7565
- }
7566
- _step() {
7567
- if (!this.hasComputedAdjacentNodeIds) {
7568
- this.computeAdjacentNodeIdsForFirstBatch(
7569
- this.currentBatchNodeIds.map((id) => this.nodeMap.get(id))
7570
- );
7571
- this.hasComputedAdjacentNodeIds = true;
7572
- }
7573
- let rootNodeId = this.currentBatchNodeIds.pop();
7574
- while (rootNodeId && this.absorbedNodeIds.has(rootNodeId)) {
7575
- rootNodeId = this.currentBatchNodeIds.pop();
7576
- }
7577
- if (!rootNodeId) {
7578
- if (this.batchHadModifications) {
7579
- this.currentBatchNodeIds = this.nextBatchNodeIds.sort((a, b) => {
7580
- const A = this.nodeMap.get(a);
7581
- const B = this.nodeMap.get(b);
7582
- return A.width * A.height - B.width * B.height;
7583
- });
7584
- this.nextBatchNodeIds = [];
7585
- this.batchHadModifications = false;
7586
- return;
7587
- }
7588
- this.solved = true;
7589
- this.newNodes.push(
7590
- ...this.nextBatchNodeIds.map((id) => this.nodeMap.get(id))
7591
- );
7592
- return;
7593
- }
7594
- const rootNode = this.nodeMap.get(rootNodeId);
7595
- let rootNodeHasGrown = false;
7596
- const adjacentNodes = this.getAdjacentSameLayerUnprocessedNodes(rootNode);
7597
- if (adjacentNodes.length === 0) {
7598
- this.nextBatchNodeIds.push(rootNodeId);
7599
- return;
7600
- }
7601
- const absorbAdjacentNodeIds = (nodesToAbsorb) => {
7602
- for (const adjNode of nodesToAbsorb) {
7603
- this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
7604
- }
7605
- rootNode._adjacentNodeIds = Array.from(
7606
- new Set(
7607
- [
7608
- ...rootNode._adjacentNodeIds ?? [],
7609
- ...nodesToAbsorb.flatMap((n) => n._adjacentNodeIds ?? [])
7610
- ].filter((id) => !this.absorbedNodeIds.has(id))
7611
- )
7612
- );
7613
- };
7614
- const adjacentNodesToLeft = adjacentNodes.filter(
7615
- (adjNode) => adjNode.center.x < rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
7616
- );
7617
- if (adjacentNodesToLeft.length > 0) {
7618
- const { width: leftAdjNodeWidth, height: leftAdjNodeHeight } = adjacentNodesToLeft[0];
7619
- const leftAdjNodesAreAllSameSize = adjacentNodesToLeft.every(
7620
- (adjNode) => adjNode.width === leftAdjNodeWidth && adjNode.height === leftAdjNodeHeight
7621
- );
7622
- const leftAdjNodesTakeUpEntireHeight = Math.abs(
7623
- adjacentNodesToLeft.reduce((acc, adjNode) => {
7624
- return acc + adjNode.height;
7625
- }, 0) - rootNode.height
7626
- ) < EPSILON3;
7627
- if (leftAdjNodesTakeUpEntireHeight && leftAdjNodesAreAllSameSize) {
7628
- rootNode.width += leftAdjNodeWidth;
7629
- rootNode.center.x = rootNode.center.x - leftAdjNodeWidth / 2;
7630
- absorbAdjacentNodeIds(adjacentNodesToLeft);
7631
- rootNodeHasGrown = true;
7632
- }
7960
+ const adjacentNodesToLeft = adjacentNodes.filter(
7961
+ (adjNode) => adjNode.center.x < rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
7962
+ );
7963
+ if (adjacentNodesToLeft.length > 0) {
7964
+ const { width: leftAdjNodeWidth, height: leftAdjNodeHeight } = adjacentNodesToLeft[0];
7965
+ const leftAdjNodesAreAllSameSize = adjacentNodesToLeft.every(
7966
+ (adjNode) => adjNode.width === leftAdjNodeWidth && adjNode.height === leftAdjNodeHeight
7967
+ );
7968
+ const leftAdjNodesTakeUpEntireHeight = Math.abs(
7969
+ adjacentNodesToLeft.reduce((acc, adjNode) => {
7970
+ return acc + adjNode.height;
7971
+ }, 0) - rootNode.height
7972
+ ) < EPSILON3;
7973
+ if (leftAdjNodesTakeUpEntireHeight && leftAdjNodesAreAllSameSize) {
7974
+ rootNode.width += leftAdjNodeWidth;
7975
+ rootNode.center.x = rootNode.center.x - leftAdjNodeWidth / 2;
7976
+ absorbAdjacentNodeIds(adjacentNodesToLeft);
7977
+ rootNodeHasGrown = true;
7978
+ }
7633
7979
  }
7634
7980
  const adjacentNodesToRight = adjacentNodes.filter(
7635
7981
  (adjNode) => adjNode.center.x > rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
@@ -7926,13 +8272,13 @@ function minimumDistanceBetweenSegments(A1, A2, B1, B2) {
7926
8272
  if (segmentsIntersect(A1, A2, B1, B2)) {
7927
8273
  return 0;
7928
8274
  }
7929
- const distA1 = pointToSegmentDistance4(A1, B1, B2);
7930
- const distA2 = pointToSegmentDistance4(A2, B1, B2);
7931
- const distB1 = pointToSegmentDistance4(B1, A1, A2);
7932
- const distB2 = pointToSegmentDistance4(B2, A1, A2);
8275
+ const distA1 = pointToSegmentDistance5(A1, B1, B2);
8276
+ const distA2 = pointToSegmentDistance5(A2, B1, B2);
8277
+ const distB1 = pointToSegmentDistance5(B1, A1, A2);
8278
+ const distB2 = pointToSegmentDistance5(B2, A1, A2);
7933
8279
  return Math.min(distA1, distA2, distB1, distB2);
7934
8280
  }
7935
- function pointToSegmentDistance4(P, Q1, Q2) {
8281
+ function pointToSegmentDistance5(P, Q1, Q2) {
7936
8282
  const v = { x: Q2.x - Q1.x, y: Q2.y - Q1.y };
7937
8283
  const w = { x: P.x - Q1.x, y: P.y - Q1.y };
7938
8284
  const c1 = dotProduct(w, v);
@@ -9110,247 +9456,665 @@ var HighDensityRouteSpatialIndex = class {
9110
9456
  distance: Math.sqrt(data.minDistSq)
9111
9457
  });
9112
9458
  }
9113
- return results;
9459
+ return results;
9460
+ }
9461
+ };
9462
+
9463
+ // lib/solvers/UselessViaRemovalSolver/SingleRouteUselessViaRemovalSolver.ts
9464
+ var SingleRouteUselessViaRemovalSolver = class extends BaseSolver {
9465
+ obstacleSHI;
9466
+ hdRouteSHI;
9467
+ unsimplifiedRoute;
9468
+ routeSections;
9469
+ currentSectionIndex;
9470
+ TRACE_THICKNESS = 0.15;
9471
+ OBSTACLE_MARGIN = 0.1;
9472
+ constructor(params) {
9473
+ super();
9474
+ this.currentSectionIndex = 1;
9475
+ this.obstacleSHI = params.obstacleSHI;
9476
+ this.hdRouteSHI = params.hdRouteSHI;
9477
+ this.unsimplifiedRoute = params.unsimplifiedRoute;
9478
+ this.routeSections = this.breakRouteIntoSections(this.unsimplifiedRoute);
9479
+ }
9480
+ breakRouteIntoSections(route) {
9481
+ const routeSections = [];
9482
+ const routePoints = route.route;
9483
+ if (routePoints.length === 0) return [];
9484
+ let currentSection = {
9485
+ startIndex: 0,
9486
+ endIndex: -1,
9487
+ z: routePoints[0].z,
9488
+ points: [routePoints[0]]
9489
+ };
9490
+ for (let i = 1; i < routePoints.length; i++) {
9491
+ if (routePoints[i].z === currentSection.z) {
9492
+ currentSection.points.push(routePoints[i]);
9493
+ } else {
9494
+ currentSection.endIndex = i - 1;
9495
+ routeSections.push(currentSection);
9496
+ currentSection = {
9497
+ startIndex: i,
9498
+ endIndex: -1,
9499
+ z: routePoints[i].z,
9500
+ points: [routePoints[i]]
9501
+ };
9502
+ }
9503
+ }
9504
+ currentSection.endIndex = routePoints.length - 1;
9505
+ routeSections.push(currentSection);
9506
+ return routeSections;
9507
+ }
9508
+ _step() {
9509
+ if (this.currentSectionIndex >= this.routeSections.length - 1) {
9510
+ this.solved = true;
9511
+ return;
9512
+ }
9513
+ const prevSection = this.routeSections[this.currentSectionIndex - 1];
9514
+ const currentSection = this.routeSections[this.currentSectionIndex];
9515
+ const nextSection = this.routeSections[this.currentSectionIndex + 1];
9516
+ if (prevSection.z !== nextSection.z) {
9517
+ this.currentSectionIndex++;
9518
+ return;
9519
+ }
9520
+ const targetZ = prevSection.z;
9521
+ if (this.canSectionMoveToLayer({ currentSection, targetZ })) {
9522
+ currentSection.z = targetZ;
9523
+ currentSection.points = currentSection.points.map((p) => ({
9524
+ ...p,
9525
+ z: targetZ
9526
+ }));
9527
+ this.currentSectionIndex += 2;
9528
+ return;
9529
+ }
9530
+ this.currentSectionIndex++;
9531
+ return;
9532
+ }
9533
+ canSectionMoveToLayer({
9534
+ currentSection,
9535
+ targetZ
9536
+ }) {
9537
+ for (let i = 0; i < currentSection.points.length - 1; i++) {
9538
+ const A = { ...currentSection.points[i], z: targetZ };
9539
+ const B = { ...currentSection.points[i + 1], z: targetZ };
9540
+ const conflictingRoutes = this.hdRouteSHI.getConflictingRoutesForSegment(
9541
+ A,
9542
+ B,
9543
+ this.TRACE_THICKNESS
9544
+ );
9545
+ for (const { conflictingRoute, distance: distance6 } of conflictingRoutes) {
9546
+ if (conflictingRoute.connectionName === this.unsimplifiedRoute.connectionName)
9547
+ continue;
9548
+ if (distance6 < this.TRACE_THICKNESS + conflictingRoute.traceThickness) {
9549
+ return false;
9550
+ }
9551
+ }
9552
+ const segmentBox = {
9553
+ centerX: (A.x + B.x) / 2,
9554
+ centerY: (A.y + B.y) / 2,
9555
+ width: Math.abs(A.x - B.x),
9556
+ height: Math.abs(A.y - B.y)
9557
+ };
9558
+ const obstacles = this.obstacleSHI.getNodesInArea(
9559
+ segmentBox.centerX,
9560
+ segmentBox.centerY,
9561
+ segmentBox.width + (this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) * 2,
9562
+ // Expand search width
9563
+ segmentBox.height + (this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) * 2
9564
+ // Expand search height
9565
+ );
9566
+ for (const obstacle of obstacles) {
9567
+ const distToObstacle = segmentToBoxMinDistance(A, B, obstacle);
9568
+ if (distToObstacle < this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) {
9569
+ return false;
9570
+ }
9571
+ }
9572
+ }
9573
+ return true;
9574
+ }
9575
+ getConstructorParams() {
9576
+ return {
9577
+ obstacleSHI: this.obstacleSHI,
9578
+ hdRouteSHI: this.hdRouteSHI,
9579
+ unsimplifiedRoute: this.unsimplifiedRoute
9580
+ };
9581
+ }
9582
+ getOptimizedHdRoute() {
9583
+ const route = this.routeSections.flatMap((section) => section.points);
9584
+ const vias = [];
9585
+ for (let i = 0; i < route.length - 1; i++) {
9586
+ if (route[i].z !== route[i + 1].z) {
9587
+ vias.push({
9588
+ x: route[i].x,
9589
+ y: route[i].y
9590
+ });
9591
+ }
9592
+ }
9593
+ return {
9594
+ connectionName: this.unsimplifiedRoute.connectionName,
9595
+ route,
9596
+ traceThickness: this.unsimplifiedRoute.traceThickness,
9597
+ vias,
9598
+ viaDiameter: this.unsimplifiedRoute.viaDiameter
9599
+ };
9600
+ }
9601
+ visualize() {
9602
+ const graphics = {
9603
+ circles: [],
9604
+ lines: [],
9605
+ points: [],
9606
+ rects: [],
9607
+ coordinateSystem: "cartesian",
9608
+ title: "Single Route Useless Via Removal Solver"
9609
+ };
9610
+ for (let i = 0; i < this.routeSections.length; i++) {
9611
+ const section = this.routeSections[i];
9612
+ graphics.lines.push({
9613
+ points: section.points,
9614
+ strokeWidth: this.TRACE_THICKNESS,
9615
+ strokeColor: i === this.currentSectionIndex ? "orange" : section.z === 0 ? "red" : "blue"
9616
+ });
9617
+ }
9618
+ return graphics;
9619
+ }
9620
+ };
9621
+
9622
+ // lib/solvers/UselessViaRemovalSolver/UselessViaRemovalSolver.ts
9623
+ var UselessViaRemovalSolver = class extends BaseSolver {
9624
+ constructor(input) {
9625
+ super();
9626
+ this.input = input;
9627
+ this.unsimplifiedHdRoutes = input.unsimplifiedHdRoutes;
9628
+ this.optimizedHdRoutes = [];
9629
+ this.unprocessedRoutes = [...input.unsimplifiedHdRoutes];
9630
+ this.obstacleSHI = new ObstacleSpatialHashIndex(input.obstacles);
9631
+ this.hdRouteSHI = new HighDensityRouteSpatialIndex(
9632
+ this.unsimplifiedHdRoutes
9633
+ );
9634
+ }
9635
+ unsimplifiedHdRoutes;
9636
+ optimizedHdRoutes;
9637
+ unprocessedRoutes;
9638
+ activeSubSolver = null;
9639
+ obstacleSHI = null;
9640
+ hdRouteSHI = null;
9641
+ _step() {
9642
+ if (this.activeSubSolver) {
9643
+ this.activeSubSolver.step();
9644
+ if (this.activeSubSolver.solved) {
9645
+ this.optimizedHdRoutes.push(this.activeSubSolver.getOptimizedHdRoute());
9646
+ this.activeSubSolver = null;
9647
+ } else if (this.activeSubSolver.failed || this.activeSubSolver.error) {
9648
+ this.error = this.activeSubSolver.error;
9649
+ this.failed = true;
9650
+ }
9651
+ return;
9652
+ }
9653
+ const unprocessedRoute = this.unprocessedRoutes.shift();
9654
+ if (!unprocessedRoute) {
9655
+ this.solved = true;
9656
+ return;
9657
+ }
9658
+ this.activeSubSolver = new SingleRouteUselessViaRemovalSolver({
9659
+ hdRouteSHI: this.hdRouteSHI,
9660
+ obstacleSHI: this.obstacleSHI,
9661
+ unsimplifiedRoute: unprocessedRoute
9662
+ });
9663
+ }
9664
+ getOptimizedHdRoutes() {
9665
+ return this.optimizedHdRoutes;
9666
+ }
9667
+ visualize() {
9668
+ const visualization = {
9669
+ lines: [],
9670
+ points: [],
9671
+ rects: [],
9672
+ circles: [],
9673
+ coordinateSystem: "cartesian",
9674
+ title: "Useless Via Removal Solver"
9675
+ };
9676
+ for (const obstacle of this.input.obstacles) {
9677
+ let fillColor = "rgba(128, 128, 128, 0.2)";
9678
+ const strokeColor = "rgba(128, 128, 128, 0.5)";
9679
+ const isOnLayer0 = obstacle.zLayers?.includes(0);
9680
+ const isOnLayer1 = obstacle.zLayers?.includes(1);
9681
+ if (isOnLayer0 && isOnLayer1) {
9682
+ fillColor = "rgba(128, 0, 128, 0.2)";
9683
+ } else if (isOnLayer0) {
9684
+ fillColor = "rgba(255, 0, 0, 0.2)";
9685
+ } else if (isOnLayer1) {
9686
+ fillColor = "rgba(0, 0, 255, 0.2)";
9687
+ }
9688
+ visualization.rects.push({
9689
+ center: obstacle.center,
9690
+ width: obstacle.width,
9691
+ height: obstacle.height,
9692
+ fill: fillColor,
9693
+ label: `Obstacle (Z: ${obstacle.zLayers?.join(", ")})`
9694
+ });
9695
+ }
9696
+ for (const route of this.optimizedHdRoutes) {
9697
+ if (route.route.length === 0) continue;
9698
+ const color = this.input.colorMap[route.connectionName] || "#888888";
9699
+ for (let i = 0; i < route.route.length - 1; i++) {
9700
+ const current = route.route[i];
9701
+ const next = route.route[i + 1];
9702
+ if (current.z === next.z) {
9703
+ visualization.lines.push({
9704
+ points: [
9705
+ { x: current.x, y: current.y },
9706
+ { x: next.x, y: next.y }
9707
+ ],
9708
+ strokeColor: current.z === 0 ? "red" : "blue",
9709
+ strokeWidth: route.traceThickness,
9710
+ label: `${route.connectionName} (z=${current.z})`
9711
+ });
9712
+ }
9713
+ }
9714
+ for (const via of route.vias) {
9715
+ visualization.circles.push({
9716
+ center: { x: via.x, y: via.y },
9717
+ radius: route.viaDiameter / 2,
9718
+ fill: "rgba(255, 0, 255, 0.5)",
9719
+ label: `${route.connectionName} via`
9720
+ });
9721
+ }
9722
+ }
9723
+ if (this.activeSubSolver) {
9724
+ visualization.lines.push(
9725
+ ...this.activeSubSolver.visualize().lines ?? []
9726
+ );
9727
+ }
9728
+ return visualization;
9729
+ }
9730
+ };
9731
+
9732
+ // lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
9733
+ var CapacityPathingSolver = class extends BaseSolver {
9734
+ connectionsWithNodes;
9735
+ usedNodeCapacityMap;
9736
+ simpleRouteJson;
9737
+ nodes;
9738
+ edges;
9739
+ GREEDY_MULTIPLIER = 1.1;
9740
+ nodeMap;
9741
+ nodeEdgeMap;
9742
+ connectionNameToGoalNodeIds;
9743
+ colorMap;
9744
+ maxDepthOfNodes;
9745
+ activeCandidateStraightLineDistance;
9746
+ debug_lastNodeCostMap;
9747
+ hyperParameters;
9748
+ constructor({
9749
+ simpleRouteJson,
9750
+ nodes,
9751
+ edges,
9752
+ colorMap,
9753
+ MAX_ITERATIONS = 1e6,
9754
+ hyperParameters = {}
9755
+ }) {
9756
+ super();
9757
+ this.MAX_ITERATIONS = MAX_ITERATIONS;
9758
+ this.simpleRouteJson = simpleRouteJson;
9759
+ this.nodes = nodes;
9760
+ this.edges = edges;
9761
+ this.colorMap = colorMap ?? {};
9762
+ const { connectionsWithNodes, connectionNameToGoalNodeIds } = this.getConnectionsWithNodes();
9763
+ this.connectionsWithNodes = connectionsWithNodes;
9764
+ this.connectionNameToGoalNodeIds = connectionNameToGoalNodeIds;
9765
+ this.hyperParameters = hyperParameters;
9766
+ this.usedNodeCapacityMap = new Map(
9767
+ this.nodes.map((node) => [node.capacityMeshNodeId, 0])
9768
+ );
9769
+ this.nodeMap = new Map(
9770
+ this.nodes.map((node) => [node.capacityMeshNodeId, node])
9771
+ );
9772
+ this.nodeEdgeMap = getNodeEdgeMap(this.edges);
9773
+ this.maxDepthOfNodes = Math.max(
9774
+ ...this.nodes.map((node) => node._depth ?? 0)
9775
+ );
9776
+ this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
9777
+ }
9778
+ getTotalCapacity(node) {
9779
+ const depth = node._depth ?? 0;
9780
+ return (this.maxDepthOfNodes - depth + 1) ** 2;
9781
+ }
9782
+ getConnectionsWithNodes() {
9783
+ const connectionsWithNodes = [];
9784
+ const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
9785
+ const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
9786
+ for (const connection of this.simpleRouteJson.connections) {
9787
+ const nodesForConnection = [];
9788
+ for (const point of connection.pointsToConnect) {
9789
+ let closestNode = this.nodes[0];
9790
+ let minDistance = Number.MAX_VALUE;
9791
+ for (const node of nodesWithTargets) {
9792
+ const distance6 = Math.sqrt(
9793
+ (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
9794
+ );
9795
+ if (distance6 < minDistance) {
9796
+ minDistance = distance6;
9797
+ closestNode = node;
9798
+ }
9799
+ }
9800
+ nodesForConnection.push(closestNode);
9801
+ }
9802
+ if (nodesForConnection.length < 2) {
9803
+ throw new Error(
9804
+ `Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
9805
+ );
9806
+ }
9807
+ connectionNameToGoalNodeIds.set(
9808
+ connection.name,
9809
+ nodesForConnection.map((n) => n.capacityMeshNodeId)
9810
+ );
9811
+ connectionsWithNodes.push({
9812
+ connection,
9813
+ nodes: nodesForConnection,
9814
+ pathFound: false,
9815
+ straightLineDistance: distance(
9816
+ nodesForConnection[0].center,
9817
+ nodesForConnection[nodesForConnection.length - 1].center
9818
+ )
9819
+ });
9820
+ }
9821
+ connectionsWithNodes.sort(
9822
+ (a, b) => a.straightLineDistance - b.straightLineDistance
9823
+ );
9824
+ return { connectionsWithNodes, connectionNameToGoalNodeIds };
9114
9825
  }
9115
- };
9116
-
9117
- // lib/solvers/UselessViaRemovalSolver/SingleRouteUselessViaRemovalSolver.ts
9118
- var SingleRouteUselessViaRemovalSolver = class extends BaseSolver {
9119
- obstacleSHI;
9120
- hdRouteSHI;
9121
- unsimplifiedRoute;
9122
- routeSections;
9123
- currentSectionIndex;
9124
- TRACE_THICKNESS = 0.15;
9125
- OBSTACLE_MARGIN = 0.1;
9126
- constructor(params) {
9127
- super();
9128
- this.currentSectionIndex = 1;
9129
- this.obstacleSHI = params.obstacleSHI;
9130
- this.hdRouteSHI = params.hdRouteSHI;
9131
- this.unsimplifiedRoute = params.unsimplifiedRoute;
9132
- this.routeSections = this.breakRouteIntoSections(this.unsimplifiedRoute);
9826
+ currentConnectionIndex = 0;
9827
+ candidates;
9828
+ visitedNodes;
9829
+ computeG(prevCandidate, node, endGoal) {
9830
+ return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
9133
9831
  }
9134
- breakRouteIntoSections(route) {
9135
- const routeSections = [];
9136
- const routePoints = route.route;
9137
- if (routePoints.length === 0) return [];
9138
- let currentSection = {
9139
- startIndex: 0,
9140
- endIndex: -1,
9141
- z: routePoints[0].z,
9142
- points: [routePoints[0]]
9143
- };
9144
- for (let i = 1; i < routePoints.length; i++) {
9145
- if (routePoints[i].z === currentSection.z) {
9146
- currentSection.points.push(routePoints[i]);
9147
- } else {
9148
- currentSection.endIndex = i - 1;
9149
- routeSections.push(currentSection);
9150
- currentSection = {
9151
- startIndex: i,
9152
- endIndex: -1,
9153
- z: routePoints[i].z,
9154
- points: [routePoints[i]]
9155
- };
9832
+ computeH(prevCandidate, node, endGoal) {
9833
+ return this.getDistanceBetweenNodes(node, endGoal);
9834
+ }
9835
+ getBacktrackedPath(candidate) {
9836
+ const path = [];
9837
+ let currentCandidate = candidate;
9838
+ while (currentCandidate) {
9839
+ path.push(currentCandidate.node);
9840
+ currentCandidate = currentCandidate.prevCandidate;
9841
+ }
9842
+ return path;
9843
+ }
9844
+ getNeighboringNodes(node) {
9845
+ return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
9846
+ (edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
9847
+ ).map((n) => this.nodeMap.get(n));
9848
+ }
9849
+ getCapacityPaths() {
9850
+ const capacityPaths = [];
9851
+ for (const connection of this.connectionsWithNodes) {
9852
+ const path = connection.path;
9853
+ if (path) {
9854
+ capacityPaths.push({
9855
+ capacityPathId: connection.connection.name,
9856
+ connectionName: connection.connection.name,
9857
+ nodeIds: path.map((node) => node.capacityMeshNodeId)
9858
+ });
9156
9859
  }
9157
9860
  }
9158
- currentSection.endIndex = routePoints.length - 1;
9159
- routeSections.push(currentSection);
9160
- return routeSections;
9861
+ return capacityPaths;
9862
+ }
9863
+ doesNodeHaveCapacityForTrace(node, prevNode) {
9864
+ const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
9865
+ const totalCapacity = this.getTotalCapacity(node);
9866
+ if (node.availableZ.length === 1 && !node._containsTarget && usedCapacity > 0)
9867
+ return false;
9868
+ let additionalCapacityRequirement = 0;
9869
+ if (node.availableZ.length > 1 && prevNode.availableZ.length === 1) {
9870
+ additionalCapacityRequirement += 0.5;
9871
+ }
9872
+ return usedCapacity + additionalCapacityRequirement < totalCapacity;
9873
+ }
9874
+ canTravelThroughObstacle(node, connectionName) {
9875
+ const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
9876
+ return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
9877
+ }
9878
+ getDistanceBetweenNodes(A, B) {
9879
+ return Math.sqrt(
9880
+ (A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
9881
+ );
9882
+ }
9883
+ reduceCapacityAlongPath(nextConnection) {
9884
+ for (const node of nextConnection.path ?? []) {
9885
+ this.usedNodeCapacityMap.set(
9886
+ node.capacityMeshNodeId,
9887
+ this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
9888
+ );
9889
+ }
9890
+ }
9891
+ isConnectedToEndGoal(node, endGoal) {
9892
+ return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
9161
9893
  }
9162
9894
  _step() {
9163
- if (this.currentSectionIndex >= this.routeSections.length - 1) {
9895
+ const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
9896
+ if (!nextConnection) {
9164
9897
  this.solved = true;
9165
9898
  return;
9166
9899
  }
9167
- const prevSection = this.routeSections[this.currentSectionIndex - 1];
9168
- const currentSection = this.routeSections[this.currentSectionIndex];
9169
- const nextSection = this.routeSections[this.currentSectionIndex + 1];
9170
- if (prevSection.z !== nextSection.z) {
9171
- this.currentSectionIndex++;
9900
+ const [start, end] = nextConnection.nodes;
9901
+ if (!this.candidates) {
9902
+ this.candidates = [{ prevCandidate: null, node: start, f: 0, g: 0, h: 0 }];
9903
+ this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
9904
+ this.visitedNodes = /* @__PURE__ */ new Set([start.capacityMeshNodeId]);
9905
+ this.activeCandidateStraightLineDistance = distance(
9906
+ start.center,
9907
+ end.center
9908
+ );
9909
+ }
9910
+ this.candidates.sort((a, b) => a.f - b.f);
9911
+ const currentCandidate = this.candidates.shift();
9912
+ if (!currentCandidate) {
9913
+ console.error(
9914
+ `Ran out of candidates on connection ${nextConnection.connection.name}`
9915
+ );
9916
+ this.currentConnectionIndex++;
9917
+ this.candidates = null;
9918
+ this.visitedNodes = null;
9919
+ this.failed = true;
9172
9920
  return;
9173
9921
  }
9174
- const targetZ = prevSection.z;
9175
- if (this.canSectionMoveToLayer({ currentSection, targetZ })) {
9176
- currentSection.z = targetZ;
9177
- currentSection.points = currentSection.points.map((p) => ({
9178
- ...p,
9179
- z: targetZ
9180
- }));
9181
- this.currentSectionIndex += 2;
9922
+ if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
9923
+ nextConnection.path = this.getBacktrackedPath({
9924
+ prevCandidate: currentCandidate,
9925
+ node: end,
9926
+ f: 0,
9927
+ g: 0,
9928
+ h: 0
9929
+ });
9930
+ this.reduceCapacityAlongPath(nextConnection);
9931
+ this.currentConnectionIndex++;
9932
+ this.candidates = null;
9933
+ this.visitedNodes = null;
9182
9934
  return;
9183
9935
  }
9184
- this.currentSectionIndex++;
9185
- return;
9186
- }
9187
- canSectionMoveToLayer({
9188
- currentSection,
9189
- targetZ
9190
- }) {
9191
- for (let i = 0; i < currentSection.points.length - 1; i++) {
9192
- const A = { ...currentSection.points[i], z: targetZ };
9193
- const B = { ...currentSection.points[i + 1], z: targetZ };
9194
- const conflictingRoutes = this.hdRouteSHI.getConflictingRoutesForSegment(
9195
- A,
9196
- B,
9197
- this.TRACE_THICKNESS
9198
- );
9199
- for (const { conflictingRoute, distance: distance6 } of conflictingRoutes) {
9200
- if (conflictingRoute.connectionName === this.unsimplifiedRoute.connectionName)
9201
- continue;
9202
- if (distance6 < this.TRACE_THICKNESS + conflictingRoute.traceThickness) {
9203
- return false;
9204
- }
9936
+ const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
9937
+ for (const neighborNode of neighborNodes) {
9938
+ if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
9939
+ continue;
9205
9940
  }
9206
- const segmentBox = {
9207
- centerX: (A.x + B.x) / 2,
9208
- centerY: (A.y + B.y) / 2,
9209
- width: Math.abs(A.x - B.x),
9210
- height: Math.abs(A.y - B.y)
9211
- };
9212
- const obstacles = this.obstacleSHI.getNodesInArea(
9213
- segmentBox.centerX,
9214
- segmentBox.centerY,
9215
- segmentBox.width,
9216
- segmentBox.height
9217
- );
9218
- for (const obstacle of obstacles) {
9219
- const distToObstacle = segmentToBoxMinDistance(A, B, obstacle);
9220
- if (distToObstacle < this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) {
9221
- return false;
9222
- }
9941
+ if (!this.doesNodeHaveCapacityForTrace(neighborNode, currentCandidate.node)) {
9942
+ continue;
9223
9943
  }
9224
- }
9225
- return true;
9226
- }
9227
- getOptimizedHdRoute() {
9228
- const route = this.routeSections.flatMap((section) => section.points);
9229
- const vias = [];
9230
- for (let i = 0; i < route.length - 1; i++) {
9231
- if (route[i].z !== route[i + 1].z) {
9232
- vias.push({
9233
- x: route[i].x,
9234
- y: route[i].y
9235
- });
9944
+ const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
9945
+ if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
9946
+ continue;
9236
9947
  }
9948
+ const g = this.computeG(currentCandidate, neighborNode, end);
9949
+ const h = this.computeH(currentCandidate, neighborNode, end);
9950
+ const f = g + h * this.GREEDY_MULTIPLIER;
9951
+ this.debug_lastNodeCostMap.set(neighborNode.capacityMeshNodeId, {
9952
+ f,
9953
+ g,
9954
+ h
9955
+ });
9956
+ const newCandidate = {
9957
+ prevCandidate: currentCandidate,
9958
+ node: neighborNode,
9959
+ f,
9960
+ g,
9961
+ h
9962
+ };
9963
+ this.candidates.push(newCandidate);
9237
9964
  }
9238
- return {
9239
- connectionName: this.unsimplifiedRoute.connectionName,
9240
- route,
9241
- traceThickness: this.unsimplifiedRoute.traceThickness,
9242
- vias,
9243
- viaDiameter: this.unsimplifiedRoute.viaDiameter
9244
- };
9965
+ this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
9245
9966
  }
9246
9967
  visualize() {
9247
9968
  const graphics = {
9248
- circles: [],
9249
9969
  lines: [],
9250
9970
  points: [],
9251
9971
  rects: [],
9252
- coordinateSystem: "cartesian",
9253
- title: "Single Route Useless Via Removal Solver"
9972
+ circles: []
9254
9973
  };
9255
- for (let i = 0; i < this.routeSections.length; i++) {
9256
- const section = this.routeSections[i];
9974
+ if (this.connectionsWithNodes) {
9975
+ for (let i = 0; i < this.connectionsWithNodes.length; i++) {
9976
+ const conn = this.connectionsWithNodes[i];
9977
+ if (conn.path && conn.path.length > 0) {
9978
+ const pathPoints = conn.path.map(
9979
+ ({ center: { x, y }, width, availableZ }) => ({
9980
+ // slight offset to allow viewing overlapping paths
9981
+ x: x + (i % 10 + i % 19) * (5e-3 * width),
9982
+ y: y + (i % 10 + i % 19) * (5e-3 * width),
9983
+ availableZ
9984
+ })
9985
+ );
9986
+ graphics.lines.push({
9987
+ points: pathPoints,
9988
+ strokeColor: this.colorMap[conn.connection.name]
9989
+ });
9990
+ for (let u = 0; u < pathPoints.length; u++) {
9991
+ const point = pathPoints[u];
9992
+ graphics.points.push({
9993
+ x: point.x,
9994
+ y: point.y,
9995
+ label: [
9996
+ `conn: ${conn.connection.name}`,
9997
+ `node: ${conn.path[u].capacityMeshNodeId}`,
9998
+ `z: ${point.availableZ.join(",")}`
9999
+ ].join("\n")
10000
+ });
10001
+ }
10002
+ }
10003
+ }
10004
+ }
10005
+ for (const node of this.nodes) {
10006
+ const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
10007
+ const totalCapacity = this.getTotalCapacity(node);
10008
+ const nodeCosts = this.debug_lastNodeCostMap.get(node.capacityMeshNodeId);
10009
+ graphics.rects.push({
10010
+ ...createRectFromCapacityNode(node, {
10011
+ rectMargin: 0.025,
10012
+ zOffset: 0.01
10013
+ }),
10014
+ label: [
10015
+ `${node.capacityMeshNodeId}`,
10016
+ `${usedCapacity}/${totalCapacity}`,
10017
+ `${node.width.toFixed(2)}x${node.height.toFixed(2)}`,
10018
+ `g: ${nodeCosts?.g !== void 0 ? nodeCosts.g.toFixed(2) : "?"}`,
10019
+ `h: ${nodeCosts?.h !== void 0 ? nodeCosts.h.toFixed(2) : "?"}`,
10020
+ `f: ${nodeCosts?.f !== void 0 ? nodeCosts.f.toFixed(2) : "?"}`,
10021
+ `z: ${node.availableZ.join(", ")}`
10022
+ ].join("\n"),
10023
+ stroke: usedCapacity > totalCapacity + 0.5 ? "red" : void 0
10024
+ });
10025
+ }
10026
+ if (this.connectionsWithNodes) {
10027
+ for (const conn of this.connectionsWithNodes) {
10028
+ if (conn.connection?.pointsToConnect) {
10029
+ for (const point of conn.connection.pointsToConnect) {
10030
+ graphics.points.push({
10031
+ x: point.x,
10032
+ y: point.y,
10033
+ label: [`pointsToConnect ${conn.connection.name}`].join("\n")
10034
+ });
10035
+ }
10036
+ }
10037
+ }
10038
+ }
10039
+ const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
10040
+ if (nextConnection) {
10041
+ const [start, end] = nextConnection.connection.pointsToConnect;
9257
10042
  graphics.lines.push({
9258
- points: section.points,
9259
- strokeWidth: this.TRACE_THICKNESS,
9260
- strokeColor: i === this.currentSectionIndex ? "orange" : section.z === 0 ? "red" : "blue"
10043
+ points: [
10044
+ { x: start.x, y: start.y },
10045
+ { x: end.x, y: end.y }
10046
+ ],
10047
+ strokeColor: "red",
10048
+ strokeDash: "10 5"
10049
+ });
10050
+ }
10051
+ if (this.candidates) {
10052
+ const topCandidates = this.candidates.slice(0, 5);
10053
+ const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
10054
+ topCandidates.forEach((candidate, index) => {
10055
+ const opacity = 0.5 * (1 - index / 5);
10056
+ const backtrackedPath = this.getBacktrackedPath(candidate);
10057
+ graphics.lines.push({
10058
+ points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
10059
+ strokeColor: safeTransparentize(
10060
+ this.colorMap[connectionName] ?? "red",
10061
+ 1 - opacity
10062
+ )
10063
+ });
9261
10064
  });
9262
10065
  }
9263
10066
  return graphics;
9264
10067
  }
9265
10068
  };
9266
10069
 
9267
- // lib/solvers/UselessViaRemovalSolver/UselessViaRemovalSolver.ts
9268
- var UselessViaRemovalSolver = class extends BaseSolver {
9269
- constructor(input) {
9270
- super();
9271
- this.input = input;
9272
- this.unsimplifiedHdRoutes = input.unsimplifiedHdRoutes;
9273
- this.optimizedHdRoutes = [];
9274
- this.unprocessedRoutes = [...input.unsimplifiedHdRoutes];
9275
- this.obstacleSHI = new ObstacleSpatialHashIndex(input.obstacles);
9276
- this.hdRouteSHI = new HighDensityRouteSpatialIndex(
9277
- this.unsimplifiedHdRoutes
9278
- );
10070
+ // lib/solvers/CapacityPathingSolver/CapacityPathingSolver5.ts
10071
+ var CapacityPathingSolver5 = class extends CapacityPathingSolver {
10072
+ NEGATIVE_CAPACITY_PENALTY_FACTOR = 1;
10073
+ REDUCED_CAPACITY_PENALTY_FACTOR = 1;
10074
+ constructor(...args) {
10075
+ super(...args);
10076
+ this.GREEDY_MULTIPLIER = 2.5;
9279
10077
  }
9280
- unsimplifiedHdRoutes;
9281
- optimizedHdRoutes;
9282
- unprocessedRoutes;
9283
- activeSubSolver = null;
9284
- obstacleSHI = null;
9285
- hdRouteSHI = null;
9286
- _step() {
9287
- if (this.activeSubSolver) {
9288
- this.activeSubSolver.step();
9289
- if (this.activeSubSolver.solved) {
9290
- this.optimizedHdRoutes.push(this.activeSubSolver.getOptimizedHdRoute());
9291
- this.activeSubSolver = null;
9292
- } else if (this.activeSubSolver.failed || this.activeSubSolver.error) {
9293
- this.error = this.activeSubSolver.error;
9294
- this.failed = true;
9295
- }
9296
- return;
9297
- }
9298
- const unprocessedRoute = this.unprocessedRoutes.shift();
9299
- if (!unprocessedRoute) {
9300
- this.solved = true;
9301
- return;
9302
- }
9303
- this.activeSubSolver = new SingleRouteUselessViaRemovalSolver({
9304
- hdRouteSHI: this.hdRouteSHI,
9305
- obstacleSHI: this.obstacleSHI,
9306
- unsimplifiedRoute: unprocessedRoute
9307
- });
10078
+ get maxCapacityFactor() {
10079
+ return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
9308
10080
  }
9309
- getOptimizedHdRoutes() {
9310
- return this.optimizedHdRoutes;
10081
+ getTotalCapacity(node) {
10082
+ return getTunedTotalCapacity1(node, this.maxCapacityFactor);
9311
10083
  }
9312
- visualize() {
9313
- const visualization = {
9314
- lines: [],
9315
- points: [],
9316
- rects: [],
9317
- circles: [],
9318
- coordinateSystem: "cartesian",
9319
- title: "Useless Via Removal Solver"
9320
- };
9321
- for (const route of this.optimizedHdRoutes) {
9322
- if (route.route.length === 0) continue;
9323
- const color = this.input.colorMap[route.connectionName] || "#888888";
9324
- for (let i = 0; i < route.route.length - 1; i++) {
9325
- const current = route.route[i];
9326
- const next = route.route[i + 1];
9327
- if (current.z === next.z) {
9328
- visualization.lines.push({
9329
- points: [
9330
- { x: current.x, y: current.y },
9331
- { x: next.x, y: next.y }
9332
- ],
9333
- strokeColor: current.z === 0 ? "red" : "blue",
9334
- strokeWidth: route.traceThickness,
9335
- label: `${route.connectionName} (z=${current.z})`
9336
- });
9337
- }
9338
- }
9339
- for (const via of route.vias) {
9340
- visualization.circles.push({
9341
- center: { x: via.x, y: via.y },
9342
- radius: route.viaDiameter / 2,
9343
- fill: "rgba(255, 0, 255, 0.5)",
9344
- label: `${route.connectionName} via`
9345
- });
9346
- }
9347
- }
9348
- if (this.activeSubSolver) {
9349
- visualization.lines.push(
9350
- ...this.activeSubSolver.visualize().lines ?? []
9351
- );
10084
+ /**
10085
+ * Penalty you pay for using this node
10086
+ */
10087
+ getNodeCapacityPenalty(node) {
10088
+ const MAX_PENALTY = node.width + node.height;
10089
+ const MIN_PENALTY = 0.05;
10090
+ const START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW = 2;
10091
+ const totalCapacity = this.getTotalCapacity(node);
10092
+ const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
10093
+ const remainingCapacity = totalCapacity - usedCapacity;
10094
+ if (remainingCapacity > START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW) {
10095
+ return MIN_PENALTY;
9352
10096
  }
9353
- return visualization;
10097
+ const penalty = (MAX_PENALTY - MIN_PENALTY) * Math.max(
10098
+ 1,
10099
+ (START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW - remainingCapacity) / (MAX_PENALTY - MIN_PENALTY)
10100
+ ) + MIN_PENALTY;
10101
+ return penalty;
10102
+ }
10103
+ /**
10104
+ * We're rewarding travel into big nodes.
10105
+ *
10106
+ * To minimize shortest path, you'd want to comment this out.
10107
+ */
10108
+ getDistanceBetweenNodes(A, B) {
10109
+ const dx = A.center.x - B.center.x;
10110
+ const dy = A.center.y - B.center.y;
10111
+ return Math.sqrt(dx ** 2 + dy ** 2);
10112
+ }
10113
+ computeG(prevCandidate, node, endGoal) {
10114
+ return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node) + this.getNodeCapacityPenalty(node);
10115
+ }
10116
+ computeH(prevCandidate, node, endGoal) {
10117
+ return this.getDistanceBetweenNodes(node, endGoal) + this.getNodeCapacityPenalty(node);
9354
10118
  }
9355
10119
  };
9356
10120
 
@@ -9390,6 +10154,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9390
10154
  nodeTargetMerger;
9391
10155
  edgeSolver;
9392
10156
  pathingSolver;
10157
+ // Updated type
9393
10158
  edgeToPortSegmentSolver;
9394
10159
  colorMap;
9395
10160
  segmentToPointSolver;
@@ -9399,8 +10164,10 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9399
10164
  highDensityStitchSolver;
9400
10165
  singleLayerNodeMerger;
9401
10166
  strawSolver;
9402
- uselessViaRemovalSolver;
9403
- multiSimplifiedPathSolver;
10167
+ uselessViaRemovalSolver1;
10168
+ uselessViaRemovalSolver2;
10169
+ multiSimplifiedPathSolver1;
10170
+ multiSimplifiedPathSolver2;
9404
10171
  startTimeOfPhase;
9405
10172
  endTimeOfPhase;
9406
10173
  timeSpentOnPhase;
@@ -9473,17 +10240,23 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9473
10240
  CapacityMeshEdgeSolver2_NodeTreeOptimization,
9474
10241
  (cms) => [cms.capacityNodes]
9475
10242
  ),
9476
- definePipelineStep("pathingSolver", CapacityPathingSolver5, (cms) => [
9477
- {
9478
- simpleRouteJson: cms.srjWithPointPairs,
9479
- nodes: cms.capacityNodes,
9480
- edges: cms.edgeSolver?.edges || [],
9481
- colorMap: cms.colorMap,
9482
- hyperParameters: {
9483
- MAX_CAPACITY_FACTOR: 1
10243
+ definePipelineStep(
10244
+ "pathingSolver",
10245
+ CapacityPathingSolver5,
10246
+ // CapacityPathingMultiSectionSolver,
10247
+ (cms) => [
10248
+ // Replaced solver class
10249
+ {
10250
+ simpleRouteJson: cms.srjWithPointPairs,
10251
+ nodes: cms.capacityNodes,
10252
+ edges: cms.edgeSolver?.edges || [],
10253
+ colorMap: cms.colorMap,
10254
+ hyperParameters: {
10255
+ MAX_CAPACITY_FACTOR: 1
10256
+ }
9484
10257
  }
9485
- }
9486
- ]),
10258
+ ]
10259
+ ),
9487
10260
  definePipelineStep(
9488
10261
  "edgeToPortSegmentSolver",
9489
10262
  CapacityEdgeToPortSegmentSolver,
@@ -9551,12 +10324,13 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9551
10324
  {
9552
10325
  connections: cms.srjWithPointPairs.connections,
9553
10326
  hdRoutes: cms.highDensityRouteSolver.routes,
10327
+ colorMap: cms.colorMap,
9554
10328
  layerCount: cms.srj.layerCount
9555
10329
  }
9556
10330
  ]
9557
10331
  ),
9558
10332
  definePipelineStep(
9559
- "uselessViaRemovalSolver",
10333
+ "uselessViaRemovalSolver1",
9560
10334
  UselessViaRemovalSolver,
9561
10335
  (cms) => [
9562
10336
  {
@@ -9568,11 +10342,35 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9568
10342
  ]
9569
10343
  ),
9570
10344
  definePipelineStep(
9571
- "multiSimplifiedPathSolver",
10345
+ "multiSimplifiedPathSolver1",
10346
+ MultiSimplifiedPathSolver,
10347
+ (cms) => [
10348
+ {
10349
+ unsimplifiedHdRoutes: cms.uselessViaRemovalSolver1?.getOptimizedHdRoutes() || cms.highDensityStitchSolver.mergedHdRoutes,
10350
+ obstacles: cms.srj.obstacles,
10351
+ connMap: cms.connMap,
10352
+ colorMap: cms.colorMap
10353
+ }
10354
+ ]
10355
+ ),
10356
+ definePipelineStep(
10357
+ "uselessViaRemovalSolver2",
10358
+ UselessViaRemovalSolver,
10359
+ (cms) => [
10360
+ {
10361
+ unsimplifiedHdRoutes: cms.multiSimplifiedPathSolver1.simplifiedHdRoutes,
10362
+ obstacles: cms.srj.obstacles,
10363
+ colorMap: cms.colorMap,
10364
+ layerCount: cms.srj.layerCount
10365
+ }
10366
+ ]
10367
+ ),
10368
+ definePipelineStep(
10369
+ "multiSimplifiedPathSolver2",
9572
10370
  MultiSimplifiedPathSolver,
9573
10371
  (cms) => [
9574
10372
  {
9575
- unsimplifiedHdRoutes: cms.uselessViaRemovalSolver?.getOptimizedHdRoutes() || cms.highDensityStitchSolver.mergedHdRoutes,
10373
+ unsimplifiedHdRoutes: cms.uselessViaRemovalSolver2?.getOptimizedHdRoutes(),
9576
10374
  obstacles: cms.srj.obstacles,
9577
10375
  connMap: cms.connMap,
9578
10376
  colorMap: cms.colorMap
@@ -9631,8 +10429,10 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9631
10429
  const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
9632
10430
  const highDensityViz = this.highDensityRouteSolver?.visualize();
9633
10431
  const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
9634
- const uselessViaRemovalViz = this.uselessViaRemovalSolver?.visualize();
9635
- const simplifiedPathSolverViz = this.multiSimplifiedPathSolver?.visualize();
10432
+ const uselessViaRemovalViz1 = this.uselessViaRemovalSolver1?.visualize();
10433
+ const uselessViaRemovalViz2 = this.uselessViaRemovalSolver2?.visualize();
10434
+ const simplifiedPathSolverViz1 = this.multiSimplifiedPathSolver1?.visualize();
10435
+ const simplifiedPathSolverViz2 = this.multiSimplifiedPathSolver2?.visualize();
9636
10436
  const problemViz = {
9637
10437
  points: [
9638
10438
  ...this.srj.connections.flatMap(
@@ -9684,8 +10484,10 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9684
10484
  segmentOptimizationViz,
9685
10485
  highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null,
9686
10486
  highDensityStitchViz,
9687
- uselessViaRemovalViz,
9688
- simplifiedPathSolverViz,
10487
+ uselessViaRemovalViz1,
10488
+ simplifiedPathSolverViz1,
10489
+ uselessViaRemovalViz2,
10490
+ simplifiedPathSolverViz2,
9689
10491
  this.solved ? combineVisualizations(
9690
10492
  problemViz,
9691
10493
  convertSrjToGraphicsObject(this.getOutputSimpleRouteJson())
@@ -9747,7 +10549,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9747
10549
  return match ? match[1] : mstConnectionName;
9748
10550
  }
9749
10551
  _getOutputHdRoutes() {
9750
- return this.multiSimplifiedPathSolver?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver?.getOptimizedHdRoutes() ?? this.highDensityStitchSolver.mergedHdRoutes;
10552
+ return this.multiSimplifiedPathSolver2?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver2?.getOptimizedHdRoutes() ?? this.multiSimplifiedPathSolver1?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver1?.getOptimizedHdRoutes() ?? this.highDensityStitchSolver.mergedHdRoutes;
9751
10553
  }
9752
10554
  /**
9753
10555
  * Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces
@@ -9759,9 +10561,9 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9759
10561
  const traces = [];
9760
10562
  const allHdRoutes = this._getOutputHdRoutes();
9761
10563
  for (const connection of this.netToPointPairsSolver?.newConnections ?? []) {
9762
- const netConnection = this.srj.connections.find(
9763
- (c) => c.name === connection.netConnectionName
9764
- );
10564
+ const netConnectionName = this.srj.connections.find(
10565
+ (c) => c.name === connection.name
10566
+ )?.netConnectionName;
9765
10567
  const hdRoutes = allHdRoutes.filter(
9766
10568
  (r) => r.connectionName === connection.name
9767
10569
  );
@@ -9770,7 +10572,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9770
10572
  const simplifiedPcbTrace = {
9771
10573
  type: "pcb_trace",
9772
10574
  pcb_trace_id: `${connection.name}_${i}`,
9773
- connection_name: this.getOriginalConnectionName(connection.name),
10575
+ connection_name: netConnectionName ?? this.getOriginalConnectionName(connection.name),
9774
10576
  route: convertHdRouteToSimplifiedRoute(hdRoute, this.srj.layerCount)
9775
10577
  };
9776
10578
  traces.push(simplifiedPcbTrace);