@tscircuit/capacity-autorouter 0.0.51 → 0.0.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +247 -88
- package/dist/index.js +2112 -1310
- 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
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
const orthDY = midSegmentDX;
|
|
3470
|
-
const orthLength = Math.sqrt(orthDX * orthDX + orthDY * orthDY);
|
|
3471
|
-
const normOrthDX = orthDX / orthLength;
|
|
3472
|
-
const normOrthDY = orthDY / orthLength;
|
|
3473
|
-
const midpointX = (via1.x + via2.x) / 2;
|
|
3474
|
-
const midpointY = (via1.y + via2.y) / 2;
|
|
3475
|
-
const calculateIntersections = () => {
|
|
3476
|
-
const intersections2 = [];
|
|
3477
|
-
const leftT = (innerEdgeBox.x - midpointX) / normOrthDX;
|
|
3478
|
-
const leftY = midpointY + leftT * normOrthDY;
|
|
3479
|
-
if (leftY >= innerEdgeBox.y && leftY <= innerEdgeBox.y + innerEdgeBox.height) {
|
|
3480
|
-
intersections2.push({ x: innerEdgeBox.x, y: leftY });
|
|
3481
|
-
}
|
|
3482
|
-
const rightT = (innerEdgeBox.x + innerEdgeBox.width - midpointX) / normOrthDX;
|
|
3483
|
-
const rightY = midpointY + rightT * normOrthDY;
|
|
3484
|
-
if (rightY >= innerEdgeBox.y && rightY <= innerEdgeBox.y + innerEdgeBox.height) {
|
|
3485
|
-
intersections2.push({
|
|
3486
|
-
x: innerEdgeBox.x + innerEdgeBox.width,
|
|
3487
|
-
y: rightY
|
|
3488
|
-
});
|
|
3489
|
-
}
|
|
3490
|
-
const topT = (innerEdgeBox.y - midpointY) / normOrthDY;
|
|
3491
|
-
const topX = midpointX + topT * normOrthDX;
|
|
3492
|
-
if (topX >= innerEdgeBox.x && topX <= innerEdgeBox.x + innerEdgeBox.width) {
|
|
3493
|
-
intersections2.push({ x: topX, y: innerEdgeBox.y });
|
|
3494
|
-
}
|
|
3495
|
-
const bottomT = (innerEdgeBox.y + innerEdgeBox.height - midpointY) / normOrthDY;
|
|
3496
|
-
const bottomX = midpointX + bottomT * normOrthDX;
|
|
3497
|
-
if (bottomX >= innerEdgeBox.x && bottomX <= innerEdgeBox.x + innerEdgeBox.width) {
|
|
3498
|
-
intersections2.push({
|
|
3499
|
-
x: bottomX,
|
|
3500
|
-
y: innerEdgeBox.y + innerEdgeBox.height
|
|
3501
|
-
});
|
|
3502
|
-
}
|
|
3503
|
-
return intersections2;
|
|
4191
|
+
handleRoutesDontCross() {
|
|
4192
|
+
const [routeA, routeB] = this.routes;
|
|
4193
|
+
const routeASolution = {
|
|
4194
|
+
connectionName: routeA.connectionName,
|
|
4195
|
+
route: [
|
|
4196
|
+
{
|
|
4197
|
+
x: routeA.startPort.x,
|
|
4198
|
+
y: routeA.startPort.y,
|
|
4199
|
+
z: routeA.startPort.z ?? 0
|
|
4200
|
+
},
|
|
4201
|
+
{
|
|
4202
|
+
x: routeA.endPort.x,
|
|
4203
|
+
y: routeA.endPort.y,
|
|
4204
|
+
z: routeA.endPort.z ?? 0
|
|
4205
|
+
}
|
|
4206
|
+
],
|
|
4207
|
+
traceThickness: this.traceThickness,
|
|
4208
|
+
viaDiameter: this.viaDiameter,
|
|
4209
|
+
vias: []
|
|
3504
4210
|
};
|
|
3505
|
-
const
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
{
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
}
|
|
3523
|
-
return [
|
|
3524
|
-
{ x: start.x, y: start.y, z: start.z ?? 0 },
|
|
3525
|
-
{ x: middlePoint1.x, y: middlePoint1.y, z: start.z ?? 0 },
|
|
3526
|
-
{ x: middlePoint2.x, y: middlePoint2.y, z: start.z ?? 0 },
|
|
3527
|
-
{ x: end.x, y: end.y, z: end.z ?? 0 }
|
|
3528
|
-
];
|
|
3529
|
-
}
|
|
3530
|
-
calculateShortestOrthogonalRoutePoints(start, end, via1, via2, otherRouteStart, otherRouteEnd) {
|
|
3531
|
-
const midSegmentCenter = {
|
|
3532
|
-
x: (via1.x + via2.x) / 2,
|
|
3533
|
-
y: (via1.y + via2.y) / 2
|
|
4211
|
+
const routeBSolution = {
|
|
4212
|
+
connectionName: routeB.connectionName,
|
|
4213
|
+
route: [
|
|
4214
|
+
{
|
|
4215
|
+
x: routeB.startPort.x,
|
|
4216
|
+
y: routeB.startPort.y,
|
|
4217
|
+
z: routeB.startPort.z ?? 0
|
|
4218
|
+
},
|
|
4219
|
+
{
|
|
4220
|
+
x: routeB.endPort.x,
|
|
4221
|
+
y: routeB.endPort.y,
|
|
4222
|
+
z: routeB.endPort.z ?? 0
|
|
4223
|
+
}
|
|
4224
|
+
],
|
|
4225
|
+
traceThickness: this.traceThickness,
|
|
4226
|
+
viaDiameter: this.viaDiameter,
|
|
4227
|
+
vias: []
|
|
3534
4228
|
};
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
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
|
}
|
|
@@ -4030,20 +4672,17 @@ function distance2(p1, p2) {
|
|
|
4030
4672
|
|
|
4031
4673
|
// lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/calculateSideTraversal.ts
|
|
4032
4674
|
var EPSILON2 = 1e-9;
|
|
4033
|
-
function calculateSegmentTraversal(startPoint, endPoint, bounds) {
|
|
4675
|
+
function calculateSegmentTraversal(startPoint, endPoint, bounds, turnDirection = "cw") {
|
|
4034
4676
|
const startAngle = pointToAngle(startPoint, bounds);
|
|
4035
|
-
|
|
4036
|
-
if (endAngle < startAngle) {
|
|
4037
|
-
endAngle += 2 * Math.PI;
|
|
4038
|
-
}
|
|
4677
|
+
const endAngle = pointToAngle(endPoint, bounds);
|
|
4039
4678
|
if (Math.abs(endAngle - startAngle) < EPSILON2) {
|
|
4040
4679
|
return { left: 0, top: 0, right: 0, bottom: 0 };
|
|
4041
4680
|
}
|
|
4042
|
-
return calculateSidePercentages(startAngle, endAngle, bounds);
|
|
4681
|
+
return calculateSidePercentages(startAngle, endAngle, bounds, turnDirection);
|
|
4043
4682
|
}
|
|
4044
|
-
function calculateTraversalPercentages(A, B, C, bounds) {
|
|
4045
|
-
const percentagesAB = calculateSegmentTraversal(A, B, bounds);
|
|
4046
|
-
const percentagesBC = calculateSegmentTraversal(B, C, bounds);
|
|
4683
|
+
function calculateTraversalPercentages(A, B, C, bounds, turnDirection) {
|
|
4684
|
+
const percentagesAB = calculateSegmentTraversal(A, B, bounds, turnDirection);
|
|
4685
|
+
const percentagesBC = calculateSegmentTraversal(B, C, bounds, turnDirection);
|
|
4047
4686
|
const totalPercentages = {
|
|
4048
4687
|
left: Math.min(1, percentagesAB.left + percentagesBC.left),
|
|
4049
4688
|
top: Math.min(1, percentagesAB.top + percentagesBC.top),
|
|
@@ -4080,7 +4719,7 @@ function pointToAngle(point, bounds) {
|
|
|
4080
4719
|
distance6 = Math.max(0, Math.min(perimeter, distance6));
|
|
4081
4720
|
return perimeter > EPSILON2 ? distance6 / perimeter * (2 * Math.PI) : 0;
|
|
4082
4721
|
}
|
|
4083
|
-
function calculateSidePercentages(startAngle, endAngle, bounds) {
|
|
4722
|
+
function calculateSidePercentages(startAngle, endAngle, bounds, turnDirection) {
|
|
4084
4723
|
const width = bounds.maxX - bounds.minX;
|
|
4085
4724
|
const height = bounds.maxY - bounds.minY;
|
|
4086
4725
|
if (width < EPSILON2 && height < EPSILON2)
|
|
@@ -4104,17 +4743,54 @@ function calculateSidePercentages(startAngle, endAngle, bounds) {
|
|
|
4104
4743
|
// Ends at 2PI
|
|
4105
4744
|
];
|
|
4106
4745
|
const result = { left: 0, top: 0, right: 0, bottom: 0 };
|
|
4107
|
-
const
|
|
4108
|
-
|
|
4746
|
+
const calculateTraversalOverlap = (sStart, sEnd, tStart, tEnd, wrapsAround) => {
|
|
4747
|
+
const effectiveSEnd = sEnd > 2 * Math.PI - EPSILON2 ? 2 * Math.PI : sEnd;
|
|
4748
|
+
if (effectiveSEnd <= sStart + EPSILON2) return 0;
|
|
4749
|
+
if (!wrapsAround) {
|
|
4750
|
+
const overlapStart = Math.max(sStart, tStart);
|
|
4751
|
+
const overlapEnd = Math.min(effectiveSEnd, tEnd);
|
|
4752
|
+
return Math.max(0, overlapEnd - overlapStart);
|
|
4753
|
+
} else {
|
|
4754
|
+
const overlap1Start = Math.max(sStart, tStart);
|
|
4755
|
+
const overlap1End = Math.min(effectiveSEnd, 2 * Math.PI);
|
|
4756
|
+
const overlap1 = Math.max(0, overlap1End - overlap1Start);
|
|
4757
|
+
const overlap2Start = Math.max(sStart, 0);
|
|
4758
|
+
const overlap2End = Math.min(effectiveSEnd, tEnd);
|
|
4759
|
+
const overlap2 = Math.max(0, overlap2End - overlap2Start);
|
|
4760
|
+
return overlap1 + overlap2;
|
|
4761
|
+
}
|
|
4762
|
+
};
|
|
4109
4763
|
for (const side of sides) {
|
|
4110
4764
|
const sideAngleRange = side.end - side.start;
|
|
4111
4765
|
if (sideAngleRange < EPSILON2 || side.length < EPSILON2) continue;
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4766
|
+
let traversedAngleOnSide = 0;
|
|
4767
|
+
if (turnDirection === "cw") {
|
|
4768
|
+
const wraps = startAngle > endAngle + EPSILON2;
|
|
4769
|
+
traversedAngleOnSide = calculateTraversalOverlap(
|
|
4770
|
+
side.start,
|
|
4771
|
+
side.end,
|
|
4772
|
+
startAngle,
|
|
4773
|
+
endAngle,
|
|
4774
|
+
wraps
|
|
4775
|
+
);
|
|
4776
|
+
} else {
|
|
4777
|
+
const wraps = endAngle > startAngle + EPSILON2;
|
|
4778
|
+
traversedAngleOnSide = calculateTraversalOverlap(
|
|
4779
|
+
side.start,
|
|
4780
|
+
side.end,
|
|
4781
|
+
endAngle,
|
|
4782
|
+
// Start of equivalent CW traversal
|
|
4783
|
+
startAngle,
|
|
4784
|
+
// End of equivalent CW traversal
|
|
4785
|
+
wraps
|
|
4786
|
+
);
|
|
4787
|
+
}
|
|
4788
|
+
if (traversedAngleOnSide > EPSILON2) {
|
|
4116
4789
|
const percentage = traversedAngleOnSide / sideAngleRange;
|
|
4117
|
-
result[side.name] += Math.max(
|
|
4790
|
+
result[side.name] += Math.max(
|
|
4791
|
+
0,
|
|
4792
|
+
Number.isFinite(percentage) ? percentage : 0
|
|
4793
|
+
);
|
|
4118
4794
|
}
|
|
4119
4795
|
}
|
|
4120
4796
|
for (const key in result) {
|
|
@@ -4126,6 +4802,28 @@ function calculateSidePercentages(startAngle, endAngle, bounds) {
|
|
|
4126
4802
|
return result;
|
|
4127
4803
|
}
|
|
4128
4804
|
|
|
4805
|
+
// lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/computeTurnDirection.ts
|
|
4806
|
+
function triangleDirection({
|
|
4807
|
+
angleA,
|
|
4808
|
+
angleB,
|
|
4809
|
+
angleC
|
|
4810
|
+
}) {
|
|
4811
|
+
const Ax = Math.cos(angleA);
|
|
4812
|
+
const Ay = Math.sin(angleA);
|
|
4813
|
+
const Bx = Math.cos(angleB);
|
|
4814
|
+
const By = Math.sin(angleB);
|
|
4815
|
+
const Cx = Math.cos(angleC);
|
|
4816
|
+
const Cy = Math.sin(angleC);
|
|
4817
|
+
const signedArea = (Bx - Ax) * (Cy - Ay) - (By - Ay) * (Cx - Ax);
|
|
4818
|
+
return signedArea < 0 ? "ccw" : "cw";
|
|
4819
|
+
}
|
|
4820
|
+
function computeTurnDirection(A, B, C, bounds) {
|
|
4821
|
+
const angleA = pointToAngle(A, bounds);
|
|
4822
|
+
const angleB = pointToAngle(B, bounds);
|
|
4823
|
+
const angleC = pointToAngle(C, bounds);
|
|
4824
|
+
return triangleDirection({ angleA, angleB, angleC });
|
|
4825
|
+
}
|
|
4826
|
+
|
|
4129
4827
|
// lib/solvers/HighDensitySolver/TwoRouteHighDensitySolver/SingleTransitionCrossingRouteSolver.ts
|
|
4130
4828
|
var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
|
|
4131
4829
|
// Input parameters
|
|
@@ -4213,7 +4911,14 @@ var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
|
|
|
4213
4911
|
const A = flatRoute.A;
|
|
4214
4912
|
const B = ntrP1;
|
|
4215
4913
|
const C = flatRoute.B;
|
|
4216
|
-
const
|
|
4914
|
+
const turnDirection = computeTurnDirection(A, B, C, this.bounds);
|
|
4915
|
+
const sideTraversal = calculateTraversalPercentages(
|
|
4916
|
+
A,
|
|
4917
|
+
B,
|
|
4918
|
+
C,
|
|
4919
|
+
this.bounds,
|
|
4920
|
+
turnDirection
|
|
4921
|
+
);
|
|
4217
4922
|
const viaBounds = {
|
|
4218
4923
|
minX: this.bounds.minX + (sideTraversal.left > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace),
|
|
4219
4924
|
minY: this.bounds.minY + (sideTraversal.bottom > 0.5 ? marginFromBorderWithTrace : marginFromBorderWithoutTrace),
|
|
@@ -4291,23 +4996,29 @@ var SingleTransitionCrossingRouteSolver = class extends BaseSolver {
|
|
|
4291
4996
|
otherRouteStart.z !== flatStart.z ? otherRouteStart : otherRouteEnd,
|
|
4292
4997
|
this.traceThickness
|
|
4293
4998
|
);
|
|
4294
|
-
const
|
|
4295
|
-
center: { x: via.x, y: via.y },
|
|
4296
|
-
radius: minDistFromViaToTrace
|
|
4297
|
-
}).E;
|
|
4298
|
-
const p3 = findPointToGetAroundCircle(p2, flatEnd, {
|
|
4999
|
+
const viaCircle = {
|
|
4299
5000
|
center: { x: via.x, y: via.y },
|
|
4300
5001
|
radius: minDistFromViaToTrace
|
|
4301
|
-
}
|
|
4302
|
-
const
|
|
4303
|
-
const
|
|
5002
|
+
};
|
|
5003
|
+
const p1 = findPointToGetAroundCircle(flatStart, p2, viaCircle).E;
|
|
5004
|
+
const p3 = findPointToGetAroundCircle(p2, flatEnd, viaCircle).E;
|
|
5005
|
+
const p0_5 = findPointToGetAroundCircle(flatStart, p1, viaCircle).E;
|
|
5006
|
+
const p1_5 = findPointToGetAroundCircle(p1, p2, viaCircle).E;
|
|
5007
|
+
const p2_5 = findPointToGetAroundCircle(p2, p3, viaCircle).E;
|
|
5008
|
+
const p3_5 = findPointToGetAroundCircle(p3, flatEnd, viaCircle).E;
|
|
5009
|
+
const p2_better = findPointToGetAroundCircle(p1_5, p2_5, viaCircle).E;
|
|
4304
5010
|
return {
|
|
4305
5011
|
connectionName: flatRouteConnectionName,
|
|
4306
5012
|
route: [
|
|
4307
5013
|
{ x: flatStart.x, y: flatStart.y, z: flatStart.z ?? 0 },
|
|
4308
|
-
|
|
4309
|
-
{ x:
|
|
4310
|
-
|
|
5014
|
+
{ x: p0_5.x, y: p0_5.y, z: flatStart.z ?? 0 },
|
|
5015
|
+
{ x: p1.x, y: p1.y, z: flatStart.z ?? 0 },
|
|
5016
|
+
{ x: p1_5.x, y: p1_5.y, z: flatStart.z ?? 0 },
|
|
5017
|
+
// { x: p2.x, y: p2.y, z: flatStart.z ?? 0 },
|
|
5018
|
+
{ x: p2_better.x, y: p2_better.y, z: flatStart.z ?? 0 },
|
|
5019
|
+
{ x: p2_5.x, y: p2_5.y, z: flatStart.z ?? 0 },
|
|
5020
|
+
{ x: p3.x, y: p3.y, z: flatStart.z ?? 0 },
|
|
5021
|
+
{ x: p3_5.x, y: p3_5.y, z: flatStart.z ?? 0 },
|
|
4311
5022
|
{ x: flatEnd.x, y: flatEnd.y, z: flatEnd.z ?? 0 }
|
|
4312
5023
|
],
|
|
4313
5024
|
traceThickness: this.traceThickness,
|
|
@@ -5213,24 +5924,40 @@ var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
|
5213
5924
|
remainingHdRoutes;
|
|
5214
5925
|
start;
|
|
5215
5926
|
end;
|
|
5927
|
+
colorMap;
|
|
5216
5928
|
constructor(opts) {
|
|
5217
5929
|
super();
|
|
5218
5930
|
this.remainingHdRoutes = [...opts.hdRoutes];
|
|
5931
|
+
this.colorMap = opts.colorMap ?? {};
|
|
5932
|
+
const firstRoute = this.remainingHdRoutes[0];
|
|
5933
|
+
const firstRouteToStartDist = Math.min(
|
|
5934
|
+
distance(firstRoute.route[0], opts.start),
|
|
5935
|
+
distance(firstRoute.route[firstRoute.route.length - 1], opts.start)
|
|
5936
|
+
);
|
|
5937
|
+
const firstRouteToEndDist = Math.min(
|
|
5938
|
+
distance(firstRoute.route[0], opts.end),
|
|
5939
|
+
distance(firstRoute.route[firstRoute.route.length - 1], opts.end)
|
|
5940
|
+
);
|
|
5941
|
+
if (firstRouteToStartDist < firstRouteToEndDist) {
|
|
5942
|
+
this.start = opts.start;
|
|
5943
|
+
this.end = opts.end;
|
|
5944
|
+
} else {
|
|
5945
|
+
this.start = opts.end;
|
|
5946
|
+
this.end = opts.start;
|
|
5947
|
+
}
|
|
5219
5948
|
this.mergedHdRoute = {
|
|
5220
|
-
connectionName: opts.connectionName ??
|
|
5949
|
+
connectionName: opts.connectionName ?? firstRoute.connectionName,
|
|
5221
5950
|
route: [
|
|
5222
5951
|
{
|
|
5223
|
-
x:
|
|
5224
|
-
y:
|
|
5225
|
-
z:
|
|
5952
|
+
x: this.start.x,
|
|
5953
|
+
y: this.start.y,
|
|
5954
|
+
z: this.start.z
|
|
5226
5955
|
}
|
|
5227
5956
|
],
|
|
5228
5957
|
vias: [],
|
|
5229
5958
|
viaDiameter: opts.hdRoutes?.[0]?.viaDiameter ?? 0.6,
|
|
5230
5959
|
traceThickness: opts.hdRoutes?.[0]?.traceThickness ?? 0.15
|
|
5231
5960
|
};
|
|
5232
|
-
this.start = opts.start;
|
|
5233
|
-
this.end = opts.end;
|
|
5234
5961
|
}
|
|
5235
5962
|
_step() {
|
|
5236
5963
|
if (this.remainingHdRoutes.length === 0) {
|
|
@@ -5316,31 +6043,29 @@ var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
|
5316
6043
|
});
|
|
5317
6044
|
}
|
|
5318
6045
|
}
|
|
5319
|
-
const colorList = Array.from(
|
|
5320
|
-
{ length: this.remainingHdRoutes.length },
|
|
5321
|
-
(_, i) => `hsl(${i * 360 / this.remainingHdRoutes.length}, 100%, 50%)`
|
|
5322
|
-
);
|
|
5323
6046
|
for (const [i, hdRoute] of this.remainingHdRoutes.entries()) {
|
|
6047
|
+
const routeColor = this.colorMap[hdRoute.connectionName] ?? "gray";
|
|
5324
6048
|
if (hdRoute.route.length > 1) {
|
|
5325
6049
|
graphics.lines?.push({
|
|
5326
6050
|
points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
|
|
5327
|
-
strokeColor:
|
|
6051
|
+
strokeColor: routeColor
|
|
5328
6052
|
});
|
|
5329
6053
|
}
|
|
5330
6054
|
for (let pi = 0; pi < hdRoute.route.length; pi++) {
|
|
5331
6055
|
const point = hdRoute.route[pi];
|
|
5332
6056
|
graphics.points?.push({
|
|
5333
6057
|
x: point.x + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
|
|
6058
|
+
// Keep slight offset for visibility
|
|
5334
6059
|
y: point.y + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
|
|
5335
|
-
color:
|
|
5336
|
-
label: `Route ${
|
|
6060
|
+
color: routeColor,
|
|
6061
|
+
label: `Route ${hdRoute.connectionName} ${point === hdRoute.route[0] ? "First" : point === hdRoute.route[hdRoute.route.length - 1] ? "Last" : ""}`
|
|
5337
6062
|
});
|
|
5338
6063
|
}
|
|
5339
6064
|
for (const via of hdRoute.vias) {
|
|
5340
6065
|
graphics.circles?.push({
|
|
5341
6066
|
center: { x: via.x, y: via.y },
|
|
5342
6067
|
radius: hdRoute.viaDiameter / 2,
|
|
5343
|
-
fill:
|
|
6068
|
+
fill: routeColor
|
|
5344
6069
|
});
|
|
5345
6070
|
}
|
|
5346
6071
|
}
|
|
@@ -5353,8 +6078,10 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
|
5353
6078
|
unsolvedRoutes;
|
|
5354
6079
|
activeSolver = null;
|
|
5355
6080
|
mergedHdRoutes = [];
|
|
6081
|
+
colorMap = {};
|
|
5356
6082
|
constructor(opts) {
|
|
5357
6083
|
super();
|
|
6084
|
+
this.colorMap = opts.colorMap ?? {};
|
|
5358
6085
|
this.unsolvedRoutes = opts.connections.map((c) => ({
|
|
5359
6086
|
connectionName: c.name,
|
|
5360
6087
|
hdRoutes: opts.hdRoutes.filter((r) => r.connectionName === c.name),
|
|
@@ -5390,7 +6117,8 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
|
5390
6117
|
connectionName: unsolvedRoute.connectionName,
|
|
5391
6118
|
hdRoutes: unsolvedRoute.hdRoutes,
|
|
5392
6119
|
start: unsolvedRoute.start,
|
|
5393
|
-
end: unsolvedRoute.end
|
|
6120
|
+
end: unsolvedRoute.end,
|
|
6121
|
+
colorMap: this.colorMap
|
|
5394
6122
|
});
|
|
5395
6123
|
}
|
|
5396
6124
|
visualize() {
|
|
@@ -5416,22 +6144,26 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
|
5416
6144
|
}
|
|
5417
6145
|
}
|
|
5418
6146
|
for (const [i, mergedRoute] of this.mergedHdRoutes.entries()) {
|
|
5419
|
-
const solvedColor = `hsl(120, 100%, ${40 + i * 10 % 40}%)`;
|
|
5420
|
-
|
|
6147
|
+
const solvedColor = this.colorMap[mergedRoute.connectionName] ?? `hsl(120, 100%, ${40 + i * 10 % 40}%)`;
|
|
6148
|
+
for (let j = 0; j < mergedRoute.route.length - 1; j++) {
|
|
6149
|
+
const p1 = mergedRoute.route[j];
|
|
6150
|
+
const p2 = mergedRoute.route[j + 1];
|
|
6151
|
+
const segmentColor = p1.z !== 0 ? safeTransparentize(solvedColor, 0.5) : solvedColor;
|
|
5421
6152
|
graphics.lines?.push({
|
|
5422
|
-
points:
|
|
5423
|
-
x:
|
|
5424
|
-
y:
|
|
5425
|
-
|
|
5426
|
-
strokeColor:
|
|
6153
|
+
points: [
|
|
6154
|
+
{ x: p1.x, y: p1.y },
|
|
6155
|
+
{ x: p2.x, y: p2.y }
|
|
6156
|
+
],
|
|
6157
|
+
strokeColor: segmentColor,
|
|
5427
6158
|
strokeWidth: mergedRoute.traceThickness
|
|
5428
6159
|
});
|
|
5429
6160
|
}
|
|
5430
6161
|
for (const point of mergedRoute.route) {
|
|
6162
|
+
const pointColor = point.z !== 0 ? safeTransparentize(solvedColor, 0.5) : solvedColor;
|
|
5431
6163
|
graphics.points?.push({
|
|
5432
6164
|
x: point.x,
|
|
5433
6165
|
y: point.y,
|
|
5434
|
-
color:
|
|
6166
|
+
color: pointColor
|
|
5435
6167
|
});
|
|
5436
6168
|
}
|
|
5437
6169
|
for (const via of mergedRoute.vias) {
|
|
@@ -5439,25 +6171,23 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
|
5439
6171
|
center: { x: via.x, y: via.y },
|
|
5440
6172
|
radius: mergedRoute.viaDiameter / 2,
|
|
5441
6173
|
fill: solvedColor
|
|
6174
|
+
// Keep vias solid color for visibility
|
|
5442
6175
|
});
|
|
5443
6176
|
}
|
|
5444
6177
|
}
|
|
5445
|
-
const
|
|
5446
|
-
|
|
5447
|
-
(_, i) => `hsl(${i * 360 / this.unsolvedRoutes.length}, 100%, 50%)`
|
|
5448
|
-
);
|
|
5449
|
-
for (const [i, unsolvedRoute] of this.unsolvedRoutes.entries()) {
|
|
6178
|
+
for (const unsolvedRoute of this.unsolvedRoutes) {
|
|
6179
|
+
const routeColor = this.colorMap[unsolvedRoute.connectionName] ?? "gray";
|
|
5450
6180
|
graphics.points?.push(
|
|
5451
6181
|
{
|
|
5452
6182
|
x: unsolvedRoute.start.x,
|
|
5453
6183
|
y: unsolvedRoute.start.y,
|
|
5454
|
-
color:
|
|
6184
|
+
color: routeColor,
|
|
5455
6185
|
label: `${unsolvedRoute.connectionName} Start`
|
|
5456
6186
|
},
|
|
5457
6187
|
{
|
|
5458
6188
|
x: unsolvedRoute.end.x,
|
|
5459
6189
|
y: unsolvedRoute.end.y,
|
|
5460
|
-
color:
|
|
6190
|
+
color: routeColor,
|
|
5461
6191
|
label: `${unsolvedRoute.connectionName} End`
|
|
5462
6192
|
}
|
|
5463
6193
|
);
|
|
@@ -5466,14 +6196,15 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
|
5466
6196
|
{ x: unsolvedRoute.start.x, y: unsolvedRoute.start.y },
|
|
5467
6197
|
{ x: unsolvedRoute.end.x, y: unsolvedRoute.end.y }
|
|
5468
6198
|
],
|
|
5469
|
-
strokeColor:
|
|
6199
|
+
strokeColor: routeColor,
|
|
5470
6200
|
strokeDash: "2 2"
|
|
5471
6201
|
});
|
|
5472
6202
|
for (const hdRoute of unsolvedRoute.hdRoutes) {
|
|
5473
6203
|
if (hdRoute.route.length > 1) {
|
|
5474
6204
|
graphics.lines?.push({
|
|
5475
6205
|
points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
|
|
5476
|
-
strokeColor: safeTransparentize(
|
|
6206
|
+
strokeColor: safeTransparentize(routeColor, 0.5),
|
|
6207
|
+
// Use routeColor
|
|
5477
6208
|
strokeDash: "10 5"
|
|
5478
6209
|
});
|
|
5479
6210
|
}
|
|
@@ -5481,7 +6212,8 @@ var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
|
5481
6212
|
graphics.circles?.push({
|
|
5482
6213
|
center: { x: via.x, y: via.y },
|
|
5483
6214
|
radius: hdRoute.viaDiameter / 2,
|
|
5484
|
-
fill:
|
|
6215
|
+
fill: routeColor
|
|
6216
|
+
// Use routeColor
|
|
5485
6217
|
});
|
|
5486
6218
|
}
|
|
5487
6219
|
}
|
|
@@ -6783,853 +7515,467 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
|
|
|
6783
7515
|
}
|
|
6784
7516
|
};
|
|
6785
7517
|
|
|
6786
|
-
// lib/
|
|
6787
|
-
var
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
height: opts.rectMargin ? node.height - opts.rectMargin * 2 : Math.max(node.height - 0.5, node.height * 0.8),
|
|
6796
|
-
fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
|
|
6797
|
-
"0,1": "rgba(0,0,0,0.1)",
|
|
6798
|
-
"0": "rgba(0,200,200, 0.1)",
|
|
6799
|
-
"1": "rgba(0,0,200, 0.1)"
|
|
6800
|
-
}[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
|
|
6801
|
-
layer: `z${node.availableZ.join(",")}`,
|
|
6802
|
-
label: [
|
|
6803
|
-
node.capacityMeshNodeId,
|
|
6804
|
-
`availableZ: ${node.availableZ.join(",")}`,
|
|
6805
|
-
`${node._containsTarget ? "containsTarget" : ""}`,
|
|
6806
|
-
`${node._containsObstacle ? "containsObstacle" : ""}`
|
|
6807
|
-
].filter(Boolean).join("\n")
|
|
6808
|
-
};
|
|
6809
|
-
};
|
|
6810
|
-
|
|
6811
|
-
// lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
|
|
6812
|
-
var CapacityPathingSolver = class extends BaseSolver {
|
|
6813
|
-
connectionsWithNodes;
|
|
6814
|
-
usedNodeCapacityMap;
|
|
6815
|
-
simpleRouteJson;
|
|
6816
|
-
nodes;
|
|
6817
|
-
edges;
|
|
6818
|
-
GREEDY_MULTIPLIER = 1.1;
|
|
6819
|
-
nodeMap;
|
|
6820
|
-
nodeEdgeMap;
|
|
6821
|
-
connectionNameToGoalNodeIds;
|
|
6822
|
-
colorMap;
|
|
6823
|
-
maxDepthOfNodes;
|
|
6824
|
-
activeCandidateStraightLineDistance;
|
|
6825
|
-
debug_lastNodeCostMap;
|
|
6826
|
-
hyperParameters;
|
|
6827
|
-
constructor({
|
|
6828
|
-
simpleRouteJson,
|
|
6829
|
-
nodes,
|
|
6830
|
-
edges,
|
|
6831
|
-
colorMap,
|
|
6832
|
-
MAX_ITERATIONS = 1e6,
|
|
6833
|
-
hyperParameters = {}
|
|
6834
|
-
}) {
|
|
7518
|
+
// lib/solvers/StrawSolver/StrawSolver.ts
|
|
7519
|
+
var StrawSolver = class extends BaseSolver {
|
|
7520
|
+
multiLayerNodes;
|
|
7521
|
+
strawNodes;
|
|
7522
|
+
skippedNodes;
|
|
7523
|
+
unprocessedNodes;
|
|
7524
|
+
strawSize;
|
|
7525
|
+
nodeIdCounter;
|
|
7526
|
+
constructor(params) {
|
|
6835
7527
|
super();
|
|
6836
|
-
this.MAX_ITERATIONS =
|
|
6837
|
-
this.
|
|
6838
|
-
this.
|
|
6839
|
-
this.
|
|
6840
|
-
this.
|
|
6841
|
-
|
|
6842
|
-
this.
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
this.nodeMap = new Map(
|
|
6849
|
-
this.nodes.map((node) => [node.capacityMeshNodeId, node])
|
|
6850
|
-
);
|
|
6851
|
-
this.nodeEdgeMap = getNodeEdgeMap(this.edges);
|
|
6852
|
-
this.maxDepthOfNodes = Math.max(
|
|
6853
|
-
...this.nodes.map((node) => node._depth ?? 0)
|
|
6854
|
-
);
|
|
6855
|
-
this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
|
|
6856
|
-
}
|
|
6857
|
-
getTotalCapacity(node) {
|
|
6858
|
-
const depth = node._depth ?? 0;
|
|
6859
|
-
return (this.maxDepthOfNodes - depth + 1) ** 2;
|
|
6860
|
-
}
|
|
6861
|
-
getConnectionsWithNodes() {
|
|
6862
|
-
const connectionsWithNodes = [];
|
|
6863
|
-
const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
|
|
6864
|
-
const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
|
|
6865
|
-
for (const connection of this.simpleRouteJson.connections) {
|
|
6866
|
-
const nodesForConnection = [];
|
|
6867
|
-
for (const point of connection.pointsToConnect) {
|
|
6868
|
-
let closestNode = this.nodes[0];
|
|
6869
|
-
let minDistance = Number.MAX_VALUE;
|
|
6870
|
-
for (const node of nodesWithTargets) {
|
|
6871
|
-
const distance6 = Math.sqrt(
|
|
6872
|
-
(node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
|
|
6873
|
-
);
|
|
6874
|
-
if (distance6 < minDistance) {
|
|
6875
|
-
minDistance = distance6;
|
|
6876
|
-
closestNode = node;
|
|
6877
|
-
}
|
|
6878
|
-
}
|
|
6879
|
-
nodesForConnection.push(closestNode);
|
|
6880
|
-
}
|
|
6881
|
-
if (nodesForConnection.length < 2) {
|
|
6882
|
-
throw new Error(
|
|
6883
|
-
`Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
|
|
6884
|
-
);
|
|
7528
|
+
this.MAX_ITERATIONS = 1e5;
|
|
7529
|
+
this.strawSize = params.strawSize ?? 0.5;
|
|
7530
|
+
this.multiLayerNodes = [];
|
|
7531
|
+
this.strawNodes = [];
|
|
7532
|
+
this.skippedNodes = [];
|
|
7533
|
+
this.nodeIdCounter = 0;
|
|
7534
|
+
this.unprocessedNodes = [];
|
|
7535
|
+
for (const node of params.nodes) {
|
|
7536
|
+
if (node.availableZ.length === 1) {
|
|
7537
|
+
this.unprocessedNodes.push(node);
|
|
7538
|
+
} else {
|
|
7539
|
+
this.multiLayerNodes.push(node);
|
|
6885
7540
|
}
|
|
6886
|
-
connectionNameToGoalNodeIds.set(
|
|
6887
|
-
connection.name,
|
|
6888
|
-
nodesForConnection.map((n) => n.capacityMeshNodeId)
|
|
6889
|
-
);
|
|
6890
|
-
connectionsWithNodes.push({
|
|
6891
|
-
connection,
|
|
6892
|
-
nodes: nodesForConnection,
|
|
6893
|
-
pathFound: false,
|
|
6894
|
-
straightLineDistance: distance(
|
|
6895
|
-
nodesForConnection[0].center,
|
|
6896
|
-
nodesForConnection[nodesForConnection.length - 1].center
|
|
6897
|
-
)
|
|
6898
|
-
});
|
|
6899
7541
|
}
|
|
6900
|
-
connectionsWithNodes.sort(
|
|
6901
|
-
(a, b) => a.straightLineDistance - b.straightLineDistance
|
|
6902
|
-
);
|
|
6903
|
-
return { connectionsWithNodes, connectionNameToGoalNodeIds };
|
|
6904
|
-
}
|
|
6905
|
-
currentConnectionIndex = 0;
|
|
6906
|
-
candidates;
|
|
6907
|
-
visitedNodes;
|
|
6908
|
-
computeG(prevCandidate, node, endGoal) {
|
|
6909
|
-
return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
|
|
6910
|
-
}
|
|
6911
|
-
computeH(prevCandidate, node, endGoal) {
|
|
6912
|
-
return this.getDistanceBetweenNodes(node, endGoal);
|
|
6913
7542
|
}
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
7543
|
+
getCapacityOfMultiLayerNodesWithinBounds(bounds) {
|
|
7544
|
+
let totalCapacity = 0;
|
|
7545
|
+
for (const node of this.multiLayerNodes) {
|
|
7546
|
+
const nodeMinX = node.center.x - node.width / 2;
|
|
7547
|
+
const nodeMaxX = node.center.x + node.width / 2;
|
|
7548
|
+
const nodeMinY = node.center.y - node.height / 2;
|
|
7549
|
+
const nodeMaxY = node.center.y + node.height / 2;
|
|
7550
|
+
const overlapMinX = Math.max(bounds.minX, nodeMinX);
|
|
7551
|
+
const overlapMaxX = Math.min(bounds.maxX, nodeMaxX);
|
|
7552
|
+
const overlapMinY = Math.max(bounds.minY, nodeMinY);
|
|
7553
|
+
const overlapMaxY = Math.min(bounds.maxY, nodeMaxY);
|
|
7554
|
+
if (overlapMinX < overlapMaxX && overlapMinY < overlapMaxY) {
|
|
7555
|
+
const overlapWidth = overlapMaxX - overlapMinX;
|
|
7556
|
+
const overlapHeight = overlapMaxY - overlapMinY;
|
|
7557
|
+
const overlapArea = overlapWidth * overlapHeight;
|
|
7558
|
+
const nodeArea = node.width * node.height;
|
|
7559
|
+
const proportion = overlapArea / nodeArea;
|
|
7560
|
+
totalCapacity += getTunedTotalCapacity1(node) * proportion;
|
|
7561
|
+
}
|
|
6920
7562
|
}
|
|
6921
|
-
return
|
|
6922
|
-
}
|
|
6923
|
-
getNeighboringNodes(node) {
|
|
6924
|
-
return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
|
|
6925
|
-
(edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
|
|
6926
|
-
).map((n) => this.nodeMap.get(n));
|
|
7563
|
+
return totalCapacity;
|
|
6927
7564
|
}
|
|
6928
|
-
|
|
6929
|
-
const
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
7565
|
+
getSurroundingCapacities(node) {
|
|
7566
|
+
const searchDistance = Math.min(node.width, node.height);
|
|
7567
|
+
const leftSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
|
|
7568
|
+
minX: node.center.x - node.width / 2 - searchDistance,
|
|
7569
|
+
maxX: node.center.x - node.width / 2,
|
|
7570
|
+
minY: node.center.y - node.height / 2,
|
|
7571
|
+
maxY: node.center.y + node.height / 2
|
|
7572
|
+
});
|
|
7573
|
+
const rightSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
|
|
7574
|
+
minX: node.center.x + node.width / 2,
|
|
7575
|
+
maxX: node.center.x + node.width / 2 + searchDistance,
|
|
7576
|
+
minY: node.center.y - node.height / 2,
|
|
7577
|
+
maxY: node.center.y + node.height / 2
|
|
7578
|
+
});
|
|
7579
|
+
const topSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
|
|
7580
|
+
minX: node.center.x - node.width / 2,
|
|
7581
|
+
maxX: node.center.x + node.width / 2,
|
|
7582
|
+
minY: node.center.y - node.height / 2 - searchDistance,
|
|
7583
|
+
maxY: node.center.y - node.height / 2
|
|
7584
|
+
});
|
|
7585
|
+
const bottomSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
|
|
7586
|
+
minX: node.center.x - node.width / 2,
|
|
7587
|
+
maxX: node.center.x + node.width / 2,
|
|
7588
|
+
minY: node.center.y + node.height / 2,
|
|
7589
|
+
maxY: node.center.y + node.height / 2 + searchDistance
|
|
7590
|
+
});
|
|
7591
|
+
return {
|
|
7592
|
+
leftSurroundingCapacity,
|
|
7593
|
+
rightSurroundingCapacity,
|
|
7594
|
+
topSurroundingCapacity,
|
|
7595
|
+
bottomSurroundingCapacity
|
|
7596
|
+
};
|
|
7597
|
+
}
|
|
7598
|
+
/**
|
|
7599
|
+
* Creates straw nodes from a single-layer node based on surrounding capacities
|
|
7600
|
+
*/
|
|
7601
|
+
createStrawsForNode(node) {
|
|
7602
|
+
const result = [];
|
|
7603
|
+
const {
|
|
7604
|
+
leftSurroundingCapacity,
|
|
7605
|
+
rightSurroundingCapacity,
|
|
7606
|
+
topSurroundingCapacity,
|
|
7607
|
+
bottomSurroundingCapacity
|
|
7608
|
+
} = this.getSurroundingCapacities(node);
|
|
7609
|
+
const horizontalCapacity = leftSurroundingCapacity + rightSurroundingCapacity;
|
|
7610
|
+
const verticalCapacity = topSurroundingCapacity + bottomSurroundingCapacity;
|
|
7611
|
+
const layerPrefersFactor = 1;
|
|
7612
|
+
const effectiveHorizontalCapacity = horizontalCapacity * layerPrefersFactor;
|
|
7613
|
+
if (effectiveHorizontalCapacity > verticalCapacity) {
|
|
7614
|
+
const numStraws = Math.floor(node.height / this.strawSize);
|
|
7615
|
+
const strawHeight = node.height / numStraws;
|
|
7616
|
+
for (let i = 0; i < numStraws; i++) {
|
|
7617
|
+
const strawCenterY = node.center.y - node.height / 2 + i * strawHeight + strawHeight / 2;
|
|
7618
|
+
result.push({
|
|
7619
|
+
capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
|
|
7620
|
+
center: { x: node.center.x, y: strawCenterY },
|
|
7621
|
+
width: node.width,
|
|
7622
|
+
height: strawHeight,
|
|
7623
|
+
layer: node.layer,
|
|
7624
|
+
availableZ: [...node.availableZ],
|
|
7625
|
+
_depth: node._depth,
|
|
7626
|
+
_strawNode: true,
|
|
7627
|
+
_strawParentCapacityMeshNodeId: node.capacityMeshNodeId
|
|
7628
|
+
});
|
|
7629
|
+
}
|
|
7630
|
+
} else {
|
|
7631
|
+
const numStraws = Math.floor(node.width / this.strawSize);
|
|
7632
|
+
const strawWidth = node.width / numStraws;
|
|
7633
|
+
for (let i = 0; i < numStraws; i++) {
|
|
7634
|
+
const strawCenterX = node.center.x - node.width / 2 + i * strawWidth + strawWidth / 2;
|
|
7635
|
+
result.push({
|
|
7636
|
+
capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
|
|
7637
|
+
center: { x: strawCenterX, y: node.center.y },
|
|
7638
|
+
width: strawWidth,
|
|
7639
|
+
height: node.height,
|
|
7640
|
+
layer: node.layer,
|
|
7641
|
+
availableZ: [...node.availableZ],
|
|
7642
|
+
_depth: node._depth,
|
|
7643
|
+
_strawNode: true,
|
|
7644
|
+
_strawParentCapacityMeshNodeId: node.capacityMeshNodeId
|
|
6937
7645
|
});
|
|
6938
7646
|
}
|
|
6939
7647
|
}
|
|
6940
|
-
return
|
|
6941
|
-
}
|
|
6942
|
-
doesNodeHaveCapacityForTrace(node, prevNode) {
|
|
6943
|
-
const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
|
|
6944
|
-
const totalCapacity = this.getTotalCapacity(node);
|
|
6945
|
-
if (node.availableZ.length === 1 && !node._containsTarget && usedCapacity > 0)
|
|
6946
|
-
return false;
|
|
6947
|
-
let additionalCapacityRequirement = 0;
|
|
6948
|
-
if (node.availableZ.length > 1 && prevNode.availableZ.length === 1) {
|
|
6949
|
-
additionalCapacityRequirement += 0.5;
|
|
6950
|
-
}
|
|
6951
|
-
return usedCapacity + additionalCapacityRequirement < totalCapacity;
|
|
6952
|
-
}
|
|
6953
|
-
canTravelThroughObstacle(node, connectionName) {
|
|
6954
|
-
const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
|
|
6955
|
-
return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
|
|
6956
|
-
}
|
|
6957
|
-
getDistanceBetweenNodes(A, B) {
|
|
6958
|
-
return Math.sqrt(
|
|
6959
|
-
(A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
|
|
6960
|
-
);
|
|
6961
|
-
}
|
|
6962
|
-
reduceCapacityAlongPath(nextConnection) {
|
|
6963
|
-
for (const node of nextConnection.path ?? []) {
|
|
6964
|
-
this.usedNodeCapacityMap.set(
|
|
6965
|
-
node.capacityMeshNodeId,
|
|
6966
|
-
this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
|
|
6967
|
-
);
|
|
6968
|
-
}
|
|
7648
|
+
return result;
|
|
6969
7649
|
}
|
|
6970
|
-
|
|
6971
|
-
return this.
|
|
7650
|
+
getResultNodes() {
|
|
7651
|
+
return [...this.multiLayerNodes, ...this.strawNodes, ...this.skippedNodes];
|
|
6972
7652
|
}
|
|
6973
7653
|
_step() {
|
|
6974
|
-
const
|
|
6975
|
-
if (!
|
|
7654
|
+
const rootNode = this.unprocessedNodes.pop();
|
|
7655
|
+
if (!rootNode) {
|
|
6976
7656
|
this.solved = true;
|
|
6977
7657
|
return;
|
|
6978
7658
|
}
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
this.candidates = [{ prevCandidate: null, node: start, f: 0, g: 0, h: 0 }];
|
|
6982
|
-
this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
|
|
6983
|
-
this.visitedNodes = /* @__PURE__ */ new Set([start.capacityMeshNodeId]);
|
|
6984
|
-
this.activeCandidateStraightLineDistance = distance(
|
|
6985
|
-
start.center,
|
|
6986
|
-
end.center
|
|
6987
|
-
);
|
|
6988
|
-
}
|
|
6989
|
-
this.candidates.sort((a, b) => a.f - b.f);
|
|
6990
|
-
const currentCandidate = this.candidates.shift();
|
|
6991
|
-
if (!currentCandidate) {
|
|
6992
|
-
console.error(
|
|
6993
|
-
`Ran out of candidates on connection ${nextConnection.connection.name}`
|
|
6994
|
-
);
|
|
6995
|
-
this.currentConnectionIndex++;
|
|
6996
|
-
this.candidates = null;
|
|
6997
|
-
this.visitedNodes = null;
|
|
6998
|
-
this.failed = true;
|
|
7659
|
+
if (rootNode.width < this.strawSize && rootNode.height < this.strawSize) {
|
|
7660
|
+
this.skippedNodes.push(rootNode);
|
|
6999
7661
|
return;
|
|
7000
7662
|
}
|
|
7001
|
-
if (
|
|
7002
|
-
|
|
7003
|
-
prevCandidate: currentCandidate,
|
|
7004
|
-
node: end,
|
|
7005
|
-
f: 0,
|
|
7006
|
-
g: 0,
|
|
7007
|
-
h: 0
|
|
7008
|
-
});
|
|
7009
|
-
this.reduceCapacityAlongPath(nextConnection);
|
|
7010
|
-
this.currentConnectionIndex++;
|
|
7011
|
-
this.candidates = null;
|
|
7012
|
-
this.visitedNodes = null;
|
|
7663
|
+
if (rootNode._containsTarget) {
|
|
7664
|
+
this.skippedNodes.push(rootNode);
|
|
7013
7665
|
return;
|
|
7014
7666
|
}
|
|
7015
|
-
const
|
|
7016
|
-
|
|
7017
|
-
if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
|
|
7018
|
-
continue;
|
|
7019
|
-
}
|
|
7020
|
-
if (!this.doesNodeHaveCapacityForTrace(neighborNode, currentCandidate.node)) {
|
|
7021
|
-
continue;
|
|
7022
|
-
}
|
|
7023
|
-
const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
|
|
7024
|
-
if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
|
|
7025
|
-
continue;
|
|
7026
|
-
}
|
|
7027
|
-
const g = this.computeG(currentCandidate, neighborNode, end);
|
|
7028
|
-
const h = this.computeH(currentCandidate, neighborNode, end);
|
|
7029
|
-
const f = g + h * this.GREEDY_MULTIPLIER;
|
|
7030
|
-
this.debug_lastNodeCostMap.set(neighborNode.capacityMeshNodeId, {
|
|
7031
|
-
f,
|
|
7032
|
-
g,
|
|
7033
|
-
h
|
|
7034
|
-
});
|
|
7035
|
-
const newCandidate = {
|
|
7036
|
-
prevCandidate: currentCandidate,
|
|
7037
|
-
node: neighborNode,
|
|
7038
|
-
f,
|
|
7039
|
-
g,
|
|
7040
|
-
h
|
|
7041
|
-
};
|
|
7042
|
-
this.candidates.push(newCandidate);
|
|
7043
|
-
}
|
|
7044
|
-
this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
|
|
7667
|
+
const strawNodes = this.createStrawsForNode(rootNode);
|
|
7668
|
+
this.strawNodes.push(...strawNodes);
|
|
7045
7669
|
}
|
|
7046
7670
|
visualize() {
|
|
7047
7671
|
const graphics = {
|
|
7672
|
+
rects: [],
|
|
7048
7673
|
lines: [],
|
|
7049
7674
|
points: [],
|
|
7050
|
-
|
|
7051
|
-
|
|
7675
|
+
circles: [],
|
|
7676
|
+
title: "Straw Solver"
|
|
7052
7677
|
};
|
|
7053
|
-
|
|
7054
|
-
for (let i = 0; i < this.connectionsWithNodes.length; i++) {
|
|
7055
|
-
const conn = this.connectionsWithNodes[i];
|
|
7056
|
-
if (conn.path && conn.path.length > 0) {
|
|
7057
|
-
const pathPoints = conn.path.map(
|
|
7058
|
-
({ center: { x, y }, width, availableZ }) => ({
|
|
7059
|
-
// slight offset to allow viewing overlapping paths
|
|
7060
|
-
x: x + (i % 10 + i % 19) * (5e-3 * width),
|
|
7061
|
-
y: y + (i % 10 + i % 19) * (5e-3 * width),
|
|
7062
|
-
availableZ
|
|
7063
|
-
})
|
|
7064
|
-
);
|
|
7065
|
-
graphics.lines.push({
|
|
7066
|
-
points: pathPoints,
|
|
7067
|
-
strokeColor: this.colorMap[conn.connection.name]
|
|
7068
|
-
});
|
|
7069
|
-
for (let u = 0; u < pathPoints.length; u++) {
|
|
7070
|
-
const point = pathPoints[u];
|
|
7071
|
-
graphics.points.push({
|
|
7072
|
-
x: point.x,
|
|
7073
|
-
y: point.y,
|
|
7074
|
-
label: [
|
|
7075
|
-
`conn: ${conn.connection.name}`,
|
|
7076
|
-
`node: ${conn.path[u].capacityMeshNodeId}`,
|
|
7077
|
-
`z: ${point.availableZ.join(",")}`
|
|
7078
|
-
].join("\n")
|
|
7079
|
-
});
|
|
7080
|
-
}
|
|
7081
|
-
}
|
|
7082
|
-
}
|
|
7083
|
-
}
|
|
7084
|
-
for (const node of this.nodes) {
|
|
7085
|
-
const nodeCosts = this.debug_lastNodeCostMap.get(node.capacityMeshNodeId);
|
|
7678
|
+
for (const node of this.unprocessedNodes) {
|
|
7086
7679
|
graphics.rects.push({
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7094
|
-
|
|
7095
|
-
`g: ${nodeCosts?.g !== void 0 ? nodeCosts.g.toFixed(2) : "?"}`,
|
|
7096
|
-
`h: ${nodeCosts?.h !== void 0 ? nodeCosts.h.toFixed(2) : "?"}`,
|
|
7097
|
-
`f: ${nodeCosts?.f !== void 0 ? nodeCosts.f.toFixed(2) : "?"}`,
|
|
7098
|
-
`z: ${node.availableZ.join(", ")}`
|
|
7099
|
-
].join("\n")
|
|
7680
|
+
center: node.center,
|
|
7681
|
+
width: node.width,
|
|
7682
|
+
height: node.height,
|
|
7683
|
+
fill: "rgba(200, 200, 200, 0.5)",
|
|
7684
|
+
stroke: "rgba(0, 0, 0, 0.5)",
|
|
7685
|
+
label: `${node.capacityMeshNodeId}
|
|
7686
|
+
Unprocessed
|
|
7687
|
+
${node.width}x${node.height}`
|
|
7100
7688
|
});
|
|
7101
7689
|
}
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
}
|
|
7115
|
-
const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
|
|
7116
|
-
if (nextConnection) {
|
|
7117
|
-
const [start, end] = nextConnection.connection.pointsToConnect;
|
|
7118
|
-
graphics.lines.push({
|
|
7119
|
-
points: [
|
|
7120
|
-
{ x: start.x, y: start.y },
|
|
7121
|
-
{ x: end.x, y: end.y }
|
|
7122
|
-
],
|
|
7123
|
-
strokeColor: "red",
|
|
7124
|
-
strokeDash: "10 5"
|
|
7690
|
+
for (const node of this.strawNodes) {
|
|
7691
|
+
const color = node.availableZ[0] === 0 ? "rgba(0, 150, 255, 0.5)" : "rgba(255, 100, 0, 0.5)";
|
|
7692
|
+
graphics.rects.push({
|
|
7693
|
+
center: node.center,
|
|
7694
|
+
width: node.width,
|
|
7695
|
+
height: node.height,
|
|
7696
|
+
fill: color,
|
|
7697
|
+
stroke: "rgba(0, 0, 0, 0.5)",
|
|
7698
|
+
label: `${node.capacityMeshNodeId}
|
|
7699
|
+
Layer: ${node.availableZ[0]}
|
|
7700
|
+
${node.width}x${node.height}`,
|
|
7701
|
+
layer: `z${node.availableZ.join(",")}`
|
|
7125
7702
|
});
|
|
7126
7703
|
}
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
)
|
|
7139
|
-
});
|
|
7704
|
+
for (const node of this.multiLayerNodes) {
|
|
7705
|
+
graphics.rects.push({
|
|
7706
|
+
center: node.center,
|
|
7707
|
+
width: node.width * 0.9,
|
|
7708
|
+
height: node.height * 0.9,
|
|
7709
|
+
fill: "rgba(100, 255, 100, 0.5)",
|
|
7710
|
+
stroke: "rgba(0, 0, 0, 0.5)",
|
|
7711
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
7712
|
+
label: `${node.capacityMeshNodeId}
|
|
7713
|
+
Layers: ${node.availableZ.join(",")}
|
|
7714
|
+
${node.width}x${node.height}`
|
|
7140
7715
|
});
|
|
7141
7716
|
}
|
|
7142
7717
|
return graphics;
|
|
7143
7718
|
}
|
|
7144
7719
|
};
|
|
7145
7720
|
|
|
7146
|
-
// lib/
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7721
|
+
// lib/utils/areNodesBordering.ts
|
|
7722
|
+
function areNodesBordering(node1, node2) {
|
|
7723
|
+
const n1Left = node1.center.x - node1.width / 2;
|
|
7724
|
+
const n1Right = node1.center.x + node1.width / 2;
|
|
7725
|
+
const n1Top = node1.center.y - node1.height / 2;
|
|
7726
|
+
const n1Bottom = node1.center.y + node1.height / 2;
|
|
7727
|
+
const n2Left = node2.center.x - node2.width / 2;
|
|
7728
|
+
const n2Right = node2.center.x + node2.width / 2;
|
|
7729
|
+
const n2Top = node2.center.y - node2.height / 2;
|
|
7730
|
+
const n2Bottom = node2.center.y + node2.height / 2;
|
|
7731
|
+
const epsilon = 1e-3;
|
|
7732
|
+
const shareVerticalBorder = (Math.abs(n1Right - n2Left) < epsilon || Math.abs(n1Left - n2Right) < epsilon) && Math.min(n1Bottom, n2Bottom) - Math.max(n1Top, n2Top) >= epsilon;
|
|
7733
|
+
const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
|
|
7734
|
+
return shareVerticalBorder || shareHorizontalBorder;
|
|
7735
|
+
}
|
|
7736
|
+
|
|
7737
|
+
// lib/utils/createRectFromCapacityNode.ts
|
|
7738
|
+
var createRectFromCapacityNode = (node, opts = {}) => {
|
|
7739
|
+
const lowestZ = Math.min(...node.availableZ);
|
|
7740
|
+
return {
|
|
7741
|
+
center: !opts.rectMargin || opts.zOffset ? {
|
|
7742
|
+
x: node.center.x + lowestZ * node.width * (opts.zOffset ?? 0.05),
|
|
7743
|
+
y: node.center.y - lowestZ * node.width * (opts.zOffset ?? 0.05)
|
|
7744
|
+
} : node.center,
|
|
7745
|
+
width: opts.rectMargin ? node.width - opts.rectMargin * 2 : Math.max(node.width - 0.5, node.width * 0.8),
|
|
7746
|
+
height: opts.rectMargin ? node.height - opts.rectMargin * 2 : Math.max(node.height - 0.5, node.height * 0.8),
|
|
7747
|
+
fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
|
|
7748
|
+
"0,1": "rgba(0,0,0,0.1)",
|
|
7749
|
+
"0": "rgba(0,200,200, 0.1)",
|
|
7750
|
+
"1": "rgba(0,0,200, 0.1)"
|
|
7751
|
+
}[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
|
|
7752
|
+
layer: `z${node.availableZ.join(",")}`,
|
|
7753
|
+
label: [
|
|
7754
|
+
node.capacityMeshNodeId,
|
|
7755
|
+
`availableZ: ${node.availableZ.join(",")}`,
|
|
7756
|
+
`${node._containsTarget ? "containsTarget" : ""}`,
|
|
7757
|
+
`${node._containsObstacle ? "containsObstacle" : ""}`
|
|
7758
|
+
].filter(Boolean).join("\n")
|
|
7759
|
+
};
|
|
7760
|
+
};
|
|
7761
|
+
|
|
7762
|
+
// lib/data-structures/CapacityNodeTree.ts
|
|
7763
|
+
var CapacityNodeTree = class {
|
|
7764
|
+
constructor(nodes) {
|
|
7765
|
+
this.nodes = nodes;
|
|
7766
|
+
this.buckets = /* @__PURE__ */ new Map();
|
|
7767
|
+
for (const node of nodes) {
|
|
7768
|
+
const nodeMinX = node.center.x - node.width / 2;
|
|
7769
|
+
const nodeMinY = node.center.y - node.height / 2;
|
|
7770
|
+
const nodeMaxX = node.center.x + node.width / 2;
|
|
7771
|
+
const nodeMaxY = node.center.y + node.height / 2;
|
|
7772
|
+
for (let x = nodeMinX; x <= nodeMaxX; x += this.CELL_SIZE) {
|
|
7773
|
+
for (let y = nodeMinY; y <= nodeMaxY; y += this.CELL_SIZE) {
|
|
7774
|
+
const bucketKey = this.getBucketKey(x, y);
|
|
7775
|
+
const bucket = this.buckets.get(bucketKey);
|
|
7776
|
+
if (!bucket) {
|
|
7777
|
+
this.buckets.set(bucketKey, [node]);
|
|
7778
|
+
} else {
|
|
7779
|
+
bucket.push(node);
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7172
7783
|
}
|
|
7173
|
-
const penalty = (MAX_PENALTY - MIN_PENALTY) * Math.max(
|
|
7174
|
-
1,
|
|
7175
|
-
(START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW - remainingCapacity) / (MAX_PENALTY - MIN_PENALTY)
|
|
7176
|
-
) + MIN_PENALTY;
|
|
7177
|
-
return penalty;
|
|
7178
|
-
}
|
|
7179
|
-
/**
|
|
7180
|
-
* We're rewarding travel into big nodes.
|
|
7181
|
-
*
|
|
7182
|
-
* To minimize shortest path, you'd want to comment this out.
|
|
7183
|
-
*/
|
|
7184
|
-
getDistanceBetweenNodes(A, B) {
|
|
7185
|
-
const dx = A.center.x - B.center.x;
|
|
7186
|
-
const dy = A.center.y - B.center.y;
|
|
7187
|
-
return Math.sqrt(dx ** 2 + dy ** 2);
|
|
7188
7784
|
}
|
|
7189
|
-
|
|
7190
|
-
|
|
7785
|
+
buckets;
|
|
7786
|
+
CELL_SIZE = 0.4;
|
|
7787
|
+
getBucketKey(x, y) {
|
|
7788
|
+
return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
|
|
7191
7789
|
}
|
|
7192
|
-
|
|
7193
|
-
|
|
7790
|
+
getNodesInArea(centerX, centerY, width, height) {
|
|
7791
|
+
const nodes = [];
|
|
7792
|
+
const alreadyAddedNodes = /* @__PURE__ */ new Set();
|
|
7793
|
+
const minX = centerX - width / 2;
|
|
7794
|
+
const minY = centerY - height / 2;
|
|
7795
|
+
const maxX = centerX + width / 2;
|
|
7796
|
+
const maxY = centerY + height / 2;
|
|
7797
|
+
for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
|
|
7798
|
+
for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
|
|
7799
|
+
const bucketKey = this.getBucketKey(x, y);
|
|
7800
|
+
const bucket = this.buckets.get(bucketKey) || [];
|
|
7801
|
+
for (const node of bucket) {
|
|
7802
|
+
if (alreadyAddedNodes.has(node.capacityMeshNodeId)) continue;
|
|
7803
|
+
alreadyAddedNodes.add(node.capacityMeshNodeId);
|
|
7804
|
+
nodes.push(node);
|
|
7805
|
+
}
|
|
7806
|
+
}
|
|
7807
|
+
}
|
|
7808
|
+
return nodes;
|
|
7194
7809
|
}
|
|
7195
7810
|
};
|
|
7196
7811
|
|
|
7197
|
-
// lib/solvers/
|
|
7198
|
-
var
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7812
|
+
// lib/solvers/SingleLayerNodeMerger/SingleLayerNodeMergerSolver.ts
|
|
7813
|
+
var EPSILON3 = 5e-3;
|
|
7814
|
+
var SingleLayerNodeMergerSolver = class extends BaseSolver {
|
|
7815
|
+
nodeMap;
|
|
7816
|
+
currentBatchNodeIds;
|
|
7817
|
+
absorbedNodeIds;
|
|
7818
|
+
nextBatchNodeIds;
|
|
7819
|
+
batchHadModifications;
|
|
7820
|
+
hasComputedAdjacentNodeIds = false;
|
|
7821
|
+
newNodes;
|
|
7822
|
+
constructor(nodes) {
|
|
7206
7823
|
super();
|
|
7824
|
+
this.nodeMap = /* @__PURE__ */ new Map();
|
|
7207
7825
|
this.MAX_ITERATIONS = 1e5;
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
this.
|
|
7212
|
-
this.
|
|
7213
|
-
|
|
7214
|
-
for (const node of
|
|
7215
|
-
if (node.availableZ.length
|
|
7216
|
-
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);
|
|
7217
7836
|
} else {
|
|
7218
|
-
|
|
7837
|
+
unprocessedNodesWithArea.push([node, node.width * node.height]);
|
|
7219
7838
|
}
|
|
7220
7839
|
}
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
const nodeMaxY = node.center.y + node.height / 2;
|
|
7229
|
-
const overlapMinX = Math.max(bounds.minX, nodeMinX);
|
|
7230
|
-
const overlapMaxX = Math.min(bounds.maxX, nodeMaxX);
|
|
7231
|
-
const overlapMinY = Math.max(bounds.minY, nodeMinY);
|
|
7232
|
-
const overlapMaxY = Math.min(bounds.maxY, nodeMaxY);
|
|
7233
|
-
if (overlapMinX < overlapMaxX && overlapMinY < overlapMaxY) {
|
|
7234
|
-
const overlapWidth = overlapMaxX - overlapMinX;
|
|
7235
|
-
const overlapHeight = overlapMaxY - overlapMinY;
|
|
7236
|
-
const overlapArea = overlapWidth * overlapHeight;
|
|
7237
|
-
const nodeArea = node.width * node.height;
|
|
7238
|
-
const proportion = overlapArea / nodeArea;
|
|
7239
|
-
totalCapacity += getTunedTotalCapacity1(node) * proportion;
|
|
7240
|
-
}
|
|
7840
|
+
unprocessedNodesWithArea.sort((a, b) => a[1] - b[1]);
|
|
7841
|
+
for (const [node, area] of unprocessedNodesWithArea) {
|
|
7842
|
+
const unprocessedNode = {
|
|
7843
|
+
...node,
|
|
7844
|
+
center: { ...node.center }
|
|
7845
|
+
};
|
|
7846
|
+
this.nodeMap.set(node.capacityMeshNodeId, unprocessedNode);
|
|
7241
7847
|
}
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
minX: node.center.x - node.width / 2 - searchDistance,
|
|
7248
|
-
maxX: node.center.x - node.width / 2,
|
|
7249
|
-
minY: node.center.y - node.height / 2,
|
|
7250
|
-
maxY: node.center.y + node.height / 2
|
|
7251
|
-
});
|
|
7252
|
-
const rightSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
|
|
7253
|
-
minX: node.center.x + node.width / 2,
|
|
7254
|
-
maxX: node.center.x + node.width / 2 + searchDistance,
|
|
7255
|
-
minY: node.center.y - node.height / 2,
|
|
7256
|
-
maxY: node.center.y + node.height / 2
|
|
7257
|
-
});
|
|
7258
|
-
const topSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
|
|
7259
|
-
minX: node.center.x - node.width / 2,
|
|
7260
|
-
maxX: node.center.x + node.width / 2,
|
|
7261
|
-
minY: node.center.y - node.height / 2 - searchDistance,
|
|
7262
|
-
maxY: node.center.y - node.height / 2
|
|
7263
|
-
});
|
|
7264
|
-
const bottomSurroundingCapacity = this.getCapacityOfMultiLayerNodesWithinBounds({
|
|
7265
|
-
minX: node.center.x - node.width / 2,
|
|
7266
|
-
maxX: node.center.x + node.width / 2,
|
|
7267
|
-
minY: node.center.y + node.height / 2,
|
|
7268
|
-
maxY: node.center.y + node.height / 2 + searchDistance
|
|
7269
|
-
});
|
|
7270
|
-
return {
|
|
7271
|
-
leftSurroundingCapacity,
|
|
7272
|
-
rightSurroundingCapacity,
|
|
7273
|
-
topSurroundingCapacity,
|
|
7274
|
-
bottomSurroundingCapacity
|
|
7275
|
-
};
|
|
7848
|
+
this.currentBatchNodeIds = unprocessedNodesWithArea.map(
|
|
7849
|
+
([node]) => node.capacityMeshNodeId
|
|
7850
|
+
);
|
|
7851
|
+
this.nextBatchNodeIds = [];
|
|
7852
|
+
this.batchHadModifications = false;
|
|
7276
7853
|
}
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
const {
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
|
|
7299
|
-
center: { x: node.center.x, y: strawCenterY },
|
|
7300
|
-
width: node.width,
|
|
7301
|
-
height: strawHeight,
|
|
7302
|
-
layer: node.layer,
|
|
7303
|
-
availableZ: [...node.availableZ],
|
|
7304
|
-
_depth: node._depth,
|
|
7305
|
-
_strawNode: true,
|
|
7306
|
-
_strawParentCapacityMeshNodeId: node.capacityMeshNodeId
|
|
7307
|
-
});
|
|
7308
|
-
}
|
|
7309
|
-
} else {
|
|
7310
|
-
const numStraws = Math.floor(node.width / this.strawSize);
|
|
7311
|
-
const strawWidth = node.width / numStraws;
|
|
7312
|
-
for (let i = 0; i < numStraws; i++) {
|
|
7313
|
-
const strawCenterX = node.center.x - node.width / 2 + i * strawWidth + strawWidth / 2;
|
|
7314
|
-
result.push({
|
|
7315
|
-
capacityMeshNodeId: `${node.capacityMeshNodeId}_straw${i}`,
|
|
7316
|
-
center: { x: strawCenterX, y: node.center.y },
|
|
7317
|
-
width: strawWidth,
|
|
7318
|
-
height: node.height,
|
|
7319
|
-
layer: node.layer,
|
|
7320
|
-
availableZ: [...node.availableZ],
|
|
7321
|
-
_depth: node._depth,
|
|
7322
|
-
_strawNode: true,
|
|
7323
|
-
_strawParentCapacityMeshNodeId: node.capacityMeshNodeId
|
|
7324
|
-
});
|
|
7854
|
+
computeAdjacentNodeIdsForFirstBatch(nodes) {
|
|
7855
|
+
const nodeTrees = [
|
|
7856
|
+
new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 0)),
|
|
7857
|
+
new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 1))
|
|
7858
|
+
];
|
|
7859
|
+
for (const node of nodes) {
|
|
7860
|
+
const adjacentNodes = [];
|
|
7861
|
+
const z = node.availableZ[0];
|
|
7862
|
+
const nodesInArea = nodeTrees[z].getNodesInArea(
|
|
7863
|
+
node.center.x,
|
|
7864
|
+
node.center.y,
|
|
7865
|
+
node.width * 4,
|
|
7866
|
+
node.height * 4
|
|
7867
|
+
);
|
|
7868
|
+
for (const unprocessedNode of nodesInArea) {
|
|
7869
|
+
if (unprocessedNode._containsTarget && unprocessedNode._targetConnectionName !== node._targetConnectionName)
|
|
7870
|
+
continue;
|
|
7871
|
+
if (unprocessedNode.capacityMeshNodeId === node.capacityMeshNodeId)
|
|
7872
|
+
continue;
|
|
7873
|
+
if (!areNodesBordering(node, unprocessedNode)) continue;
|
|
7874
|
+
adjacentNodes.push(unprocessedNode);
|
|
7325
7875
|
}
|
|
7876
|
+
node._adjacentNodeIds = adjacentNodes.map((n) => n.capacityMeshNodeId);
|
|
7326
7877
|
}
|
|
7327
|
-
return result;
|
|
7328
7878
|
}
|
|
7329
|
-
|
|
7330
|
-
|
|
7879
|
+
// getAdjacentSameLayerUnprocessedNodes1(rootNode: CapacityMeshNode) {
|
|
7880
|
+
// const adjacentNodes: CapacityMeshNode[] = []
|
|
7881
|
+
// for (const unprocessedNodeId of this.currentBatchNodeIds) {
|
|
7882
|
+
// const unprocessedNode = this.nodeMap.get(unprocessedNodeId)!
|
|
7883
|
+
// if (!areNodesBordering(rootNode, unprocessedNode)) continue
|
|
7884
|
+
// if (unprocessedNode.availableZ[0] !== rootNode.availableZ[0]) continue
|
|
7885
|
+
// if (
|
|
7886
|
+
// unprocessedNode._containsTarget &&
|
|
7887
|
+
// unprocessedNode._targetConnectionName !== rootNode._targetConnectionName
|
|
7888
|
+
// )
|
|
7889
|
+
// continue
|
|
7890
|
+
// if (this.absorbedNodeIds.has(unprocessedNodeId)) continue
|
|
7891
|
+
// adjacentNodes.push(unprocessedNode)
|
|
7892
|
+
// }
|
|
7893
|
+
// return adjacentNodes
|
|
7894
|
+
// }
|
|
7895
|
+
getAdjacentSameLayerUnprocessedNodes(rootNode) {
|
|
7896
|
+
return this.getAdjacentSameLayerUnprocessedNodes2(rootNode);
|
|
7897
|
+
}
|
|
7898
|
+
getAdjacentSameLayerUnprocessedNodes2(rootNode) {
|
|
7899
|
+
const adjacentNodes = [];
|
|
7900
|
+
const unprocessedAdjNodes = Array.from(
|
|
7901
|
+
new Set(
|
|
7902
|
+
(rootNode._adjacentNodeIds ?? []).map((a) => this.nodeMap.get(a))
|
|
7903
|
+
)
|
|
7904
|
+
);
|
|
7905
|
+
unprocessedAdjNodes.sort((a, b) => a.width * a.height - b.width * b.height);
|
|
7906
|
+
for (const unprocessedNode of unprocessedAdjNodes) {
|
|
7907
|
+
if (this.absorbedNodeIds.has(unprocessedNode.capacityMeshNodeId)) continue;
|
|
7908
|
+
adjacentNodes.push(unprocessedNode);
|
|
7909
|
+
}
|
|
7910
|
+
return adjacentNodes;
|
|
7331
7911
|
}
|
|
7332
7912
|
_step() {
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7913
|
+
if (!this.hasComputedAdjacentNodeIds) {
|
|
7914
|
+
this.computeAdjacentNodeIdsForFirstBatch(
|
|
7915
|
+
this.currentBatchNodeIds.map((id) => this.nodeMap.get(id))
|
|
7916
|
+
);
|
|
7917
|
+
this.hasComputedAdjacentNodeIds = true;
|
|
7337
7918
|
}
|
|
7338
|
-
|
|
7339
|
-
|
|
7919
|
+
let rootNodeId = this.currentBatchNodeIds.pop();
|
|
7920
|
+
while (rootNodeId && this.absorbedNodeIds.has(rootNodeId)) {
|
|
7921
|
+
rootNodeId = this.currentBatchNodeIds.pop();
|
|
7922
|
+
}
|
|
7923
|
+
if (!rootNodeId) {
|
|
7924
|
+
if (this.batchHadModifications) {
|
|
7925
|
+
this.currentBatchNodeIds = this.nextBatchNodeIds.sort((a, b) => {
|
|
7926
|
+
const A = this.nodeMap.get(a);
|
|
7927
|
+
const B = this.nodeMap.get(b);
|
|
7928
|
+
return A.width * A.height - B.width * B.height;
|
|
7929
|
+
});
|
|
7930
|
+
this.nextBatchNodeIds = [];
|
|
7931
|
+
this.batchHadModifications = false;
|
|
7932
|
+
return;
|
|
7933
|
+
}
|
|
7934
|
+
this.solved = true;
|
|
7935
|
+
this.newNodes.push(
|
|
7936
|
+
...this.nextBatchNodeIds.map((id) => this.nodeMap.get(id))
|
|
7937
|
+
);
|
|
7340
7938
|
return;
|
|
7341
7939
|
}
|
|
7342
|
-
|
|
7343
|
-
|
|
7940
|
+
const rootNode = this.nodeMap.get(rootNodeId);
|
|
7941
|
+
let rootNodeHasGrown = false;
|
|
7942
|
+
const adjacentNodes = this.getAdjacentSameLayerUnprocessedNodes(rootNode);
|
|
7943
|
+
if (adjacentNodes.length === 0) {
|
|
7944
|
+
this.nextBatchNodeIds.push(rootNodeId);
|
|
7344
7945
|
return;
|
|
7345
7946
|
}
|
|
7346
|
-
const
|
|
7347
|
-
|
|
7348
|
-
|
|
7349
|
-
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7947
|
+
const absorbAdjacentNodeIds = (nodesToAbsorb) => {
|
|
7948
|
+
for (const adjNode of nodesToAbsorb) {
|
|
7949
|
+
this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
|
|
7950
|
+
}
|
|
7951
|
+
rootNode._adjacentNodeIds = Array.from(
|
|
7952
|
+
new Set(
|
|
7953
|
+
[
|
|
7954
|
+
...rootNode._adjacentNodeIds ?? [],
|
|
7955
|
+
...nodesToAbsorb.flatMap((n) => n._adjacentNodeIds ?? [])
|
|
7956
|
+
].filter((id) => !this.absorbedNodeIds.has(id))
|
|
7957
|
+
)
|
|
7958
|
+
);
|
|
7356
7959
|
};
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
center
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
stroke: "rgba(0, 0, 0, 0.5)",
|
|
7377
|
-
label: `${node.capacityMeshNodeId}
|
|
7378
|
-
Layer: ${node.availableZ[0]}
|
|
7379
|
-
${node.width}x${node.height}`,
|
|
7380
|
-
layer: `z${node.availableZ.join(",")}`
|
|
7381
|
-
});
|
|
7382
|
-
}
|
|
7383
|
-
for (const node of this.multiLayerNodes) {
|
|
7384
|
-
graphics.rects.push({
|
|
7385
|
-
center: node.center,
|
|
7386
|
-
width: node.width * 0.9,
|
|
7387
|
-
height: node.height * 0.9,
|
|
7388
|
-
fill: "rgba(100, 255, 100, 0.5)",
|
|
7389
|
-
stroke: "rgba(0, 0, 0, 0.5)",
|
|
7390
|
-
layer: `z${node.availableZ.join(",")}`,
|
|
7391
|
-
label: `${node.capacityMeshNodeId}
|
|
7392
|
-
Layers: ${node.availableZ.join(",")}
|
|
7393
|
-
${node.width}x${node.height}`
|
|
7394
|
-
});
|
|
7395
|
-
}
|
|
7396
|
-
return graphics;
|
|
7397
|
-
}
|
|
7398
|
-
};
|
|
7399
|
-
|
|
7400
|
-
// lib/utils/areNodesBordering.ts
|
|
7401
|
-
function areNodesBordering(node1, node2) {
|
|
7402
|
-
const n1Left = node1.center.x - node1.width / 2;
|
|
7403
|
-
const n1Right = node1.center.x + node1.width / 2;
|
|
7404
|
-
const n1Top = node1.center.y - node1.height / 2;
|
|
7405
|
-
const n1Bottom = node1.center.y + node1.height / 2;
|
|
7406
|
-
const n2Left = node2.center.x - node2.width / 2;
|
|
7407
|
-
const n2Right = node2.center.x + node2.width / 2;
|
|
7408
|
-
const n2Top = node2.center.y - node2.height / 2;
|
|
7409
|
-
const n2Bottom = node2.center.y + node2.height / 2;
|
|
7410
|
-
const epsilon = 1e-3;
|
|
7411
|
-
const shareVerticalBorder = (Math.abs(n1Right - n2Left) < epsilon || Math.abs(n1Left - n2Right) < epsilon) && Math.min(n1Bottom, n2Bottom) - Math.max(n1Top, n2Top) >= epsilon;
|
|
7412
|
-
const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
|
|
7413
|
-
return shareVerticalBorder || shareHorizontalBorder;
|
|
7414
|
-
}
|
|
7415
|
-
|
|
7416
|
-
// lib/data-structures/CapacityNodeTree.ts
|
|
7417
|
-
var CapacityNodeTree = class {
|
|
7418
|
-
constructor(nodes) {
|
|
7419
|
-
this.nodes = nodes;
|
|
7420
|
-
this.buckets = /* @__PURE__ */ new Map();
|
|
7421
|
-
for (const node of nodes) {
|
|
7422
|
-
const nodeMinX = node.center.x - node.width / 2;
|
|
7423
|
-
const nodeMinY = node.center.y - node.height / 2;
|
|
7424
|
-
const nodeMaxX = node.center.x + node.width / 2;
|
|
7425
|
-
const nodeMaxY = node.center.y + node.height / 2;
|
|
7426
|
-
for (let x = nodeMinX; x <= nodeMaxX; x += this.CELL_SIZE) {
|
|
7427
|
-
for (let y = nodeMinY; y <= nodeMaxY; y += this.CELL_SIZE) {
|
|
7428
|
-
const bucketKey = this.getBucketKey(x, y);
|
|
7429
|
-
const bucket = this.buckets.get(bucketKey);
|
|
7430
|
-
if (!bucket) {
|
|
7431
|
-
this.buckets.set(bucketKey, [node]);
|
|
7432
|
-
} else {
|
|
7433
|
-
bucket.push(node);
|
|
7434
|
-
}
|
|
7435
|
-
}
|
|
7436
|
-
}
|
|
7437
|
-
}
|
|
7438
|
-
}
|
|
7439
|
-
buckets;
|
|
7440
|
-
CELL_SIZE = 0.4;
|
|
7441
|
-
getBucketKey(x, y) {
|
|
7442
|
-
return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
|
|
7443
|
-
}
|
|
7444
|
-
getNodesInArea(centerX, centerY, width, height) {
|
|
7445
|
-
const nodes = [];
|
|
7446
|
-
const alreadyAddedNodes = /* @__PURE__ */ new Set();
|
|
7447
|
-
const minX = centerX - width / 2;
|
|
7448
|
-
const minY = centerY - height / 2;
|
|
7449
|
-
const maxX = centerX + width / 2;
|
|
7450
|
-
const maxY = centerY + height / 2;
|
|
7451
|
-
for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
|
|
7452
|
-
for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
|
|
7453
|
-
const bucketKey = this.getBucketKey(x, y);
|
|
7454
|
-
const bucket = this.buckets.get(bucketKey) || [];
|
|
7455
|
-
for (const node of bucket) {
|
|
7456
|
-
if (alreadyAddedNodes.has(node.capacityMeshNodeId)) continue;
|
|
7457
|
-
alreadyAddedNodes.add(node.capacityMeshNodeId);
|
|
7458
|
-
nodes.push(node);
|
|
7459
|
-
}
|
|
7460
|
-
}
|
|
7461
|
-
}
|
|
7462
|
-
return nodes;
|
|
7463
|
-
}
|
|
7464
|
-
};
|
|
7465
|
-
|
|
7466
|
-
// lib/solvers/SingleLayerNodeMerger/SingleLayerNodeMergerSolver.ts
|
|
7467
|
-
var EPSILON3 = 5e-3;
|
|
7468
|
-
var SingleLayerNodeMergerSolver = class extends BaseSolver {
|
|
7469
|
-
nodeMap;
|
|
7470
|
-
currentBatchNodeIds;
|
|
7471
|
-
absorbedNodeIds;
|
|
7472
|
-
nextBatchNodeIds;
|
|
7473
|
-
batchHadModifications;
|
|
7474
|
-
hasComputedAdjacentNodeIds = false;
|
|
7475
|
-
newNodes;
|
|
7476
|
-
constructor(nodes) {
|
|
7477
|
-
super();
|
|
7478
|
-
this.nodeMap = /* @__PURE__ */ new Map();
|
|
7479
|
-
this.MAX_ITERATIONS = 1e5;
|
|
7480
|
-
for (const node of nodes) {
|
|
7481
|
-
this.nodeMap.set(node.capacityMeshNodeId, node);
|
|
7482
|
-
}
|
|
7483
|
-
this.newNodes = [];
|
|
7484
|
-
this.absorbedNodeIds = /* @__PURE__ */ new Set();
|
|
7485
|
-
const unprocessedNodesWithArea = [];
|
|
7486
|
-
for (const node of nodes) {
|
|
7487
|
-
if (node.availableZ.length > 1) {
|
|
7488
|
-
this.newNodes.push(node);
|
|
7489
|
-
this.absorbedNodeIds.add(node.capacityMeshNodeId);
|
|
7490
|
-
} else {
|
|
7491
|
-
unprocessedNodesWithArea.push([node, node.width * node.height]);
|
|
7492
|
-
}
|
|
7493
|
-
}
|
|
7494
|
-
unprocessedNodesWithArea.sort((a, b) => a[1] - b[1]);
|
|
7495
|
-
for (const [node, area] of unprocessedNodesWithArea) {
|
|
7496
|
-
const unprocessedNode = {
|
|
7497
|
-
...node,
|
|
7498
|
-
center: { ...node.center }
|
|
7499
|
-
};
|
|
7500
|
-
this.nodeMap.set(node.capacityMeshNodeId, unprocessedNode);
|
|
7501
|
-
}
|
|
7502
|
-
this.currentBatchNodeIds = unprocessedNodesWithArea.map(
|
|
7503
|
-
([node]) => node.capacityMeshNodeId
|
|
7504
|
-
);
|
|
7505
|
-
this.nextBatchNodeIds = [];
|
|
7506
|
-
this.batchHadModifications = false;
|
|
7507
|
-
}
|
|
7508
|
-
computeAdjacentNodeIdsForFirstBatch(nodes) {
|
|
7509
|
-
const nodeTrees = [
|
|
7510
|
-
new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 0)),
|
|
7511
|
-
new CapacityNodeTree(nodes.filter((n) => n.availableZ[0] === 1))
|
|
7512
|
-
];
|
|
7513
|
-
for (const node of nodes) {
|
|
7514
|
-
const adjacentNodes = [];
|
|
7515
|
-
const z = node.availableZ[0];
|
|
7516
|
-
const nodesInArea = nodeTrees[z].getNodesInArea(
|
|
7517
|
-
node.center.x,
|
|
7518
|
-
node.center.y,
|
|
7519
|
-
node.width * 4,
|
|
7520
|
-
node.height * 4
|
|
7521
|
-
);
|
|
7522
|
-
for (const unprocessedNode of nodesInArea) {
|
|
7523
|
-
if (unprocessedNode._containsTarget && unprocessedNode._targetConnectionName !== node._targetConnectionName)
|
|
7524
|
-
continue;
|
|
7525
|
-
if (unprocessedNode.capacityMeshNodeId === node.capacityMeshNodeId)
|
|
7526
|
-
continue;
|
|
7527
|
-
if (!areNodesBordering(node, unprocessedNode)) continue;
|
|
7528
|
-
adjacentNodes.push(unprocessedNode);
|
|
7529
|
-
}
|
|
7530
|
-
node._adjacentNodeIds = adjacentNodes.map((n) => n.capacityMeshNodeId);
|
|
7531
|
-
}
|
|
7532
|
-
}
|
|
7533
|
-
// getAdjacentSameLayerUnprocessedNodes1(rootNode: CapacityMeshNode) {
|
|
7534
|
-
// const adjacentNodes: CapacityMeshNode[] = []
|
|
7535
|
-
// for (const unprocessedNodeId of this.currentBatchNodeIds) {
|
|
7536
|
-
// const unprocessedNode = this.nodeMap.get(unprocessedNodeId)!
|
|
7537
|
-
// if (!areNodesBordering(rootNode, unprocessedNode)) continue
|
|
7538
|
-
// if (unprocessedNode.availableZ[0] !== rootNode.availableZ[0]) continue
|
|
7539
|
-
// if (
|
|
7540
|
-
// unprocessedNode._containsTarget &&
|
|
7541
|
-
// unprocessedNode._targetConnectionName !== rootNode._targetConnectionName
|
|
7542
|
-
// )
|
|
7543
|
-
// continue
|
|
7544
|
-
// if (this.absorbedNodeIds.has(unprocessedNodeId)) continue
|
|
7545
|
-
// adjacentNodes.push(unprocessedNode)
|
|
7546
|
-
// }
|
|
7547
|
-
// return adjacentNodes
|
|
7548
|
-
// }
|
|
7549
|
-
getAdjacentSameLayerUnprocessedNodes(rootNode) {
|
|
7550
|
-
return this.getAdjacentSameLayerUnprocessedNodes2(rootNode);
|
|
7551
|
-
}
|
|
7552
|
-
getAdjacentSameLayerUnprocessedNodes2(rootNode) {
|
|
7553
|
-
const adjacentNodes = [];
|
|
7554
|
-
const unprocessedAdjNodes = Array.from(
|
|
7555
|
-
new Set(
|
|
7556
|
-
(rootNode._adjacentNodeIds ?? []).map((a) => this.nodeMap.get(a))
|
|
7557
|
-
)
|
|
7558
|
-
);
|
|
7559
|
-
unprocessedAdjNodes.sort((a, b) => a.width * a.height - b.width * b.height);
|
|
7560
|
-
for (const unprocessedNode of unprocessedAdjNodes) {
|
|
7561
|
-
if (this.absorbedNodeIds.has(unprocessedNode.capacityMeshNodeId)) continue;
|
|
7562
|
-
adjacentNodes.push(unprocessedNode);
|
|
7563
|
-
}
|
|
7564
|
-
return adjacentNodes;
|
|
7565
|
-
}
|
|
7566
|
-
_step() {
|
|
7567
|
-
if (!this.hasComputedAdjacentNodeIds) {
|
|
7568
|
-
this.computeAdjacentNodeIdsForFirstBatch(
|
|
7569
|
-
this.currentBatchNodeIds.map((id) => this.nodeMap.get(id))
|
|
7570
|
-
);
|
|
7571
|
-
this.hasComputedAdjacentNodeIds = true;
|
|
7572
|
-
}
|
|
7573
|
-
let rootNodeId = this.currentBatchNodeIds.pop();
|
|
7574
|
-
while (rootNodeId && this.absorbedNodeIds.has(rootNodeId)) {
|
|
7575
|
-
rootNodeId = this.currentBatchNodeIds.pop();
|
|
7576
|
-
}
|
|
7577
|
-
if (!rootNodeId) {
|
|
7578
|
-
if (this.batchHadModifications) {
|
|
7579
|
-
this.currentBatchNodeIds = this.nextBatchNodeIds.sort((a, b) => {
|
|
7580
|
-
const A = this.nodeMap.get(a);
|
|
7581
|
-
const B = this.nodeMap.get(b);
|
|
7582
|
-
return A.width * A.height - B.width * B.height;
|
|
7583
|
-
});
|
|
7584
|
-
this.nextBatchNodeIds = [];
|
|
7585
|
-
this.batchHadModifications = false;
|
|
7586
|
-
return;
|
|
7587
|
-
}
|
|
7588
|
-
this.solved = true;
|
|
7589
|
-
this.newNodes.push(
|
|
7590
|
-
...this.nextBatchNodeIds.map((id) => this.nodeMap.get(id))
|
|
7591
|
-
);
|
|
7592
|
-
return;
|
|
7593
|
-
}
|
|
7594
|
-
const rootNode = this.nodeMap.get(rootNodeId);
|
|
7595
|
-
let rootNodeHasGrown = false;
|
|
7596
|
-
const adjacentNodes = this.getAdjacentSameLayerUnprocessedNodes(rootNode);
|
|
7597
|
-
if (adjacentNodes.length === 0) {
|
|
7598
|
-
this.nextBatchNodeIds.push(rootNodeId);
|
|
7599
|
-
return;
|
|
7600
|
-
}
|
|
7601
|
-
const absorbAdjacentNodeIds = (nodesToAbsorb) => {
|
|
7602
|
-
for (const adjNode of nodesToAbsorb) {
|
|
7603
|
-
this.absorbedNodeIds.add(adjNode.capacityMeshNodeId);
|
|
7604
|
-
}
|
|
7605
|
-
rootNode._adjacentNodeIds = Array.from(
|
|
7606
|
-
new Set(
|
|
7607
|
-
[
|
|
7608
|
-
...rootNode._adjacentNodeIds ?? [],
|
|
7609
|
-
...nodesToAbsorb.flatMap((n) => n._adjacentNodeIds ?? [])
|
|
7610
|
-
].filter((id) => !this.absorbedNodeIds.has(id))
|
|
7611
|
-
)
|
|
7612
|
-
);
|
|
7613
|
-
};
|
|
7614
|
-
const adjacentNodesToLeft = adjacentNodes.filter(
|
|
7615
|
-
(adjNode) => adjNode.center.x < rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
|
|
7616
|
-
);
|
|
7617
|
-
if (adjacentNodesToLeft.length > 0) {
|
|
7618
|
-
const { width: leftAdjNodeWidth, height: leftAdjNodeHeight } = adjacentNodesToLeft[0];
|
|
7619
|
-
const leftAdjNodesAreAllSameSize = adjacentNodesToLeft.every(
|
|
7620
|
-
(adjNode) => adjNode.width === leftAdjNodeWidth && adjNode.height === leftAdjNodeHeight
|
|
7621
|
-
);
|
|
7622
|
-
const leftAdjNodesTakeUpEntireHeight = Math.abs(
|
|
7623
|
-
adjacentNodesToLeft.reduce((acc, adjNode) => {
|
|
7624
|
-
return acc + adjNode.height;
|
|
7625
|
-
}, 0) - rootNode.height
|
|
7626
|
-
) < EPSILON3;
|
|
7627
|
-
if (leftAdjNodesTakeUpEntireHeight && leftAdjNodesAreAllSameSize) {
|
|
7628
|
-
rootNode.width += leftAdjNodeWidth;
|
|
7629
|
-
rootNode.center.x = rootNode.center.x - leftAdjNodeWidth / 2;
|
|
7630
|
-
absorbAdjacentNodeIds(adjacentNodesToLeft);
|
|
7631
|
-
rootNodeHasGrown = true;
|
|
7632
|
-
}
|
|
7960
|
+
const adjacentNodesToLeft = adjacentNodes.filter(
|
|
7961
|
+
(adjNode) => adjNode.center.x < rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
|
|
7962
|
+
);
|
|
7963
|
+
if (adjacentNodesToLeft.length > 0) {
|
|
7964
|
+
const { width: leftAdjNodeWidth, height: leftAdjNodeHeight } = adjacentNodesToLeft[0];
|
|
7965
|
+
const leftAdjNodesAreAllSameSize = adjacentNodesToLeft.every(
|
|
7966
|
+
(adjNode) => adjNode.width === leftAdjNodeWidth && adjNode.height === leftAdjNodeHeight
|
|
7967
|
+
);
|
|
7968
|
+
const leftAdjNodesTakeUpEntireHeight = Math.abs(
|
|
7969
|
+
adjacentNodesToLeft.reduce((acc, adjNode) => {
|
|
7970
|
+
return acc + adjNode.height;
|
|
7971
|
+
}, 0) - rootNode.height
|
|
7972
|
+
) < EPSILON3;
|
|
7973
|
+
if (leftAdjNodesTakeUpEntireHeight && leftAdjNodesAreAllSameSize) {
|
|
7974
|
+
rootNode.width += leftAdjNodeWidth;
|
|
7975
|
+
rootNode.center.x = rootNode.center.x - leftAdjNodeWidth / 2;
|
|
7976
|
+
absorbAdjacentNodeIds(adjacentNodesToLeft);
|
|
7977
|
+
rootNodeHasGrown = true;
|
|
7978
|
+
}
|
|
7633
7979
|
}
|
|
7634
7980
|
const adjacentNodesToRight = adjacentNodes.filter(
|
|
7635
7981
|
(adjNode) => adjNode.center.x > rootNode.center.x && Math.abs(adjNode.center.y - rootNode.center.y) < rootNode.height / 2
|
|
@@ -7926,13 +8272,13 @@ function minimumDistanceBetweenSegments(A1, A2, B1, B2) {
|
|
|
7926
8272
|
if (segmentsIntersect(A1, A2, B1, B2)) {
|
|
7927
8273
|
return 0;
|
|
7928
8274
|
}
|
|
7929
|
-
const distA1 =
|
|
7930
|
-
const distA2 =
|
|
7931
|
-
const distB1 =
|
|
7932
|
-
const distB2 =
|
|
8275
|
+
const distA1 = pointToSegmentDistance5(A1, B1, B2);
|
|
8276
|
+
const distA2 = pointToSegmentDistance5(A2, B1, B2);
|
|
8277
|
+
const distB1 = pointToSegmentDistance5(B1, A1, A2);
|
|
8278
|
+
const distB2 = pointToSegmentDistance5(B2, A1, A2);
|
|
7933
8279
|
return Math.min(distA1, distA2, distB1, distB2);
|
|
7934
8280
|
}
|
|
7935
|
-
function
|
|
8281
|
+
function pointToSegmentDistance5(P, Q1, Q2) {
|
|
7936
8282
|
const v = { x: Q2.x - Q1.x, y: Q2.y - Q1.y };
|
|
7937
8283
|
const w = { x: P.x - Q1.x, y: P.y - Q1.y };
|
|
7938
8284
|
const c1 = dotProduct(w, v);
|
|
@@ -9110,247 +9456,665 @@ var HighDensityRouteSpatialIndex = class {
|
|
|
9110
9456
|
distance: Math.sqrt(data.minDistSq)
|
|
9111
9457
|
});
|
|
9112
9458
|
}
|
|
9113
|
-
return results;
|
|
9459
|
+
return results;
|
|
9460
|
+
}
|
|
9461
|
+
};
|
|
9462
|
+
|
|
9463
|
+
// lib/solvers/UselessViaRemovalSolver/SingleRouteUselessViaRemovalSolver.ts
|
|
9464
|
+
var SingleRouteUselessViaRemovalSolver = class extends BaseSolver {
|
|
9465
|
+
obstacleSHI;
|
|
9466
|
+
hdRouteSHI;
|
|
9467
|
+
unsimplifiedRoute;
|
|
9468
|
+
routeSections;
|
|
9469
|
+
currentSectionIndex;
|
|
9470
|
+
TRACE_THICKNESS = 0.15;
|
|
9471
|
+
OBSTACLE_MARGIN = 0.1;
|
|
9472
|
+
constructor(params) {
|
|
9473
|
+
super();
|
|
9474
|
+
this.currentSectionIndex = 1;
|
|
9475
|
+
this.obstacleSHI = params.obstacleSHI;
|
|
9476
|
+
this.hdRouteSHI = params.hdRouteSHI;
|
|
9477
|
+
this.unsimplifiedRoute = params.unsimplifiedRoute;
|
|
9478
|
+
this.routeSections = this.breakRouteIntoSections(this.unsimplifiedRoute);
|
|
9479
|
+
}
|
|
9480
|
+
breakRouteIntoSections(route) {
|
|
9481
|
+
const routeSections = [];
|
|
9482
|
+
const routePoints = route.route;
|
|
9483
|
+
if (routePoints.length === 0) return [];
|
|
9484
|
+
let currentSection = {
|
|
9485
|
+
startIndex: 0,
|
|
9486
|
+
endIndex: -1,
|
|
9487
|
+
z: routePoints[0].z,
|
|
9488
|
+
points: [routePoints[0]]
|
|
9489
|
+
};
|
|
9490
|
+
for (let i = 1; i < routePoints.length; i++) {
|
|
9491
|
+
if (routePoints[i].z === currentSection.z) {
|
|
9492
|
+
currentSection.points.push(routePoints[i]);
|
|
9493
|
+
} else {
|
|
9494
|
+
currentSection.endIndex = i - 1;
|
|
9495
|
+
routeSections.push(currentSection);
|
|
9496
|
+
currentSection = {
|
|
9497
|
+
startIndex: i,
|
|
9498
|
+
endIndex: -1,
|
|
9499
|
+
z: routePoints[i].z,
|
|
9500
|
+
points: [routePoints[i]]
|
|
9501
|
+
};
|
|
9502
|
+
}
|
|
9503
|
+
}
|
|
9504
|
+
currentSection.endIndex = routePoints.length - 1;
|
|
9505
|
+
routeSections.push(currentSection);
|
|
9506
|
+
return routeSections;
|
|
9507
|
+
}
|
|
9508
|
+
_step() {
|
|
9509
|
+
if (this.currentSectionIndex >= this.routeSections.length - 1) {
|
|
9510
|
+
this.solved = true;
|
|
9511
|
+
return;
|
|
9512
|
+
}
|
|
9513
|
+
const prevSection = this.routeSections[this.currentSectionIndex - 1];
|
|
9514
|
+
const currentSection = this.routeSections[this.currentSectionIndex];
|
|
9515
|
+
const nextSection = this.routeSections[this.currentSectionIndex + 1];
|
|
9516
|
+
if (prevSection.z !== nextSection.z) {
|
|
9517
|
+
this.currentSectionIndex++;
|
|
9518
|
+
return;
|
|
9519
|
+
}
|
|
9520
|
+
const targetZ = prevSection.z;
|
|
9521
|
+
if (this.canSectionMoveToLayer({ currentSection, targetZ })) {
|
|
9522
|
+
currentSection.z = targetZ;
|
|
9523
|
+
currentSection.points = currentSection.points.map((p) => ({
|
|
9524
|
+
...p,
|
|
9525
|
+
z: targetZ
|
|
9526
|
+
}));
|
|
9527
|
+
this.currentSectionIndex += 2;
|
|
9528
|
+
return;
|
|
9529
|
+
}
|
|
9530
|
+
this.currentSectionIndex++;
|
|
9531
|
+
return;
|
|
9532
|
+
}
|
|
9533
|
+
canSectionMoveToLayer({
|
|
9534
|
+
currentSection,
|
|
9535
|
+
targetZ
|
|
9536
|
+
}) {
|
|
9537
|
+
for (let i = 0; i < currentSection.points.length - 1; i++) {
|
|
9538
|
+
const A = { ...currentSection.points[i], z: targetZ };
|
|
9539
|
+
const B = { ...currentSection.points[i + 1], z: targetZ };
|
|
9540
|
+
const conflictingRoutes = this.hdRouteSHI.getConflictingRoutesForSegment(
|
|
9541
|
+
A,
|
|
9542
|
+
B,
|
|
9543
|
+
this.TRACE_THICKNESS
|
|
9544
|
+
);
|
|
9545
|
+
for (const { conflictingRoute, distance: distance6 } of conflictingRoutes) {
|
|
9546
|
+
if (conflictingRoute.connectionName === this.unsimplifiedRoute.connectionName)
|
|
9547
|
+
continue;
|
|
9548
|
+
if (distance6 < this.TRACE_THICKNESS + conflictingRoute.traceThickness) {
|
|
9549
|
+
return false;
|
|
9550
|
+
}
|
|
9551
|
+
}
|
|
9552
|
+
const segmentBox = {
|
|
9553
|
+
centerX: (A.x + B.x) / 2,
|
|
9554
|
+
centerY: (A.y + B.y) / 2,
|
|
9555
|
+
width: Math.abs(A.x - B.x),
|
|
9556
|
+
height: Math.abs(A.y - B.y)
|
|
9557
|
+
};
|
|
9558
|
+
const obstacles = this.obstacleSHI.getNodesInArea(
|
|
9559
|
+
segmentBox.centerX,
|
|
9560
|
+
segmentBox.centerY,
|
|
9561
|
+
segmentBox.width + (this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) * 2,
|
|
9562
|
+
// Expand search width
|
|
9563
|
+
segmentBox.height + (this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) * 2
|
|
9564
|
+
// Expand search height
|
|
9565
|
+
);
|
|
9566
|
+
for (const obstacle of obstacles) {
|
|
9567
|
+
const distToObstacle = segmentToBoxMinDistance(A, B, obstacle);
|
|
9568
|
+
if (distToObstacle < this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) {
|
|
9569
|
+
return false;
|
|
9570
|
+
}
|
|
9571
|
+
}
|
|
9572
|
+
}
|
|
9573
|
+
return true;
|
|
9574
|
+
}
|
|
9575
|
+
getConstructorParams() {
|
|
9576
|
+
return {
|
|
9577
|
+
obstacleSHI: this.obstacleSHI,
|
|
9578
|
+
hdRouteSHI: this.hdRouteSHI,
|
|
9579
|
+
unsimplifiedRoute: this.unsimplifiedRoute
|
|
9580
|
+
};
|
|
9581
|
+
}
|
|
9582
|
+
getOptimizedHdRoute() {
|
|
9583
|
+
const route = this.routeSections.flatMap((section) => section.points);
|
|
9584
|
+
const vias = [];
|
|
9585
|
+
for (let i = 0; i < route.length - 1; i++) {
|
|
9586
|
+
if (route[i].z !== route[i + 1].z) {
|
|
9587
|
+
vias.push({
|
|
9588
|
+
x: route[i].x,
|
|
9589
|
+
y: route[i].y
|
|
9590
|
+
});
|
|
9591
|
+
}
|
|
9592
|
+
}
|
|
9593
|
+
return {
|
|
9594
|
+
connectionName: this.unsimplifiedRoute.connectionName,
|
|
9595
|
+
route,
|
|
9596
|
+
traceThickness: this.unsimplifiedRoute.traceThickness,
|
|
9597
|
+
vias,
|
|
9598
|
+
viaDiameter: this.unsimplifiedRoute.viaDiameter
|
|
9599
|
+
};
|
|
9600
|
+
}
|
|
9601
|
+
visualize() {
|
|
9602
|
+
const graphics = {
|
|
9603
|
+
circles: [],
|
|
9604
|
+
lines: [],
|
|
9605
|
+
points: [],
|
|
9606
|
+
rects: [],
|
|
9607
|
+
coordinateSystem: "cartesian",
|
|
9608
|
+
title: "Single Route Useless Via Removal Solver"
|
|
9609
|
+
};
|
|
9610
|
+
for (let i = 0; i < this.routeSections.length; i++) {
|
|
9611
|
+
const section = this.routeSections[i];
|
|
9612
|
+
graphics.lines.push({
|
|
9613
|
+
points: section.points,
|
|
9614
|
+
strokeWidth: this.TRACE_THICKNESS,
|
|
9615
|
+
strokeColor: i === this.currentSectionIndex ? "orange" : section.z === 0 ? "red" : "blue"
|
|
9616
|
+
});
|
|
9617
|
+
}
|
|
9618
|
+
return graphics;
|
|
9619
|
+
}
|
|
9620
|
+
};
|
|
9621
|
+
|
|
9622
|
+
// lib/solvers/UselessViaRemovalSolver/UselessViaRemovalSolver.ts
|
|
9623
|
+
var UselessViaRemovalSolver = class extends BaseSolver {
|
|
9624
|
+
constructor(input) {
|
|
9625
|
+
super();
|
|
9626
|
+
this.input = input;
|
|
9627
|
+
this.unsimplifiedHdRoutes = input.unsimplifiedHdRoutes;
|
|
9628
|
+
this.optimizedHdRoutes = [];
|
|
9629
|
+
this.unprocessedRoutes = [...input.unsimplifiedHdRoutes];
|
|
9630
|
+
this.obstacleSHI = new ObstacleSpatialHashIndex(input.obstacles);
|
|
9631
|
+
this.hdRouteSHI = new HighDensityRouteSpatialIndex(
|
|
9632
|
+
this.unsimplifiedHdRoutes
|
|
9633
|
+
);
|
|
9634
|
+
}
|
|
9635
|
+
unsimplifiedHdRoutes;
|
|
9636
|
+
optimizedHdRoutes;
|
|
9637
|
+
unprocessedRoutes;
|
|
9638
|
+
activeSubSolver = null;
|
|
9639
|
+
obstacleSHI = null;
|
|
9640
|
+
hdRouteSHI = null;
|
|
9641
|
+
_step() {
|
|
9642
|
+
if (this.activeSubSolver) {
|
|
9643
|
+
this.activeSubSolver.step();
|
|
9644
|
+
if (this.activeSubSolver.solved) {
|
|
9645
|
+
this.optimizedHdRoutes.push(this.activeSubSolver.getOptimizedHdRoute());
|
|
9646
|
+
this.activeSubSolver = null;
|
|
9647
|
+
} else if (this.activeSubSolver.failed || this.activeSubSolver.error) {
|
|
9648
|
+
this.error = this.activeSubSolver.error;
|
|
9649
|
+
this.failed = true;
|
|
9650
|
+
}
|
|
9651
|
+
return;
|
|
9652
|
+
}
|
|
9653
|
+
const unprocessedRoute = this.unprocessedRoutes.shift();
|
|
9654
|
+
if (!unprocessedRoute) {
|
|
9655
|
+
this.solved = true;
|
|
9656
|
+
return;
|
|
9657
|
+
}
|
|
9658
|
+
this.activeSubSolver = new SingleRouteUselessViaRemovalSolver({
|
|
9659
|
+
hdRouteSHI: this.hdRouteSHI,
|
|
9660
|
+
obstacleSHI: this.obstacleSHI,
|
|
9661
|
+
unsimplifiedRoute: unprocessedRoute
|
|
9662
|
+
});
|
|
9663
|
+
}
|
|
9664
|
+
getOptimizedHdRoutes() {
|
|
9665
|
+
return this.optimizedHdRoutes;
|
|
9666
|
+
}
|
|
9667
|
+
visualize() {
|
|
9668
|
+
const visualization = {
|
|
9669
|
+
lines: [],
|
|
9670
|
+
points: [],
|
|
9671
|
+
rects: [],
|
|
9672
|
+
circles: [],
|
|
9673
|
+
coordinateSystem: "cartesian",
|
|
9674
|
+
title: "Useless Via Removal Solver"
|
|
9675
|
+
};
|
|
9676
|
+
for (const obstacle of this.input.obstacles) {
|
|
9677
|
+
let fillColor = "rgba(128, 128, 128, 0.2)";
|
|
9678
|
+
const strokeColor = "rgba(128, 128, 128, 0.5)";
|
|
9679
|
+
const isOnLayer0 = obstacle.zLayers?.includes(0);
|
|
9680
|
+
const isOnLayer1 = obstacle.zLayers?.includes(1);
|
|
9681
|
+
if (isOnLayer0 && isOnLayer1) {
|
|
9682
|
+
fillColor = "rgba(128, 0, 128, 0.2)";
|
|
9683
|
+
} else if (isOnLayer0) {
|
|
9684
|
+
fillColor = "rgba(255, 0, 0, 0.2)";
|
|
9685
|
+
} else if (isOnLayer1) {
|
|
9686
|
+
fillColor = "rgba(0, 0, 255, 0.2)";
|
|
9687
|
+
}
|
|
9688
|
+
visualization.rects.push({
|
|
9689
|
+
center: obstacle.center,
|
|
9690
|
+
width: obstacle.width,
|
|
9691
|
+
height: obstacle.height,
|
|
9692
|
+
fill: fillColor,
|
|
9693
|
+
label: `Obstacle (Z: ${obstacle.zLayers?.join(", ")})`
|
|
9694
|
+
});
|
|
9695
|
+
}
|
|
9696
|
+
for (const route of this.optimizedHdRoutes) {
|
|
9697
|
+
if (route.route.length === 0) continue;
|
|
9698
|
+
const color = this.input.colorMap[route.connectionName] || "#888888";
|
|
9699
|
+
for (let i = 0; i < route.route.length - 1; i++) {
|
|
9700
|
+
const current = route.route[i];
|
|
9701
|
+
const next = route.route[i + 1];
|
|
9702
|
+
if (current.z === next.z) {
|
|
9703
|
+
visualization.lines.push({
|
|
9704
|
+
points: [
|
|
9705
|
+
{ x: current.x, y: current.y },
|
|
9706
|
+
{ x: next.x, y: next.y }
|
|
9707
|
+
],
|
|
9708
|
+
strokeColor: current.z === 0 ? "red" : "blue",
|
|
9709
|
+
strokeWidth: route.traceThickness,
|
|
9710
|
+
label: `${route.connectionName} (z=${current.z})`
|
|
9711
|
+
});
|
|
9712
|
+
}
|
|
9713
|
+
}
|
|
9714
|
+
for (const via of route.vias) {
|
|
9715
|
+
visualization.circles.push({
|
|
9716
|
+
center: { x: via.x, y: via.y },
|
|
9717
|
+
radius: route.viaDiameter / 2,
|
|
9718
|
+
fill: "rgba(255, 0, 255, 0.5)",
|
|
9719
|
+
label: `${route.connectionName} via`
|
|
9720
|
+
});
|
|
9721
|
+
}
|
|
9722
|
+
}
|
|
9723
|
+
if (this.activeSubSolver) {
|
|
9724
|
+
visualization.lines.push(
|
|
9725
|
+
...this.activeSubSolver.visualize().lines ?? []
|
|
9726
|
+
);
|
|
9727
|
+
}
|
|
9728
|
+
return visualization;
|
|
9729
|
+
}
|
|
9730
|
+
};
|
|
9731
|
+
|
|
9732
|
+
// lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
|
|
9733
|
+
var CapacityPathingSolver = class extends BaseSolver {
|
|
9734
|
+
connectionsWithNodes;
|
|
9735
|
+
usedNodeCapacityMap;
|
|
9736
|
+
simpleRouteJson;
|
|
9737
|
+
nodes;
|
|
9738
|
+
edges;
|
|
9739
|
+
GREEDY_MULTIPLIER = 1.1;
|
|
9740
|
+
nodeMap;
|
|
9741
|
+
nodeEdgeMap;
|
|
9742
|
+
connectionNameToGoalNodeIds;
|
|
9743
|
+
colorMap;
|
|
9744
|
+
maxDepthOfNodes;
|
|
9745
|
+
activeCandidateStraightLineDistance;
|
|
9746
|
+
debug_lastNodeCostMap;
|
|
9747
|
+
hyperParameters;
|
|
9748
|
+
constructor({
|
|
9749
|
+
simpleRouteJson,
|
|
9750
|
+
nodes,
|
|
9751
|
+
edges,
|
|
9752
|
+
colorMap,
|
|
9753
|
+
MAX_ITERATIONS = 1e6,
|
|
9754
|
+
hyperParameters = {}
|
|
9755
|
+
}) {
|
|
9756
|
+
super();
|
|
9757
|
+
this.MAX_ITERATIONS = MAX_ITERATIONS;
|
|
9758
|
+
this.simpleRouteJson = simpleRouteJson;
|
|
9759
|
+
this.nodes = nodes;
|
|
9760
|
+
this.edges = edges;
|
|
9761
|
+
this.colorMap = colorMap ?? {};
|
|
9762
|
+
const { connectionsWithNodes, connectionNameToGoalNodeIds } = this.getConnectionsWithNodes();
|
|
9763
|
+
this.connectionsWithNodes = connectionsWithNodes;
|
|
9764
|
+
this.connectionNameToGoalNodeIds = connectionNameToGoalNodeIds;
|
|
9765
|
+
this.hyperParameters = hyperParameters;
|
|
9766
|
+
this.usedNodeCapacityMap = new Map(
|
|
9767
|
+
this.nodes.map((node) => [node.capacityMeshNodeId, 0])
|
|
9768
|
+
);
|
|
9769
|
+
this.nodeMap = new Map(
|
|
9770
|
+
this.nodes.map((node) => [node.capacityMeshNodeId, node])
|
|
9771
|
+
);
|
|
9772
|
+
this.nodeEdgeMap = getNodeEdgeMap(this.edges);
|
|
9773
|
+
this.maxDepthOfNodes = Math.max(
|
|
9774
|
+
...this.nodes.map((node) => node._depth ?? 0)
|
|
9775
|
+
);
|
|
9776
|
+
this.debug_lastNodeCostMap = /* @__PURE__ */ new Map();
|
|
9777
|
+
}
|
|
9778
|
+
getTotalCapacity(node) {
|
|
9779
|
+
const depth = node._depth ?? 0;
|
|
9780
|
+
return (this.maxDepthOfNodes - depth + 1) ** 2;
|
|
9781
|
+
}
|
|
9782
|
+
getConnectionsWithNodes() {
|
|
9783
|
+
const connectionsWithNodes = [];
|
|
9784
|
+
const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
|
|
9785
|
+
const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
|
|
9786
|
+
for (const connection of this.simpleRouteJson.connections) {
|
|
9787
|
+
const nodesForConnection = [];
|
|
9788
|
+
for (const point of connection.pointsToConnect) {
|
|
9789
|
+
let closestNode = this.nodes[0];
|
|
9790
|
+
let minDistance = Number.MAX_VALUE;
|
|
9791
|
+
for (const node of nodesWithTargets) {
|
|
9792
|
+
const distance6 = Math.sqrt(
|
|
9793
|
+
(node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
|
|
9794
|
+
);
|
|
9795
|
+
if (distance6 < minDistance) {
|
|
9796
|
+
minDistance = distance6;
|
|
9797
|
+
closestNode = node;
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
nodesForConnection.push(closestNode);
|
|
9801
|
+
}
|
|
9802
|
+
if (nodesForConnection.length < 2) {
|
|
9803
|
+
throw new Error(
|
|
9804
|
+
`Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
|
|
9805
|
+
);
|
|
9806
|
+
}
|
|
9807
|
+
connectionNameToGoalNodeIds.set(
|
|
9808
|
+
connection.name,
|
|
9809
|
+
nodesForConnection.map((n) => n.capacityMeshNodeId)
|
|
9810
|
+
);
|
|
9811
|
+
connectionsWithNodes.push({
|
|
9812
|
+
connection,
|
|
9813
|
+
nodes: nodesForConnection,
|
|
9814
|
+
pathFound: false,
|
|
9815
|
+
straightLineDistance: distance(
|
|
9816
|
+
nodesForConnection[0].center,
|
|
9817
|
+
nodesForConnection[nodesForConnection.length - 1].center
|
|
9818
|
+
)
|
|
9819
|
+
});
|
|
9820
|
+
}
|
|
9821
|
+
connectionsWithNodes.sort(
|
|
9822
|
+
(a, b) => a.straightLineDistance - b.straightLineDistance
|
|
9823
|
+
);
|
|
9824
|
+
return { connectionsWithNodes, connectionNameToGoalNodeIds };
|
|
9114
9825
|
}
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
hdRouteSHI;
|
|
9121
|
-
unsimplifiedRoute;
|
|
9122
|
-
routeSections;
|
|
9123
|
-
currentSectionIndex;
|
|
9124
|
-
TRACE_THICKNESS = 0.15;
|
|
9125
|
-
OBSTACLE_MARGIN = 0.1;
|
|
9126
|
-
constructor(params) {
|
|
9127
|
-
super();
|
|
9128
|
-
this.currentSectionIndex = 1;
|
|
9129
|
-
this.obstacleSHI = params.obstacleSHI;
|
|
9130
|
-
this.hdRouteSHI = params.hdRouteSHI;
|
|
9131
|
-
this.unsimplifiedRoute = params.unsimplifiedRoute;
|
|
9132
|
-
this.routeSections = this.breakRouteIntoSections(this.unsimplifiedRoute);
|
|
9826
|
+
currentConnectionIndex = 0;
|
|
9827
|
+
candidates;
|
|
9828
|
+
visitedNodes;
|
|
9829
|
+
computeG(prevCandidate, node, endGoal) {
|
|
9830
|
+
return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
|
|
9133
9831
|
}
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
9140
|
-
|
|
9141
|
-
|
|
9142
|
-
|
|
9143
|
-
}
|
|
9144
|
-
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9832
|
+
computeH(prevCandidate, node, endGoal) {
|
|
9833
|
+
return this.getDistanceBetweenNodes(node, endGoal);
|
|
9834
|
+
}
|
|
9835
|
+
getBacktrackedPath(candidate) {
|
|
9836
|
+
const path = [];
|
|
9837
|
+
let currentCandidate = candidate;
|
|
9838
|
+
while (currentCandidate) {
|
|
9839
|
+
path.push(currentCandidate.node);
|
|
9840
|
+
currentCandidate = currentCandidate.prevCandidate;
|
|
9841
|
+
}
|
|
9842
|
+
return path;
|
|
9843
|
+
}
|
|
9844
|
+
getNeighboringNodes(node) {
|
|
9845
|
+
return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
|
|
9846
|
+
(edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
|
|
9847
|
+
).map((n) => this.nodeMap.get(n));
|
|
9848
|
+
}
|
|
9849
|
+
getCapacityPaths() {
|
|
9850
|
+
const capacityPaths = [];
|
|
9851
|
+
for (const connection of this.connectionsWithNodes) {
|
|
9852
|
+
const path = connection.path;
|
|
9853
|
+
if (path) {
|
|
9854
|
+
capacityPaths.push({
|
|
9855
|
+
capacityPathId: connection.connection.name,
|
|
9856
|
+
connectionName: connection.connection.name,
|
|
9857
|
+
nodeIds: path.map((node) => node.capacityMeshNodeId)
|
|
9858
|
+
});
|
|
9156
9859
|
}
|
|
9157
9860
|
}
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9861
|
+
return capacityPaths;
|
|
9862
|
+
}
|
|
9863
|
+
doesNodeHaveCapacityForTrace(node, prevNode) {
|
|
9864
|
+
const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
|
|
9865
|
+
const totalCapacity = this.getTotalCapacity(node);
|
|
9866
|
+
if (node.availableZ.length === 1 && !node._containsTarget && usedCapacity > 0)
|
|
9867
|
+
return false;
|
|
9868
|
+
let additionalCapacityRequirement = 0;
|
|
9869
|
+
if (node.availableZ.length > 1 && prevNode.availableZ.length === 1) {
|
|
9870
|
+
additionalCapacityRequirement += 0.5;
|
|
9871
|
+
}
|
|
9872
|
+
return usedCapacity + additionalCapacityRequirement < totalCapacity;
|
|
9873
|
+
}
|
|
9874
|
+
canTravelThroughObstacle(node, connectionName) {
|
|
9875
|
+
const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
|
|
9876
|
+
return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
|
|
9877
|
+
}
|
|
9878
|
+
getDistanceBetweenNodes(A, B) {
|
|
9879
|
+
return Math.sqrt(
|
|
9880
|
+
(A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
|
|
9881
|
+
);
|
|
9882
|
+
}
|
|
9883
|
+
reduceCapacityAlongPath(nextConnection) {
|
|
9884
|
+
for (const node of nextConnection.path ?? []) {
|
|
9885
|
+
this.usedNodeCapacityMap.set(
|
|
9886
|
+
node.capacityMeshNodeId,
|
|
9887
|
+
this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
|
|
9888
|
+
);
|
|
9889
|
+
}
|
|
9890
|
+
}
|
|
9891
|
+
isConnectedToEndGoal(node, endGoal) {
|
|
9892
|
+
return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
|
|
9161
9893
|
}
|
|
9162
9894
|
_step() {
|
|
9163
|
-
|
|
9895
|
+
const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
|
|
9896
|
+
if (!nextConnection) {
|
|
9164
9897
|
this.solved = true;
|
|
9165
9898
|
return;
|
|
9166
9899
|
}
|
|
9167
|
-
const
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
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;
|
|
9172
9920
|
return;
|
|
9173
9921
|
}
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9922
|
+
if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
|
|
9923
|
+
nextConnection.path = this.getBacktrackedPath({
|
|
9924
|
+
prevCandidate: currentCandidate,
|
|
9925
|
+
node: end,
|
|
9926
|
+
f: 0,
|
|
9927
|
+
g: 0,
|
|
9928
|
+
h: 0
|
|
9929
|
+
});
|
|
9930
|
+
this.reduceCapacityAlongPath(nextConnection);
|
|
9931
|
+
this.currentConnectionIndex++;
|
|
9932
|
+
this.candidates = null;
|
|
9933
|
+
this.visitedNodes = null;
|
|
9182
9934
|
return;
|
|
9183
9935
|
}
|
|
9184
|
-
this.
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
currentSection,
|
|
9189
|
-
targetZ
|
|
9190
|
-
}) {
|
|
9191
|
-
for (let i = 0; i < currentSection.points.length - 1; i++) {
|
|
9192
|
-
const A = { ...currentSection.points[i], z: targetZ };
|
|
9193
|
-
const B = { ...currentSection.points[i + 1], z: targetZ };
|
|
9194
|
-
const conflictingRoutes = this.hdRouteSHI.getConflictingRoutesForSegment(
|
|
9195
|
-
A,
|
|
9196
|
-
B,
|
|
9197
|
-
this.TRACE_THICKNESS
|
|
9198
|
-
);
|
|
9199
|
-
for (const { conflictingRoute, distance: distance6 } of conflictingRoutes) {
|
|
9200
|
-
if (conflictingRoute.connectionName === this.unsimplifiedRoute.connectionName)
|
|
9201
|
-
continue;
|
|
9202
|
-
if (distance6 < this.TRACE_THICKNESS + conflictingRoute.traceThickness) {
|
|
9203
|
-
return false;
|
|
9204
|
-
}
|
|
9936
|
+
const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
|
|
9937
|
+
for (const neighborNode of neighborNodes) {
|
|
9938
|
+
if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
|
|
9939
|
+
continue;
|
|
9205
9940
|
}
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
centerY: (A.y + B.y) / 2,
|
|
9209
|
-
width: Math.abs(A.x - B.x),
|
|
9210
|
-
height: Math.abs(A.y - B.y)
|
|
9211
|
-
};
|
|
9212
|
-
const obstacles = this.obstacleSHI.getNodesInArea(
|
|
9213
|
-
segmentBox.centerX,
|
|
9214
|
-
segmentBox.centerY,
|
|
9215
|
-
segmentBox.width,
|
|
9216
|
-
segmentBox.height
|
|
9217
|
-
);
|
|
9218
|
-
for (const obstacle of obstacles) {
|
|
9219
|
-
const distToObstacle = segmentToBoxMinDistance(A, B, obstacle);
|
|
9220
|
-
if (distToObstacle < this.TRACE_THICKNESS + this.OBSTACLE_MARGIN) {
|
|
9221
|
-
return false;
|
|
9222
|
-
}
|
|
9941
|
+
if (!this.doesNodeHaveCapacityForTrace(neighborNode, currentCandidate.node)) {
|
|
9942
|
+
continue;
|
|
9223
9943
|
}
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
getOptimizedHdRoute() {
|
|
9228
|
-
const route = this.routeSections.flatMap((section) => section.points);
|
|
9229
|
-
const vias = [];
|
|
9230
|
-
for (let i = 0; i < route.length - 1; i++) {
|
|
9231
|
-
if (route[i].z !== route[i + 1].z) {
|
|
9232
|
-
vias.push({
|
|
9233
|
-
x: route[i].x,
|
|
9234
|
-
y: route[i].y
|
|
9235
|
-
});
|
|
9944
|
+
const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
|
|
9945
|
+
if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
|
|
9946
|
+
continue;
|
|
9236
9947
|
}
|
|
9948
|
+
const g = this.computeG(currentCandidate, neighborNode, end);
|
|
9949
|
+
const h = this.computeH(currentCandidate, neighborNode, end);
|
|
9950
|
+
const f = g + h * this.GREEDY_MULTIPLIER;
|
|
9951
|
+
this.debug_lastNodeCostMap.set(neighborNode.capacityMeshNodeId, {
|
|
9952
|
+
f,
|
|
9953
|
+
g,
|
|
9954
|
+
h
|
|
9955
|
+
});
|
|
9956
|
+
const newCandidate = {
|
|
9957
|
+
prevCandidate: currentCandidate,
|
|
9958
|
+
node: neighborNode,
|
|
9959
|
+
f,
|
|
9960
|
+
g,
|
|
9961
|
+
h
|
|
9962
|
+
};
|
|
9963
|
+
this.candidates.push(newCandidate);
|
|
9237
9964
|
}
|
|
9238
|
-
|
|
9239
|
-
connectionName: this.unsimplifiedRoute.connectionName,
|
|
9240
|
-
route,
|
|
9241
|
-
traceThickness: this.unsimplifiedRoute.traceThickness,
|
|
9242
|
-
vias,
|
|
9243
|
-
viaDiameter: this.unsimplifiedRoute.viaDiameter
|
|
9244
|
-
};
|
|
9965
|
+
this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
|
|
9245
9966
|
}
|
|
9246
9967
|
visualize() {
|
|
9247
9968
|
const graphics = {
|
|
9248
|
-
circles: [],
|
|
9249
9969
|
lines: [],
|
|
9250
9970
|
points: [],
|
|
9251
9971
|
rects: [],
|
|
9252
|
-
|
|
9253
|
-
title: "Single Route Useless Via Removal Solver"
|
|
9972
|
+
circles: []
|
|
9254
9973
|
};
|
|
9255
|
-
|
|
9256
|
-
|
|
9974
|
+
if (this.connectionsWithNodes) {
|
|
9975
|
+
for (let i = 0; i < this.connectionsWithNodes.length; i++) {
|
|
9976
|
+
const conn = this.connectionsWithNodes[i];
|
|
9977
|
+
if (conn.path && conn.path.length > 0) {
|
|
9978
|
+
const pathPoints = conn.path.map(
|
|
9979
|
+
({ center: { x, y }, width, availableZ }) => ({
|
|
9980
|
+
// slight offset to allow viewing overlapping paths
|
|
9981
|
+
x: x + (i % 10 + i % 19) * (5e-3 * width),
|
|
9982
|
+
y: y + (i % 10 + i % 19) * (5e-3 * width),
|
|
9983
|
+
availableZ
|
|
9984
|
+
})
|
|
9985
|
+
);
|
|
9986
|
+
graphics.lines.push({
|
|
9987
|
+
points: pathPoints,
|
|
9988
|
+
strokeColor: this.colorMap[conn.connection.name]
|
|
9989
|
+
});
|
|
9990
|
+
for (let u = 0; u < pathPoints.length; u++) {
|
|
9991
|
+
const point = pathPoints[u];
|
|
9992
|
+
graphics.points.push({
|
|
9993
|
+
x: point.x,
|
|
9994
|
+
y: point.y,
|
|
9995
|
+
label: [
|
|
9996
|
+
`conn: ${conn.connection.name}`,
|
|
9997
|
+
`node: ${conn.path[u].capacityMeshNodeId}`,
|
|
9998
|
+
`z: ${point.availableZ.join(",")}`
|
|
9999
|
+
].join("\n")
|
|
10000
|
+
});
|
|
10001
|
+
}
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
}
|
|
10005
|
+
for (const node of this.nodes) {
|
|
10006
|
+
const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
|
|
10007
|
+
const totalCapacity = this.getTotalCapacity(node);
|
|
10008
|
+
const nodeCosts = this.debug_lastNodeCostMap.get(node.capacityMeshNodeId);
|
|
10009
|
+
graphics.rects.push({
|
|
10010
|
+
...createRectFromCapacityNode(node, {
|
|
10011
|
+
rectMargin: 0.025,
|
|
10012
|
+
zOffset: 0.01
|
|
10013
|
+
}),
|
|
10014
|
+
label: [
|
|
10015
|
+
`${node.capacityMeshNodeId}`,
|
|
10016
|
+
`${usedCapacity}/${totalCapacity}`,
|
|
10017
|
+
`${node.width.toFixed(2)}x${node.height.toFixed(2)}`,
|
|
10018
|
+
`g: ${nodeCosts?.g !== void 0 ? nodeCosts.g.toFixed(2) : "?"}`,
|
|
10019
|
+
`h: ${nodeCosts?.h !== void 0 ? nodeCosts.h.toFixed(2) : "?"}`,
|
|
10020
|
+
`f: ${nodeCosts?.f !== void 0 ? nodeCosts.f.toFixed(2) : "?"}`,
|
|
10021
|
+
`z: ${node.availableZ.join(", ")}`
|
|
10022
|
+
].join("\n"),
|
|
10023
|
+
stroke: usedCapacity > totalCapacity + 0.5 ? "red" : void 0
|
|
10024
|
+
});
|
|
10025
|
+
}
|
|
10026
|
+
if (this.connectionsWithNodes) {
|
|
10027
|
+
for (const conn of this.connectionsWithNodes) {
|
|
10028
|
+
if (conn.connection?.pointsToConnect) {
|
|
10029
|
+
for (const point of conn.connection.pointsToConnect) {
|
|
10030
|
+
graphics.points.push({
|
|
10031
|
+
x: point.x,
|
|
10032
|
+
y: point.y,
|
|
10033
|
+
label: [`pointsToConnect ${conn.connection.name}`].join("\n")
|
|
10034
|
+
});
|
|
10035
|
+
}
|
|
10036
|
+
}
|
|
10037
|
+
}
|
|
10038
|
+
}
|
|
10039
|
+
const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
|
|
10040
|
+
if (nextConnection) {
|
|
10041
|
+
const [start, end] = nextConnection.connection.pointsToConnect;
|
|
9257
10042
|
graphics.lines.push({
|
|
9258
|
-
points:
|
|
9259
|
-
|
|
9260
|
-
|
|
10043
|
+
points: [
|
|
10044
|
+
{ x: start.x, y: start.y },
|
|
10045
|
+
{ x: end.x, y: end.y }
|
|
10046
|
+
],
|
|
10047
|
+
strokeColor: "red",
|
|
10048
|
+
strokeDash: "10 5"
|
|
10049
|
+
});
|
|
10050
|
+
}
|
|
10051
|
+
if (this.candidates) {
|
|
10052
|
+
const topCandidates = this.candidates.slice(0, 5);
|
|
10053
|
+
const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
|
|
10054
|
+
topCandidates.forEach((candidate, index) => {
|
|
10055
|
+
const opacity = 0.5 * (1 - index / 5);
|
|
10056
|
+
const backtrackedPath = this.getBacktrackedPath(candidate);
|
|
10057
|
+
graphics.lines.push({
|
|
10058
|
+
points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
|
|
10059
|
+
strokeColor: safeTransparentize(
|
|
10060
|
+
this.colorMap[connectionName] ?? "red",
|
|
10061
|
+
1 - opacity
|
|
10062
|
+
)
|
|
10063
|
+
});
|
|
9261
10064
|
});
|
|
9262
10065
|
}
|
|
9263
10066
|
return graphics;
|
|
9264
10067
|
}
|
|
9265
10068
|
};
|
|
9266
10069
|
|
|
9267
|
-
// lib/solvers/
|
|
9268
|
-
var
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
this.
|
|
9274
|
-
this.unprocessedRoutes = [...input.unsimplifiedHdRoutes];
|
|
9275
|
-
this.obstacleSHI = new ObstacleSpatialHashIndex(input.obstacles);
|
|
9276
|
-
this.hdRouteSHI = new HighDensityRouteSpatialIndex(
|
|
9277
|
-
this.unsimplifiedHdRoutes
|
|
9278
|
-
);
|
|
10070
|
+
// lib/solvers/CapacityPathingSolver/CapacityPathingSolver5.ts
|
|
10071
|
+
var CapacityPathingSolver5 = class extends CapacityPathingSolver {
|
|
10072
|
+
NEGATIVE_CAPACITY_PENALTY_FACTOR = 1;
|
|
10073
|
+
REDUCED_CAPACITY_PENALTY_FACTOR = 1;
|
|
10074
|
+
constructor(...args) {
|
|
10075
|
+
super(...args);
|
|
10076
|
+
this.GREEDY_MULTIPLIER = 2.5;
|
|
9279
10077
|
}
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
unprocessedRoutes;
|
|
9283
|
-
activeSubSolver = null;
|
|
9284
|
-
obstacleSHI = null;
|
|
9285
|
-
hdRouteSHI = null;
|
|
9286
|
-
_step() {
|
|
9287
|
-
if (this.activeSubSolver) {
|
|
9288
|
-
this.activeSubSolver.step();
|
|
9289
|
-
if (this.activeSubSolver.solved) {
|
|
9290
|
-
this.optimizedHdRoutes.push(this.activeSubSolver.getOptimizedHdRoute());
|
|
9291
|
-
this.activeSubSolver = null;
|
|
9292
|
-
} else if (this.activeSubSolver.failed || this.activeSubSolver.error) {
|
|
9293
|
-
this.error = this.activeSubSolver.error;
|
|
9294
|
-
this.failed = true;
|
|
9295
|
-
}
|
|
9296
|
-
return;
|
|
9297
|
-
}
|
|
9298
|
-
const unprocessedRoute = this.unprocessedRoutes.shift();
|
|
9299
|
-
if (!unprocessedRoute) {
|
|
9300
|
-
this.solved = true;
|
|
9301
|
-
return;
|
|
9302
|
-
}
|
|
9303
|
-
this.activeSubSolver = new SingleRouteUselessViaRemovalSolver({
|
|
9304
|
-
hdRouteSHI: this.hdRouteSHI,
|
|
9305
|
-
obstacleSHI: this.obstacleSHI,
|
|
9306
|
-
unsimplifiedRoute: unprocessedRoute
|
|
9307
|
-
});
|
|
10078
|
+
get maxCapacityFactor() {
|
|
10079
|
+
return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
|
|
9308
10080
|
}
|
|
9309
|
-
|
|
9310
|
-
return this.
|
|
10081
|
+
getTotalCapacity(node) {
|
|
10082
|
+
return getTunedTotalCapacity1(node, this.maxCapacityFactor);
|
|
9311
10083
|
}
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
for (let i = 0; i < route.route.length - 1; i++) {
|
|
9325
|
-
const current = route.route[i];
|
|
9326
|
-
const next = route.route[i + 1];
|
|
9327
|
-
if (current.z === next.z) {
|
|
9328
|
-
visualization.lines.push({
|
|
9329
|
-
points: [
|
|
9330
|
-
{ x: current.x, y: current.y },
|
|
9331
|
-
{ x: next.x, y: next.y }
|
|
9332
|
-
],
|
|
9333
|
-
strokeColor: current.z === 0 ? "red" : "blue",
|
|
9334
|
-
strokeWidth: route.traceThickness,
|
|
9335
|
-
label: `${route.connectionName} (z=${current.z})`
|
|
9336
|
-
});
|
|
9337
|
-
}
|
|
9338
|
-
}
|
|
9339
|
-
for (const via of route.vias) {
|
|
9340
|
-
visualization.circles.push({
|
|
9341
|
-
center: { x: via.x, y: via.y },
|
|
9342
|
-
radius: route.viaDiameter / 2,
|
|
9343
|
-
fill: "rgba(255, 0, 255, 0.5)",
|
|
9344
|
-
label: `${route.connectionName} via`
|
|
9345
|
-
});
|
|
9346
|
-
}
|
|
9347
|
-
}
|
|
9348
|
-
if (this.activeSubSolver) {
|
|
9349
|
-
visualization.lines.push(
|
|
9350
|
-
...this.activeSubSolver.visualize().lines ?? []
|
|
9351
|
-
);
|
|
10084
|
+
/**
|
|
10085
|
+
* Penalty you pay for using this node
|
|
10086
|
+
*/
|
|
10087
|
+
getNodeCapacityPenalty(node) {
|
|
10088
|
+
const MAX_PENALTY = node.width + node.height;
|
|
10089
|
+
const MIN_PENALTY = 0.05;
|
|
10090
|
+
const START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW = 2;
|
|
10091
|
+
const totalCapacity = this.getTotalCapacity(node);
|
|
10092
|
+
const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
|
|
10093
|
+
const remainingCapacity = totalCapacity - usedCapacity;
|
|
10094
|
+
if (remainingCapacity > START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW) {
|
|
10095
|
+
return MIN_PENALTY;
|
|
9352
10096
|
}
|
|
9353
|
-
|
|
10097
|
+
const penalty = (MAX_PENALTY - MIN_PENALTY) * Math.max(
|
|
10098
|
+
1,
|
|
10099
|
+
(START_PENALIZING_CAPACITY_WHEN_IT_DROPS_BELOW - remainingCapacity) / (MAX_PENALTY - MIN_PENALTY)
|
|
10100
|
+
) + MIN_PENALTY;
|
|
10101
|
+
return penalty;
|
|
10102
|
+
}
|
|
10103
|
+
/**
|
|
10104
|
+
* We're rewarding travel into big nodes.
|
|
10105
|
+
*
|
|
10106
|
+
* To minimize shortest path, you'd want to comment this out.
|
|
10107
|
+
*/
|
|
10108
|
+
getDistanceBetweenNodes(A, B) {
|
|
10109
|
+
const dx = A.center.x - B.center.x;
|
|
10110
|
+
const dy = A.center.y - B.center.y;
|
|
10111
|
+
return Math.sqrt(dx ** 2 + dy ** 2);
|
|
10112
|
+
}
|
|
10113
|
+
computeG(prevCandidate, node, endGoal) {
|
|
10114
|
+
return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node) + this.getNodeCapacityPenalty(node);
|
|
10115
|
+
}
|
|
10116
|
+
computeH(prevCandidate, node, endGoal) {
|
|
10117
|
+
return this.getDistanceBetweenNodes(node, endGoal) + this.getNodeCapacityPenalty(node);
|
|
9354
10118
|
}
|
|
9355
10119
|
};
|
|
9356
10120
|
|
|
@@ -9390,6 +10154,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9390
10154
|
nodeTargetMerger;
|
|
9391
10155
|
edgeSolver;
|
|
9392
10156
|
pathingSolver;
|
|
10157
|
+
// Updated type
|
|
9393
10158
|
edgeToPortSegmentSolver;
|
|
9394
10159
|
colorMap;
|
|
9395
10160
|
segmentToPointSolver;
|
|
@@ -9399,8 +10164,10 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9399
10164
|
highDensityStitchSolver;
|
|
9400
10165
|
singleLayerNodeMerger;
|
|
9401
10166
|
strawSolver;
|
|
9402
|
-
|
|
9403
|
-
|
|
10167
|
+
uselessViaRemovalSolver1;
|
|
10168
|
+
uselessViaRemovalSolver2;
|
|
10169
|
+
multiSimplifiedPathSolver1;
|
|
10170
|
+
multiSimplifiedPathSolver2;
|
|
9404
10171
|
startTimeOfPhase;
|
|
9405
10172
|
endTimeOfPhase;
|
|
9406
10173
|
timeSpentOnPhase;
|
|
@@ -9473,17 +10240,23 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9473
10240
|
CapacityMeshEdgeSolver2_NodeTreeOptimization,
|
|
9474
10241
|
(cms) => [cms.capacityNodes]
|
|
9475
10242
|
),
|
|
9476
|
-
definePipelineStep(
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
10243
|
+
definePipelineStep(
|
|
10244
|
+
"pathingSolver",
|
|
10245
|
+
CapacityPathingSolver5,
|
|
10246
|
+
// CapacityPathingMultiSectionSolver,
|
|
10247
|
+
(cms) => [
|
|
10248
|
+
// Replaced solver class
|
|
10249
|
+
{
|
|
10250
|
+
simpleRouteJson: cms.srjWithPointPairs,
|
|
10251
|
+
nodes: cms.capacityNodes,
|
|
10252
|
+
edges: cms.edgeSolver?.edges || [],
|
|
10253
|
+
colorMap: cms.colorMap,
|
|
10254
|
+
hyperParameters: {
|
|
10255
|
+
MAX_CAPACITY_FACTOR: 1
|
|
10256
|
+
}
|
|
9484
10257
|
}
|
|
9485
|
-
|
|
9486
|
-
|
|
10258
|
+
]
|
|
10259
|
+
),
|
|
9487
10260
|
definePipelineStep(
|
|
9488
10261
|
"edgeToPortSegmentSolver",
|
|
9489
10262
|
CapacityEdgeToPortSegmentSolver,
|
|
@@ -9551,12 +10324,13 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9551
10324
|
{
|
|
9552
10325
|
connections: cms.srjWithPointPairs.connections,
|
|
9553
10326
|
hdRoutes: cms.highDensityRouteSolver.routes,
|
|
10327
|
+
colorMap: cms.colorMap,
|
|
9554
10328
|
layerCount: cms.srj.layerCount
|
|
9555
10329
|
}
|
|
9556
10330
|
]
|
|
9557
10331
|
),
|
|
9558
10332
|
definePipelineStep(
|
|
9559
|
-
"
|
|
10333
|
+
"uselessViaRemovalSolver1",
|
|
9560
10334
|
UselessViaRemovalSolver,
|
|
9561
10335
|
(cms) => [
|
|
9562
10336
|
{
|
|
@@ -9568,11 +10342,35 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9568
10342
|
]
|
|
9569
10343
|
),
|
|
9570
10344
|
definePipelineStep(
|
|
9571
|
-
"
|
|
10345
|
+
"multiSimplifiedPathSolver1",
|
|
10346
|
+
MultiSimplifiedPathSolver,
|
|
10347
|
+
(cms) => [
|
|
10348
|
+
{
|
|
10349
|
+
unsimplifiedHdRoutes: cms.uselessViaRemovalSolver1?.getOptimizedHdRoutes() || cms.highDensityStitchSolver.mergedHdRoutes,
|
|
10350
|
+
obstacles: cms.srj.obstacles,
|
|
10351
|
+
connMap: cms.connMap,
|
|
10352
|
+
colorMap: cms.colorMap
|
|
10353
|
+
}
|
|
10354
|
+
]
|
|
10355
|
+
),
|
|
10356
|
+
definePipelineStep(
|
|
10357
|
+
"uselessViaRemovalSolver2",
|
|
10358
|
+
UselessViaRemovalSolver,
|
|
10359
|
+
(cms) => [
|
|
10360
|
+
{
|
|
10361
|
+
unsimplifiedHdRoutes: cms.multiSimplifiedPathSolver1.simplifiedHdRoutes,
|
|
10362
|
+
obstacles: cms.srj.obstacles,
|
|
10363
|
+
colorMap: cms.colorMap,
|
|
10364
|
+
layerCount: cms.srj.layerCount
|
|
10365
|
+
}
|
|
10366
|
+
]
|
|
10367
|
+
),
|
|
10368
|
+
definePipelineStep(
|
|
10369
|
+
"multiSimplifiedPathSolver2",
|
|
9572
10370
|
MultiSimplifiedPathSolver,
|
|
9573
10371
|
(cms) => [
|
|
9574
10372
|
{
|
|
9575
|
-
unsimplifiedHdRoutes: cms.
|
|
10373
|
+
unsimplifiedHdRoutes: cms.uselessViaRemovalSolver2?.getOptimizedHdRoutes(),
|
|
9576
10374
|
obstacles: cms.srj.obstacles,
|
|
9577
10375
|
connMap: cms.connMap,
|
|
9578
10376
|
colorMap: cms.colorMap
|
|
@@ -9631,8 +10429,10 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9631
10429
|
const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
|
|
9632
10430
|
const highDensityViz = this.highDensityRouteSolver?.visualize();
|
|
9633
10431
|
const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
|
|
9634
|
-
const
|
|
9635
|
-
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();
|
|
9636
10436
|
const problemViz = {
|
|
9637
10437
|
points: [
|
|
9638
10438
|
...this.srj.connections.flatMap(
|
|
@@ -9684,8 +10484,10 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9684
10484
|
segmentOptimizationViz,
|
|
9685
10485
|
highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null,
|
|
9686
10486
|
highDensityStitchViz,
|
|
9687
|
-
|
|
9688
|
-
|
|
10487
|
+
uselessViaRemovalViz1,
|
|
10488
|
+
simplifiedPathSolverViz1,
|
|
10489
|
+
uselessViaRemovalViz2,
|
|
10490
|
+
simplifiedPathSolverViz2,
|
|
9689
10491
|
this.solved ? combineVisualizations(
|
|
9690
10492
|
problemViz,
|
|
9691
10493
|
convertSrjToGraphicsObject(this.getOutputSimpleRouteJson())
|
|
@@ -9747,7 +10549,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9747
10549
|
return match ? match[1] : mstConnectionName;
|
|
9748
10550
|
}
|
|
9749
10551
|
_getOutputHdRoutes() {
|
|
9750
|
-
return this.
|
|
10552
|
+
return this.multiSimplifiedPathSolver2?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver2?.getOptimizedHdRoutes() ?? this.multiSimplifiedPathSolver1?.simplifiedHdRoutes ?? this.uselessViaRemovalSolver1?.getOptimizedHdRoutes() ?? this.highDensityStitchSolver.mergedHdRoutes;
|
|
9751
10553
|
}
|
|
9752
10554
|
/**
|
|
9753
10555
|
* Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces
|
|
@@ -9759,9 +10561,9 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9759
10561
|
const traces = [];
|
|
9760
10562
|
const allHdRoutes = this._getOutputHdRoutes();
|
|
9761
10563
|
for (const connection of this.netToPointPairsSolver?.newConnections ?? []) {
|
|
9762
|
-
const
|
|
9763
|
-
(c) => c.name === connection.
|
|
9764
|
-
);
|
|
10564
|
+
const netConnectionName = this.srj.connections.find(
|
|
10565
|
+
(c) => c.name === connection.name
|
|
10566
|
+
)?.netConnectionName;
|
|
9765
10567
|
const hdRoutes = allHdRoutes.filter(
|
|
9766
10568
|
(r) => r.connectionName === connection.name
|
|
9767
10569
|
);
|
|
@@ -9770,7 +10572,7 @@ var AutoroutingPipelineSolver = class extends BaseSolver {
|
|
|
9770
10572
|
const simplifiedPcbTrace = {
|
|
9771
10573
|
type: "pcb_trace",
|
|
9772
10574
|
pcb_trace_id: `${connection.name}_${i}`,
|
|
9773
|
-
connection_name: this.getOriginalConnectionName(connection.name),
|
|
10575
|
+
connection_name: netConnectionName ?? this.getOriginalConnectionName(connection.name),
|
|
9774
10576
|
route: convertHdRouteToSimplifiedRoute(hdRoute, this.srj.layerCount)
|
|
9775
10577
|
};
|
|
9776
10578
|
traces.push(simplifiedPcbTrace);
|