@tscircuit/schematic-trace-solver 0.0.40 → 0.0.42

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.
Files changed (57) hide show
  1. package/dist/index.d.ts +155 -9
  2. package/dist/index.js +998 -479
  3. package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +238 -0
  4. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +61 -26
  5. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +94 -192
  6. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +13 -24
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +205 -0
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +144 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +117 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts +132 -0
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{balanceLShapes.ts → sub-solvers/TraceCleanupSolver/balanceLShapes.ts} +12 -10
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisions.ts → sub-solvers/TraceCleanupSolver/hasCollisions.ts} +1 -1
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisionsWithLabels.ts → sub-solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts} +1 -1
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{minimizeTurnsWithFilteredLabels.ts → sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts} +6 -6
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{simplifyPath.ts → sub-solvers/TraceCleanupSolver/simplifyPath.ts} +1 -1
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{turnMinimization.ts → sub-solvers/TraceCleanupSolver/turnMinimization.ts} +0 -2
  17. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +104 -0
  18. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +24 -154
  19. package/lib/utils/does-trace-overlap-with-existing-traces.ts +33 -0
  20. package/package.json +1 -1
  21. package/site/examples/example25.page.tsx +4 -0
  22. package/tests/assets/1.input.json +379 -0
  23. package/tests/assets/2.input.json +330 -0
  24. package/tests/assets/3.input.json +388 -0
  25. package/tests/assets/MergedNetLabelObstacles.test.input.json +154 -0
  26. package/tests/assets/OverlapAvoidanceStepSolver.test.input.json +682 -0
  27. package/tests/assets/SingleOverlapSolver.test.input.json +170 -0
  28. package/tests/assets/TraceCleanupSolver.test.input.json +216 -0
  29. package/tests/assets/TraceLabelOverlapAvoidanceSolver.test.input.json +424 -0
  30. package/tests/assets/example25.json +96 -0
  31. package/tests/examples/__snapshots__/example03.snap.svg +67 -76
  32. package/tests/examples/__snapshots__/example10.snap.svg +24 -30
  33. package/tests/examples/__snapshots__/example13.snap.svg +81 -84
  34. package/tests/examples/__snapshots__/example15.snap.svg +5 -8
  35. package/tests/examples/__snapshots__/example21.snap.svg +68 -74
  36. package/tests/examples/__snapshots__/example25.snap.svg +3 -3
  37. package/tests/examples/__snapshots__/example26.snap.svg +3 -3
  38. package/tests/examples/__snapshots__/example28.snap.svg +273 -0
  39. package/tests/examples/example28.test.ts +13 -0
  40. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.test.ts +18 -0
  41. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +243 -0
  42. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +223 -0
  43. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +205 -0
  44. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +184 -0
  45. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView01.test.ts +29 -0
  46. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView02.test.ts +27 -0
  47. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +39 -0
  48. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/MergedNetLabelObstacles.test.ts +14 -0
  49. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/OverlapAvoidanceStepSolver.test.ts +20 -0
  50. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/SingleOverlapSolver.test.ts +10 -0
  51. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/TraceCleanupSolver.test.ts +19 -0
  52. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +140 -0
  53. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +216 -0
  54. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +125 -0
  55. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
  56. /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{countTurns.ts → sub-solvers/TraceCleanupSolver/countTurns.ts} +0 -0
  57. /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{tryConnectPoints.ts → sub-solvers/TraceCleanupSolver/tryConnectPoints.ts} +0 -0
package/dist/index.js CHANGED
@@ -2196,48 +2196,193 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2196
2196
  }
2197
2197
  };
2198
2198
 
2199
- // lib/solvers/GuidelinesSolver/GuidelinesSolver.ts
2200
- import { getBounds as getBounds3 } from "graphics-debug";
2201
-
2202
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts
2203
- var detectTraceLabelOverlap = (traces, netLabels) => {
2204
- const overlaps = [];
2205
- for (const trace of traces) {
2206
- for (const label of netLabels) {
2207
- const labelBounds = getRectBounds(label.center, label.width, label.height);
2208
- for (let i = 0; i < trace.tracePath.length - 1; i++) {
2209
- const p1 = trace.tracePath[i];
2210
- const p2 = trace.tracePath[i + 1];
2211
- if (segmentIntersectsRect2(p1, p2, labelBounds)) {
2212
- if (trace.globalConnNetId === label.globalConnNetId) {
2213
- break;
2199
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts
2200
+ var MergedNetLabelObstacleSolver = class extends BaseSolver {
2201
+ input;
2202
+ output;
2203
+ inputProblem;
2204
+ traces;
2205
+ constructor(solverInput) {
2206
+ super();
2207
+ this.input = solverInput;
2208
+ this.inputProblem = solverInput.inputProblem;
2209
+ this.traces = solverInput.traces;
2210
+ this.output = {
2211
+ netLabelPlacements: solverInput.netLabelPlacements,
2212
+ mergedLabelNetIdMap: {}
2213
+ };
2214
+ }
2215
+ _step() {
2216
+ const originalLabels = this.input.netLabelPlacements;
2217
+ const mergedLabelNetIdMap = {};
2218
+ if (!originalLabels || originalLabels.length === 0) {
2219
+ this.output = {
2220
+ netLabelPlacements: [],
2221
+ mergedLabelNetIdMap: {}
2222
+ };
2223
+ this.solved = true;
2224
+ return;
2225
+ }
2226
+ const labelGroups = {};
2227
+ for (const p of originalLabels) {
2228
+ if (p.pinIds.length === 0) continue;
2229
+ const chipId = p.pinIds[0].split(".")[0];
2230
+ if (!chipId) continue;
2231
+ const key = `${chipId}-${p.orientation}`;
2232
+ if (!(key in labelGroups)) {
2233
+ labelGroups[key] = [];
2234
+ }
2235
+ labelGroups[key].push(p);
2236
+ }
2237
+ const finalPlacements = [];
2238
+ for (const [key, group] of Object.entries(labelGroups)) {
2239
+ if (group.length <= 1) {
2240
+ finalPlacements.push(...group);
2241
+ continue;
2242
+ }
2243
+ let minX = Infinity;
2244
+ let minY = Infinity;
2245
+ let maxX = -Infinity;
2246
+ let maxY = -Infinity;
2247
+ for (const p of group) {
2248
+ const bounds = getRectBounds(p.center, p.width, p.height);
2249
+ minX = Math.min(minX, bounds.minX);
2250
+ minY = Math.min(minY, bounds.minY);
2251
+ maxX = Math.max(maxX, bounds.maxX);
2252
+ maxY = Math.max(maxY, bounds.maxY);
2253
+ }
2254
+ const newWidth = maxX - minX;
2255
+ const newHeight = maxY - minY;
2256
+ const template = group[0];
2257
+ const syntheticId = `merged-group-${key}`;
2258
+ const originalNetIds = new Set(group.map((p) => p.globalConnNetId));
2259
+ mergedLabelNetIdMap[syntheticId] = originalNetIds;
2260
+ finalPlacements.push({
2261
+ ...template,
2262
+ globalConnNetId: syntheticId,
2263
+ width: newWidth,
2264
+ height: newHeight,
2265
+ center: { x: minX + newWidth / 2, y: minY + newHeight / 2 },
2266
+ pinIds: [...new Set(group.flatMap((p) => p.pinIds))],
2267
+ mspConnectionPairIds: [
2268
+ ...new Set(group.flatMap((p) => p.mspConnectionPairIds))
2269
+ ]
2270
+ });
2271
+ }
2272
+ this.output = {
2273
+ netLabelPlacements: finalPlacements,
2274
+ mergedLabelNetIdMap
2275
+ };
2276
+ this.solved = true;
2277
+ }
2278
+ getOutput() {
2279
+ return this.output;
2280
+ }
2281
+ visualize() {
2282
+ const graphics = visualizeInputProblem(this.inputProblem, {
2283
+ chipAlpha: 0.1,
2284
+ connectionAlpha: 0.1
2285
+ });
2286
+ if (!graphics.rects) graphics.rects = [];
2287
+ if (!graphics.lines) graphics.lines = [];
2288
+ if (!graphics.points) graphics.points = [];
2289
+ if (!graphics.texts) graphics.texts = [];
2290
+ const originalLabelsById = /* @__PURE__ */ new Map();
2291
+ for (const label of this.input.netLabelPlacements) {
2292
+ originalLabelsById.set(label.globalConnNetId, label);
2293
+ }
2294
+ for (const trace of this.traces) {
2295
+ const line = {
2296
+ points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
2297
+ strokeColor: "blue"
2298
+ };
2299
+ graphics.lines.push(line);
2300
+ }
2301
+ for (const finalLabel of this.output.netLabelPlacements) {
2302
+ const isMerged = finalLabel.globalConnNetId.startsWith("merged-group-");
2303
+ const color = getColorFromString(finalLabel.globalConnNetId);
2304
+ if (isMerged) {
2305
+ graphics.rects.push({
2306
+ center: finalLabel.center,
2307
+ width: finalLabel.width,
2308
+ height: finalLabel.height,
2309
+ fill: color.replace(/, 1\)/, ", 0.2)"),
2310
+ // semi-transparent
2311
+ stroke: color,
2312
+ label: finalLabel.globalConnNetId
2313
+ });
2314
+ const originalNetIds = this.output.mergedLabelNetIdMap[finalLabel.globalConnNetId];
2315
+ if (originalNetIds) {
2316
+ for (const originalNetId of originalNetIds) {
2317
+ const originalLabel = originalLabelsById.get(originalNetId);
2318
+ if (originalLabel) {
2319
+ const bounds = getRectBounds(
2320
+ originalLabel.center,
2321
+ originalLabel.width,
2322
+ originalLabel.height
2323
+ );
2324
+ const p1 = { x: bounds.minX, y: bounds.minY };
2325
+ const p2 = { x: bounds.maxX, y: bounds.minY };
2326
+ const p3 = { x: bounds.maxX, y: bounds.maxY };
2327
+ const p4 = { x: bounds.minX, y: bounds.maxY };
2328
+ graphics.lines.push({
2329
+ points: [p1, p2, p3, p4, p1],
2330
+ strokeColor: color,
2331
+ strokeDash: "4 4"
2332
+ });
2333
+ graphics.lines.push({
2334
+ points: [originalLabel.center, finalLabel.center],
2335
+ strokeColor: color,
2336
+ strokeDash: "2 2"
2337
+ });
2338
+ }
2214
2339
  }
2215
- overlaps.push({ trace, label });
2216
- break;
2217
2340
  }
2341
+ } else {
2342
+ graphics.rects.push({
2343
+ center: finalLabel.center,
2344
+ width: finalLabel.width,
2345
+ height: finalLabel.height,
2346
+ stroke: color,
2347
+ label: finalLabel.globalConnNetId
2348
+ });
2218
2349
  }
2219
2350
  }
2351
+ return graphics;
2220
2352
  }
2221
- return overlaps;
2222
2353
  };
2223
2354
 
2224
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts
2225
- var findTraceViolationZone = (path, labelBounds) => {
2226
- const isPointInside = (p) => p.x > labelBounds.minX && p.x < labelBounds.maxX && p.y > labelBounds.minY && p.y < labelBounds.maxY;
2227
- let firstInsideIndex = -1;
2228
- let lastInsideIndex = -1;
2229
- for (let i = 0; i < path.length; i++) {
2230
- if (isPointInside(path[i])) {
2231
- if (firstInsideIndex === -1) {
2232
- firstInsideIndex = i;
2355
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/hasCollisions.ts
2356
+ var hasCollisions = (pathSegments, obstacles) => {
2357
+ for (let i = 0; i < pathSegments.length - 1; i++) {
2358
+ const p1 = pathSegments[i];
2359
+ const p2 = pathSegments[i + 1];
2360
+ for (const obstacle of obstacles) {
2361
+ if (segmentIntersectsRect(p1, p2, obstacle)) {
2362
+ return true;
2233
2363
  }
2234
- lastInsideIndex = i;
2235
2364
  }
2236
2365
  }
2237
- return { firstInsideIndex, lastInsideIndex };
2366
+ return false;
2238
2367
  };
2239
2368
 
2240
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/simplifyPath.ts
2369
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/countTurns.ts
2370
+ var countTurns = (points) => {
2371
+ let turns = 0;
2372
+ for (let i = 1; i < points.length - 1; i++) {
2373
+ const prev = points[i - 1];
2374
+ const curr = points[i];
2375
+ const next = points[i + 1];
2376
+ const prevVertical = prev.x === curr.x;
2377
+ const nextVertical = curr.x === next.x;
2378
+ if (prevVertical !== nextVertical) {
2379
+ turns++;
2380
+ }
2381
+ }
2382
+ return turns;
2383
+ };
2384
+
2385
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/simplifyPath.ts
2241
2386
  var simplifyPath = (path) => {
2242
2387
  if (path.length < 3) return path;
2243
2388
  const newPath = [path[0]];
@@ -2266,282 +2411,40 @@ var simplifyPath = (path) => {
2266
2411
  return finalPath;
2267
2412
  };
2268
2413
 
2269
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts
2270
- var trySnipAndReconnect = ({
2271
- initialTrace,
2272
- firstInsideIndex,
2273
- lastInsideIndex,
2274
- labelBounds,
2275
- obstacles
2276
- }) => {
2277
- if (firstInsideIndex <= 0 || lastInsideIndex >= initialTrace.tracePath.length - 1) {
2278
- return null;
2279
- }
2280
- const entryPoint = initialTrace.tracePath[firstInsideIndex - 1];
2281
- const exitPoint = initialTrace.tracePath[lastInsideIndex + 1];
2282
- const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex);
2283
- const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1);
2284
- const candidateDetours = [];
2285
- if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
2286
- candidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }]);
2287
- candidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }]);
2288
- } else if (entryPoint.x === exitPoint.x || entryPoint.y === exitPoint.y) {
2289
- const newPath = [...pathToEntry, ...pathFromExit];
2290
- const simplified = simplifyPath(newPath);
2291
- if (!isPathCollidingWithObstacles(simplified, obstacles)) {
2292
- return { ...initialTrace, tracePath: simplified };
2293
- }
2294
- }
2295
- for (let i = 0; i < candidateDetours.length; i++) {
2296
- const detour = candidateDetours[i];
2297
- const newFullPath = [...pathToEntry, ...detour, ...pathFromExit];
2298
- const simplified = simplifyPath(newFullPath);
2299
- if (!isPathCollidingWithObstacles(simplified, obstacles)) {
2300
- return { ...initialTrace, tracePath: simplified };
2301
- }
2302
- }
2303
- candidateDetours.length = 0;
2304
- const buffer = 0.1;
2305
- const leftX = labelBounds.minX - buffer;
2306
- const rightX = labelBounds.maxX + buffer;
2307
- const topY = labelBounds.maxY + buffer;
2308
- const bottomY = labelBounds.minY - buffer;
2309
- if ((entryPoint.x <= labelBounds.minX || exitPoint.x <= labelBounds.minX) && entryPoint.x < labelBounds.maxX && exitPoint.x < labelBounds.maxX) {
2310
- candidateDetours.push([
2311
- { x: leftX, y: entryPoint.y },
2312
- { x: leftX, y: exitPoint.y }
2313
- ]);
2314
- }
2315
- if ((entryPoint.x >= labelBounds.maxX || exitPoint.x >= labelBounds.maxX) && entryPoint.x > labelBounds.minX && exitPoint.x > labelBounds.minX) {
2316
- candidateDetours.push([
2317
- { x: rightX, y: entryPoint.y },
2318
- { x: rightX, y: exitPoint.y }
2319
- ]);
2320
- }
2321
- if ((entryPoint.y >= labelBounds.maxY || exitPoint.y >= labelBounds.maxY) && entryPoint.y > labelBounds.minY && exitPoint.y > labelBounds.minY) {
2322
- candidateDetours.push([
2323
- { x: entryPoint.x, y: topY },
2324
- { x: exitPoint.x, y: topY }
2325
- ]);
2326
- }
2327
- if ((entryPoint.y <= labelBounds.minY || exitPoint.y <= labelBounds.minY) && entryPoint.y < labelBounds.maxY && exitPoint.y < labelBounds.maxY) {
2328
- candidateDetours.push([
2329
- { x: entryPoint.x, y: bottomY },
2330
- { x: exitPoint.x, y: bottomY }
2331
- ]);
2414
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/tryConnectPoints.ts
2415
+ var tryConnectPoints = (start, end) => {
2416
+ const candidates = [];
2417
+ if (start.x === end.x || start.y === end.y) {
2418
+ candidates.push([start, end]);
2419
+ } else {
2420
+ candidates.push([start, { x: end.x, y: start.y }, end]);
2421
+ candidates.push([start, { x: start.x, y: end.y }, end]);
2332
2422
  }
2333
- for (let i = 0; i < candidateDetours.length; i++) {
2334
- const detour = candidateDetours[i];
2335
- const newFullPath = [...pathToEntry, ...detour, ...pathFromExit];
2336
- const simplified = simplifyPath(newFullPath);
2337
- if (!isPathCollidingWithObstacles(simplified, obstacles)) {
2338
- return { ...initialTrace, tracePath: simplified };
2423
+ return candidates;
2424
+ };
2425
+
2426
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts
2427
+ var hasCollisionsWithLabels = (pathSegments, labels) => {
2428
+ for (let i = 0; i < pathSegments.length - 1; i++) {
2429
+ const p1 = pathSegments[i];
2430
+ const p2 = pathSegments[i + 1];
2431
+ for (const label of labels) {
2432
+ if (segmentIntersectsRect(p1, p2, label)) {
2433
+ return true;
2434
+ }
2339
2435
  }
2340
2436
  }
2341
- return null;
2437
+ return false;
2342
2438
  };
2343
- var tryFourPointDetour = ({
2344
- initialTrace,
2345
- label,
2346
- labelBounds,
2439
+
2440
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/turnMinimization.ts
2441
+ var minimizeTurns = ({
2442
+ path,
2347
2443
  obstacles,
2348
- paddingBuffer,
2349
- detourCount
2444
+ labelBounds
2350
2445
  }) => {
2351
- let collidingSegIndex = -1;
2352
- for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
2353
- if (segmentIntersectsRect(
2354
- initialTrace.tracePath[i],
2355
- initialTrace.tracePath[i + 1],
2356
- labelBounds
2357
- )) {
2358
- collidingSegIndex = i;
2359
- break;
2360
- }
2361
- }
2362
- if (collidingSegIndex === -1) return initialTrace;
2363
- const pA = initialTrace.tracePath[collidingSegIndex];
2364
- const pB = initialTrace.tracePath[collidingSegIndex + 1];
2365
- if (!pA || !pB) return null;
2366
- const candidateDetours = [];
2367
- const paddedLabelBounds = getRectBounds(
2368
- label.center,
2369
- label.width,
2370
- label.height
2371
- );
2372
- const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
2373
- if (isVertical(pA, pB)) {
2374
- const xCandidates = [
2375
- paddedLabelBounds.maxX + effectivePadding,
2376
- paddedLabelBounds.minX - effectivePadding
2377
- ];
2378
- for (const newX of xCandidates) {
2379
- candidateDetours.push(
2380
- pB.y > pA.y ? [
2381
- { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
2382
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2383
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2384
- { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding }
2385
- ] : [
2386
- { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
2387
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
2388
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
2389
- { x: pB.x, y: paddedLabelBounds.minY - effectivePadding }
2390
- ]
2391
- );
2392
- }
2393
- } else {
2394
- const yCandidates = [
2395
- paddedLabelBounds.maxY + effectivePadding,
2396
- paddedLabelBounds.minY - effectivePadding
2397
- ];
2398
- for (const newY of yCandidates) {
2399
- candidateDetours.push(
2400
- pB.x > pA.x ? [
2401
- { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
2402
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2403
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2404
- { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y }
2405
- ] : [
2406
- { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
2407
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
2408
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
2409
- { x: paddedLabelBounds.minX - effectivePadding, y: pB.y }
2410
- ]
2411
- );
2412
- }
2413
- }
2414
- for (const detourPoints of candidateDetours) {
2415
- const finalPath = [
2416
- ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
2417
- ...detourPoints,
2418
- ...initialTrace.tracePath.slice(collidingSegIndex + 1)
2419
- ];
2420
- const simplifiedFinalPath = simplifyPath(finalPath);
2421
- if (!isPathCollidingWithObstacles(simplifiedFinalPath, obstacles)) {
2422
- return { ...initialTrace, tracePath: simplifiedFinalPath };
2423
- }
2424
- }
2425
- return null;
2426
- };
2427
-
2428
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts
2429
- var rerouteCollidingTrace = ({
2430
- trace,
2431
- label,
2432
- problem,
2433
- paddingBuffer,
2434
- detourCount
2435
- }) => {
2436
- const initialTrace = { ...trace, tracePath: simplifyPath(trace.tracePath) };
2437
- if (trace.globalConnNetId === label.globalConnNetId) {
2438
- return initialTrace;
2439
- }
2440
- const obstacles = getObstacleRects(problem);
2441
- const labelPadding = paddingBuffer;
2442
- const labelBoundsRaw = getRectBounds(label.center, label.width, label.height);
2443
- const labelBounds = {
2444
- minX: labelBoundsRaw.minX - labelPadding,
2445
- minY: labelBoundsRaw.minY - labelPadding,
2446
- maxX: labelBoundsRaw.maxX + labelPadding,
2447
- maxY: labelBoundsRaw.maxY + labelPadding,
2448
- chipId: `netlabel-${label.netId}`
2449
- };
2450
- const fourPointResult = tryFourPointDetour({
2451
- initialTrace,
2452
- label,
2453
- labelBounds,
2454
- obstacles,
2455
- paddingBuffer,
2456
- detourCount
2457
- });
2458
- if (fourPointResult) {
2459
- initialTrace.tracePath = fourPointResult.tracePath;
2460
- }
2461
- const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
2462
- initialTrace.tracePath,
2463
- labelBounds
2464
- );
2465
- const snipReconnectResult = trySnipAndReconnect({
2466
- initialTrace,
2467
- firstInsideIndex,
2468
- lastInsideIndex,
2469
- labelBounds,
2470
- obstacles
2471
- });
2472
- if (snipReconnectResult) {
2473
- return snipReconnectResult;
2474
- }
2475
- if (fourPointResult) {
2476
- return fourPointResult;
2477
- }
2478
- return initialTrace;
2479
- };
2480
-
2481
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/hasCollisions.ts
2482
- var hasCollisions = (pathSegments, obstacles) => {
2483
- for (let i = 0; i < pathSegments.length - 1; i++) {
2484
- const p1 = pathSegments[i];
2485
- const p2 = pathSegments[i + 1];
2486
- for (const obstacle of obstacles) {
2487
- if (segmentIntersectsRect(p1, p2, obstacle)) {
2488
- return true;
2489
- }
2490
- }
2491
- }
2492
- return false;
2493
- };
2494
-
2495
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/countTurns.ts
2496
- var countTurns = (points) => {
2497
- let turns = 0;
2498
- for (let i = 1; i < points.length - 1; i++) {
2499
- const prev = points[i - 1];
2500
- const curr = points[i];
2501
- const next = points[i + 1];
2502
- const prevVertical = prev.x === curr.x;
2503
- const nextVertical = curr.x === next.x;
2504
- if (prevVertical !== nextVertical) {
2505
- turns++;
2506
- }
2507
- }
2508
- return turns;
2509
- };
2510
-
2511
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/tryConnectPoints.ts
2512
- var tryConnectPoints = (start, end) => {
2513
- const candidates = [];
2514
- if (start.x === end.x || start.y === end.y) {
2515
- candidates.push([start, end]);
2516
- } else {
2517
- candidates.push([start, { x: end.x, y: start.y }, end]);
2518
- candidates.push([start, { x: start.x, y: end.y }, end]);
2519
- }
2520
- return candidates;
2521
- };
2522
-
2523
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/hasCollisionsWithLabels.ts
2524
- var hasCollisionsWithLabels = (pathSegments, labels) => {
2525
- for (let i = 0; i < pathSegments.length - 1; i++) {
2526
- const p1 = pathSegments[i];
2527
- const p2 = pathSegments[i + 1];
2528
- for (const label of labels) {
2529
- if (segmentIntersectsRect(p1, p2, label)) {
2530
- return true;
2531
- }
2532
- }
2533
- }
2534
- return false;
2535
- };
2536
-
2537
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/turnMinimization.ts
2538
- var minimizeTurns = ({
2539
- path,
2540
- obstacles,
2541
- labelBounds
2542
- }) => {
2543
- if (path.length <= 2) {
2544
- return path;
2446
+ if (path.length <= 2) {
2447
+ return path;
2545
2448
  }
2546
2449
  const recognizeStairStepPattern = (pathToCheck, startIdx) => {
2547
2450
  if (startIdx >= pathToCheck.length - 3) return -1;
@@ -2595,7 +2498,6 @@ var minimizeTurns = ({
2595
2498
  );
2596
2499
  if (!collidesWithObstacles && !collidesWithLabels) {
2597
2500
  const newTurns = countTurns(testPath);
2598
- const turnsRemoved = stairEndIdx - startIdx - 1;
2599
2501
  optimizedPath = testPath;
2600
2502
  currentTurns = newTurns;
2601
2503
  improved = true;
@@ -2677,16 +2579,16 @@ var minimizeTurns = ({
2677
2579
  return finalSimplifiedPath;
2678
2580
  };
2679
2581
 
2680
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/minimizeTurnsWithFilteredLabels.ts
2582
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts
2681
2583
  var minimizeTurnsWithFilteredLabels = ({
2682
2584
  traces,
2683
- problem,
2585
+ inputProblem,
2684
2586
  allLabelPlacements,
2685
2587
  mergedLabelNetIdMap,
2686
2588
  paddingBuffer
2687
2589
  }) => {
2688
2590
  let changesMade = false;
2689
- const obstacles = getObstacleRects(problem);
2591
+ const obstacles = getObstacleRects(inputProblem);
2690
2592
  const newTraces = traces.map((trace) => {
2691
2593
  const originalPath = trace.tracePath;
2692
2594
  const filteredLabels = allLabelPlacements.filter((label) => {
@@ -2724,10 +2626,10 @@ var minimizeTurnsWithFilteredLabels = ({
2724
2626
  }
2725
2627
  };
2726
2628
 
2727
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts
2629
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/balanceLShapes.ts
2728
2630
  var balanceLShapes = ({
2729
2631
  traces,
2730
- problem,
2632
+ inputProblem,
2731
2633
  allLabelPlacements
2732
2634
  }) => {
2733
2635
  const TOLERANCE = 1e-5;
@@ -2752,7 +2654,7 @@ var balanceLShapes = ({
2752
2654
  }
2753
2655
  }
2754
2656
  }
2755
- const obstacles = getObstacleRects(problem).map((obs) => ({
2657
+ const obstacles = getObstacleRects(inputProblem).map((obs) => ({
2756
2658
  ...obs,
2757
2659
  minX: obs.minX + TOLERANCE,
2758
2660
  maxX: obs.maxX - TOLERANCE,
@@ -2779,7 +2681,7 @@ var balanceLShapes = ({
2779
2681
  }));
2780
2682
  };
2781
2683
  const newTraces = traces.map((trace) => {
2782
- let newPath = [...trace.tracePath];
2684
+ const newPath = [...trace.tracePath];
2783
2685
  if (newPath.length < 4) {
2784
2686
  return { ...trace };
2785
2687
  }
@@ -2789,7 +2691,8 @@ var balanceLShapes = ({
2789
2691
  );
2790
2692
  if (newPath.length === 4) {
2791
2693
  const [p0, p1, p2, p3] = newPath;
2792
- let p1New, p2New;
2694
+ let p1New;
2695
+ let p2New;
2793
2696
  const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
2794
2697
  if (isHVHShape) {
2795
2698
  const idealX = (p0.x + p3.x) / 2;
@@ -2828,7 +2731,8 @@ var balanceLShapes = ({
2828
2731
  if (!isValidZShape) {
2829
2732
  continue;
2830
2733
  }
2831
- let p2New, p3New;
2734
+ let p2New;
2735
+ let p3New;
2832
2736
  const len1Original = isHVHZShape ? Math.abs(p1.x - p2.x) : Math.abs(p1.y - p2.y);
2833
2737
  const len2Original = isHVHZShape ? Math.abs(p3.x - p4.x) : Math.abs(p3.y - p4.y);
2834
2738
  if (Math.abs(len1Original - len2Original) < 1e-3) {
@@ -2851,198 +2755,610 @@ var balanceLShapes = ({
2851
2755
  i = 0;
2852
2756
  }
2853
2757
  }
2854
- const finalSimplifiedPath = simplifyPath(newPath);
2855
- return {
2856
- ...trace,
2857
- tracePath: finalSimplifiedPath
2858
- };
2859
- });
2860
- if (changesMade) {
2861
- return newTraces;
2862
- } else {
2863
- return null;
2758
+ const finalSimplifiedPath = simplifyPath(newPath);
2759
+ return {
2760
+ ...trace,
2761
+ tracePath: finalSimplifiedPath
2762
+ };
2763
+ });
2764
+ if (changesMade) {
2765
+ return newTraces;
2766
+ } else {
2767
+ return null;
2768
+ }
2769
+ };
2770
+
2771
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts
2772
+ var TraceCleanupSolver = class extends BaseSolver {
2773
+ input;
2774
+ outputTraces;
2775
+ pipelineStepIndex = 0;
2776
+ tracesToProcess;
2777
+ minimizedTraces = null;
2778
+ balancedTraces = null;
2779
+ constructor(solverInput) {
2780
+ super();
2781
+ this.input = solverInput;
2782
+ this.outputTraces = [...solverInput.allTraces];
2783
+ this.tracesToProcess = this.outputTraces.filter(
2784
+ (t) => this.input.targetTraceIds.has(t.mspPairId)
2785
+ );
2786
+ }
2787
+ _step() {
2788
+ const {
2789
+ targetTraceIds,
2790
+ inputProblem,
2791
+ allLabelPlacements,
2792
+ mergedLabelNetIdMap,
2793
+ paddingBuffer
2794
+ } = this.input;
2795
+ if (targetTraceIds.size === 0) {
2796
+ this.solved = true;
2797
+ return;
2798
+ }
2799
+ switch (this.pipelineStepIndex) {
2800
+ case 0: {
2801
+ const minimizedTracesResult = minimizeTurnsWithFilteredLabels({
2802
+ traces: this.tracesToProcess,
2803
+ inputProblem,
2804
+ allLabelPlacements,
2805
+ mergedLabelNetIdMap,
2806
+ paddingBuffer
2807
+ });
2808
+ this.minimizedTraces = minimizedTracesResult ?? this.tracesToProcess;
2809
+ const tracesMap = new Map(
2810
+ this.input.allTraces.map((t) => [t.mspPairId, t])
2811
+ );
2812
+ for (const trace of this.minimizedTraces) {
2813
+ tracesMap.set(trace.mspPairId, trace);
2814
+ }
2815
+ this.outputTraces = Array.from(tracesMap.values());
2816
+ this.pipelineStepIndex++;
2817
+ break;
2818
+ }
2819
+ case 1: {
2820
+ const balancedTracesResult = balanceLShapes({
2821
+ traces: this.minimizedTraces,
2822
+ inputProblem,
2823
+ allLabelPlacements
2824
+ });
2825
+ this.balancedTraces = balancedTracesResult ?? this.minimizedTraces;
2826
+ const tracesMap = new Map(
2827
+ this.input.allTraces.map((t) => [t.mspPairId, t])
2828
+ );
2829
+ for (const trace of this.balancedTraces) {
2830
+ tracesMap.set(trace.mspPairId, trace);
2831
+ }
2832
+ this.outputTraces = Array.from(tracesMap.values());
2833
+ this.pipelineStepIndex++;
2834
+ break;
2835
+ }
2836
+ case 2: {
2837
+ this.solved = true;
2838
+ break;
2839
+ }
2840
+ }
2841
+ }
2842
+ getOutput() {
2843
+ return {
2844
+ traces: this.outputTraces
2845
+ };
2846
+ }
2847
+ visualize() {
2848
+ const graphics = visualizeInputProblem(this.input.inputProblem, {
2849
+ chipAlpha: 0.1,
2850
+ connectionAlpha: 0.1
2851
+ });
2852
+ if (!graphics.lines) graphics.lines = [];
2853
+ if (!graphics.points) graphics.points = [];
2854
+ if (!graphics.rects) graphics.rects = [];
2855
+ if (!graphics.circles) graphics.circles = [];
2856
+ if (!graphics.texts) graphics.texts = [];
2857
+ for (const trace of this.outputTraces) {
2858
+ const line = {
2859
+ points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
2860
+ strokeColor: "blue"
2861
+ };
2862
+ graphics.lines.push(line);
2863
+ }
2864
+ return graphics;
2865
+ }
2866
+ };
2867
+
2868
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts
2869
+ var detectTraceLabelOverlap = (traces, netLabels) => {
2870
+ const overlaps = [];
2871
+ for (const trace of traces) {
2872
+ for (const label of netLabels) {
2873
+ const labelBounds = getRectBounds(label.center, label.width, label.height);
2874
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
2875
+ const p1 = trace.tracePath[i];
2876
+ const p2 = trace.tracePath[i + 1];
2877
+ if (segmentIntersectsRect2(p1, p2, labelBounds)) {
2878
+ if (trace.globalConnNetId === label.globalConnNetId) {
2879
+ break;
2880
+ }
2881
+ overlaps.push({ trace, label });
2882
+ break;
2883
+ }
2884
+ }
2885
+ }
2886
+ }
2887
+ return overlaps;
2888
+ };
2889
+
2890
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts
2891
+ var findTraceViolationZone = (path, labelBounds) => {
2892
+ const isPointInside = (p) => p.x > labelBounds.minX && p.x < labelBounds.maxX && p.y > labelBounds.minY && p.y < labelBounds.maxY;
2893
+ let firstInsideIndex = -1;
2894
+ let lastInsideIndex = -1;
2895
+ for (let i = 0; i < path.length; i++) {
2896
+ if (isPointInside(path[i])) {
2897
+ if (firstInsideIndex === -1) {
2898
+ firstInsideIndex = i;
2899
+ }
2900
+ lastInsideIndex = i;
2901
+ }
2902
+ }
2903
+ return { firstInsideIndex, lastInsideIndex };
2904
+ };
2905
+
2906
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts
2907
+ var generateSnipAndReconnectCandidates = ({
2908
+ initialTrace,
2909
+ firstInsideIndex,
2910
+ lastInsideIndex,
2911
+ labelBounds,
2912
+ paddingBuffer,
2913
+ detourCount
2914
+ }) => {
2915
+ if (firstInsideIndex <= 0 || lastInsideIndex >= initialTrace.tracePath.length - 1) {
2916
+ return [];
2917
+ }
2918
+ const entryPoint = initialTrace.tracePath[firstInsideIndex - 1];
2919
+ const exitPoint = initialTrace.tracePath[lastInsideIndex + 1];
2920
+ const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex);
2921
+ const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1);
2922
+ const allCandidateDetours = [];
2923
+ if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
2924
+ allCandidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }]);
2925
+ allCandidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }]);
2926
+ } else if (entryPoint.x === exitPoint.x || entryPoint.y === exitPoint.y) {
2927
+ allCandidateDetours.push([]);
2928
+ }
2929
+ const buffer = paddingBuffer + detourCount * paddingBuffer;
2930
+ const leftX = labelBounds.minX - buffer;
2931
+ const rightX = labelBounds.maxX + buffer;
2932
+ const topY = labelBounds.maxY + buffer;
2933
+ const bottomY = labelBounds.minY - buffer;
2934
+ if ((entryPoint.x <= labelBounds.minX || exitPoint.x <= labelBounds.minX) && entryPoint.x < labelBounds.maxX && exitPoint.x < labelBounds.maxX) {
2935
+ allCandidateDetours.push([
2936
+ { x: leftX, y: entryPoint.y },
2937
+ { x: leftX, y: exitPoint.y }
2938
+ ]);
2939
+ }
2940
+ if ((entryPoint.x >= labelBounds.maxX || exitPoint.x >= labelBounds.maxX) && entryPoint.x > labelBounds.minX && exitPoint.x > labelBounds.minX) {
2941
+ allCandidateDetours.push([
2942
+ { x: rightX, y: entryPoint.y },
2943
+ { x: rightX, y: exitPoint.y }
2944
+ ]);
2945
+ }
2946
+ if ((entryPoint.y >= labelBounds.maxY || exitPoint.y >= labelBounds.maxY) && entryPoint.y > labelBounds.minY && exitPoint.y > labelBounds.minY) {
2947
+ allCandidateDetours.push([
2948
+ { x: entryPoint.x, y: topY },
2949
+ { x: exitPoint.x, y: topY }
2950
+ ]);
2951
+ }
2952
+ if ((entryPoint.y <= labelBounds.minY || exitPoint.y <= labelBounds.minY) && entryPoint.y < labelBounds.maxY && exitPoint.y < labelBounds.maxY) {
2953
+ allCandidateDetours.push([
2954
+ { x: entryPoint.x, y: bottomY },
2955
+ { x: exitPoint.x, y: bottomY }
2956
+ ]);
2957
+ }
2958
+ return allCandidateDetours.map((detour) => [
2959
+ ...pathToEntry,
2960
+ ...detour,
2961
+ ...pathFromExit
2962
+ ]);
2963
+ };
2964
+
2965
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts
2966
+ var generateFourPointDetourCandidates = ({
2967
+ initialTrace,
2968
+ label,
2969
+ labelBounds,
2970
+ paddingBuffer,
2971
+ detourCount
2972
+ }) => {
2973
+ let collidingSegIndex = -1;
2974
+ for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
2975
+ if (segmentIntersectsRect(
2976
+ initialTrace.tracePath[i],
2977
+ initialTrace.tracePath[i + 1],
2978
+ labelBounds
2979
+ )) {
2980
+ collidingSegIndex = i;
2981
+ break;
2982
+ }
2983
+ }
2984
+ if (collidingSegIndex === -1) return [];
2985
+ const pA = initialTrace.tracePath[collidingSegIndex];
2986
+ const pB = initialTrace.tracePath[collidingSegIndex + 1];
2987
+ if (!pA || !pB) return [];
2988
+ const candidateDetours = [];
2989
+ const paddedLabelBounds = getRectBounds(
2990
+ label.center,
2991
+ label.width,
2992
+ label.height
2993
+ );
2994
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
2995
+ if (isVertical(pA, pB)) {
2996
+ const xCandidates = [
2997
+ paddedLabelBounds.maxX + effectivePadding,
2998
+ paddedLabelBounds.minX - effectivePadding
2999
+ ];
3000
+ for (const newX of xCandidates) {
3001
+ candidateDetours.push(
3002
+ pB.y > pA.y ? [
3003
+ { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
3004
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
3005
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
3006
+ { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding }
3007
+ ] : [
3008
+ { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
3009
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
3010
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
3011
+ { x: pB.x, y: paddedLabelBounds.minY - effectivePadding }
3012
+ ]
3013
+ );
3014
+ }
3015
+ } else {
3016
+ const yCandidates = [
3017
+ paddedLabelBounds.maxY + effectivePadding,
3018
+ paddedLabelBounds.minY - effectivePadding
3019
+ ];
3020
+ for (const newY of yCandidates) {
3021
+ candidateDetours.push(
3022
+ pB.x > pA.x ? [
3023
+ { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
3024
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
3025
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
3026
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y }
3027
+ ] : [
3028
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
3029
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
3030
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
3031
+ { x: paddedLabelBounds.minX - effectivePadding, y: pB.y }
3032
+ ]
3033
+ );
3034
+ }
3035
+ }
3036
+ return candidateDetours.map((detourPoints) => [
3037
+ ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
3038
+ ...detourPoints,
3039
+ ...initialTrace.tracePath.slice(collidingSegIndex + 1)
3040
+ ]);
3041
+ };
3042
+
3043
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts
3044
+ var generateRerouteCandidates = ({
3045
+ trace,
3046
+ label,
3047
+ paddingBuffer,
3048
+ detourCount
3049
+ }) => {
3050
+ const initialTrace = { ...trace, tracePath: simplifyPath(trace.tracePath) };
3051
+ if (trace.globalConnNetId === label.globalConnNetId) {
3052
+ return [initialTrace.tracePath];
3053
+ }
3054
+ const labelPadding = paddingBuffer;
3055
+ const labelBoundsRaw = getRectBounds(label.center, label.width, label.height);
3056
+ const labelBounds = {
3057
+ minX: labelBoundsRaw.minX - labelPadding,
3058
+ minY: labelBoundsRaw.minY - labelPadding,
3059
+ maxX: labelBoundsRaw.maxX + labelPadding,
3060
+ maxY: labelBoundsRaw.maxY + labelPadding,
3061
+ chipId: `netlabel-${label.netId}`
3062
+ };
3063
+ const fourPointCandidates = generateFourPointDetourCandidates({
3064
+ initialTrace,
3065
+ label,
3066
+ labelBounds,
3067
+ paddingBuffer,
3068
+ detourCount
3069
+ });
3070
+ const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
3071
+ initialTrace.tracePath,
3072
+ labelBounds
3073
+ );
3074
+ const snipReconnectCandidates = generateSnipAndReconnectCandidates({
3075
+ initialTrace,
3076
+ firstInsideIndex,
3077
+ lastInsideIndex,
3078
+ labelBounds,
3079
+ paddingBuffer,
3080
+ detourCount
3081
+ });
3082
+ return [...fourPointCandidates, ...snipReconnectCandidates];
3083
+ };
3084
+
3085
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
3086
+ var SingleOverlapSolver = class extends BaseSolver {
3087
+ queuedCandidatePaths;
3088
+ solvedTracePath = null;
3089
+ initialTrace;
3090
+ problem;
3091
+ obstacles;
3092
+ label;
3093
+ constructor(solverInput) {
3094
+ super();
3095
+ this.initialTrace = solverInput.trace;
3096
+ this.problem = solverInput.problem;
3097
+ this.label = solverInput.label;
3098
+ const candidates = generateRerouteCandidates({
3099
+ ...solverInput
3100
+ });
3101
+ const getPathLength = (pts) => {
3102
+ let len = 0;
3103
+ for (let i = 0; i < pts.length - 1; i++) {
3104
+ const dx = pts[i + 1].x - pts[i].x;
3105
+ const dy = pts[i + 1].y - pts[i].y;
3106
+ len += Math.sqrt(dx * dx + dy * dy);
3107
+ }
3108
+ return len;
3109
+ };
3110
+ this.queuedCandidatePaths = candidates.sort(
3111
+ (a, b) => getPathLength(a) - getPathLength(b)
3112
+ );
3113
+ this.obstacles = getObstacleRects(this.problem);
3114
+ }
3115
+ _step() {
3116
+ if (this.queuedCandidatePaths.length === 0) {
3117
+ this.failed = true;
3118
+ return;
3119
+ }
3120
+ const nextCandidatePath = this.queuedCandidatePaths.shift();
3121
+ const simplifiedPath = simplifyPath(nextCandidatePath);
3122
+ if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles)) {
3123
+ this.solvedTracePath = simplifiedPath;
3124
+ this.solved = true;
3125
+ }
3126
+ }
3127
+ visualize() {
3128
+ const graphics = visualizeInputProblem(this.problem, {
3129
+ chipAlpha: 0.1,
3130
+ connectionAlpha: 0.1
3131
+ });
3132
+ if (!graphics.lines) graphics.lines = [];
3133
+ if (!graphics.rects) graphics.rects = [];
3134
+ graphics.lines.push({
3135
+ points: this.initialTrace.tracePath,
3136
+ strokeColor: "red",
3137
+ strokeDash: "4 4"
3138
+ });
3139
+ graphics.rects.push({
3140
+ center: this.label.center,
3141
+ width: this.label.width,
3142
+ height: this.label.height,
3143
+ fill: "rgba(255, 0, 0, 0.2)"
3144
+ });
3145
+ if (this.queuedCandidatePaths.length > 0) {
3146
+ graphics.lines.push({
3147
+ points: this.queuedCandidatePaths[0],
3148
+ strokeColor: "orange"
3149
+ });
3150
+ }
3151
+ if (this.solvedTracePath) {
3152
+ graphics.lines.push({
3153
+ points: this.solvedTracePath,
3154
+ strokeColor: "green"
3155
+ });
3156
+ }
3157
+ return graphics;
2864
3158
  }
2865
3159
  };
2866
3160
 
2867
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts
2868
- var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2869
- problem;
2870
- traces;
2871
- netTempLabelPlacements;
3161
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts
3162
+ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3163
+ inputProblem;
2872
3164
  netLabelPlacements;
2873
- updatedTraces;
2874
3165
  mergedLabelNetIdMap;
2875
- detourCountByLabel;
3166
+ allTraces;
3167
+ modifiedTraces = [];
3168
+ detourCountByLabel = {};
2876
3169
  PADDING_BUFFER = 0.1;
3170
+ activeSubSolver = null;
3171
+ overlapQueue = [];
3172
+ recentlyFailed = /* @__PURE__ */ new Set();
2877
3173
  constructor(solverInput) {
2878
3174
  super();
2879
- this.problem = solverInput.inputProblem;
2880
- this.traces = solverInput.traces;
2881
- this.updatedTraces = [...solverInput.traces];
2882
- this.mergedLabelNetIdMap = {};
2883
- this.detourCountByLabel = {};
2884
- const originalLabels = solverInput.netLabelPlacements;
2885
- this.netLabelPlacements = originalLabels;
2886
- if (!originalLabels || originalLabels.length === 0) {
2887
- this.netTempLabelPlacements = [];
2888
- return;
2889
- }
2890
- const labelGroups = {};
2891
- for (const p of originalLabels) {
2892
- if (p.pinIds.length === 0) continue;
2893
- const chipId = p.pinIds[0].split(".")[0];
2894
- if (!chipId) continue;
2895
- const key = `${chipId}-${p.orientation}`;
2896
- if (!(key in labelGroups)) {
2897
- labelGroups[key] = [];
2898
- }
2899
- labelGroups[key].push(p);
2900
- }
2901
- const finalPlacements = [];
2902
- for (const [key, group] of Object.entries(labelGroups)) {
2903
- if (group.length <= 1) {
2904
- finalPlacements.push(...group);
2905
- continue;
2906
- }
2907
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
2908
- for (const p of group) {
2909
- const bounds = getRectBounds(p.center, p.width, p.height);
2910
- minX = Math.min(minX, bounds.minX);
2911
- minY = Math.min(minY, bounds.minY);
2912
- maxX = Math.max(maxX, bounds.maxX);
2913
- maxY = Math.max(maxY, bounds.maxY);
2914
- }
2915
- const newWidth = maxX - minX;
2916
- const newHeight = maxY - minY;
2917
- const template = group[0];
2918
- const syntheticId = `merged-group-${key}`;
2919
- const originalNetIds = new Set(group.map((p) => p.globalConnNetId));
2920
- this.mergedLabelNetIdMap[syntheticId] = originalNetIds;
2921
- finalPlacements.push({
2922
- ...template,
2923
- globalConnNetId: syntheticId,
2924
- width: newWidth,
2925
- height: newHeight,
2926
- center: { x: minX + newWidth / 2, y: minY + newHeight / 2 },
2927
- pinIds: [...new Set(group.flatMap((p) => p.pinIds))],
2928
- mspConnectionPairIds: [
2929
- ...new Set(group.flatMap((p) => p.mspConnectionPairIds))
2930
- ]
2931
- });
2932
- }
2933
- this.netTempLabelPlacements = finalPlacements;
3175
+ this.inputProblem = solverInput.inputProblem;
3176
+ this.netLabelPlacements = solverInput.netLabelPlacements;
3177
+ this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap;
3178
+ this.allTraces = [...solverInput.traces];
2934
3179
  }
2935
3180
  _step() {
2936
- if (!this.traces || this.traces.length === 0 || !this.netTempLabelPlacements || this.netTempLabelPlacements.length === 0) {
2937
- this.solved = true;
3181
+ if (this.activeSubSolver) {
3182
+ this.activeSubSolver.step();
3183
+ if (this.activeSubSolver.solved) {
3184
+ const solvedPath = this.activeSubSolver.solvedTracePath;
3185
+ if (solvedPath) {
3186
+ const traceIndex = this.allTraces.findIndex(
3187
+ (t) => t.mspPairId === this.activeSubSolver.initialTrace.mspPairId
3188
+ );
3189
+ if (traceIndex !== -1) {
3190
+ this.allTraces[traceIndex].tracePath = solvedPath;
3191
+ this.modifiedTraces.push(this.allTraces[traceIndex]);
3192
+ }
3193
+ }
3194
+ this.activeSubSolver = null;
3195
+ this.recentlyFailed.clear();
3196
+ } else if (this.activeSubSolver.failed) {
3197
+ const overlapId = `${this.activeSubSolver.initialTrace.mspPairId}-${this.activeSubSolver.label.globalConnNetId}`;
3198
+ this.recentlyFailed.add(overlapId);
3199
+ this.activeSubSolver = null;
3200
+ }
2938
3201
  return;
2939
3202
  }
2940
- this.detourCountByLabel = {};
2941
3203
  const overlaps = detectTraceLabelOverlap(
2942
- this.traces,
2943
- this.netTempLabelPlacements
2944
- );
2945
- if (overlaps.length === 0) {
2946
- this.solved = true;
2947
- return;
2948
- }
2949
- const unfriendlyOverlaps = overlaps.filter((o) => {
3204
+ this.allTraces,
3205
+ this.netLabelPlacements
3206
+ ).filter((o) => {
2950
3207
  const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId];
2951
3208
  if (originalNetIds) {
2952
3209
  return !originalNetIds.has(o.trace.globalConnNetId);
2953
3210
  }
2954
3211
  return o.trace.globalConnNetId !== o.label.globalConnNetId;
2955
3212
  });
2956
- if (unfriendlyOverlaps.length === 0) {
3213
+ if (overlaps.length === 0) {
2957
3214
  this.solved = true;
2958
3215
  return;
2959
3216
  }
2960
- const updatedTracesMap = {};
2961
- for (const trace of this.traces) {
2962
- updatedTracesMap[trace.mspPairId] = trace;
3217
+ const nonFailedOverlaps = overlaps.filter((o) => {
3218
+ const overlapId = `${o.trace.mspPairId}-${o.label.globalConnNetId}`;
3219
+ return !this.recentlyFailed.has(overlapId);
3220
+ });
3221
+ if (nonFailedOverlaps.length === 0) {
3222
+ this.solved = true;
3223
+ return;
2963
3224
  }
2964
- const processedTraceIds = /* @__PURE__ */ new Set();
2965
- for (const overlap of unfriendlyOverlaps) {
2966
- if (processedTraceIds.has(overlap.trace.mspPairId)) {
2967
- continue;
2968
- }
2969
- const currentTraceState = updatedTracesMap[overlap.trace.mspPairId];
2970
- const labelId = overlap.label.globalConnNetId;
2971
- const detourCount = this.detourCountByLabel[labelId] || 0;
2972
- const newTrace = rerouteCollidingTrace({
2973
- trace: currentTraceState,
2974
- label: overlap.label,
2975
- problem: this.problem,
2976
- paddingBuffer: this.PADDING_BUFFER,
2977
- detourCount
2978
- });
2979
- if (newTrace.tracePath !== currentTraceState.tracePath) {
3225
+ this.overlapQueue = nonFailedOverlaps;
3226
+ const nextOverlap = this.overlapQueue.shift();
3227
+ if (nextOverlap) {
3228
+ const traceToFix = this.allTraces.find(
3229
+ (t) => t.mspPairId === nextOverlap.trace.mspPairId
3230
+ );
3231
+ if (traceToFix) {
3232
+ const labelId = nextOverlap.label.globalConnNetId;
3233
+ const detourCount = this.detourCountByLabel[labelId] || 0;
2980
3234
  this.detourCountByLabel[labelId] = detourCount + 1;
3235
+ this.activeSubSolver = new SingleOverlapSolver({
3236
+ trace: traceToFix,
3237
+ label: nextOverlap.label,
3238
+ problem: this.inputProblem,
3239
+ paddingBuffer: this.PADDING_BUFFER,
3240
+ detourCount
3241
+ });
2981
3242
  }
2982
- updatedTracesMap[currentTraceState.mspPairId] = newTrace;
2983
- processedTraceIds.add(currentTraceState.mspPairId);
2984
- }
2985
- this.updatedTraces = Object.values(updatedTracesMap);
2986
- const minimizedTraces = minimizeTurnsWithFilteredLabels({
2987
- traces: this.updatedTraces,
2988
- problem: this.problem,
2989
- allLabelPlacements: this.netTempLabelPlacements,
2990
- // Use temp labels which include merged ones
2991
- mergedLabelNetIdMap: this.mergedLabelNetIdMap,
2992
- paddingBuffer: this.PADDING_BUFFER
2993
- });
2994
- if (minimizedTraces) {
2995
- this.updatedTraces = minimizedTraces;
2996
3243
  }
2997
- const balancedTraces = balanceLShapes({
2998
- traces: this.updatedTraces,
2999
- problem: this.problem,
3000
- allLabelPlacements: this.netLabelPlacements
3001
- });
3002
- if (balancedTraces) {
3003
- this.updatedTraces = balancedTraces;
3244
+ }
3245
+ getOutput() {
3246
+ return {
3247
+ allTraces: this.allTraces,
3248
+ modifiedTraces: this.modifiedTraces
3249
+ };
3250
+ }
3251
+ visualize() {
3252
+ if (this.activeSubSolver) {
3253
+ return this.activeSubSolver.visualize();
3004
3254
  }
3005
- const finalLabelPlacementSolver = new NetLabelPlacementSolver({
3006
- inputProblem: this.problem,
3007
- inputTraceMap: Object.fromEntries(
3008
- this.updatedTraces.map((trace) => [trace.mspPairId, trace])
3009
- )
3010
- });
3011
- finalLabelPlacementSolver.solve();
3012
- if (!finalLabelPlacementSolver.failed) {
3013
- this.netLabelPlacements = finalLabelPlacementSolver.netLabelPlacements;
3255
+ const graphics = visualizeInputProblem(this.inputProblem);
3256
+ if (!graphics.lines) graphics.lines = [];
3257
+ for (const trace of this.allTraces) {
3258
+ graphics.lines.push({
3259
+ points: trace.tracePath,
3260
+ strokeColor: "purple"
3261
+ });
3262
+ }
3263
+ return graphics;
3264
+ }
3265
+ };
3266
+
3267
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts
3268
+ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
3269
+ inputProblem;
3270
+ traces;
3271
+ netLabelPlacements;
3272
+ // sub-solver instances
3273
+ labelMergingSolver;
3274
+ overlapAvoidanceSolver;
3275
+ traceCleanupSolver;
3276
+ pipelineStepIndex = 0;
3277
+ constructor(solverInput) {
3278
+ super();
3279
+ this.inputProblem = solverInput.inputProblem;
3280
+ this.traces = solverInput.traces;
3281
+ this.netLabelPlacements = solverInput.netLabelPlacements;
3282
+ }
3283
+ _step() {
3284
+ if (this.activeSubSolver) {
3285
+ this.activeSubSolver.step();
3286
+ if (this.activeSubSolver.solved) {
3287
+ this.activeSubSolver = null;
3288
+ this.pipelineStepIndex++;
3289
+ } else if (this.activeSubSolver.failed) {
3290
+ this.failed = true;
3291
+ this.activeSubSolver = null;
3292
+ }
3293
+ return;
3294
+ }
3295
+ switch (this.pipelineStepIndex) {
3296
+ case 0:
3297
+ this.labelMergingSolver = new MergedNetLabelObstacleSolver({
3298
+ netLabelPlacements: this.netLabelPlacements,
3299
+ inputProblem: this.inputProblem,
3300
+ traces: this.traces
3301
+ });
3302
+ this.activeSubSolver = this.labelMergingSolver;
3303
+ break;
3304
+ case 1:
3305
+ this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
3306
+ inputProblem: this.inputProblem,
3307
+ traces: this.traces,
3308
+ netLabelPlacements: this.labelMergingSolver.getOutput().netLabelPlacements,
3309
+ mergedLabelNetIdMap: this.labelMergingSolver.getOutput().mergedLabelNetIdMap
3310
+ });
3311
+ this.activeSubSolver = this.overlapAvoidanceSolver;
3312
+ break;
3313
+ case 2:
3314
+ this.traceCleanupSolver = new TraceCleanupSolver({
3315
+ inputProblem: this.inputProblem,
3316
+ allTraces: this.overlapAvoidanceSolver.getOutput().allTraces,
3317
+ targetTraceIds: new Set(
3318
+ this.overlapAvoidanceSolver.getOutput().modifiedTraces.map(
3319
+ (t) => t.mspPairId
3320
+ )
3321
+ ),
3322
+ allLabelPlacements: this.labelMergingSolver.getOutput().netLabelPlacements,
3323
+ mergedLabelNetIdMap: this.labelMergingSolver.getOutput().mergedLabelNetIdMap,
3324
+ paddingBuffer: 0.01
3325
+ });
3326
+ this.activeSubSolver = this.traceCleanupSolver;
3327
+ break;
3328
+ default:
3329
+ this.solved = true;
3330
+ break;
3014
3331
  }
3015
- this.solved = true;
3016
3332
  }
3017
3333
  getOutput() {
3018
3334
  return {
3019
- traces: this.updatedTraces,
3020
- netLabelPlacements: this.netLabelPlacements
3335
+ traces: this.traceCleanupSolver?.getOutput().traces ?? this.traces,
3336
+ netLabelPlacements: this.labelMergingSolver?.getOutput().netLabelPlacements ?? this.netLabelPlacements
3021
3337
  };
3022
3338
  }
3023
3339
  visualize() {
3024
- const graphics = visualizeInputProblem(this.problem);
3340
+ if (this.activeSubSolver) {
3341
+ return this.activeSubSolver.visualize();
3342
+ }
3343
+ const graphics = visualizeInputProblem(this.inputProblem);
3025
3344
  if (!graphics.lines) graphics.lines = [];
3026
- if (!graphics.circles) graphics.circles = [];
3027
- if (!graphics.texts) graphics.texts = [];
3028
3345
  if (!graphics.rects) graphics.rects = [];
3029
- for (const trace of this.updatedTraces) {
3346
+ const output = this.getOutput();
3347
+ for (const trace of output.traces) {
3030
3348
  graphics.lines.push({
3031
3349
  points: trace.tracePath,
3032
3350
  strokeColor: "purple"
3033
3351
  });
3034
3352
  }
3035
- for (const p of this.netLabelPlacements) {
3353
+ for (const label of output.netLabelPlacements) {
3354
+ const color = getColorFromString(label.globalConnNetId, 0.3);
3036
3355
  graphics.rects.push({
3037
- center: p.center,
3038
- width: p.width,
3039
- height: p.height,
3040
- fill: getColorFromString(p.globalConnNetId, 0.35)
3041
- });
3042
- graphics.points.push({
3043
- x: p.anchorPoint.x,
3044
- y: p.anchorPoint.y,
3045
- color: getColorFromString(p.globalConnNetId, 0.9)
3356
+ center: label.center,
3357
+ width: label.width,
3358
+ height: label.height,
3359
+ fill: color,
3360
+ stroke: color.replace("0.3", "1"),
3361
+ label: label.globalConnNetId
3046
3362
  });
3047
3363
  }
3048
3364
  return graphics;
@@ -3100,22 +3416,208 @@ var expandChipsToFitPins = (problem) => {
3100
3416
  }
3101
3417
  };
3102
3418
 
3419
+ // lib/utils/does-trace-overlap-with-existing-traces.ts
3420
+ import { doSegmentsIntersect } from "@tscircuit/math-utils";
3421
+ function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
3422
+ for (let i = 0; i < newTracePath.length - 1; i++) {
3423
+ const newSegmentP1 = newTracePath[i];
3424
+ const newSegmentP2 = newTracePath[i + 1];
3425
+ for (const existingTrace of existingTraces) {
3426
+ for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
3427
+ const existingSegmentP1 = existingTrace.tracePath[j];
3428
+ const existingSegmentP2 = existingTrace.tracePath[j + 1];
3429
+ if (doSegmentsIntersect(
3430
+ newSegmentP1,
3431
+ newSegmentP2,
3432
+ existingSegmentP1,
3433
+ existingSegmentP2
3434
+ )) {
3435
+ return true;
3436
+ }
3437
+ }
3438
+ }
3439
+ }
3440
+ return false;
3441
+ }
3442
+
3443
+ // lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
3444
+ var NEAREST_NEIGHBOR_COUNT = 3;
3445
+ var distance = (p1, p2) => {
3446
+ return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
3447
+ };
3448
+ var LongDistancePairSolver = class extends BaseSolver {
3449
+ constructor(params) {
3450
+ super();
3451
+ this.params = params;
3452
+ const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
3453
+ this.inputProblem = inputProblem;
3454
+ this.allSolvedTraces = [...alreadySolvedTraces];
3455
+ const primaryConnectedPinIds = /* @__PURE__ */ new Set();
3456
+ for (const pair of primaryMspConnectionPairs) {
3457
+ primaryConnectedPinIds.add(pair.pins[0].pinId);
3458
+ primaryConnectedPinIds.add(pair.pins[1].pinId);
3459
+ }
3460
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
3461
+ this.netConnMap = netConnMap;
3462
+ const pinMap = /* @__PURE__ */ new Map();
3463
+ for (const chip of inputProblem.chips) {
3464
+ this.chipMap[chip.chipId] = chip;
3465
+ for (const pin of chip.pins) {
3466
+ pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
3467
+ }
3468
+ }
3469
+ const candidatePairs = [];
3470
+ const addedPairKeys = /* @__PURE__ */ new Set();
3471
+ for (const netId of Object.keys(netConnMap.netMap)) {
3472
+ const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId);
3473
+ if (allPinIdsInNet.length < 2) continue;
3474
+ const unconnectedPinIds = allPinIdsInNet.filter(
3475
+ (pinId) => !primaryConnectedPinIds.has(pinId)
3476
+ );
3477
+ for (const unconnectedPinId of unconnectedPinIds) {
3478
+ const sourcePin = pinMap.get(unconnectedPinId);
3479
+ if (!sourcePin) continue;
3480
+ const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
3481
+ const targetPin = pinMap.get(otherPinId);
3482
+ if (!targetPin) return [];
3483
+ return [
3484
+ {
3485
+ pin: targetPin,
3486
+ distance: distance(sourcePin, targetPin)
3487
+ }
3488
+ ];
3489
+ }).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);
3490
+ for (const neighbor of neighbors) {
3491
+ const pair = [sourcePin, neighbor.pin];
3492
+ const pairKey = pair.map((p) => p.pinId).sort().join("--");
3493
+ if (!addedPairKeys.has(pairKey)) {
3494
+ candidatePairs.push(pair);
3495
+ addedPairKeys.add(pairKey);
3496
+ }
3497
+ }
3498
+ }
3499
+ }
3500
+ this.queuedCandidatePairs = candidatePairs;
3501
+ }
3502
+ solvedLongDistanceTraces = [];
3503
+ queuedCandidatePairs = [];
3504
+ currentCandidatePair = null;
3505
+ subSolver = null;
3506
+ chipMap = {};
3507
+ inputProblem;
3508
+ netConnMap;
3509
+ newlyConnectedPinIds = /* @__PURE__ */ new Set();
3510
+ allSolvedTraces = [];
3511
+ getConstructorParams() {
3512
+ return this.params;
3513
+ }
3514
+ _step() {
3515
+ if (this.subSolver?.solved) {
3516
+ const newTracePath = this.subSolver.solvedTracePath;
3517
+ if (newTracePath && this.currentCandidatePair) {
3518
+ const isTraceClear = !doesTraceOverlapWithExistingTraces(
3519
+ newTracePath,
3520
+ this.allSolvedTraces
3521
+ );
3522
+ if (isTraceClear) {
3523
+ const [p1, p2] = this.currentCandidatePair;
3524
+ const globalConnNetId = this.netConnMap.getNetConnectedToId(p1.pinId);
3525
+ const mspPairId = `${p1.pinId}-${p2.pinId}`;
3526
+ const newSolvedTrace = {
3527
+ mspPairId,
3528
+ dcConnNetId: globalConnNetId,
3529
+ globalConnNetId,
3530
+ pins: [p1, p2],
3531
+ tracePath: newTracePath,
3532
+ mspConnectionPairIds: [mspPairId],
3533
+ pinIds: [p1.pinId, p2.pinId]
3534
+ };
3535
+ this.solvedLongDistanceTraces.push(newSolvedTrace);
3536
+ this.allSolvedTraces.push(newSolvedTrace);
3537
+ this.newlyConnectedPinIds.add(p1.pinId);
3538
+ this.newlyConnectedPinIds.add(p2.pinId);
3539
+ }
3540
+ }
3541
+ this.subSolver = null;
3542
+ this.currentCandidatePair = null;
3543
+ } else if (this.subSolver?.failed) {
3544
+ this.subSolver = null;
3545
+ this.currentCandidatePair = null;
3546
+ }
3547
+ if (this.subSolver) {
3548
+ this.subSolver.step();
3549
+ return;
3550
+ }
3551
+ while (this.queuedCandidatePairs.length > 0) {
3552
+ const nextPair = this.queuedCandidatePairs.shift();
3553
+ const [p1, p2] = nextPair;
3554
+ if (this.newlyConnectedPinIds.has(p1.pinId) || this.newlyConnectedPinIds.has(p2.pinId)) {
3555
+ continue;
3556
+ }
3557
+ this.currentCandidatePair = nextPair;
3558
+ this.subSolver = new SchematicTraceSingleLineSolver2({
3559
+ inputProblem: this.params.inputProblem,
3560
+ pins: this.currentCandidatePair,
3561
+ chipMap: this.chipMap
3562
+ });
3563
+ return;
3564
+ }
3565
+ this.solved = true;
3566
+ }
3567
+ visualize() {
3568
+ if (this.subSolver) {
3569
+ return this.subSolver.visualize();
3570
+ }
3571
+ const graphics = visualizeInputProblem(this.inputProblem);
3572
+ for (const trace of this.solvedLongDistanceTraces) {
3573
+ graphics.lines.push({
3574
+ points: trace.tracePath,
3575
+ strokeColor: "purple"
3576
+ });
3577
+ }
3578
+ for (const [p1, p2] of this.queuedCandidatePairs) {
3579
+ graphics.lines.push({
3580
+ points: [p1, p2],
3581
+ strokeColor: "gray",
3582
+ strokeDash: "4 4"
3583
+ });
3584
+ }
3585
+ return graphics;
3586
+ }
3587
+ getOutput() {
3588
+ if (!this.solved) {
3589
+ return { newTraces: [], allTracesMerged: this.params.alreadySolvedTraces };
3590
+ }
3591
+ return {
3592
+ newTraces: this.solvedLongDistanceTraces,
3593
+ allTracesMerged: [
3594
+ ...this.params.alreadySolvedTraces,
3595
+ ...this.solvedLongDistanceTraces
3596
+ ]
3597
+ };
3598
+ }
3599
+ };
3600
+
3103
3601
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
3104
3602
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
3105
3603
  return {
3106
3604
  solverName,
3107
3605
  solverClass,
3108
3606
  getConstructorParams,
3109
- onSolved: opts.onSolved
3607
+ onSolved: opts.onSolved,
3608
+ shouldSkip: opts.shouldSkip
3110
3609
  };
3111
3610
  }
3112
3611
  var SchematicTracePipelineSolver = class extends BaseSolver {
3113
3612
  mspConnectionPairSolver;
3114
3613
  // guidelinesSolver?: GuidelinesSolver
3115
3614
  schematicTraceLinesSolver;
3615
+ longDistancePairSolver;
3116
3616
  traceOverlapShiftSolver;
3117
3617
  netLabelPlacementSolver;
3618
+ labelMergingSolver;
3118
3619
  traceLabelOverlapAvoidanceSolver;
3620
+ traceCleanupSolver;
3119
3621
  startTimeOfPhase;
3120
3622
  endTimeOfPhase;
3121
3623
  timeSpentOnPhase;
@@ -3155,6 +3657,17 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3155
3657
  // guidelines: this.guidelinesSolver!.guidelines,
3156
3658
  chipMap: this.mspConnectionPairSolver.chipMap
3157
3659
  }
3660
+ ]
3661
+ ),
3662
+ definePipelineStep(
3663
+ "longDistancePairSolver",
3664
+ LongDistancePairSolver,
3665
+ (instance) => [
3666
+ {
3667
+ inputProblem: instance.inputProblem,
3668
+ primaryMspConnectionPairs: instance.mspConnectionPairSolver.mspConnectionPairs,
3669
+ alreadySolvedTraces: instance.schematicTraceLinesSolver.solvedTracePaths
3670
+ }
3158
3671
  ],
3159
3672
  {
3160
3673
  onSolved: (schematicTraceLinesSolver) => {
@@ -3167,7 +3680,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3167
3680
  () => [
3168
3681
  {
3169
3682
  inputProblem: this.inputProblem,
3170
- inputTracePaths: this.schematicTraceLinesSolver.solvedTracePaths,
3683
+ inputTracePaths: this.longDistancePairSolver?.getOutput().allTracesMerged,
3171
3684
  globalConnMap: this.mspConnectionPairSolver.globalConnMap
3172
3685
  }
3173
3686
  ],
@@ -3183,10 +3696,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3183
3696
  {
3184
3697
  inputProblem: this.inputProblem,
3185
3698
  inputTraceMap: this.traceOverlapShiftSolver?.correctedTraceMap ?? Object.fromEntries(
3186
- this.schematicTraceLinesSolver.solvedTracePaths.map((p) => [
3187
- p.mspPairId,
3188
- p
3189
- ])
3699
+ this.longDistancePairSolver.getOutput().allTracesMerged.map(
3700
+ (p) => [p.mspPairId, p]
3701
+ )
3190
3702
  )
3191
3703
  }
3192
3704
  ],
@@ -3200,10 +3712,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3200
3712
  TraceLabelOverlapAvoidanceSolver,
3201
3713
  (instance) => {
3202
3714
  const traceMap = instance.traceOverlapShiftSolver?.correctedTraceMap ?? Object.fromEntries(
3203
- instance.schematicTraceLinesSolver.solvedTracePaths.map((p) => [
3204
- p.mspPairId,
3205
- p
3206
- ])
3715
+ instance.longDistancePairSolver.getOutput().allTracesMerged.map((p) => [p.mspPairId, p])
3207
3716
  );
3208
3717
  const traces = Object.values(traceMap);
3209
3718
  const netLabelPlacements = instance.netLabelPlacementSolver.netLabelPlacements;
@@ -3214,14 +3723,21 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3214
3723
  netLabelPlacements
3215
3724
  }
3216
3725
  ];
3217
- },
3218
- {
3219
- onSolved: (instance) => {
3220
- if (instance.traceLabelOverlapAvoidanceSolver && instance.netLabelPlacementSolver) {
3221
- const { netLabelPlacements } = instance.traceLabelOverlapAvoidanceSolver.getOutput();
3222
- instance.netLabelPlacementSolver.netLabelPlacements = netLabelPlacements;
3726
+ }
3727
+ ),
3728
+ definePipelineStep(
3729
+ "netLabelPlacementSolver",
3730
+ NetLabelPlacementSolver,
3731
+ (instance) => {
3732
+ const traces = instance.traceCleanupSolver?.getOutput().traces ?? instance.traceLabelOverlapAvoidanceSolver.getOutput().traces;
3733
+ return [
3734
+ {
3735
+ inputProblem: instance.inputProblem,
3736
+ inputTraceMap: Object.fromEntries(
3737
+ traces.map((trace) => [trace.mspPairId, trace])
3738
+ )
3223
3739
  }
3224
- }
3740
+ ];
3225
3741
  }
3226
3742
  )
3227
3743
  ];
@@ -3307,14 +3823,17 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3307
3823
  return viz;
3308
3824
  })
3309
3825
  ];
3310
- if (visualizations.length === 1) return visualizations[0];
3311
- return {
3826
+ if (visualizations.length === 1) {
3827
+ return visualizations[0];
3828
+ }
3829
+ const finalGraphics = {
3312
3830
  points: visualizations.flatMap((v) => v.points || []),
3313
3831
  rects: visualizations.flatMap((v) => v.rects || []),
3314
3832
  lines: visualizations.flatMap((v) => v.lines || []),
3315
3833
  circles: visualizations.flatMap((v) => v.circles || []),
3316
3834
  texts: visualizations.flatMap((v) => v.texts || [])
3317
3835
  };
3836
+ return finalGraphics;
3318
3837
  }
3319
3838
  /**
3320
3839
  * A lightweight version of the visualize method that can be used to stream