@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.d.ts +247 -88
- package/dist/index.js +1896 -1163
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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.
|
|
3388
|
-
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
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
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
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
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
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
|
-
|
|
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
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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 ??
|
|
5949
|
+
connectionName: opts.connectionName ?? firstRoute.connectionName,
|
|
5290
5950
|
route: [
|
|
5291
5951
|
{
|
|
5292
|
-
x:
|
|
5293
|
-
y:
|
|
5294
|
-
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:
|
|
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:
|
|
5405
|
-
label: `Route ${
|
|
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:
|
|
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
|
-
|
|
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:
|
|
5492
|
-
x:
|
|
5493
|
-
y:
|
|
5494
|
-
|
|
5495
|
-
strokeColor:
|
|
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:
|
|
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
|
|
5515
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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/
|
|
6856
|
-
var
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
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 =
|
|
6906
|
-
this.
|
|
6907
|
-
this.
|
|
6908
|
-
this.
|
|
6909
|
-
this.
|
|
6910
|
-
|
|
6911
|
-
this.
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
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
|
-
|
|
6998
|
-
|
|
6999
|
-
for (const
|
|
7000
|
-
const
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
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
|
|
7563
|
+
return totalCapacity;
|
|
7010
7564
|
}
|
|
7011
|
-
|
|
7012
|
-
const
|
|
7013
|
-
const
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
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
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
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
|
-
|
|
7040
|
-
return this.
|
|
7650
|
+
getResultNodes() {
|
|
7651
|
+
return [...this.multiLayerNodes, ...this.strawNodes, ...this.skippedNodes];
|
|
7041
7652
|
}
|
|
7042
7653
|
_step() {
|
|
7043
|
-
const
|
|
7044
|
-
if (!
|
|
7654
|
+
const rootNode = this.unprocessedNodes.pop();
|
|
7655
|
+
if (!rootNode) {
|
|
7045
7656
|
this.solved = true;
|
|
7046
7657
|
return;
|
|
7047
7658
|
}
|
|
7048
|
-
|
|
7049
|
-
|
|
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 (
|
|
7071
|
-
|
|
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
|
|
7085
|
-
|
|
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
|
-
|
|
7120
|
-
|
|
7675
|
+
circles: [],
|
|
7676
|
+
title: "Straw Solver"
|
|
7121
7677
|
};
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
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.
|
|
7154
|
-
const
|
|
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
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
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
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
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/
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
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
|
-
|
|
7259
|
-
|
|
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
|
-
|
|
7262
|
-
|
|
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/
|
|
7267
|
-
var
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
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
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
this.
|
|
7281
|
-
this.
|
|
7282
|
-
|
|
7283
|
-
for (const node of
|
|
7284
|
-
if (node.availableZ.length
|
|
7285
|
-
this.
|
|
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
|
-
|
|
7837
|
+
unprocessedNodesWithArea.push([node, node.width * node.height]);
|
|
7288
7838
|
}
|
|
7289
7839
|
}
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
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
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
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
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
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
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
}
|
|
9213
|
-
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
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
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
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
|
-
|
|
9895
|
+
const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
|
|
9896
|
+
if (!nextConnection) {
|
|
9233
9897
|
this.solved = true;
|
|
9234
9898
|
return;
|
|
9235
9899
|
}
|
|
9236
|
-
const
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
this.
|
|
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
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
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.
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
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
|
-
|
|
9276
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9322
|
-
title: "Single Route Useless Via Removal Solver"
|
|
9972
|
+
circles: []
|
|
9323
9973
|
};
|
|
9324
|
-
|
|
9325
|
-
|
|
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:
|
|
9328
|
-
|
|
9329
|
-
|
|
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/
|
|
9337
|
-
var
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
this.
|
|
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
|
-
|
|
9350
|
-
|
|
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
|
-
|
|
9379
|
-
return this.
|
|
10081
|
+
getTotalCapacity(node) {
|
|
10082
|
+
return getTunedTotalCapacity1(node, this.maxCapacityFactor);
|
|
9380
10083
|
}
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9472
|
-
|
|
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(
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
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
|
|
9704
|
-
const
|
|
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
|
-
|
|
9757
|
-
|
|
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.
|
|
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
|
|
9832
|
-
(c) => c.name === connection.
|
|
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);
|