@tscircuit/schematic-trace-solver 0.0.41 → 0.0.43

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 (58) hide show
  1. package/dist/index.d.ts +118 -8
  2. package/dist/index.js +1067 -748
  3. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +48 -16
  4. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +133 -0
  5. package/lib/solvers/TraceCleanupSolver/balanceZShapes.ts +200 -0
  6. package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/hasCollisions.ts +1 -1
  7. package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/hasCollisionsWithLabels.ts +1 -1
  8. package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +76 -0
  9. package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/simplifyPath.ts +1 -1
  10. package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/turnMinimization.ts +0 -2
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +74 -192
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +13 -24
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +205 -0
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +144 -0
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +117 -0
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +104 -0
  17. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +24 -154
  18. package/package.json +1 -1
  19. package/site/examples/example26.page.tsx +4 -0
  20. package/tests/assets/1.input.json +379 -0
  21. package/tests/assets/2.input.json +330 -0
  22. package/tests/assets/3.input.json +388 -0
  23. package/tests/assets/MergedNetLabelObstacles.test.input.json +154 -0
  24. package/tests/assets/OverlapAvoidanceStepSolver.test.input.json +682 -0
  25. package/tests/assets/SingleOverlapSolver.test.input.json +170 -0
  26. package/tests/assets/TraceCleanupSolver.test.input.json +216 -0
  27. package/tests/assets/TraceLabelOverlapAvoidanceSolver.test.input.json +424 -0
  28. package/tests/assets/example26.json +1206 -0
  29. package/tests/examples/__snapshots__/example02.snap.svg +1 -1
  30. package/tests/examples/__snapshots__/example09.snap.svg +34 -34
  31. package/tests/examples/__snapshots__/example11.snap.svg +13 -13
  32. package/tests/examples/__snapshots__/example12.snap.svg +3 -3
  33. package/tests/examples/__snapshots__/example13.snap.svg +12 -12
  34. package/tests/examples/__snapshots__/example16.snap.svg +4 -4
  35. package/tests/examples/__snapshots__/example19.snap.svg +9 -9
  36. package/tests/examples/__snapshots__/example28.snap.svg +3 -3
  37. package/tests/examples/__snapshots__/example29.snap.svg +1208 -0
  38. package/tests/examples/example29.test.ts +13 -0
  39. package/tests/solvers/TraceCleanupSolver/TraceCleanupSolver.test.ts +19 -0
  40. package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
  41. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.test.ts +18 -0
  42. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +243 -0
  43. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +223 -0
  44. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +205 -0
  45. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +184 -0
  46. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView01.test.ts +29 -0
  47. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView02.test.ts +27 -0
  48. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +39 -0
  49. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/MergedNetLabelObstacles.test.ts +14 -0
  50. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/OverlapAvoidanceStepSolver.test.ts +20 -0
  51. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/SingleOverlapSolver.test.ts +10 -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/lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts +0 -207
  56. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/minimizeTurnsWithFilteredLabels.ts +0 -66
  57. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/countTurns.ts +0 -0
  58. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/tryConnectPoints.ts +0 -0
package/dist/index.js CHANGED
@@ -2196,8 +2196,161 @@ 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";
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
+ }
2339
+ }
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
+ });
2349
+ }
2350
+ }
2351
+ return graphics;
2352
+ }
2353
+ };
2201
2354
 
2202
2355
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts
2203
2356
  var detectTraceLabelOverlap = (traces, netLabels) => {
@@ -2237,114 +2390,70 @@ var findTraceViolationZone = (path, labelBounds) => {
2237
2390
  return { firstInsideIndex, lastInsideIndex };
2238
2391
  };
2239
2392
 
2240
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/simplifyPath.ts
2241
- var simplifyPath = (path) => {
2242
- if (path.length < 3) return path;
2243
- const newPath = [path[0]];
2244
- for (let i = 1; i < path.length - 1; i++) {
2245
- const p1 = newPath[newPath.length - 1];
2246
- const p2 = path[i];
2247
- const p3 = path[i + 1];
2248
- if (isVertical(p1, p2) && isVertical(p2, p3) || isHorizontal(p1, p2) && isHorizontal(p2, p3)) {
2249
- continue;
2250
- }
2251
- newPath.push(p2);
2252
- }
2253
- newPath.push(path[path.length - 1]);
2254
- if (newPath.length < 3) return newPath;
2255
- const finalPath = [newPath[0]];
2256
- for (let i = 1; i < newPath.length - 1; i++) {
2257
- const p1 = finalPath[finalPath.length - 1];
2258
- const p2 = newPath[i];
2259
- const p3 = newPath[i + 1];
2260
- if (isVertical(p1, p2) && isVertical(p2, p3) || isHorizontal(p1, p2) && isHorizontal(p2, p3)) {
2261
- continue;
2262
- }
2263
- finalPath.push(p2);
2264
- }
2265
- finalPath.push(newPath[newPath.length - 1]);
2266
- return finalPath;
2267
- };
2268
-
2269
2393
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts
2270
- var trySnipAndReconnect = ({
2394
+ var generateSnipAndReconnectCandidates = ({
2271
2395
  initialTrace,
2272
2396
  firstInsideIndex,
2273
2397
  lastInsideIndex,
2274
2398
  labelBounds,
2275
- obstacles
2399
+ paddingBuffer,
2400
+ detourCount
2276
2401
  }) => {
2277
2402
  if (firstInsideIndex <= 0 || lastInsideIndex >= initialTrace.tracePath.length - 1) {
2278
- return null;
2403
+ return [];
2279
2404
  }
2280
2405
  const entryPoint = initialTrace.tracePath[firstInsideIndex - 1];
2281
2406
  const exitPoint = initialTrace.tracePath[lastInsideIndex + 1];
2282
2407
  const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex);
2283
2408
  const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1);
2284
- const candidateDetours = [];
2409
+ const allCandidateDetours = [];
2285
2410
  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 }]);
2411
+ allCandidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }]);
2412
+ allCandidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }]);
2288
2413
  } 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
- }
2414
+ allCandidateDetours.push([]);
2294
2415
  }
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;
2416
+ const buffer = paddingBuffer + detourCount * paddingBuffer;
2305
2417
  const leftX = labelBounds.minX - buffer;
2306
2418
  const rightX = labelBounds.maxX + buffer;
2307
2419
  const topY = labelBounds.maxY + buffer;
2308
2420
  const bottomY = labelBounds.minY - buffer;
2309
2421
  if ((entryPoint.x <= labelBounds.minX || exitPoint.x <= labelBounds.minX) && entryPoint.x < labelBounds.maxX && exitPoint.x < labelBounds.maxX) {
2310
- candidateDetours.push([
2422
+ allCandidateDetours.push([
2311
2423
  { x: leftX, y: entryPoint.y },
2312
2424
  { x: leftX, y: exitPoint.y }
2313
2425
  ]);
2314
2426
  }
2315
2427
  if ((entryPoint.x >= labelBounds.maxX || exitPoint.x >= labelBounds.maxX) && entryPoint.x > labelBounds.minX && exitPoint.x > labelBounds.minX) {
2316
- candidateDetours.push([
2428
+ allCandidateDetours.push([
2317
2429
  { x: rightX, y: entryPoint.y },
2318
2430
  { x: rightX, y: exitPoint.y }
2319
2431
  ]);
2320
2432
  }
2321
2433
  if ((entryPoint.y >= labelBounds.maxY || exitPoint.y >= labelBounds.maxY) && entryPoint.y > labelBounds.minY && exitPoint.y > labelBounds.minY) {
2322
- candidateDetours.push([
2434
+ allCandidateDetours.push([
2323
2435
  { x: entryPoint.x, y: topY },
2324
2436
  { x: exitPoint.x, y: topY }
2325
2437
  ]);
2326
2438
  }
2327
2439
  if ((entryPoint.y <= labelBounds.minY || exitPoint.y <= labelBounds.minY) && entryPoint.y < labelBounds.maxY && exitPoint.y < labelBounds.maxY) {
2328
- candidateDetours.push([
2440
+ allCandidateDetours.push([
2329
2441
  { x: entryPoint.x, y: bottomY },
2330
2442
  { x: exitPoint.x, y: bottomY }
2331
2443
  ]);
2332
2444
  }
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 };
2339
- }
2340
- }
2341
- return null;
2445
+ return allCandidateDetours.map((detour) => [
2446
+ ...pathToEntry,
2447
+ ...detour,
2448
+ ...pathFromExit
2449
+ ]);
2342
2450
  };
2343
- var tryFourPointDetour = ({
2451
+
2452
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts
2453
+ var generateFourPointDetourCandidates = ({
2344
2454
  initialTrace,
2345
2455
  label,
2346
2456
  labelBounds,
2347
- obstacles,
2348
2457
  paddingBuffer,
2349
2458
  detourCount
2350
2459
  }) => {
@@ -2359,10 +2468,10 @@ var tryFourPointDetour = ({
2359
2468
  break;
2360
2469
  }
2361
2470
  }
2362
- if (collidingSegIndex === -1) return initialTrace;
2471
+ if (collidingSegIndex === -1) return [];
2363
2472
  const pA = initialTrace.tracePath[collidingSegIndex];
2364
2473
  const pB = initialTrace.tracePath[collidingSegIndex + 1];
2365
- if (!pA || !pB) return null;
2474
+ if (!pA || !pB) return [];
2366
2475
  const candidateDetours = [];
2367
2476
  const paddedLabelBounds = getRectBounds(
2368
2477
  label.center,
@@ -2411,33 +2520,53 @@ var tryFourPointDetour = ({
2411
2520
  );
2412
2521
  }
2413
2522
  }
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 };
2523
+ return candidateDetours.map((detourPoints) => [
2524
+ ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
2525
+ ...detourPoints,
2526
+ ...initialTrace.tracePath.slice(collidingSegIndex + 1)
2527
+ ]);
2528
+ };
2529
+
2530
+ // lib/solvers/TraceCleanupSolver/simplifyPath.ts
2531
+ var simplifyPath = (path) => {
2532
+ if (path.length < 3) return path;
2533
+ const newPath = [path[0]];
2534
+ for (let i = 1; i < path.length - 1; i++) {
2535
+ const p1 = newPath[newPath.length - 1];
2536
+ const p2 = path[i];
2537
+ const p3 = path[i + 1];
2538
+ if (isVertical(p1, p2) && isVertical(p2, p3) || isHorizontal(p1, p2) && isHorizontal(p2, p3)) {
2539
+ continue;
2423
2540
  }
2541
+ newPath.push(p2);
2424
2542
  }
2425
- return null;
2543
+ newPath.push(path[path.length - 1]);
2544
+ if (newPath.length < 3) return newPath;
2545
+ const finalPath = [newPath[0]];
2546
+ for (let i = 1; i < newPath.length - 1; i++) {
2547
+ const p1 = finalPath[finalPath.length - 1];
2548
+ const p2 = newPath[i];
2549
+ const p3 = newPath[i + 1];
2550
+ if (isVertical(p1, p2) && isVertical(p2, p3) || isHorizontal(p1, p2) && isHorizontal(p2, p3)) {
2551
+ continue;
2552
+ }
2553
+ finalPath.push(p2);
2554
+ }
2555
+ finalPath.push(newPath[newPath.length - 1]);
2556
+ return finalPath;
2426
2557
  };
2427
2558
 
2428
2559
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts
2429
- var rerouteCollidingTrace = ({
2560
+ var generateRerouteCandidates = ({
2430
2561
  trace,
2431
2562
  label,
2432
- problem,
2433
2563
  paddingBuffer,
2434
2564
  detourCount
2435
2565
  }) => {
2436
2566
  const initialTrace = { ...trace, tracePath: simplifyPath(trace.tracePath) };
2437
2567
  if (trace.globalConnNetId === label.globalConnNetId) {
2438
- return initialTrace;
2568
+ return [initialTrace.tracePath];
2439
2569
  }
2440
- const obstacles = getObstacleRects(problem);
2441
2570
  const labelPadding = paddingBuffer;
2442
2571
  const labelBoundsRaw = getRectBounds(label.center, label.width, label.height);
2443
2572
  const labelBounds = {
@@ -2447,98 +2576,589 @@ var rerouteCollidingTrace = ({
2447
2576
  maxY: labelBoundsRaw.maxY + labelPadding,
2448
2577
  chipId: `netlabel-${label.netId}`
2449
2578
  };
2450
- const fourPointResult = tryFourPointDetour({
2579
+ const fourPointCandidates = generateFourPointDetourCandidates({
2451
2580
  initialTrace,
2452
2581
  label,
2453
2582
  labelBounds,
2454
- obstacles,
2455
2583
  paddingBuffer,
2456
2584
  detourCount
2457
2585
  });
2458
- if (fourPointResult) {
2459
- initialTrace.tracePath = fourPointResult.tracePath;
2460
- }
2461
2586
  const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
2462
2587
  initialTrace.tracePath,
2463
2588
  labelBounds
2464
2589
  );
2465
- const snipReconnectResult = trySnipAndReconnect({
2590
+ const snipReconnectCandidates = generateSnipAndReconnectCandidates({
2466
2591
  initialTrace,
2467
2592
  firstInsideIndex,
2468
2593
  lastInsideIndex,
2469
2594
  labelBounds,
2470
- obstacles
2595
+ paddingBuffer,
2596
+ detourCount
2471
2597
  });
2472
- if (snipReconnectResult) {
2473
- return snipReconnectResult;
2474
- }
2475
- if (fourPointResult) {
2476
- return fourPointResult;
2477
- }
2478
- return initialTrace;
2598
+ return [...fourPointCandidates, ...snipReconnectCandidates];
2479
2599
  };
2480
2600
 
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
- }
2601
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts
2602
+ var SingleOverlapSolver = class extends BaseSolver {
2603
+ queuedCandidatePaths;
2604
+ solvedTracePath = null;
2605
+ initialTrace;
2606
+ problem;
2607
+ obstacles;
2608
+ label;
2609
+ constructor(solverInput) {
2610
+ super();
2611
+ this.initialTrace = solverInput.trace;
2612
+ this.problem = solverInput.problem;
2613
+ this.label = solverInput.label;
2614
+ const candidates = generateRerouteCandidates({
2615
+ ...solverInput
2616
+ });
2617
+ const getPathLength = (pts) => {
2618
+ let len = 0;
2619
+ for (let i = 0; i < pts.length - 1; i++) {
2620
+ const dx = pts[i + 1].x - pts[i].x;
2621
+ const dy = pts[i + 1].y - pts[i].y;
2622
+ len += Math.sqrt(dx * dx + dy * dy);
2623
+ }
2624
+ return len;
2625
+ };
2626
+ this.queuedCandidatePaths = candidates.sort(
2627
+ (a, b) => getPathLength(a) - getPathLength(b)
2628
+ );
2629
+ this.obstacles = getObstacleRects(this.problem);
2630
+ }
2631
+ _step() {
2632
+ if (this.queuedCandidatePaths.length === 0) {
2633
+ this.failed = true;
2634
+ return;
2635
+ }
2636
+ const nextCandidatePath = this.queuedCandidatePaths.shift();
2637
+ const simplifiedPath = simplifyPath(nextCandidatePath);
2638
+ if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles)) {
2639
+ this.solvedTracePath = simplifiedPath;
2640
+ this.solved = true;
2490
2641
  }
2491
2642
  }
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++;
2643
+ visualize() {
2644
+ const graphics = visualizeInputProblem(this.problem, {
2645
+ chipAlpha: 0.1,
2646
+ connectionAlpha: 0.1
2647
+ });
2648
+ if (!graphics.lines) graphics.lines = [];
2649
+ if (!graphics.rects) graphics.rects = [];
2650
+ graphics.lines.push({
2651
+ points: this.initialTrace.tracePath,
2652
+ strokeColor: "red",
2653
+ strokeDash: "4 4"
2654
+ });
2655
+ graphics.rects.push({
2656
+ center: this.label.center,
2657
+ width: this.label.width,
2658
+ height: this.label.height,
2659
+ fill: "rgba(255, 0, 0, 0.2)"
2660
+ });
2661
+ if (this.queuedCandidatePaths.length > 0) {
2662
+ graphics.lines.push({
2663
+ points: this.queuedCandidatePaths[0],
2664
+ strokeColor: "orange"
2665
+ });
2666
+ }
2667
+ if (this.solvedTracePath) {
2668
+ graphics.lines.push({
2669
+ points: this.solvedTracePath,
2670
+ strokeColor: "green"
2671
+ });
2506
2672
  }
2673
+ return graphics;
2507
2674
  }
2508
- return turns;
2509
2675
  };
2510
2676
 
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]);
2677
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts
2678
+ var OverlapAvoidanceStepSolver = class extends BaseSolver {
2679
+ inputProblem;
2680
+ netLabelPlacements;
2681
+ mergedLabelNetIdMap;
2682
+ allTraces;
2683
+ modifiedTraces = [];
2684
+ detourCountByLabel = {};
2685
+ PADDING_BUFFER = 0.1;
2686
+ activeSubSolver = null;
2687
+ overlapQueue = [];
2688
+ recentlyFailed = /* @__PURE__ */ new Set();
2689
+ constructor(solverInput) {
2690
+ super();
2691
+ this.inputProblem = solverInput.inputProblem;
2692
+ this.netLabelPlacements = solverInput.netLabelPlacements;
2693
+ this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap;
2694
+ this.allTraces = [...solverInput.traces];
2519
2695
  }
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;
2696
+ _step() {
2697
+ if (this.activeSubSolver) {
2698
+ this.activeSubSolver.step();
2699
+ if (this.activeSubSolver.solved) {
2700
+ const solvedPath = this.activeSubSolver.solvedTracePath;
2701
+ if (solvedPath) {
2702
+ const traceIndex = this.allTraces.findIndex(
2703
+ (t) => t.mspPairId === this.activeSubSolver.initialTrace.mspPairId
2704
+ );
2705
+ if (traceIndex !== -1) {
2706
+ this.allTraces[traceIndex].tracePath = solvedPath;
2707
+ this.modifiedTraces.push(this.allTraces[traceIndex]);
2708
+ }
2709
+ }
2710
+ this.activeSubSolver = null;
2711
+ this.recentlyFailed.clear();
2712
+ } else if (this.activeSubSolver.failed) {
2713
+ const overlapId = `${this.activeSubSolver.initialTrace.mspPairId}-${this.activeSubSolver.label.globalConnNetId}`;
2714
+ this.recentlyFailed.add(overlapId);
2715
+ this.activeSubSolver = null;
2531
2716
  }
2717
+ return;
2532
2718
  }
2533
- }
2534
- return false;
2535
- };
2536
-
2537
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/turnMinimization.ts
2538
- var minimizeTurns = ({
2539
- path,
2540
- obstacles,
2541
- labelBounds
2719
+ const overlaps = detectTraceLabelOverlap(
2720
+ this.allTraces,
2721
+ this.netLabelPlacements
2722
+ ).filter((o) => {
2723
+ const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId];
2724
+ if (originalNetIds) {
2725
+ return !originalNetIds.has(o.trace.globalConnNetId);
2726
+ }
2727
+ return o.trace.globalConnNetId !== o.label.globalConnNetId;
2728
+ });
2729
+ if (overlaps.length === 0) {
2730
+ this.solved = true;
2731
+ return;
2732
+ }
2733
+ const nonFailedOverlaps = overlaps.filter((o) => {
2734
+ const overlapId = `${o.trace.mspPairId}-${o.label.globalConnNetId}`;
2735
+ return !this.recentlyFailed.has(overlapId);
2736
+ });
2737
+ if (nonFailedOverlaps.length === 0) {
2738
+ this.solved = true;
2739
+ return;
2740
+ }
2741
+ this.overlapQueue = nonFailedOverlaps;
2742
+ const nextOverlap = this.overlapQueue.shift();
2743
+ if (nextOverlap) {
2744
+ const traceToFix = this.allTraces.find(
2745
+ (t) => t.mspPairId === nextOverlap.trace.mspPairId
2746
+ );
2747
+ if (traceToFix) {
2748
+ const labelId = nextOverlap.label.globalConnNetId;
2749
+ const detourCount = this.detourCountByLabel[labelId] || 0;
2750
+ this.detourCountByLabel[labelId] = detourCount + 1;
2751
+ this.activeSubSolver = new SingleOverlapSolver({
2752
+ trace: traceToFix,
2753
+ label: nextOverlap.label,
2754
+ problem: this.inputProblem,
2755
+ paddingBuffer: this.PADDING_BUFFER,
2756
+ detourCount
2757
+ });
2758
+ }
2759
+ }
2760
+ }
2761
+ getOutput() {
2762
+ return {
2763
+ allTraces: this.allTraces,
2764
+ modifiedTraces: this.modifiedTraces
2765
+ };
2766
+ }
2767
+ visualize() {
2768
+ if (this.activeSubSolver) {
2769
+ return this.activeSubSolver.visualize();
2770
+ }
2771
+ const graphics = visualizeInputProblem(this.inputProblem);
2772
+ if (!graphics.lines) graphics.lines = [];
2773
+ for (const trace of this.allTraces) {
2774
+ graphics.lines.push({
2775
+ points: trace.tracePath,
2776
+ strokeColor: "purple"
2777
+ });
2778
+ }
2779
+ return graphics;
2780
+ }
2781
+ };
2782
+
2783
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts
2784
+ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2785
+ inputProblem;
2786
+ traces;
2787
+ netLabelPlacements;
2788
+ // sub-solver instances
2789
+ labelMergingSolver;
2790
+ overlapAvoidanceSolver;
2791
+ pipelineStepIndex = 0;
2792
+ constructor(solverInput) {
2793
+ super();
2794
+ this.inputProblem = solverInput.inputProblem;
2795
+ this.traces = solverInput.traces;
2796
+ this.netLabelPlacements = solverInput.netLabelPlacements;
2797
+ }
2798
+ _step() {
2799
+ if (this.activeSubSolver) {
2800
+ this.activeSubSolver.step();
2801
+ if (this.activeSubSolver.solved) {
2802
+ this.activeSubSolver = null;
2803
+ this.pipelineStepIndex++;
2804
+ } else if (this.activeSubSolver.failed) {
2805
+ this.failed = true;
2806
+ this.activeSubSolver = null;
2807
+ }
2808
+ return;
2809
+ }
2810
+ switch (this.pipelineStepIndex) {
2811
+ case 0:
2812
+ this.labelMergingSolver = new MergedNetLabelObstacleSolver({
2813
+ netLabelPlacements: this.netLabelPlacements,
2814
+ inputProblem: this.inputProblem,
2815
+ traces: this.traces
2816
+ });
2817
+ this.activeSubSolver = this.labelMergingSolver;
2818
+ break;
2819
+ case 1:
2820
+ this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
2821
+ inputProblem: this.inputProblem,
2822
+ traces: this.traces,
2823
+ netLabelPlacements: this.labelMergingSolver.getOutput().netLabelPlacements,
2824
+ mergedLabelNetIdMap: this.labelMergingSolver.getOutput().mergedLabelNetIdMap
2825
+ });
2826
+ this.activeSubSolver = this.overlapAvoidanceSolver;
2827
+ break;
2828
+ default:
2829
+ this.solved = true;
2830
+ break;
2831
+ }
2832
+ }
2833
+ getOutput() {
2834
+ return {
2835
+ traces: this.overlapAvoidanceSolver?.getOutput().allTraces ?? this.traces,
2836
+ netLabelPlacements: this.labelMergingSolver?.getOutput().netLabelPlacements ?? this.netLabelPlacements
2837
+ };
2838
+ }
2839
+ visualize() {
2840
+ if (this.activeSubSolver) {
2841
+ return this.activeSubSolver.visualize();
2842
+ }
2843
+ const graphics = visualizeInputProblem(this.inputProblem);
2844
+ if (!graphics.lines) graphics.lines = [];
2845
+ if (!graphics.rects) graphics.rects = [];
2846
+ const output = this.getOutput();
2847
+ for (const trace of output.traces) {
2848
+ graphics.lines.push({
2849
+ points: trace.tracePath,
2850
+ strokeColor: "purple"
2851
+ });
2852
+ }
2853
+ for (const label of output.netLabelPlacements) {
2854
+ const color = getColorFromString(label.globalConnNetId, 0.3);
2855
+ graphics.rects.push({
2856
+ center: label.center,
2857
+ width: label.width,
2858
+ height: label.height,
2859
+ fill: color,
2860
+ stroke: color.replace("0.3", "1"),
2861
+ label: label.globalConnNetId
2862
+ });
2863
+ }
2864
+ return graphics;
2865
+ }
2866
+ };
2867
+
2868
+ // lib/solvers/SchematicTracePipelineSolver/correctPinsInsideChip.ts
2869
+ var correctPinsInsideChips = (problem) => {
2870
+ for (const chip of problem.chips) {
2871
+ const bounds = getInputChipBounds(chip);
2872
+ for (const pin of chip.pins) {
2873
+ const isInside = pin.x > bounds.minX && pin.x < bounds.maxX && pin.y > bounds.minY && pin.y < bounds.maxY;
2874
+ if (!isInside) continue;
2875
+ const distLeft = pin.x - bounds.minX;
2876
+ const distRight = bounds.maxX - pin.x;
2877
+ const distBottom = pin.y - bounds.minY;
2878
+ const distTop = bounds.maxY - pin.y;
2879
+ const minDist = Math.min(distLeft, distRight, distBottom, distTop);
2880
+ if (minDist === distLeft) {
2881
+ pin.x = bounds.minX;
2882
+ } else if (minDist === distRight) {
2883
+ pin.x = bounds.maxX;
2884
+ } else if (minDist === distBottom) {
2885
+ pin.y = bounds.minY;
2886
+ } else {
2887
+ pin.y = bounds.maxY;
2888
+ }
2889
+ pin._facingDirection = void 0;
2890
+ }
2891
+ }
2892
+ };
2893
+
2894
+ // lib/solvers/SchematicTracePipelineSolver/expandChipsToFitPins.ts
2895
+ var expandChipsToFitPins = (problem) => {
2896
+ for (const chip of problem.chips) {
2897
+ const halfWidth = chip.width / 2;
2898
+ const halfHeight = chip.height / 2;
2899
+ let maxDx = 0;
2900
+ let maxDy = 0;
2901
+ for (const pin of chip.pins) {
2902
+ const dx = Math.abs(pin.x - chip.center.x);
2903
+ const dy = Math.abs(pin.y - chip.center.y);
2904
+ if (dx > maxDx) maxDx = dx;
2905
+ if (dy > maxDy) maxDy = dy;
2906
+ }
2907
+ const newHalfWidth = Math.max(halfWidth, maxDx);
2908
+ const newHalfHeight = Math.max(halfHeight, maxDy);
2909
+ if (newHalfWidth > halfWidth || newHalfHeight > halfHeight) {
2910
+ chip.width = newHalfWidth * 2;
2911
+ chip.height = newHalfHeight * 2;
2912
+ for (const pin of chip.pins) {
2913
+ pin._facingDirection = void 0;
2914
+ }
2915
+ }
2916
+ }
2917
+ };
2918
+
2919
+ // lib/utils/does-trace-overlap-with-existing-traces.ts
2920
+ import { doSegmentsIntersect } from "@tscircuit/math-utils";
2921
+ function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
2922
+ for (let i = 0; i < newTracePath.length - 1; i++) {
2923
+ const newSegmentP1 = newTracePath[i];
2924
+ const newSegmentP2 = newTracePath[i + 1];
2925
+ for (const existingTrace of existingTraces) {
2926
+ for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
2927
+ const existingSegmentP1 = existingTrace.tracePath[j];
2928
+ const existingSegmentP2 = existingTrace.tracePath[j + 1];
2929
+ if (doSegmentsIntersect(
2930
+ newSegmentP1,
2931
+ newSegmentP2,
2932
+ existingSegmentP1,
2933
+ existingSegmentP2
2934
+ )) {
2935
+ return true;
2936
+ }
2937
+ }
2938
+ }
2939
+ }
2940
+ return false;
2941
+ }
2942
+
2943
+ // lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
2944
+ var NEAREST_NEIGHBOR_COUNT = 3;
2945
+ var distance = (p1, p2) => {
2946
+ return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
2947
+ };
2948
+ var LongDistancePairSolver = class extends BaseSolver {
2949
+ constructor(params) {
2950
+ super();
2951
+ this.params = params;
2952
+ const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
2953
+ this.inputProblem = inputProblem;
2954
+ this.allSolvedTraces = [...alreadySolvedTraces];
2955
+ const primaryConnectedPinIds = /* @__PURE__ */ new Set();
2956
+ for (const pair of primaryMspConnectionPairs) {
2957
+ primaryConnectedPinIds.add(pair.pins[0].pinId);
2958
+ primaryConnectedPinIds.add(pair.pins[1].pinId);
2959
+ }
2960
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
2961
+ this.netConnMap = netConnMap;
2962
+ const pinMap = /* @__PURE__ */ new Map();
2963
+ for (const chip of inputProblem.chips) {
2964
+ this.chipMap[chip.chipId] = chip;
2965
+ for (const pin of chip.pins) {
2966
+ pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
2967
+ }
2968
+ }
2969
+ const candidatePairs = [];
2970
+ const addedPairKeys = /* @__PURE__ */ new Set();
2971
+ for (const netId of Object.keys(netConnMap.netMap)) {
2972
+ const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId);
2973
+ if (allPinIdsInNet.length < 2) continue;
2974
+ const unconnectedPinIds = allPinIdsInNet.filter(
2975
+ (pinId) => !primaryConnectedPinIds.has(pinId)
2976
+ );
2977
+ for (const unconnectedPinId of unconnectedPinIds) {
2978
+ const sourcePin = pinMap.get(unconnectedPinId);
2979
+ if (!sourcePin) continue;
2980
+ const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
2981
+ const targetPin = pinMap.get(otherPinId);
2982
+ if (!targetPin) return [];
2983
+ return [
2984
+ {
2985
+ pin: targetPin,
2986
+ distance: distance(sourcePin, targetPin)
2987
+ }
2988
+ ];
2989
+ }).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);
2990
+ for (const neighbor of neighbors) {
2991
+ const pair = [sourcePin, neighbor.pin];
2992
+ const pairKey = pair.map((p) => p.pinId).sort().join("--");
2993
+ if (!addedPairKeys.has(pairKey)) {
2994
+ candidatePairs.push(pair);
2995
+ addedPairKeys.add(pairKey);
2996
+ }
2997
+ }
2998
+ }
2999
+ }
3000
+ this.queuedCandidatePairs = candidatePairs;
3001
+ }
3002
+ solvedLongDistanceTraces = [];
3003
+ queuedCandidatePairs = [];
3004
+ currentCandidatePair = null;
3005
+ subSolver = null;
3006
+ chipMap = {};
3007
+ inputProblem;
3008
+ netConnMap;
3009
+ newlyConnectedPinIds = /* @__PURE__ */ new Set();
3010
+ allSolvedTraces = [];
3011
+ getConstructorParams() {
3012
+ return this.params;
3013
+ }
3014
+ _step() {
3015
+ if (this.subSolver?.solved) {
3016
+ const newTracePath = this.subSolver.solvedTracePath;
3017
+ if (newTracePath && this.currentCandidatePair) {
3018
+ const isTraceClear = !doesTraceOverlapWithExistingTraces(
3019
+ newTracePath,
3020
+ this.allSolvedTraces
3021
+ );
3022
+ if (isTraceClear) {
3023
+ const [p1, p2] = this.currentCandidatePair;
3024
+ const globalConnNetId = this.netConnMap.getNetConnectedToId(p1.pinId);
3025
+ const mspPairId = `${p1.pinId}-${p2.pinId}`;
3026
+ const newSolvedTrace = {
3027
+ mspPairId,
3028
+ dcConnNetId: globalConnNetId,
3029
+ globalConnNetId,
3030
+ pins: [p1, p2],
3031
+ tracePath: newTracePath,
3032
+ mspConnectionPairIds: [mspPairId],
3033
+ pinIds: [p1.pinId, p2.pinId]
3034
+ };
3035
+ this.solvedLongDistanceTraces.push(newSolvedTrace);
3036
+ this.allSolvedTraces.push(newSolvedTrace);
3037
+ this.newlyConnectedPinIds.add(p1.pinId);
3038
+ this.newlyConnectedPinIds.add(p2.pinId);
3039
+ }
3040
+ }
3041
+ this.subSolver = null;
3042
+ this.currentCandidatePair = null;
3043
+ } else if (this.subSolver?.failed) {
3044
+ this.subSolver = null;
3045
+ this.currentCandidatePair = null;
3046
+ }
3047
+ if (this.subSolver) {
3048
+ this.subSolver.step();
3049
+ return;
3050
+ }
3051
+ while (this.queuedCandidatePairs.length > 0) {
3052
+ const nextPair = this.queuedCandidatePairs.shift();
3053
+ const [p1, p2] = nextPair;
3054
+ if (this.newlyConnectedPinIds.has(p1.pinId) || this.newlyConnectedPinIds.has(p2.pinId)) {
3055
+ continue;
3056
+ }
3057
+ this.currentCandidatePair = nextPair;
3058
+ this.subSolver = new SchematicTraceSingleLineSolver2({
3059
+ inputProblem: this.params.inputProblem,
3060
+ pins: this.currentCandidatePair,
3061
+ chipMap: this.chipMap
3062
+ });
3063
+ return;
3064
+ }
3065
+ this.solved = true;
3066
+ }
3067
+ visualize() {
3068
+ if (this.subSolver) {
3069
+ return this.subSolver.visualize();
3070
+ }
3071
+ const graphics = visualizeInputProblem(this.inputProblem);
3072
+ for (const trace of this.solvedLongDistanceTraces) {
3073
+ graphics.lines.push({
3074
+ points: trace.tracePath,
3075
+ strokeColor: "purple"
3076
+ });
3077
+ }
3078
+ for (const [p1, p2] of this.queuedCandidatePairs) {
3079
+ graphics.lines.push({
3080
+ points: [p1, p2],
3081
+ strokeColor: "gray",
3082
+ strokeDash: "4 4"
3083
+ });
3084
+ }
3085
+ return graphics;
3086
+ }
3087
+ getOutput() {
3088
+ if (!this.solved) {
3089
+ return { newTraces: [], allTracesMerged: this.params.alreadySolvedTraces };
3090
+ }
3091
+ return {
3092
+ newTraces: this.solvedLongDistanceTraces,
3093
+ allTracesMerged: [
3094
+ ...this.params.alreadySolvedTraces,
3095
+ ...this.solvedLongDistanceTraces
3096
+ ]
3097
+ };
3098
+ }
3099
+ };
3100
+
3101
+ // lib/solvers/TraceCleanupSolver/hasCollisions.ts
3102
+ var hasCollisions = (pathSegments, obstacles) => {
3103
+ for (let i = 0; i < pathSegments.length - 1; i++) {
3104
+ const p1 = pathSegments[i];
3105
+ const p2 = pathSegments[i + 1];
3106
+ for (const obstacle of obstacles) {
3107
+ if (segmentIntersectsRect(p1, p2, obstacle)) {
3108
+ return true;
3109
+ }
3110
+ }
3111
+ }
3112
+ return false;
3113
+ };
3114
+
3115
+ // lib/solvers/TraceCleanupSolver/countTurns.ts
3116
+ var countTurns = (points) => {
3117
+ let turns = 0;
3118
+ for (let i = 1; i < points.length - 1; i++) {
3119
+ const prev = points[i - 1];
3120
+ const curr = points[i];
3121
+ const next = points[i + 1];
3122
+ const prevVertical = prev.x === curr.x;
3123
+ const nextVertical = curr.x === next.x;
3124
+ if (prevVertical !== nextVertical) {
3125
+ turns++;
3126
+ }
3127
+ }
3128
+ return turns;
3129
+ };
3130
+
3131
+ // lib/solvers/TraceCleanupSolver/tryConnectPoints.ts
3132
+ var tryConnectPoints = (start, end) => {
3133
+ const candidates = [];
3134
+ if (start.x === end.x || start.y === end.y) {
3135
+ candidates.push([start, end]);
3136
+ } else {
3137
+ candidates.push([start, { x: end.x, y: start.y }, end]);
3138
+ candidates.push([start, { x: start.x, y: end.y }, end]);
3139
+ }
3140
+ return candidates;
3141
+ };
3142
+
3143
+ // lib/solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts
3144
+ var hasCollisionsWithLabels = (pathSegments, labels) => {
3145
+ for (let i = 0; i < pathSegments.length - 1; i++) {
3146
+ const p1 = pathSegments[i];
3147
+ const p2 = pathSegments[i + 1];
3148
+ for (const label of labels) {
3149
+ if (segmentIntersectsRect(p1, p2, label)) {
3150
+ return true;
3151
+ }
3152
+ }
3153
+ }
3154
+ return false;
3155
+ };
3156
+
3157
+ // lib/solvers/TraceCleanupSolver/turnMinimization.ts
3158
+ var minimizeTurns = ({
3159
+ path,
3160
+ obstacles,
3161
+ labelBounds
2542
3162
  }) => {
2543
3163
  if (path.length <= 2) {
2544
3164
  return path;
@@ -2595,7 +3215,6 @@ var minimizeTurns = ({
2595
3215
  );
2596
3216
  if (!collidesWithObstacles && !collidesWithLabels) {
2597
3217
  const newTurns = countTurns(testPath);
2598
- const turnsRemoved = stairEndIdx - startIdx - 1;
2599
3218
  optimizedPath = testPath;
2600
3219
  currentTurns = newTurns;
2601
3220
  improved = true;
@@ -2665,621 +3284,294 @@ var minimizeTurns = ({
2665
3284
  labelBounds
2666
3285
  );
2667
3286
  if (!collidesWithObstacles && !collidesWithLabels) {
2668
- optimizedPath = testPath;
2669
- improved = true;
2670
- break;
2671
- }
2672
- }
2673
- }
2674
- }
2675
- }
2676
- const finalSimplifiedPath = simplifyPath(optimizedPath);
2677
- return finalSimplifiedPath;
2678
- };
2679
-
2680
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/minimizeTurnsWithFilteredLabels.ts
2681
- var minimizeTurnsWithFilteredLabels = ({
2682
- traces,
2683
- problem,
2684
- allLabelPlacements,
2685
- mergedLabelNetIdMap,
2686
- paddingBuffer
2687
- }) => {
2688
- let changesMade = false;
2689
- const obstacles = getObstacleRects(problem);
2690
- const newTraces = traces.map((trace) => {
2691
- const originalPath = trace.tracePath;
2692
- const filteredLabels = allLabelPlacements.filter((label) => {
2693
- const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
2694
- if (originalNetIds) {
2695
- return !originalNetIds.has(trace.globalConnNetId);
2696
- }
2697
- return label.globalConnNetId !== trace.globalConnNetId;
2698
- });
2699
- const labelBounds = filteredLabels.map((nl) => ({
2700
- minX: nl.center.x - nl.width / 2 - paddingBuffer,
2701
- maxX: nl.center.x + nl.width / 2 + paddingBuffer,
2702
- minY: nl.center.y - nl.height / 2 - paddingBuffer,
2703
- maxY: nl.center.y + nl.height / 2 + paddingBuffer
2704
- }));
2705
- const newPath = minimizeTurns({
2706
- path: originalPath,
2707
- obstacles,
2708
- labelBounds
2709
- });
2710
- if (newPath.length !== originalPath.length || newPath.some(
2711
- (p, i) => p.x !== originalPath[i].x || p.y !== originalPath[i].y
2712
- )) {
2713
- changesMade = true;
2714
- }
2715
- return {
2716
- ...trace,
2717
- tracePath: newPath
2718
- };
2719
- });
2720
- if (changesMade) {
2721
- return newTraces;
2722
- } else {
2723
- return null;
2724
- }
2725
- };
2726
-
2727
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts
2728
- var balanceLShapes = ({
2729
- traces,
2730
- problem,
2731
- allLabelPlacements
2732
- }) => {
2733
- const TOLERANCE = 1e-5;
2734
- let changesMade = false;
2735
- for (const trace of traces) {
2736
- if (trace.tracePath.length === 4) {
2737
- const [p0, p1, p2, p3] = trace.tracePath;
2738
- const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
2739
- const isVHVShape = p0.x === p1.x && p1.y === p2.y && p2.x === p3.x;
2740
- const isCollinearHorizontal = p0.y === p1.y && p1.y === p2.y && p2.y === p3.y;
2741
- const isCollinearVertical = p0.x === p1.x && p1.x === p2.x && p2.x === p3.x;
2742
- const isCollinear = isCollinearHorizontal || isCollinearVertical;
2743
- let isSameDirection = false;
2744
- if (isHVHShape) {
2745
- isSameDirection = Math.sign(p1.x - p0.x) === Math.sign(p3.x - p2.x);
2746
- } else if (isVHVShape) {
2747
- isSameDirection = Math.sign(p1.y - p0.y) === Math.sign(p3.y - p2.y);
2748
- }
2749
- const isValidZShape = (isHVHShape || isVHVShape) && !isCollinear && isSameDirection;
2750
- if (!isValidZShape) {
2751
- return null;
2752
- }
2753
- }
2754
- }
2755
- const obstacles = getObstacleRects(problem).map((obs) => ({
2756
- ...obs,
2757
- minX: obs.minX + TOLERANCE,
2758
- maxX: obs.maxX - TOLERANCE,
2759
- minY: obs.minY + TOLERANCE,
2760
- maxY: obs.maxY - TOLERANCE
2761
- }));
2762
- const segmentIntersectsAnyRect = (p1, p2, rects) => {
2763
- for (const rect of rects) {
2764
- if (segmentIntersectsRect(p1, p2, rect)) {
2765
- return true;
2766
- }
2767
- }
2768
- return false;
2769
- };
2770
- const getLabelBounds = (labels, traceNetId) => {
2771
- const filteredLabels = labels.filter(
2772
- (label) => label.globalConnNetId !== traceNetId
2773
- );
2774
- return filteredLabels.map((nl) => ({
2775
- minX: nl.center.x - nl.width / 2 + TOLERANCE,
2776
- maxX: nl.center.x + nl.width / 2 - TOLERANCE,
2777
- minY: nl.center.y - nl.height / 2 + TOLERANCE,
2778
- maxY: nl.center.y + nl.height / 2 - TOLERANCE
2779
- }));
2780
- };
2781
- const newTraces = traces.map((trace) => {
2782
- let newPath = [...trace.tracePath];
2783
- if (newPath.length < 4) {
2784
- return { ...trace };
2785
- }
2786
- const labelBounds = getLabelBounds(
2787
- allLabelPlacements,
2788
- trace.globalConnNetId
2789
- );
2790
- if (newPath.length === 4) {
2791
- const [p0, p1, p2, p3] = newPath;
2792
- let p1New, p2New;
2793
- const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
2794
- if (isHVHShape) {
2795
- const idealX = (p0.x + p3.x) / 2;
2796
- p1New = { x: idealX, y: p1.y };
2797
- p2New = { x: idealX, y: p2.y };
2798
- } else {
2799
- const idealY = (p0.y + p3.y) / 2;
2800
- p1New = { x: p1.x, y: idealY };
2801
- p2New = { x: p2.x, y: idealY };
2802
- }
2803
- const collides = segmentIntersectsAnyRect(p0, p1New, obstacles) || segmentIntersectsAnyRect(p1New, p2New, obstacles) || segmentIntersectsAnyRect(p2New, p3, obstacles) || segmentIntersectsAnyRect(p0, p1New, labelBounds) || segmentIntersectsAnyRect(p1New, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3, labelBounds);
2804
- if (!collides) {
2805
- newPath[1] = p1New;
2806
- newPath[2] = p2New;
2807
- changesMade = true;
2808
- }
2809
- return { ...trace, tracePath: simplifyPath(newPath) };
2810
- }
2811
- for (let i = 1; i < newPath.length - 4; i++) {
2812
- const p1 = newPath[i];
2813
- const p2 = newPath[i + 1];
2814
- const p3 = newPath[i + 2];
2815
- const p4 = newPath[i + 3];
2816
- const isHVHZShape = p1.y === p2.y && p2.x === p3.x && p3.y === p4.y;
2817
- const isVHVZShape = p1.x === p2.x && p2.y === p3.y && p3.x === p4.x;
2818
- const isCollinearHorizontal = p1.y === p2.y && p2.y === p3.y && p3.y === p4.y;
2819
- const isCollinearVertical = p1.x === p2.x && p2.x === p3.x && p3.x === p4.x;
2820
- const isCollinear = isCollinearHorizontal || isCollinearVertical;
2821
- let isSameDirection = false;
2822
- if (isHVHZShape) {
2823
- isSameDirection = Math.sign(p2.x - p1.x) === Math.sign(p4.x - p3.x);
2824
- } else if (isVHVZShape) {
2825
- isSameDirection = Math.sign(p2.y - p1.y) === Math.sign(p4.y - p3.y);
2826
- }
2827
- const isValidZShape = (isHVHZShape || isVHVZShape) && !isCollinear && isSameDirection;
2828
- if (!isValidZShape) {
2829
- continue;
2830
- }
2831
- let p2New, p3New;
2832
- const len1Original = isHVHZShape ? Math.abs(p1.x - p2.x) : Math.abs(p1.y - p2.y);
2833
- const len2Original = isHVHZShape ? Math.abs(p3.x - p4.x) : Math.abs(p3.y - p4.y);
2834
- if (Math.abs(len1Original - len2Original) < 1e-3) {
2835
- continue;
2836
- }
2837
- if (isHVHZShape) {
2838
- const idealX = (p1.x + p4.x) / 2;
2839
- p2New = { x: idealX, y: p2.y };
2840
- p3New = { x: idealX, y: p3.y };
2841
- } else {
2842
- const idealY = (p1.y + p4.y) / 2;
2843
- p2New = { x: p2.x, y: idealY };
2844
- p3New = { x: p3.x, y: idealY };
2845
- }
2846
- const collides = segmentIntersectsAnyRect(p1, p2New, obstacles) || segmentIntersectsAnyRect(p2New, p3New, obstacles) || segmentIntersectsAnyRect(p3New, p4, obstacles) || segmentIntersectsAnyRect(p1, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3New, labelBounds) || segmentIntersectsAnyRect(p3New, p4, labelBounds);
2847
- if (!collides) {
2848
- newPath[i + 1] = p2New;
2849
- newPath[i + 2] = p3New;
2850
- changesMade = true;
2851
- i = 0;
2852
- }
2853
- }
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;
2864
- }
2865
- };
2866
-
2867
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts
2868
- var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2869
- problem;
2870
- traces;
2871
- netTempLabelPlacements;
2872
- netLabelPlacements;
2873
- updatedTraces;
2874
- mergedLabelNetIdMap;
2875
- detourCountByLabel;
2876
- PADDING_BUFFER = 0.1;
2877
- constructor(solverInput) {
2878
- 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;
2934
- }
2935
- _step() {
2936
- if (!this.traces || this.traces.length === 0 || !this.netTempLabelPlacements || this.netTempLabelPlacements.length === 0) {
2937
- this.solved = true;
2938
- return;
2939
- }
2940
- this.detourCountByLabel = {};
2941
- 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) => {
2950
- const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId];
2951
- if (originalNetIds) {
2952
- return !originalNetIds.has(o.trace.globalConnNetId);
2953
- }
2954
- return o.trace.globalConnNetId !== o.label.globalConnNetId;
2955
- });
2956
- if (unfriendlyOverlaps.length === 0) {
2957
- this.solved = true;
2958
- return;
2959
- }
2960
- const updatedTracesMap = {};
2961
- for (const trace of this.traces) {
2962
- updatedTracesMap[trace.mspPairId] = trace;
2963
- }
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) {
2980
- this.detourCountByLabel[labelId] = detourCount + 1;
2981
- }
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
- }
2997
- const balancedTraces = balanceLShapes({
2998
- traces: this.updatedTraces,
2999
- problem: this.problem,
3000
- allLabelPlacements: this.netLabelPlacements
3001
- });
3002
- if (balancedTraces) {
3003
- this.updatedTraces = balancedTraces;
3004
- }
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;
3014
- }
3015
- this.solved = true;
3016
- }
3017
- getOutput() {
3018
- return {
3019
- traces: this.updatedTraces,
3020
- netLabelPlacements: this.netLabelPlacements
3021
- };
3022
- }
3023
- visualize() {
3024
- const graphics = visualizeInputProblem(this.problem);
3025
- if (!graphics.lines) graphics.lines = [];
3026
- if (!graphics.circles) graphics.circles = [];
3027
- if (!graphics.texts) graphics.texts = [];
3028
- if (!graphics.rects) graphics.rects = [];
3029
- for (const trace of this.updatedTraces) {
3030
- graphics.lines.push({
3031
- points: trace.tracePath,
3032
- strokeColor: "purple"
3033
- });
3034
- }
3035
- for (const p of this.netLabelPlacements) {
3036
- 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)
3046
- });
3047
- }
3048
- return graphics;
3049
- }
3050
- };
3051
-
3052
- // lib/solvers/SchematicTracePipelineSolver/correctPinsInsideChip.ts
3053
- var correctPinsInsideChips = (problem) => {
3054
- for (const chip of problem.chips) {
3055
- const bounds = getInputChipBounds(chip);
3056
- for (const pin of chip.pins) {
3057
- const isInside = pin.x > bounds.minX && pin.x < bounds.maxX && pin.y > bounds.minY && pin.y < bounds.maxY;
3058
- if (!isInside) continue;
3059
- const distLeft = pin.x - bounds.minX;
3060
- const distRight = bounds.maxX - pin.x;
3061
- const distBottom = pin.y - bounds.minY;
3062
- const distTop = bounds.maxY - pin.y;
3063
- const minDist = Math.min(distLeft, distRight, distBottom, distTop);
3064
- if (minDist === distLeft) {
3065
- pin.x = bounds.minX;
3066
- } else if (minDist === distRight) {
3067
- pin.x = bounds.maxX;
3068
- } else if (minDist === distBottom) {
3069
- pin.y = bounds.minY;
3070
- } else {
3071
- pin.y = bounds.maxY;
3072
- }
3073
- pin._facingDirection = void 0;
3074
- }
3075
- }
3076
- };
3077
-
3078
- // lib/solvers/SchematicTracePipelineSolver/expandChipsToFitPins.ts
3079
- var expandChipsToFitPins = (problem) => {
3080
- for (const chip of problem.chips) {
3081
- const halfWidth = chip.width / 2;
3082
- const halfHeight = chip.height / 2;
3083
- let maxDx = 0;
3084
- let maxDy = 0;
3085
- for (const pin of chip.pins) {
3086
- const dx = Math.abs(pin.x - chip.center.x);
3087
- const dy = Math.abs(pin.y - chip.center.y);
3088
- if (dx > maxDx) maxDx = dx;
3089
- if (dy > maxDy) maxDy = dy;
3090
- }
3091
- const newHalfWidth = Math.max(halfWidth, maxDx);
3092
- const newHalfHeight = Math.max(halfHeight, maxDy);
3093
- if (newHalfWidth > halfWidth || newHalfHeight > halfHeight) {
3094
- chip.width = newHalfWidth * 2;
3095
- chip.height = newHalfHeight * 2;
3096
- for (const pin of chip.pins) {
3097
- pin._facingDirection = void 0;
3287
+ optimizedPath = testPath;
3288
+ improved = true;
3289
+ break;
3290
+ }
3291
+ }
3098
3292
  }
3099
3293
  }
3100
3294
  }
3295
+ const finalSimplifiedPath = simplifyPath(optimizedPath);
3296
+ return finalSimplifiedPath;
3101
3297
  };
3102
3298
 
3103
- // lib/utils/does-trace-overlap-with-existing-traces.ts
3104
- import { doSegmentsIntersect } from "@tscircuit/math-utils";
3105
- function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
3106
- for (let i = 0; i < newTracePath.length - 1; i++) {
3107
- const newSegmentP1 = newTracePath[i];
3108
- const newSegmentP2 = newTracePath[i + 1];
3109
- for (const existingTrace of existingTraces) {
3110
- for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
3111
- const existingSegmentP1 = existingTrace.tracePath[j];
3112
- const existingSegmentP2 = existingTrace.tracePath[j + 1];
3113
- if (doSegmentsIntersect(
3114
- newSegmentP1,
3115
- newSegmentP2,
3116
- existingSegmentP1,
3117
- existingSegmentP2
3118
- )) {
3119
- return true;
3120
- }
3121
- }
3122
- }
3299
+ // lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts
3300
+ var minimizeTurnsWithFilteredLabels = ({
3301
+ targetMspConnectionPairId,
3302
+ traces,
3303
+ inputProblem,
3304
+ allLabelPlacements,
3305
+ mergedLabelNetIdMap,
3306
+ paddingBuffer
3307
+ }) => {
3308
+ const targetTrace = traces.find(
3309
+ (t) => t.mspPairId === targetMspConnectionPairId
3310
+ );
3311
+ if (!targetTrace) {
3312
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`);
3123
3313
  }
3124
- return false;
3125
- }
3126
-
3127
- // lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
3128
- var NEAREST_NEIGHBOR_COUNT = 3;
3129
- var distance = (p1, p2) => {
3130
- return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
3314
+ const obstacleTraces = traces.filter(
3315
+ (t) => t.mspPairId !== targetMspConnectionPairId
3316
+ );
3317
+ const TRACE_WIDTH = 0.01;
3318
+ const traceObstacles = obstacleTraces.flatMap(
3319
+ (trace, i) => trace.tracePath.slice(0, -1).map((p1, pi) => {
3320
+ const p2 = trace.tracePath[pi + 1];
3321
+ return {
3322
+ chipId: `trace-obstacle-${i}-${pi}`,
3323
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
3324
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
3325
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
3326
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2
3327
+ };
3328
+ })
3329
+ );
3330
+ const staticObstacles = getObstacleRects(inputProblem);
3331
+ const combinedObstacles = [...staticObstacles, ...traceObstacles];
3332
+ const originalPath = targetTrace.tracePath;
3333
+ const filteredLabels = allLabelPlacements.filter((label) => {
3334
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
3335
+ if (originalNetIds) {
3336
+ return !originalNetIds.has(targetTrace.globalConnNetId);
3337
+ }
3338
+ return label.globalConnNetId !== targetTrace.globalConnNetId;
3339
+ });
3340
+ const labelBounds = filteredLabels.map((nl) => ({
3341
+ minX: nl.center.x - nl.width / 2 - paddingBuffer,
3342
+ maxX: nl.center.x + nl.width / 2 + paddingBuffer,
3343
+ minY: nl.center.y - nl.height / 2 - paddingBuffer,
3344
+ maxY: nl.center.y + nl.height / 2 + paddingBuffer
3345
+ }));
3346
+ const newPath = minimizeTurns({
3347
+ path: originalPath,
3348
+ obstacles: combinedObstacles,
3349
+ labelBounds
3350
+ });
3351
+ return {
3352
+ ...targetTrace,
3353
+ tracePath: newPath
3354
+ };
3131
3355
  };
3132
- var LongDistancePairSolver = class extends BaseSolver {
3133
- constructor(params) {
3134
- super();
3135
- this.params = params;
3136
- const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
3137
- this.inputProblem = inputProblem;
3138
- this.allSolvedTraces = [...alreadySolvedTraces];
3139
- const primaryConnectedPinIds = /* @__PURE__ */ new Set();
3140
- for (const pair of primaryMspConnectionPairs) {
3141
- primaryConnectedPinIds.add(pair.pins[0].pinId);
3142
- primaryConnectedPinIds.add(pair.pins[1].pinId);
3143
- }
3144
- const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
3145
- this.netConnMap = netConnMap;
3146
- const pinMap = /* @__PURE__ */ new Map();
3147
- for (const chip of inputProblem.chips) {
3148
- this.chipMap[chip.chipId] = chip;
3149
- for (const pin of chip.pins) {
3150
- pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
3356
+
3357
+ // lib/solvers/TraceCleanupSolver/balanceZShapes.ts
3358
+ var balanceZShapes = ({
3359
+ targetMspConnectionPairId,
3360
+ traces,
3361
+ inputProblem,
3362
+ allLabelPlacements,
3363
+ mergedLabelNetIdMap,
3364
+ paddingBuffer
3365
+ }) => {
3366
+ const targetTrace = traces.find(
3367
+ (t) => t.mspPairId === targetMspConnectionPairId
3368
+ );
3369
+ if (!targetTrace) {
3370
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`);
3371
+ }
3372
+ const TOLERANCE = 1e-5;
3373
+ const obstacleTraces = traces.filter(
3374
+ (t) => t.mspPairId !== targetMspConnectionPairId
3375
+ );
3376
+ const TRACE_WIDTH = 0.01;
3377
+ const traceObstacles = obstacleTraces.flatMap(
3378
+ (trace, i) => trace.tracePath.slice(0, -1).map((p1, pi) => {
3379
+ const p2 = trace.tracePath[pi + 1];
3380
+ return {
3381
+ chipId: `trace-obstacle-${i}-${pi}`,
3382
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
3383
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
3384
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
3385
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2
3386
+ };
3387
+ })
3388
+ );
3389
+ const staticObstacles = getObstacleRects(inputProblem).map((obs) => ({
3390
+ ...obs,
3391
+ minX: obs.minX + TOLERANCE,
3392
+ maxX: obs.maxX - TOLERANCE,
3393
+ minY: obs.minY + TOLERANCE,
3394
+ maxY: obs.maxY - TOLERANCE
3395
+ }));
3396
+ const combinedObstacles = [...staticObstacles, ...traceObstacles];
3397
+ const segmentIntersectsAnyRect = (p1, p2, rects) => {
3398
+ for (const rect of rects) {
3399
+ if (segmentIntersectsRect(p1, p2, rect)) {
3400
+ return true;
3151
3401
  }
3152
3402
  }
3153
- const candidatePairs = [];
3154
- const addedPairKeys = /* @__PURE__ */ new Set();
3155
- for (const netId of Object.keys(netConnMap.netMap)) {
3156
- const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId);
3157
- if (allPinIdsInNet.length < 2) continue;
3158
- const unconnectedPinIds = allPinIdsInNet.filter(
3159
- (pinId) => !primaryConnectedPinIds.has(pinId)
3160
- );
3161
- for (const unconnectedPinId of unconnectedPinIds) {
3162
- const sourcePin = pinMap.get(unconnectedPinId);
3163
- if (!sourcePin) continue;
3164
- const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
3165
- const targetPin = pinMap.get(otherPinId);
3166
- if (!targetPin) return [];
3167
- return [
3168
- {
3169
- pin: targetPin,
3170
- distance: distance(sourcePin, targetPin)
3171
- }
3172
- ];
3173
- }).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);
3174
- for (const neighbor of neighbors) {
3175
- const pair = [sourcePin, neighbor.pin];
3176
- const pairKey = pair.map((p) => p.pinId).sort().join("--");
3177
- if (!addedPairKeys.has(pairKey)) {
3178
- candidatePairs.push(pair);
3179
- addedPairKeys.add(pairKey);
3180
- }
3181
- }
3182
- }
3403
+ return false;
3404
+ };
3405
+ const filteredLabels = allLabelPlacements.filter((label) => {
3406
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
3407
+ if (originalNetIds) {
3408
+ return !originalNetIds.has(targetTrace.globalConnNetId);
3409
+ }
3410
+ return label.globalConnNetId !== targetTrace.globalConnNetId;
3411
+ });
3412
+ const labelBounds = filteredLabels.map((nl) => ({
3413
+ minX: nl.center.x - nl.width / 2 + TOLERANCE,
3414
+ maxX: nl.center.x + nl.width / 2 - TOLERANCE,
3415
+ minY: nl.center.y - nl.height / 2 + TOLERANCE,
3416
+ maxY: nl.center.y + nl.height / 2 - TOLERANCE
3417
+ }));
3418
+ const newPath = [...targetTrace.tracePath];
3419
+ if (newPath.length < 4) {
3420
+ return { ...targetTrace };
3421
+ }
3422
+ if (newPath.length === 4) {
3423
+ const [p0, p1, p2, p3] = newPath;
3424
+ let p1New;
3425
+ let p2New;
3426
+ const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y;
3427
+ if (isHVHShape) {
3428
+ const idealX = (p0.x + p3.x) / 2;
3429
+ p1New = { x: idealX, y: p1.y };
3430
+ p2New = { x: idealX, y: p2.y };
3431
+ } else {
3432
+ const idealY = (p0.y + p3.y) / 2;
3433
+ p1New = { x: p1.x, y: idealY };
3434
+ p2New = { x: p2.x, y: idealY };
3435
+ }
3436
+ const collides = segmentIntersectsAnyRect(p0, p1New, combinedObstacles) || segmentIntersectsAnyRect(p1New, p2New, combinedObstacles) || segmentIntersectsAnyRect(p2New, p3, combinedObstacles) || segmentIntersectsAnyRect(p0, p1New, labelBounds) || segmentIntersectsAnyRect(p1New, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3, labelBounds);
3437
+ if (!collides) {
3438
+ newPath[1] = p1New;
3439
+ newPath[2] = p2New;
3440
+ }
3441
+ return { ...targetTrace, tracePath: simplifyPath(newPath) };
3442
+ }
3443
+ for (let i = 1; i < newPath.length - 4; i++) {
3444
+ const p1 = newPath[i];
3445
+ const p2 = newPath[i + 1];
3446
+ const p3 = newPath[i + 2];
3447
+ const p4 = newPath[i + 3];
3448
+ const isHVHZShape = p1.y === p2.y && p2.x === p3.x && p3.y === p4.y;
3449
+ const isVHVZShape = p1.x === p2.x && p2.y === p3.y && p3.x === p4.x;
3450
+ const isCollinearHorizontal = p1.y === p2.y && p2.y === p3.y && p3.y === p4.y;
3451
+ const isCollinearVertical = p1.x === p2.x && p2.x === p3.x && p3.x === p4.x;
3452
+ const isCollinear = isCollinearHorizontal || isCollinearVertical;
3453
+ let isSameDirection = false;
3454
+ if (isHVHZShape) {
3455
+ isSameDirection = Math.sign(p2.x - p1.x) === Math.sign(p4.x - p3.x);
3456
+ } else if (isVHVZShape) {
3457
+ isSameDirection = Math.sign(p2.y - p1.y) === Math.sign(p4.y - p3.y);
3458
+ }
3459
+ const isValidZShape = (isHVHZShape || isVHVZShape) && !isCollinear && isSameDirection;
3460
+ if (!isValidZShape) {
3461
+ continue;
3462
+ }
3463
+ let p2New;
3464
+ let p3New;
3465
+ const len1Original = isHVHZShape ? Math.abs(p1.x - p2.x) : Math.abs(p1.y - p2.y);
3466
+ const len2Original = isHVHZShape ? Math.abs(p3.x - p4.x) : Math.abs(p3.y - p4.y);
3467
+ if (Math.abs(len1Original - len2Original) < 1e-3) {
3468
+ continue;
3469
+ }
3470
+ if (isHVHZShape) {
3471
+ const idealX = (p1.x + p4.x) / 2;
3472
+ p2New = { x: idealX, y: p2.y };
3473
+ p3New = { x: idealX, y: p3.y };
3474
+ } else {
3475
+ const idealY = (p1.y + p4.y) / 2;
3476
+ p2New = { x: p2.x, y: idealY };
3477
+ p3New = { x: p3.x, y: idealY };
3478
+ }
3479
+ const collides = segmentIntersectsAnyRect(p1, p2New, combinedObstacles) || segmentIntersectsAnyRect(p2New, p3New, combinedObstacles) || segmentIntersectsAnyRect(p3New, p4, combinedObstacles) || segmentIntersectsAnyRect(p1, p2New, labelBounds) || segmentIntersectsAnyRect(p2New, p3New, labelBounds) || segmentIntersectsAnyRect(p3New, p4, labelBounds);
3480
+ if (!collides) {
3481
+ newPath[i + 1] = p2New;
3482
+ newPath[i + 2] = p3New;
3483
+ i = 0;
3183
3484
  }
3184
- this.queuedCandidatePairs = candidatePairs;
3185
3485
  }
3186
- solvedLongDistanceTraces = [];
3187
- queuedCandidatePairs = [];
3188
- currentCandidatePair = null;
3189
- subSolver = null;
3190
- chipMap = {};
3191
- inputProblem;
3192
- netConnMap;
3193
- newlyConnectedPinIds = /* @__PURE__ */ new Set();
3194
- allSolvedTraces = [];
3195
- getConstructorParams() {
3196
- return this.params;
3486
+ const finalSimplifiedPath = simplifyPath(newPath);
3487
+ return {
3488
+ ...targetTrace,
3489
+ tracePath: finalSimplifiedPath
3490
+ };
3491
+ };
3492
+
3493
+ // lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts
3494
+ var TraceCleanupSolver = class extends BaseSolver {
3495
+ input;
3496
+ outputTraces;
3497
+ traceIdQueue;
3498
+ tracesMap;
3499
+ pipelineStep = "minimizing_turns";
3500
+ activeTraceId = null;
3501
+ // New property
3502
+ constructor(solverInput) {
3503
+ super();
3504
+ this.input = solverInput;
3505
+ this.outputTraces = [...solverInput.allTraces];
3506
+ this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]));
3507
+ this.traceIdQueue = Array.from(solverInput.targetTraceIds);
3197
3508
  }
3198
3509
  _step() {
3199
- if (this.subSolver?.solved) {
3200
- const newTracePath = this.subSolver.solvedTracePath;
3201
- if (newTracePath && this.currentCandidatePair) {
3202
- const isTraceClear = !doesTraceOverlapWithExistingTraces(
3203
- newTracePath,
3204
- this.allSolvedTraces
3205
- );
3206
- if (isTraceClear) {
3207
- const [p1, p2] = this.currentCandidatePair;
3208
- const globalConnNetId = this.netConnMap.getNetConnectedToId(p1.pinId);
3209
- const mspPairId = `${p1.pinId}-${p2.pinId}`;
3210
- const newSolvedTrace = {
3211
- mspPairId,
3212
- dcConnNetId: globalConnNetId,
3213
- globalConnNetId,
3214
- pins: [p1, p2],
3215
- tracePath: newTracePath,
3216
- mspConnectionPairIds: [mspPairId],
3217
- pinIds: [p1.pinId, p2.pinId]
3218
- };
3219
- this.solvedLongDistanceTraces.push(newSolvedTrace);
3220
- this.allSolvedTraces.push(newSolvedTrace);
3221
- this.newlyConnectedPinIds.add(p1.pinId);
3222
- this.newlyConnectedPinIds.add(p2.pinId);
3223
- }
3224
- }
3225
- this.subSolver = null;
3226
- this.currentCandidatePair = null;
3227
- } else if (this.subSolver?.failed) {
3228
- this.subSolver = null;
3229
- this.currentCandidatePair = null;
3510
+ if (this.pipelineStep === "minimizing_turns" && this.traceIdQueue.length === 0) {
3511
+ this.pipelineStep = "balancing_l_shapes";
3512
+ this.traceIdQueue = Array.from(this.input.targetTraceIds);
3230
3513
  }
3231
- if (this.subSolver) {
3232
- this.subSolver.step();
3514
+ if (this.pipelineStep === "balancing_l_shapes" && this.traceIdQueue.length === 0) {
3515
+ this.solved = true;
3233
3516
  return;
3234
3517
  }
3235
- while (this.queuedCandidatePairs.length > 0) {
3236
- const nextPair = this.queuedCandidatePairs.shift();
3237
- const [p1, p2] = nextPair;
3238
- if (this.newlyConnectedPinIds.has(p1.pinId) || this.newlyConnectedPinIds.has(p2.pinId)) {
3239
- continue;
3240
- }
3241
- this.currentCandidatePair = nextPair;
3242
- this.subSolver = new SchematicTraceSingleLineSolver2({
3243
- inputProblem: this.params.inputProblem,
3244
- pins: this.currentCandidatePair,
3245
- chipMap: this.chipMap
3246
- });
3518
+ const targetMspConnectionPairId = this.traceIdQueue.shift();
3519
+ this.activeTraceId = targetMspConnectionPairId;
3520
+ const originalTrace = this.tracesMap.get(targetMspConnectionPairId);
3521
+ const { tracePath } = originalTrace;
3522
+ const is4PointRectangle = (path) => {
3523
+ if (path.length !== 4) return false;
3524
+ const [p0, p1, p2, p3] = path;
3525
+ const isHVHC = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x;
3526
+ const isVHVC = p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y;
3527
+ return isHVHC || isVHVC;
3528
+ };
3529
+ if (is4PointRectangle(tracePath)) {
3247
3530
  return;
3248
3531
  }
3249
- this.solved = true;
3250
- }
3251
- visualize() {
3252
- if (this.subSolver) {
3253
- return this.subSolver.visualize();
3254
- }
3255
- const graphics = visualizeInputProblem(this.inputProblem);
3256
- for (const trace of this.solvedLongDistanceTraces) {
3257
- graphics.lines.push({
3258
- points: trace.tracePath,
3259
- strokeColor: "purple"
3532
+ const allTraces = Array.from(this.tracesMap.values());
3533
+ let updatedTrace;
3534
+ if (this.pipelineStep === "minimizing_turns") {
3535
+ updatedTrace = minimizeTurnsWithFilteredLabels({
3536
+ ...this.input,
3537
+ targetMspConnectionPairId,
3538
+ traces: allTraces
3260
3539
  });
3261
- }
3262
- for (const [p1, p2] of this.queuedCandidatePairs) {
3263
- graphics.lines.push({
3264
- points: [p1, p2],
3265
- strokeColor: "gray",
3266
- strokeDash: "4 4"
3540
+ } else {
3541
+ updatedTrace = balanceZShapes({
3542
+ ...this.input,
3543
+ targetMspConnectionPairId,
3544
+ traces: allTraces
3267
3545
  });
3268
3546
  }
3269
- return graphics;
3547
+ this.tracesMap.set(targetMspConnectionPairId, updatedTrace);
3548
+ this.outputTraces = Array.from(this.tracesMap.values());
3270
3549
  }
3271
3550
  getOutput() {
3272
- if (!this.solved) {
3273
- return { newTraces: [], allTracesMerged: this.params.alreadySolvedTraces };
3274
- }
3275
3551
  return {
3276
- newTraces: this.solvedLongDistanceTraces,
3277
- allTracesMerged: [
3278
- ...this.params.alreadySolvedTraces,
3279
- ...this.solvedLongDistanceTraces
3280
- ]
3552
+ traces: this.outputTraces
3281
3553
  };
3282
3554
  }
3555
+ visualize() {
3556
+ const graphics = visualizeInputProblem(this.input.inputProblem, {
3557
+ chipAlpha: 0.1,
3558
+ connectionAlpha: 0.1
3559
+ });
3560
+ if (!graphics.lines) graphics.lines = [];
3561
+ if (!graphics.points) graphics.points = [];
3562
+ if (!graphics.rects) graphics.rects = [];
3563
+ if (!graphics.circles) graphics.circles = [];
3564
+ if (!graphics.texts) graphics.texts = [];
3565
+ for (const trace of this.outputTraces) {
3566
+ const line = {
3567
+ points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
3568
+ strokeColor: trace.mspPairId === this.activeTraceId ? "red" : "blue"
3569
+ // Highlight active trace
3570
+ };
3571
+ graphics.lines.push(line);
3572
+ }
3573
+ return graphics;
3574
+ }
3283
3575
  };
3284
3576
 
3285
3577
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
@@ -3299,7 +3591,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3299
3591
  longDistancePairSolver;
3300
3592
  traceOverlapShiftSolver;
3301
3593
  netLabelPlacementSolver;
3594
+ labelMergingSolver;
3302
3595
  traceLabelOverlapAvoidanceSolver;
3596
+ traceCleanupSolver;
3303
3597
  startTimeOfPhase;
3304
3598
  endTimeOfPhase;
3305
3599
  timeSpentOnPhase;
@@ -3405,14 +3699,36 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3405
3699
  netLabelPlacements
3406
3700
  }
3407
3701
  ];
3408
- },
3409
- {
3410
- onSolved: (instance) => {
3411
- if (instance.traceLabelOverlapAvoidanceSolver && instance.netLabelPlacementSolver) {
3412
- const { netLabelPlacements } = instance.traceLabelOverlapAvoidanceSolver.getOutput();
3413
- instance.netLabelPlacementSolver.netLabelPlacements = netLabelPlacements;
3414
- }
3702
+ }
3703
+ ),
3704
+ definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
3705
+ const prevSolverOutput = instance.traceLabelOverlapAvoidanceSolver.getOutput();
3706
+ const traces = prevSolverOutput.traces;
3707
+ const labelMergingOutput = instance.traceLabelOverlapAvoidanceSolver.labelMergingSolver.getOutput();
3708
+ return [
3709
+ {
3710
+ inputProblem: instance.inputProblem,
3711
+ allTraces: traces,
3712
+ targetTraceIds: new Set(traces.map((t) => t.mspPairId)),
3713
+ allLabelPlacements: labelMergingOutput.netLabelPlacements,
3714
+ mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
3715
+ paddingBuffer: 0.1
3415
3716
  }
3717
+ ];
3718
+ }),
3719
+ definePipelineStep(
3720
+ "netLabelPlacementSolver",
3721
+ NetLabelPlacementSolver,
3722
+ (instance) => {
3723
+ const traces = instance.traceCleanupSolver?.getOutput().traces ?? instance.traceLabelOverlapAvoidanceSolver.getOutput().traces;
3724
+ return [
3725
+ {
3726
+ inputProblem: instance.inputProblem,
3727
+ inputTraceMap: Object.fromEntries(
3728
+ traces.map((trace) => [trace.mspPairId, trace])
3729
+ )
3730
+ }
3731
+ ];
3416
3732
  }
3417
3733
  )
3418
3734
  ];
@@ -3498,14 +3814,17 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3498
3814
  return viz;
3499
3815
  })
3500
3816
  ];
3501
- if (visualizations.length === 1) return visualizations[0];
3502
- return {
3817
+ if (visualizations.length === 1) {
3818
+ return visualizations[0];
3819
+ }
3820
+ const finalGraphics = {
3503
3821
  points: visualizations.flatMap((v) => v.points || []),
3504
3822
  rects: visualizations.flatMap((v) => v.rects || []),
3505
3823
  lines: visualizations.flatMap((v) => v.lines || []),
3506
3824
  circles: visualizations.flatMap((v) => v.circles || []),
3507
3825
  texts: visualizations.flatMap((v) => v.texts || [])
3508
3826
  };
3827
+ return finalGraphics;
3509
3828
  }
3510
3829
  /**
3511
3830
  * A lightweight version of the visualize method that can be used to stream