@tscircuit/capacity-autorouter 0.0.52 → 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
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: []
3465
4210
  };
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;
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: []
3504
4228
  };
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
3534
- };
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
  }
@@ -5282,24 +5924,40 @@ var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
5282
5924
  remainingHdRoutes;
5283
5925
  start;
5284
5926
  end;
5927
+ colorMap;
5285
5928
  constructor(opts) {
5286
5929
  super();
5287
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
+ }
5288
5948
  this.mergedHdRoute = {
5289
- connectionName: opts.connectionName ?? opts.hdRoutes[0].connectionName,
5949
+ connectionName: opts.connectionName ?? firstRoute.connectionName,
5290
5950
  route: [
5291
5951
  {
5292
- x: opts.start.x,
5293
- y: opts.start.y,
5294
- z: opts.start.z
5952
+ x: this.start.x,
5953
+ y: this.start.y,
5954
+ z: this.start.z
5295
5955
  }
5296
5956
  ],
5297
5957
  vias: [],
5298
5958
  viaDiameter: opts.hdRoutes?.[0]?.viaDiameter ?? 0.6,
5299
5959
  traceThickness: opts.hdRoutes?.[0]?.traceThickness ?? 0.15
5300
5960
  };
5301
- this.start = opts.start;
5302
- this.end = opts.end;
5303
5961
  }
5304
5962
  _step() {
5305
5963
  if (this.remainingHdRoutes.length === 0) {
@@ -5385,31 +6043,29 @@ var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
5385
6043
  });
5386
6044
  }
5387
6045
  }
5388
- const colorList = Array.from(
5389
- { length: this.remainingHdRoutes.length },
5390
- (_, i) => `hsl(${i * 360 / this.remainingHdRoutes.length}, 100%, 50%)`
5391
- );
5392
6046
  for (const [i, hdRoute] of this.remainingHdRoutes.entries()) {
6047
+ const routeColor = this.colorMap[hdRoute.connectionName] ?? "gray";
5393
6048
  if (hdRoute.route.length > 1) {
5394
6049
  graphics.lines?.push({
5395
6050
  points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
5396
- strokeColor: colorList[i]
6051
+ strokeColor: routeColor
5397
6052
  });
5398
6053
  }
5399
6054
  for (let pi = 0; pi < hdRoute.route.length; pi++) {
5400
6055
  const point = hdRoute.route[pi];
5401
6056
  graphics.points?.push({
5402
6057
  x: point.x + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
6058
+ // Keep slight offset for visibility
5403
6059
  y: point.y + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
5404
- color: colorList[i],
5405
- 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" : ""}`
5406
6062
  });
5407
6063
  }
5408
6064
  for (const via of hdRoute.vias) {
5409
6065
  graphics.circles?.push({
5410
6066
  center: { x: via.x, y: via.y },
5411
6067
  radius: hdRoute.viaDiameter / 2,
5412
- fill: colorList[i]
6068
+ fill: routeColor
5413
6069
  });
5414
6070
  }
5415
6071
  }
@@ -5422,8 +6078,10 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5422
6078
  unsolvedRoutes;
5423
6079
  activeSolver = null;
5424
6080
  mergedHdRoutes = [];
6081
+ colorMap = {};
5425
6082
  constructor(opts) {
5426
6083
  super();
6084
+ this.colorMap = opts.colorMap ?? {};
5427
6085
  this.unsolvedRoutes = opts.connections.map((c) => ({
5428
6086
  connectionName: c.name,
5429
6087
  hdRoutes: opts.hdRoutes.filter((r) => r.connectionName === c.name),
@@ -5459,7 +6117,8 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5459
6117
  connectionName: unsolvedRoute.connectionName,
5460
6118
  hdRoutes: unsolvedRoute.hdRoutes,
5461
6119
  start: unsolvedRoute.start,
5462
- end: unsolvedRoute.end
6120
+ end: unsolvedRoute.end,
6121
+ colorMap: this.colorMap
5463
6122
  });
5464
6123
  }
5465
6124
  visualize() {
@@ -5485,22 +6144,26 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5485
6144
  }
5486
6145
  }
5487
6146
  for (const [i, mergedRoute] of this.mergedHdRoutes.entries()) {
5488
- const solvedColor = `hsl(120, 100%, ${40 + i * 10 % 40}%)`;
5489
- 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;
5490
6152
  graphics.lines?.push({
5491
- points: mergedRoute.route.map((point) => ({
5492
- x: point.x,
5493
- y: point.y
5494
- })),
5495
- strokeColor: solvedColor,
6153
+ points: [
6154
+ { x: p1.x, y: p1.y },
6155
+ { x: p2.x, y: p2.y }
6156
+ ],
6157
+ strokeColor: segmentColor,
5496
6158
  strokeWidth: mergedRoute.traceThickness
5497
6159
  });
5498
6160
  }
5499
6161
  for (const point of mergedRoute.route) {
6162
+ const pointColor = point.z !== 0 ? safeTransparentize(solvedColor, 0.5) : solvedColor;
5500
6163
  graphics.points?.push({
5501
6164
  x: point.x,
5502
6165
  y: point.y,
5503
- color: solvedColor
6166
+ color: pointColor
5504
6167
  });
5505
6168
  }
5506
6169
  for (const via of mergedRoute.vias) {
@@ -5508,25 +6171,23 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5508
6171
  center: { x: via.x, y: via.y },
5509
6172
  radius: mergedRoute.viaDiameter / 2,
5510
6173
  fill: solvedColor
6174
+ // Keep vias solid color for visibility
5511
6175
  });
5512
6176
  }
5513
6177
  }
5514
- const colorList = Array.from(
5515
- { length: this.unsolvedRoutes.length },
5516
- (_, i) => `hsl(${i * 360 / this.unsolvedRoutes.length}, 100%, 50%)`
5517
- );
5518
- for (const [i, unsolvedRoute] of this.unsolvedRoutes.entries()) {
6178
+ for (const unsolvedRoute of this.unsolvedRoutes) {
6179
+ const routeColor = this.colorMap[unsolvedRoute.connectionName] ?? "gray";
5519
6180
  graphics.points?.push(
5520
6181
  {
5521
6182
  x: unsolvedRoute.start.x,
5522
6183
  y: unsolvedRoute.start.y,
5523
- color: colorList[i],
6184
+ color: routeColor,
5524
6185
  label: `${unsolvedRoute.connectionName} Start`
5525
6186
  },
5526
6187
  {
5527
6188
  x: unsolvedRoute.end.x,
5528
6189
  y: unsolvedRoute.end.y,
5529
- color: colorList[i],
6190
+ color: routeColor,
5530
6191
  label: `${unsolvedRoute.connectionName} End`
5531
6192
  }
5532
6193
  );
@@ -5535,14 +6196,15 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5535
6196
  { x: unsolvedRoute.start.x, y: unsolvedRoute.start.y },
5536
6197
  { x: unsolvedRoute.end.x, y: unsolvedRoute.end.y }
5537
6198
  ],
5538
- strokeColor: colorList[i],
6199
+ strokeColor: routeColor,
5539
6200
  strokeDash: "2 2"
5540
6201
  });
5541
6202
  for (const hdRoute of unsolvedRoute.hdRoutes) {
5542
6203
  if (hdRoute.route.length > 1) {
5543
6204
  graphics.lines?.push({
5544
6205
  points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
5545
- strokeColor: safeTransparentize(colorList[i], 0.5),
6206
+ strokeColor: safeTransparentize(routeColor, 0.5),
6207
+ // Use routeColor
5546
6208
  strokeDash: "10 5"
5547
6209
  });
5548
6210
  }
@@ -5550,7 +6212,8 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
5550
6212
  graphics.circles?.push({
5551
6213
  center: { x: via.x, y: via.y },
5552
6214
  radius: hdRoute.viaDiameter / 2,
5553
- fill: colorList[i]
6215
+ fill: routeColor
6216
+ // Use routeColor
5554
6217
  });
5555
6218
  }
5556
6219
  }
@@ -6852,727 +7515,341 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
6852
7515
  }
6853
7516
  };
6854
7517
 
6855
- // lib/utils/createRectFromCapacityNode.ts
6856
- var createRectFromCapacityNode = (node, opts = {}) => {
6857
- const lowestZ = Math.min(...node.availableZ);
6858
- return {
6859
- center: !opts.rectMargin || opts.zOffset ? {
6860
- x: node.center.x + lowestZ * node.width * (opts.zOffset ?? 0.05),
6861
- y: node.center.y - lowestZ * node.width * (opts.zOffset ?? 0.05)
6862
- } : node.center,
6863
- width: opts.rectMargin ? node.width - opts.rectMargin * 2 : Math.max(node.width - 0.5, node.width * 0.8),
6864
- height: opts.rectMargin ? node.height - opts.rectMargin * 2 : Math.max(node.height - 0.5, node.height * 0.8),
6865
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
6866
- "0,1": "rgba(0,0,0,0.1)",
6867
- "0": "rgba(0,200,200, 0.1)",
6868
- "1": "rgba(0,0,200, 0.1)"
6869
- }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
6870
- layer: `z${node.availableZ.join(",")}`,
6871
- label: [
6872
- node.capacityMeshNodeId,
6873
- `availableZ: ${node.availableZ.join(",")}`,
6874
- `${node._containsTarget ? "containsTarget" : ""}`,
6875
- `${node._containsObstacle ? "containsObstacle" : ""}`
6876
- ].filter(Boolean).join("\n")
6877
- };
6878
- };
6879
-
6880
- // lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
6881
- var CapacityPathingSolver = class extends BaseSolver {
6882
- connectionsWithNodes;
6883
- usedNodeCapacityMap;
6884
- simpleRouteJson;
6885
- nodes;
6886
- edges;
6887
- GREEDY_MULTIPLIER = 1.1;
6888
- nodeMap;
6889
- nodeEdgeMap;
6890
- connectionNameToGoalNodeIds;
6891
- colorMap;
6892
- maxDepthOfNodes;
6893
- activeCandidateStraightLineDistance;
6894
- debug_lastNodeCostMap;
6895
- hyperParameters;
6896
- constructor({
6897
- simpleRouteJson,
6898
- nodes,
6899
- edges,
6900
- colorMap,
6901
- MAX_ITERATIONS = 1e6,
6902
- hyperParameters = {}
6903
- }) {
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) {
6904
7527
  super();
6905
- this.MAX_ITERATIONS = MAX_ITERATIONS;
6906
- this.simpleRouteJson = simpleRouteJson;
6907
- this.nodes = nodes;
6908
- this.edges = edges;
6909
- this.colorMap = colorMap ?? {};
6910
- const { connectionsWithNodes, connectionNameToGoalNodeIds } = this.getConnectionsWithNodes();
6911
- this.connectionsWithNodes = connectionsWithNodes;
6912
- this.connectionNameToGoalNodeIds = connectionNameToGoalNodeIds;
6913
- this.hyperParameters = hyperParameters;
6914
- this.usedNodeCapacityMap = new Map(
6915
- this.nodes.map((node) => [node.capacityMeshNodeId, 0])
6916
- );
6917
- this.nodeMap = new Map(
6918
- this.nodes.map((node) => [node.capacityMeshNodeId, node])
6919
- );
6920
- this.nodeEdgeMap = getNodeEdgeMap(this.edges);
6921
- this.maxDepthOfNodes = Math.max(
6922
- ...this.nodes.map((node) => node._depth ?? 0)
6923
- );
6924
- this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
6925
- }
6926
- getTotalCapacity(node) {
6927
- const depth = node._depth ?? 0;
6928
- return (this.maxDepthOfNodes - depth + 1) ** 2;
6929
- }
6930
- getConnectionsWithNodes() {
6931
- const connectionsWithNodes = [];
6932
- const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
6933
- const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
6934
- for (const connection of this.simpleRouteJson.connections) {
6935
- const nodesForConnection = [];
6936
- for (const point of connection.pointsToConnect) {
6937
- let closestNode = this.nodes[0];
6938
- let minDistance = Number.MAX_VALUE;
6939
- for (const node of nodesWithTargets) {
6940
- const distance6 = Math.sqrt(
6941
- (node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
6942
- );
6943
- if (distance6 < minDistance) {
6944
- minDistance = distance6;
6945
- closestNode = node;
6946
- }
6947
- }
6948
- nodesForConnection.push(closestNode);
6949
- }
6950
- if (nodesForConnection.length < 2) {
6951
- throw new Error(
6952
- `Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
6953
- );
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);
6954
7540
  }
6955
- connectionNameToGoalNodeIds.set(
6956
- connection.name,
6957
- nodesForConnection.map((n) => n.capacityMeshNodeId)
6958
- );
6959
- connectionsWithNodes.push({
6960
- connection,
6961
- nodes: nodesForConnection,
6962
- pathFound: false,
6963
- straightLineDistance: distance(
6964
- nodesForConnection[0].center,
6965
- nodesForConnection[nodesForConnection.length - 1].center
6966
- )
6967
- });
6968
- }
6969
- connectionsWithNodes.sort(
6970
- (a, b) => a.straightLineDistance - b.straightLineDistance
6971
- );
6972
- return { connectionsWithNodes, connectionNameToGoalNodeIds };
6973
- }
6974
- currentConnectionIndex = 0;
6975
- candidates;
6976
- visitedNodes;
6977
- computeG(prevCandidate, node, endGoal) {
6978
- return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
6979
- }
6980
- computeH(prevCandidate, node, endGoal) {
6981
- return this.getDistanceBetweenNodes(node, endGoal);
6982
- }
6983
- getBacktrackedPath(candidate) {
6984
- const path = [];
6985
- let currentCandidate = candidate;
6986
- while (currentCandidate) {
6987
- path.push(currentCandidate.node);
6988
- currentCandidate = currentCandidate.prevCandidate;
6989
7541
  }
6990
- return path;
6991
- }
6992
- getNeighboringNodes(node) {
6993
- return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
6994
- (edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
6995
- ).map((n) => this.nodeMap.get(n));
6996
7542
  }
6997
- getCapacityPaths() {
6998
- const capacityPaths = [];
6999
- for (const connection of this.connectionsWithNodes) {
7000
- const path = connection.path;
7001
- if (path) {
7002
- capacityPaths.push({
7003
- capacityPathId: connection.connection.name,
7004
- connectionName: connection.connection.name,
7005
- nodeIds: path.map((node) => node.capacityMeshNodeId)
7006
- });
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;
7007
7561
  }
7008
7562
  }
7009
- return capacityPaths;
7563
+ return totalCapacity;
7010
7564
  }
7011
- doesNodeHaveCapacityForTrace(node, prevNode) {
7012
- const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
7013
- const totalCapacity = this.getTotalCapacity(node);
7014
- if (node.availableZ.length === 1 && !node._containsTarget && usedCapacity > 0)
7015
- return false;
7016
- let additionalCapacityRequirement = 0;
7017
- if (node.availableZ.length > 1 && prevNode.availableZ.length === 1) {
7018
- additionalCapacityRequirement += 0.5;
7019
- }
7020
- return usedCapacity + additionalCapacityRequirement < totalCapacity;
7021
- }
7022
- canTravelThroughObstacle(node, connectionName) {
7023
- const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
7024
- return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
7025
- }
7026
- getDistanceBetweenNodes(A, B) {
7027
- return Math.sqrt(
7028
- (A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
7029
- );
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
+ };
7030
7597
  }
7031
- reduceCapacityAlongPath(nextConnection) {
7032
- for (const node of nextConnection.path ?? []) {
7033
- this.usedNodeCapacityMap.set(
7034
- node.capacityMeshNodeId,
7035
- this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
7036
- );
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
7645
+ });
7646
+ }
7037
7647
  }
7648
+ return result;
7038
7649
  }
7039
- isConnectedToEndGoal(node, endGoal) {
7040
- return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
7650
+ getResultNodes() {
7651
+ return [...this.multiLayerNodes, ...this.strawNodes, ...this.skippedNodes];
7041
7652
  }
7042
7653
  _step() {
7043
- const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
7044
- if (!nextConnection) {
7654
+ const rootNode = this.unprocessedNodes.pop();
7655
+ if (!rootNode) {
7045
7656
  this.solved = true;
7046
7657
  return;
7047
7658
  }
7048
- const [start, end] = nextConnection.nodes;
7049
- if (!this.candidates) {
7050
- this.candidates = [{ prevCandidate: null, node: start, f: 0, g: 0, h: 0 }];
7051
- this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
7052
- this.visitedNodes = /* @__PURE__ */ new Set([start.capacityMeshNodeId]);
7053
- this.activeCandidateStraightLineDistance = distance(
7054
- start.center,
7055
- end.center
7056
- );
7057
- }
7058
- this.candidates.sort((a, b) => a.f - b.f);
7059
- const currentCandidate = this.candidates.shift();
7060
- if (!currentCandidate) {
7061
- console.error(
7062
- `Ran out of candidates on connection ${nextConnection.connection.name}`
7063
- );
7064
- this.currentConnectionIndex++;
7065
- this.candidates = null;
7066
- this.visitedNodes = null;
7067
- this.failed = true;
7659
+ if (rootNode.width < this.strawSize && rootNode.height < this.strawSize) {
7660
+ this.skippedNodes.push(rootNode);
7068
7661
  return;
7069
7662
  }
7070
- if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
7071
- nextConnection.path = this.getBacktrackedPath({
7072
- prevCandidate: currentCandidate,
7073
- node: end,
7074
- f: 0,
7075
- g: 0,
7076
- h: 0
7077
- });
7078
- this.reduceCapacityAlongPath(nextConnection);
7079
- this.currentConnectionIndex++;
7080
- this.candidates = null;
7081
- this.visitedNodes = null;
7663
+ if (rootNode._containsTarget) {
7664
+ this.skippedNodes.push(rootNode);
7082
7665
  return;
7083
7666
  }
7084
- const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
7085
- for (const neighborNode of neighborNodes) {
7086
- if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
7087
- continue;
7088
- }
7089
- if (!this.doesNodeHaveCapacityForTrace(neighborNode, currentCandidate.node)) {
7090
- continue;
7091
- }
7092
- const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
7093
- if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
7094
- continue;
7095
- }
7096
- const g = this.computeG(currentCandidate, neighborNode, end);
7097
- const h = this.computeH(currentCandidate, neighborNode, end);
7098
- const f = g + h * this.GREEDY_MULTIPLIER;
7099
- this.debug_lastNodeCostMap.set(neighborNode.capacityMeshNodeId, {
7100
- f,
7101
- g,
7102
- h
7103
- });
7104
- const newCandidate = {
7105
- prevCandidate: currentCandidate,
7106
- node: neighborNode,
7107
- f,
7108
- g,
7109
- h
7110
- };
7111
- this.candidates.push(newCandidate);
7112
- }
7113
- this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
7667
+ const strawNodes = this.createStrawsForNode(rootNode);
7668
+ this.strawNodes.push(...strawNodes);
7114
7669
  }
7115
7670
  visualize() {
7116
7671
  const graphics = {
7672
+ rects: [],
7117
7673
  lines: [],
7118
7674
  points: [],
7119
- rects: [],
7120
- circles: []
7675
+ circles: [],
7676
+ title: "Straw Solver"
7121
7677
  };
7122
- if (this.connectionsWithNodes) {
7123
- for (let i = 0; i < this.connectionsWithNodes.length; i++) {
7124
- const conn = this.connectionsWithNodes[i];
7125
- if (conn.path && conn.path.length > 0) {
7126
- const pathPoints = conn.path.map(
7127
- ({ center: { x, y }, width, availableZ }) => ({
7128
- // slight offset to allow viewing overlapping paths
7129
- x: x + (i % 10 + i % 19) * (5e-3 * width),
7130
- y: y + (i % 10 + i % 19) * (5e-3 * width),
7131
- availableZ
7132
- })
7133
- );
7134
- graphics.lines.push({
7135
- points: pathPoints,
7136
- strokeColor: this.colorMap[conn.connection.name]
7137
- });
7138
- for (let u = 0; u < pathPoints.length; u++) {
7139
- const point = pathPoints[u];
7140
- graphics.points.push({
7141
- x: point.x,
7142
- y: point.y,
7143
- label: [
7144
- `conn: ${conn.connection.name}`,
7145
- `node: ${conn.path[u].capacityMeshNodeId}`,
7146
- `z: ${point.availableZ.join(",")}`
7147
- ].join("\n")
7148
- });
7149
- }
7150
- }
7151
- }
7678
+ for (const node of this.unprocessedNodes) {
7679
+ graphics.rects.push({
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}`
7688
+ });
7152
7689
  }
7153
- for (const node of this.nodes) {
7154
- const nodeCosts = this.debug_lastNodeCostMap.get(node.capacityMeshNodeId);
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)";
7155
7692
  graphics.rects.push({
7156
- ...createRectFromCapacityNode(node, {
7157
- rectMargin: 0.025,
7158
- zOffset: 0.01
7159
- }),
7160
- label: [
7161
- `${node.capacityMeshNodeId}`,
7162
- `${this.usedNodeCapacityMap.get(node.capacityMeshNodeId)}/${this.getTotalCapacity(node).toFixed(2)}`,
7163
- `${node.width.toFixed(2)}x${node.height.toFixed(2)}`,
7164
- `g: ${nodeCosts?.g !== void 0 ? nodeCosts.g.toFixed(2) : "?"}`,
7165
- `h: ${nodeCosts?.h !== void 0 ? nodeCosts.h.toFixed(2) : "?"}`,
7166
- `f: ${nodeCosts?.f !== void 0 ? nodeCosts.f.toFixed(2) : "?"}`,
7167
- `z: ${node.availableZ.join(", ")}`
7168
- ].join("\n")
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(",")}`
7169
7702
  });
7170
7703
  }
7171
- if (this.connectionsWithNodes) {
7172
- for (const conn of this.connectionsWithNodes) {
7173
- if (conn.connection?.pointsToConnect) {
7174
- for (const point of conn.connection.pointsToConnect) {
7175
- graphics.points.push({
7176
- x: point.x,
7177
- y: point.y,
7178
- label: [`pointsToConnect ${conn.connection.name}`].join("\n")
7179
- });
7180
- }
7181
- }
7182
- }
7183
- }
7184
- const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
7185
- if (nextConnection) {
7186
- const [start, end] = nextConnection.connection.pointsToConnect;
7187
- graphics.lines.push({
7188
- points: [
7189
- { x: start.x, y: start.y },
7190
- { x: end.x, y: end.y }
7191
- ],
7192
- strokeColor: "red",
7193
- strokeDash: "10 5"
7194
- });
7195
- }
7196
- if (this.candidates) {
7197
- const topCandidates = this.candidates.slice(0, 5);
7198
- const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
7199
- topCandidates.forEach((candidate, index) => {
7200
- const opacity = 0.5 * (1 - index / 5);
7201
- const backtrackedPath = this.getBacktrackedPath(candidate);
7202
- graphics.lines.push({
7203
- points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
7204
- strokeColor: safeTransparentize(
7205
- this.colorMap[connectionName] ?? "red",
7206
- 1 - opacity
7207
- )
7208
- });
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}`
7209
7715
  });
7210
7716
  }
7211
7717
  return graphics;
7212
7718
  }
7213
7719
  };
7214
7720
 
7215
- // lib/solvers/CapacityPathingSolver/CapacityPathingSolver5.ts
7216
- var CapacityPathingSolver5 = class extends CapacityPathingSolver {
7217
- NEGATIVE_CAPACITY_PENALTY_FACTOR = 1;
7218
- REDUCED_CAPACITY_PENALTY_FACTOR = 1;
7219
- constructor(...args) {
7220
- super(...args);
7221
- this.GREEDY_MULTIPLIER = 2.5;
7222
- }
7223
- get maxCapacityFactor() {
7224
- return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
7225
- }
7226
- getTotalCapacity(node) {
7227
- return getTunedTotalCapacity1(node, this.maxCapacityFactor);
7228
- }
7229
- /**
7230
- * Penalty you pay for using this node
7231
- */
7232
- getNodeCapacityPenalty(node) {
7233
- const MAX_PENALTY = node.width + node.height;
7234
- const MIN_PENALTY = 0.05;
7235
- const START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW = 2;
7236
- const totalCapacity = this.getTotalCapacity(node);
7237
- const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
7238
- const remainingCapacity = totalCapacity - usedCapacity;
7239
- if (remainingCapacity > START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW) {
7240
- 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
+ }
7241
7783
  }
7242
- const penalty = (MAX_PENALTY - MIN_PENALTY) * Math.max(
7243
- 1,
7244
- (START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW - remainingCapacity) / (MAX_PENALTY - MIN_PENALTY)
7245
- ) + MIN_PENALTY;
7246
- return penalty;
7247
- }
7248
- /**
7249
- * We're rewarding travel into big nodes.
7250
- *
7251
- * To minimize shortest path, you'd want to comment this out.
7252
- */
7253
- getDistanceBetweenNodes(A, B) {
7254
- const dx = A.center.x - B.center.x;
7255
- const dy = A.center.y - B.center.y;
7256
- return Math.sqrt(dx ** 2 + dy ** 2);
7257
7784
  }
7258
- computeG(prevCandidate, node, endGoal) {
7259
- 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)}`;
7260
7789
  }
7261
- computeH(prevCandidate, node, endGoal) {
7262
- 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;
7263
7809
  }
7264
7810
  };
7265
7811
 
7266
- // lib/solvers/StrawSolver/StrawSolver.ts
7267
- var StrawSolver = class extends BaseSolver {
7268
- multiLayerNodes;
7269
- strawNodes;
7270
- skippedNodes;
7271
- unprocessedNodes;
7272
- strawSize;
7273
- nodeIdCounter;
7274
- 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) {
7275
7823
  super();
7824
+ this.nodeMap = /* @__PURE__ */ new Map();
7276
7825
  this.MAX_ITERATIONS = 1e5;
7277
- this.strawSize = params.strawSize ?? 0.5;
7278
- this.multiLayerNodes = [];
7279
- this.strawNodes = [];
7280
- this.skippedNodes = [];
7281
- this.nodeIdCounter = 0;
7282
- this.unprocessedNodes = [];
7283
- for (const node of params.nodes) {
7284
- if (node.availableZ.length === 1) {
7285
- 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);
7286
7836
  } else {
7287
- this.multiLayerNodes.push(node);
7837
+ unprocessedNodesWithArea.push([node, node.width * node.height]);
7288
7838
  }
7289
7839
  }
7290
- }
7291
- getCapacityOfMultiLayerNodesWithinBounds(bounds) {
7292
- let totalCapacity = 0;
7293
- for (const node of this.multiLayerNodes) {
7294
- const nodeMinX = node.center.x - node.width / 2;
7295
- const nodeMaxX = node.center.x + node.width / 2;
7296
- const nodeMinY = node.center.y - node.height / 2;
7297
- const nodeMaxY = node.center.y + node.height / 2;
7298
- const overlapMinX = Math.max(bounds.minX, nodeMinX);
7299
- const overlapMaxX = Math.min(bounds.maxX, nodeMaxX);
7300
- const overlapMinY = Math.max(bounds.minY, nodeMinY);
7301
- const overlapMaxY = Math.min(bounds.maxY, nodeMaxY);
7302
- if (overlapMinX < overlapMaxX && overlapMinY < overlapMaxY) {
7303
- const overlapWidth = overlapMaxX - overlapMinX;
7304
- const overlapHeight = overlapMaxY - overlapMinY;
7305
- const overlapArea = overlapWidth * overlapHeight;
7306
- const nodeArea = node.width * node.height;
7307
- const proportion = overlapArea / nodeArea;
7308
- totalCapacity += getTunedTotalCapacity1(node) * proportion;
7309
- }
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);
7310
7847
  }
7311
- return totalCapacity;
7312
- }
7313
- getSurroundingCapacities(node) {
7314
- const searchDistance = Math.min(node.width, node.height);
7315
- const leftSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7316
- minX: node.center.x - node.width / 2 - searchDistance,
7317
- maxX: node.center.x - node.width / 2,
7318
- minY: node.center.y - node.height / 2,
7319
- maxY: node.center.y + node.height / 2
7320
- });
7321
- const rightSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7322
- minX: node.center.x + node.width / 2,
7323
- maxX: node.center.x + node.width / 2 + searchDistance,
7324
- minY: node.center.y - node.height / 2,
7325
- maxY: node.center.y + node.height / 2
7326
- });
7327
- const topSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7328
- minX: node.center.x - node.width / 2,
7329
- maxX: node.center.x + node.width / 2,
7330
- minY: node.center.y - node.height / 2 - searchDistance,
7331
- maxY: node.center.y - node.height / 2
7332
- });
7333
- const bottomSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
7334
- minX: node.center.x - node.width / 2,
7335
- maxX: node.center.x + node.width / 2,
7336
- minY: node.center.y + node.height / 2,
7337
- maxY: node.center.y + node.height / 2 + searchDistance
7338
- });
7339
- return {
7340
- leftSurroundingCapacity,
7341
- rightSurroundingCapacity,
7342
- topSurroundingCapacity,
7343
- bottomSurroundingCapacity
7344
- };
7345
- }
7346
- /**
7347
- * Creates straw nodes from a single-layer node based on surrounding capacities
7348
- */
7349
- createStrawsForNode(node) {
7350
- const result = [];
7351
- const {
7352
- leftSurroundingCapacity,
7353
- rightSurroundingCapacity,
7354
- topSurroundingCapacity,
7355
- bottomSurroundingCapacity
7356
- } = this.getSurroundingCapacities(node);
7357
- const horizontalCapacity = leftSurroundingCapacity + rightSurroundingCapacity;
7358
- const verticalCapacity = topSurroundingCapacity + bottomSurroundingCapacity;
7359
- const layerPrefersFactor = 1;
7360
- const effectiveHorizontalCapacity = horizontalCapacity * layerPrefersFactor;
7361
- if (effectiveHorizontalCapacity > verticalCapacity) {
7362
- const numStraws = Math.floor(node.height / this.strawSize);
7363
- const strawHeight = node.height / numStraws;
7364
- for (let i = 0; i < numStraws; i++) {
7365
- const strawCenterY = node.center.y - node.height / 2 + i * strawHeight + strawHeight / 2;
7366
- result.push({
7367
- capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
7368
- center: { x: node.center.x, y: strawCenterY },
7369
- width: node.width,
7370
- height: strawHeight,
7371
- layer: node.layer,
7372
- availableZ: [...node.availableZ],
7373
- _depth: node._depth,
7374
- _strawNode: true,
7375
- _strawParentCapacityMeshNodeId: node.capacityMeshNodeId
7376
- });
7377
- }
7378
- } else {
7379
- const numStraws = Math.floor(node.width / this.strawSize);
7380
- const strawWidth = node.width / numStraws;
7381
- for (let i = 0; i < numStraws; i++) {
7382
- const strawCenterX = node.center.x - node.width / 2 + i * strawWidth + strawWidth / 2;
7383
- result.push({
7384
- capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
7385
- center: { x: strawCenterX, y: node.center.y },
7386
- width: strawWidth,
7387
- height: node.height,
7388
- layer: node.layer,
7389
- availableZ: [...node.availableZ],
7390
- _depth: node._depth,
7391
- _strawNode: true,
7392
- _strawParentCapacityMeshNodeId: node.capacityMeshNodeId
7393
- });
7394
- }
7395
- }
7396
- return result;
7397
- }
7398
- getResultNodes() {
7399
- return [...this.multiLayerNodes, ...this.strawNodes, ...this.skippedNodes];
7400
- }
7401
- _step() {
7402
- const rootNode = this.unprocessedNodes.pop();
7403
- if (!rootNode) {
7404
- this.solved = true;
7405
- return;
7406
- }
7407
- if (rootNode.width < this.strawSize && rootNode.height < this.strawSize) {
7408
- this.skippedNodes.push(rootNode);
7409
- return;
7410
- }
7411
- if (rootNode._containsTarget) {
7412
- this.skippedNodes.push(rootNode);
7413
- return;
7414
- }
7415
- const strawNodes = this.createStrawsForNode(rootNode);
7416
- this.strawNodes.push(...strawNodes);
7417
- }
7418
- visualize() {
7419
- const graphics = {
7420
- rects: [],
7421
- lines: [],
7422
- points: [],
7423
- circles: [],
7424
- title: "Straw Solver"
7425
- };
7426
- for (const node of this.unprocessedNodes) {
7427
- graphics.rects.push({
7428
- center: node.center,
7429
- width: node.width,
7430
- height: node.height,
7431
- fill: "rgba(200, 200, 200, 0.5)",
7432
- stroke: "rgba(0, 0, 0, 0.5)",
7433
- label: `${node.capacityMeshNodeId}
7434
- Unprocessed
7435
- ${node.width}x${node.height}`
7436
- });
7437
- }
7438
- for (const node of this.strawNodes) {
7439
- const color = node.availableZ[0] === 0 ? "rgba(0, 150, 255, 0.5)" : "rgba(255, 100, 0, 0.5)";
7440
- graphics.rects.push({
7441
- center: node.center,
7442
- width: node.width,
7443
- height: node.height,
7444
- fill: color,
7445
- stroke: "rgba(0, 0, 0, 0.5)",
7446
- label: `${node.capacityMeshNodeId}
7447
- Layer: ${node.availableZ[0]}
7448
- ${node.width}x${node.height}`,
7449
- layer: `z${node.availableZ.join(",")}`
7450
- });
7451
- }
7452
- for (const node of this.multiLayerNodes) {
7453
- graphics.rects.push({
7454
- center: node.center,
7455
- width: node.width * 0.9,
7456
- height: node.height * 0.9,
7457
- fill: "rgba(100, 255, 100, 0.5)",
7458
- stroke: "rgba(0, 0, 0, 0.5)",
7459
- layer: `z${node.availableZ.join(",")}`,
7460
- label: `${node.capacityMeshNodeId}
7461
- Layers: ${node.availableZ.join(",")}
7462
- ${node.width}x${node.height}`
7463
- });
7464
- }
7465
- return graphics;
7466
- }
7467
- };
7468
-
7469
- // lib/utils/areNodesBordering.ts
7470
- function areNodesBordering(node1, node2) {
7471
- const n1Left = node1.center.x - node1.width / 2;
7472
- const n1Right = node1.center.x + node1.width / 2;
7473
- const n1Top = node1.center.y - node1.height / 2;
7474
- const n1Bottom = node1.center.y + node1.height / 2;
7475
- const n2Left = node2.center.x - node2.width / 2;
7476
- const n2Right = node2.center.x + node2.width / 2;
7477
- const n2Top = node2.center.y - node2.height / 2;
7478
- const n2Bottom = node2.center.y + node2.height / 2;
7479
- const epsilon = 1e-3;
7480
- const shareVerticalBorder = (Math.abs(n1Right - n2Left) < epsilon || Math.abs(n1Left - n2Right) < epsilon) && Math.min(n1Bottom, n2Bottom) - Math.max(n1Top, n2Top) >= epsilon;
7481
- const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
7482
- return shareVerticalBorder || shareHorizontalBorder;
7483
- }
7484
-
7485
- // lib/data-structures/CapacityNodeTree.ts
7486
- var CapacityNodeTree = class {
7487
- constructor(nodes) {
7488
- this.nodes = nodes;
7489
- this.buckets = /* @__PURE__ */ new Map();
7490
- for (const node of nodes) {
7491
- const nodeMinX = node.center.x - node.width / 2;
7492
- const nodeMinY = node.center.y - node.height / 2;
7493
- const nodeMaxX = node.center.x + node.width / 2;
7494
- const nodeMaxY = node.center.y + node.height / 2;
7495
- for (let x = nodeMinX; x <= nodeMaxX; x += this.CELL_SIZE) {
7496
- for (let y = nodeMinY; y <= nodeMaxY; y += this.CELL_SIZE) {
7497
- const bucketKey = this.getBucketKey(x, y);
7498
- const bucket = this.buckets.get(bucketKey);
7499
- if (!bucket) {
7500
- this.buckets.set(bucketKey, [node]);
7501
- } else {
7502
- bucket.push(node);
7503
- }
7504
- }
7505
- }
7506
- }
7507
- }
7508
- buckets;
7509
- CELL_SIZE = 0.4;
7510
- getBucketKey(x, y) {
7511
- return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
7512
- }
7513
- getNodesInArea(centerX, centerY, width, height) {
7514
- const nodes = [];
7515
- const alreadyAddedNodes = /* @__PURE__ */ new Set();
7516
- const minX = centerX - width / 2;
7517
- const minY = centerY - height / 2;
7518
- const maxX = centerX + width / 2;
7519
- const maxY = centerY + height / 2;
7520
- for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
7521
- for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
7522
- const bucketKey = this.getBucketKey(x, y);
7523
- const bucket = this.buckets.get(bucketKey) || [];
7524
- for (const node of bucket) {
7525
- if (alreadyAddedNodes.has(node.capacityMeshNodeId)) continue;
7526
- alreadyAddedNodes.add(node.capacityMeshNodeId);
7527
- nodes.push(node);
7528
- }
7529
- }
7530
- }
7531
- return nodes;
7532
- }
7533
- };
7534
-
7535
- // lib/solvers/SingleLayerNodeMerger/SingleLayerNodeMergerSolver.ts
7536
- var EPSILON3 = 5e-3;
7537
- var SingleLayerNodeMergerSolver = class extends BaseSolver {
7538
- nodeMap;
7539
- currentBatchNodeIds;
7540
- absorbedNodeIds;
7541
- nextBatchNodeIds;
7542
- batchHadModifications;
7543
- hasComputedAdjacentNodeIds = false;
7544
- newNodes;
7545
- constructor(nodes) {
7546
- super();
7547
- this.nodeMap = /* @__PURE__ */ new Map();
7548
- this.MAX_ITERATIONS = 1e5;
7549
- for (const node of nodes) {
7550
- this.nodeMap.set(node.capacityMeshNodeId, node);
7551
- }
7552
- this.newNodes = [];
7553
- this.absorbedNodeIds = /* @__PURE__ */ new Set();
7554
- const unprocessedNodesWithArea = [];
7555
- for (const node of nodes) {
7556
- if (node.availableZ.length > 1) {
7557
- this.newNodes.push(node);
7558
- this.absorbedNodeIds.add(node.capacityMeshNodeId);
7559
- } else {
7560
- unprocessedNodesWithArea.push([node, node.width * node.height]);
7561
- }
7562
- }
7563
- unprocessedNodesWithArea.sort((a, b) => a[1] - b[1]);
7564
- for (const [node, area] of unprocessedNodesWithArea) {
7565
- const unprocessedNode = {
7566
- ...node,
7567
- center: { ...node.center }
7568
- };
7569
- this.nodeMap.set(node.capacityMeshNodeId, unprocessedNode);
7570
- }
7571
- this.currentBatchNodeIds = unprocessedNodesWithArea.map(
7572
- ([node]) => node.capacityMeshNodeId
7573
- );
7574
- this.nextBatchNodeIds = [];
7575
- this.batchHadModifications = false;
7848
+ this.currentBatchNodeIds = unprocessedNodesWithArea.map(
7849
+ ([node]) => node.capacityMeshNodeId
7850
+ );
7851
+ this.nextBatchNodeIds = [];
7852
+ this.batchHadModifications = false;
7576
7853
  }
7577
7854
  computeAdjacentNodeIdsForFirstBatch(nodes) {
7578
7855
  const nodeTrees = [
@@ -9179,247 +9456,665 @@ var HighDensityRouteSpatialIndex = class {
9179
9456
  distance: Math.sqrt(data.minDistSq)
9180
9457
  });
9181
9458
  }
9182
- 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 };
9183
9825
  }
9184
- };
9185
-
9186
- // lib/solvers/UselessViaRemovalSolver/SingleRouteUselessViaRemovalSolver.ts
9187
- var SingleRouteUselessViaRemovalSolver = class extends BaseSolver {
9188
- obstacleSHI;
9189
- hdRouteSHI;
9190
- unsimplifiedRoute;
9191
- routeSections;
9192
- currentSectionIndex;
9193
- TRACE_THICKNESS = 0.15;
9194
- OBSTACLE_MARGIN = 0.1;
9195
- constructor(params) {
9196
- super();
9197
- this.currentSectionIndex = 1;
9198
- this.obstacleSHI = params.obstacleSHI;
9199
- this.hdRouteSHI = params.hdRouteSHI;
9200
- this.unsimplifiedRoute = params.unsimplifiedRoute;
9201
- 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);
9202
9831
  }
9203
- breakRouteIntoSections(route) {
9204
- const routeSections = [];
9205
- const routePoints = route.route;
9206
- if (routePoints.length === 0) return [];
9207
- let currentSection = {
9208
- startIndex: 0,
9209
- endIndex: -1,
9210
- z: routePoints[0].z,
9211
- points: [routePoints[0]]
9212
- };
9213
- for (let i = 1; i < routePoints.length; i++) {
9214
- if (routePoints[i].z === currentSection.z) {
9215
- currentSection.points.push(routePoints[i]);
9216
- } else {
9217
- currentSection.endIndex = i - 1;
9218
- routeSections.push(currentSection);
9219
- currentSection = {
9220
- startIndex: i,
9221
- endIndex: -1,
9222
- z: routePoints[i].z,
9223
- points: [routePoints[i]]
9224
- };
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
+ });
9225
9859
  }
9226
9860
  }
9227
- currentSection.endIndex = routePoints.length - 1;
9228
- routeSections.push(currentSection);
9229
- 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));
9230
9893
  }
9231
9894
  _step() {
9232
- if (this.currentSectionIndex >= this.routeSections.length - 1) {
9895
+ const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
9896
+ if (!nextConnection) {
9233
9897
  this.solved = true;
9234
9898
  return;
9235
9899
  }
9236
- const prevSection = this.routeSections[this.currentSectionIndex - 1];
9237
- const currentSection = this.routeSections[this.currentSectionIndex];
9238
- const nextSection = this.routeSections[this.currentSectionIndex + 1];
9239
- if (prevSection.z !== nextSection.z) {
9240
- 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;
9241
9920
  return;
9242
9921
  }
9243
- const targetZ = prevSection.z;
9244
- if (this.canSectionMoveToLayer({ currentSection, targetZ })) {
9245
- currentSection.z = targetZ;
9246
- currentSection.points = currentSection.points.map((p) => ({
9247
- ...p,
9248
- z: targetZ
9249
- }));
9250
- 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;
9251
9934
  return;
9252
9935
  }
9253
- this.currentSectionIndex++;
9254
- return;
9255
- }
9256
- canSectionMoveToLayer({
9257
- currentSection,
9258
- targetZ
9259
- }) {
9260
- for (let i = 0; i < currentSection.points.length - 1; i++) {
9261
- const A = { ...currentSection.points[i], z: targetZ };
9262
- const B = { ...currentSection.points[i + 1], z: targetZ };
9263
- const conflictingRoutes = this.hdRouteSHI.getConflictingRoutesForSegment(
9264
- A,
9265
- B,
9266
- this.TRACE_THICKNESS
9267
- );
9268
- for (const { conflictingRoute, distance: distance6 } of conflictingRoutes) {
9269
- if (conflictingRoute.connectionName === this.unsimplifiedRoute.connectionName)
9270
- continue;
9271
- if (distance6 < this.TRACE_THICKNESS + conflictingRoute.traceThickness) {
9272
- return false;
9273
- }
9936
+ const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
9937
+ for (const neighborNode of neighborNodes) {
9938
+ if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
9939
+ continue;
9274
9940
  }
9275
- const segmentBox = {
9276
- centerX: (A.x + B.x) / 2,
9277
- centerY: (A.y + B.y) / 2,
9278
- width: Math.abs(A.x - B.x),
9279
- height: Math.abs(A.y - B.y)
9280
- };
9281
- const obstacles = this.obstacleSHI.getNodesInArea(
9282
- segmentBox.centerX,
9283
- segmentBox.centerY,
9284
- segmentBox.width,
9285
- segmentBox.height
9286
- );
9287
- for (const obstacle of obstacles) {
9288
- const distToObstacle = segmentToBoxMinDistance(A, B, obstacle);
9289
- if (distToObstacle < this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) {
9290
- return false;
9291
- }
9941
+ if (!this.doesNodeHaveCapacityForTrace(neighborNode, currentCandidate.node)) {
9942
+ continue;
9292
9943
  }
9293
- }
9294
- return true;
9295
- }
9296
- getOptimizedHdRoute() {
9297
- const route = this.routeSections.flatMap((section) => section.points);
9298
- const vias = [];
9299
- for (let i = 0; i < route.length - 1; i++) {
9300
- if (route[i].z !== route[i + 1].z) {
9301
- vias.push({
9302
- x: route[i].x,
9303
- y: route[i].y
9304
- });
9944
+ const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
9945
+ if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
9946
+ continue;
9305
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);
9306
9964
  }
9307
- return {
9308
- connectionName: this.unsimplifiedRoute.connectionName,
9309
- route,
9310
- traceThickness: this.unsimplifiedRoute.traceThickness,
9311
- vias,
9312
- viaDiameter: this.unsimplifiedRoute.viaDiameter
9313
- };
9965
+ this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
9314
9966
  }
9315
9967
  visualize() {
9316
9968
  const graphics = {
9317
- circles: [],
9318
9969
  lines: [],
9319
9970
  points: [],
9320
9971
  rects: [],
9321
- coordinateSystem: "cartesian",
9322
- title: "Single Route Useless Via Removal Solver"
9972
+ circles: []
9323
9973
  };
9324
- for (let i = 0; i < this.routeSections.length; i++) {
9325
- 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;
9326
10042
  graphics.lines.push({
9327
- points: section.points,
9328
- strokeWidth: this.TRACE_THICKNESS,
9329
- 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
+ });
9330
10064
  });
9331
10065
  }
9332
10066
  return graphics;
9333
10067
  }
9334
10068
  };
9335
10069
 
9336
- // lib/solvers/UselessViaRemovalSolver/UselessViaRemovalSolver.ts
9337
- var UselessViaRemovalSolver = class extends BaseSolver {
9338
- constructor(input) {
9339
- super();
9340
- this.input = input;
9341
- this.unsimplifiedHdRoutes = input.unsimplifiedHdRoutes;
9342
- this.optimizedHdRoutes = [];
9343
- this.unprocessedRoutes = [...input.unsimplifiedHdRoutes];
9344
- this.obstacleSHI = new ObstacleSpatialHashIndex(input.obstacles);
9345
- this.hdRouteSHI = new HighDensityRouteSpatialIndex(
9346
- this.unsimplifiedHdRoutes
9347
- );
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;
9348
10077
  }
9349
- unsimplifiedHdRoutes;
9350
- optimizedHdRoutes;
9351
- unprocessedRoutes;
9352
- activeSubSolver = null;
9353
- obstacleSHI = null;
9354
- hdRouteSHI = null;
9355
- _step() {
9356
- if (this.activeSubSolver) {
9357
- this.activeSubSolver.step();
9358
- if (this.activeSubSolver.solved) {
9359
- this.optimizedHdRoutes.push(this.activeSubSolver.getOptimizedHdRoute());
9360
- this.activeSubSolver = null;
9361
- } else if (this.activeSubSolver.failed || this.activeSubSolver.error) {
9362
- this.error = this.activeSubSolver.error;
9363
- this.failed = true;
9364
- }
9365
- return;
9366
- }
9367
- const unprocessedRoute = this.unprocessedRoutes.shift();
9368
- if (!unprocessedRoute) {
9369
- this.solved = true;
9370
- return;
9371
- }
9372
- this.activeSubSolver = new SingleRouteUselessViaRemovalSolver({
9373
- hdRouteSHI: this.hdRouteSHI,
9374
- obstacleSHI: this.obstacleSHI,
9375
- unsimplifiedRoute: unprocessedRoute
9376
- });
10078
+ get maxCapacityFactor() {
10079
+ return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
9377
10080
  }
9378
- getOptimizedHdRoutes() {
9379
- return this.optimizedHdRoutes;
10081
+ getTotalCapacity(node) {
10082
+ return getTunedTotalCapacity1(node, this.maxCapacityFactor);
9380
10083
  }
9381
- visualize() {
9382
- const visualization = {
9383
- lines: [],
9384
- points: [],
9385
- rects: [],
9386
- circles: [],
9387
- coordinateSystem: "cartesian",
9388
- title: "Useless Via Removal Solver"
9389
- };
9390
- for (const route of this.optimizedHdRoutes) {
9391
- if (route.route.length === 0) continue;
9392
- const color = this.input.colorMap[route.connectionName] || "#888888";
9393
- for (let i = 0; i < route.route.length - 1; i++) {
9394
- const current = route.route[i];
9395
- const next = route.route[i + 1];
9396
- if (current.z === next.z) {
9397
- visualization.lines.push({
9398
- points: [
9399
- { x: current.x, y: current.y },
9400
- { x: next.x, y: next.y }
9401
- ],
9402
- strokeColor: current.z === 0 ? "red" : "blue",
9403
- strokeWidth: route.traceThickness,
9404
- label: `${route.connectionName} (z=${current.z})`
9405
- });
9406
- }
9407
- }
9408
- for (const via of route.vias) {
9409
- visualization.circles.push({
9410
- center: { x: via.x, y: via.y },
9411
- radius: route.viaDiameter / 2,
9412
- fill: "rgba(255, 0, 255, 0.5)",
9413
- label: `${route.connectionName} via`
9414
- });
9415
- }
9416
- }
9417
- if (this.activeSubSolver) {
9418
- visualization.lines.push(
9419
- ...this.activeSubSolver.visualize().lines ?? []
9420
- );
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;
9421
10096
  }
9422
- 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);
9423
10118
  }
9424
10119
  };
9425
10120
 
@@ -9459,6 +10154,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9459
10154
  nodeTargetMerger;
9460
10155
  edgeSolver;
9461
10156
  pathingSolver;
10157
+ // Updated type
9462
10158
  edgeToPortSegmentSolver;
9463
10159
  colorMap;
9464
10160
  segmentToPointSolver;
@@ -9468,8 +10164,10 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9468
10164
  highDensityStitchSolver;
9469
10165
  singleLayerNodeMerger;
9470
10166
  strawSolver;
9471
- uselessViaRemovalSolver;
9472
- multiSimplifiedPathSolver;
10167
+ uselessViaRemovalSolver1;
10168
+ uselessViaRemovalSolver2;
10169
+ multiSimplifiedPathSolver1;
10170
+ multiSimplifiedPathSolver2;
9473
10171
  startTimeOfPhase;
9474
10172
  endTimeOfPhase;
9475
10173
  timeSpentOnPhase;
@@ -9542,17 +10240,23 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9542
10240
  CapacityMeshEdgeSolver2_NodeTreeOptimization,
9543
10241
  (cms) => [cms.capacityNodes]
9544
10242
  ),
9545
- definePipelineStep("pathingSolver", CapacityPathingSolver5, (cms) => [
9546
- {
9547
- simpleRouteJson: cms.srjWithPointPairs,
9548
- nodes: cms.capacityNodes,
9549
- edges: cms.edgeSolver?.edges || [],
9550
- colorMap: cms.colorMap,
9551
- hyperParameters: {
9552
- 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
+ }
9553
10257
  }
9554
- }
9555
- ]),
10258
+ ]
10259
+ ),
9556
10260
  definePipelineStep(
9557
10261
  "edgeToPortSegmentSolver",
9558
10262
  CapacityEdgeToPortSegmentSolver,
@@ -9620,12 +10324,13 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9620
10324
  {
9621
10325
  connections: cms.srjWithPointPairs.connections,
9622
10326
  hdRoutes: cms.highDensityRouteSolver.routes,
10327
+ colorMap: cms.colorMap,
9623
10328
  layerCount: cms.srj.layerCount
9624
10329
  }
9625
10330
  ]
9626
10331
  ),
9627
10332
  definePipelineStep(
9628
- "uselessViaRemovalSolver",
10333
+ "uselessViaRemovalSolver1",
9629
10334
  UselessViaRemovalSolver,
9630
10335
  (cms) => [
9631
10336
  {
@@ -9637,11 +10342,35 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9637
10342
  ]
9638
10343
  ),
9639
10344
  definePipelineStep(
9640
- "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",
9641
10370
  MultiSimplifiedPathSolver,
9642
10371
  (cms) => [
9643
10372
  {
9644
- unsimplifiedHdRoutes: cms.uselessViaRemovalSolver?.getOptimizedHdRoutes() || cms.highDensityStitchSolver.mergedHdRoutes,
10373
+ unsimplifiedHdRoutes: cms.uselessViaRemovalSolver2?.getOptimizedHdRoutes(),
9645
10374
  obstacles: cms.srj.obstacles,
9646
10375
  connMap: cms.connMap,
9647
10376
  colorMap: cms.colorMap
@@ -9700,8 +10429,10 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9700
10429
  const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
9701
10430
  const highDensityViz = this.highDensityRouteSolver?.visualize();
9702
10431
  const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
9703
- const uselessViaRemovalViz = this.uselessViaRemovalSolver?.visualize();
9704
- 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();
9705
10436
  const problemViz = {
9706
10437
  points: [
9707
10438
  ...this.srj.connections.flatMap(
@@ -9753,8 +10484,10 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9753
10484
  segmentOptimizationViz,
9754
10485
  highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null,
9755
10486
  highDensityStitchViz,
9756
- uselessViaRemovalViz,
9757
- simplifiedPathSolverViz,
10487
+ uselessViaRemovalViz1,
10488
+ simplifiedPathSolverViz1,
10489
+ uselessViaRemovalViz2,
10490
+ simplifiedPathSolverViz2,
9758
10491
  this.solved ? combineVisualizations(
9759
10492
  problemViz,
9760
10493
  convertSrjToGraphicsObject(this.getOutputSimpleRouteJson())
@@ -9816,7 +10549,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9816
10549
  return match ? match[1] : mstConnectionName;
9817
10550
  }
9818
10551
  _getOutputHdRoutes() {
9819
- 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;
9820
10553
  }
9821
10554
  /**
9822
10555
  * Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces
@@ -9828,9 +10561,9 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9828
10561
  const traces = [];
9829
10562
  const allHdRoutes = this._getOutputHdRoutes();
9830
10563
  for (const connection of this.netToPointPairsSolver?.newConnections ?? []) {
9831
- const netConnection = this.srj.connections.find(
9832
- (c) => c.name === connection.netConnectionName
9833
- );
10564
+ const netConnectionName = this.srj.connections.find(
10565
+ (c) => c.name === connection.name
10566
+ )?.netConnectionName;
9834
10567
  const hdRoutes = allHdRoutes.filter(
9835
10568
  (r) => r.connectionName === connection.name
9836
10569
  );
@@ -9839,7 +10572,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
9839
10572
  const simplifiedPcbTrace = {
9840
10573
  type: "pcb_trace",
9841
10574
  pcb_trace_id: `${connection.name}_${i}`,
9842
- connection_name: this.getOriginalConnectionName(connection.name),
10575
+ connection_name: netConnectionName ?? this.getOriginalConnectionName(connection.name),
9843
10576
  route: convertHdRouteToSimplifiedRoute(hdRoute, this.srj.layerCount)
9844
10577
  };
9845
10578
  traces.push(simplifiedPcbTrace);