@tscircuit/schematic-trace-solver 0.0.41 → 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 (46) hide show
  1. package/dist/index.d.ts +118 -7
  2. package/dist/index.js +760 -432
  3. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +30 -17
  4. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +94 -192
  5. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +13 -24
  6. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +205 -0
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +144 -0
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +117 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts +132 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{balanceLShapes.ts → sub-solvers/TraceCleanupSolver/balanceLShapes.ts} +12 -10
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisions.ts → sub-solvers/TraceCleanupSolver/hasCollisions.ts} +1 -1
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisionsWithLabels.ts → sub-solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts} +1 -1
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{minimizeTurnsWithFilteredLabels.ts → sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts} +6 -6
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{simplifyPath.ts → sub-solvers/TraceCleanupSolver/simplifyPath.ts} +1 -1
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{turnMinimization.ts → sub-solvers/TraceCleanupSolver/turnMinimization.ts} +0 -2
  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/tests/assets/1.input.json +379 -0
  20. package/tests/assets/2.input.json +330 -0
  21. package/tests/assets/3.input.json +388 -0
  22. package/tests/assets/MergedNetLabelObstacles.test.input.json +154 -0
  23. package/tests/assets/OverlapAvoidanceStepSolver.test.input.json +682 -0
  24. package/tests/assets/SingleOverlapSolver.test.input.json +170 -0
  25. package/tests/assets/TraceCleanupSolver.test.input.json +216 -0
  26. package/tests/assets/TraceLabelOverlapAvoidanceSolver.test.input.json +424 -0
  27. package/tests/examples/__snapshots__/example25.snap.svg +3 -3
  28. package/tests/examples/__snapshots__/example26.snap.svg +3 -3
  29. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.test.ts +18 -0
  30. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +243 -0
  31. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +223 -0
  32. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +205 -0
  33. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +184 -0
  34. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView01.test.ts +29 -0
  35. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView02.test.ts +27 -0
  36. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +39 -0
  37. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/MergedNetLabelObstacles.test.ts +14 -0
  38. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/OverlapAvoidanceStepSolver.test.ts +20 -0
  39. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/SingleOverlapSolver.test.ts +10 -0
  40. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/TraceCleanupSolver.test.ts +19 -0
  41. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +140 -0
  42. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +216 -0
  43. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +125 -0
  44. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
  45. /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{countTurns.ts → sub-solvers/TraceCleanupSolver/countTurns.ts} +0 -0
  46. /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,249 +2411,7 @@ 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
- ]);
2332
- }
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;
2342
- };
2343
- var tryFourPointDetour = ({
2344
- initialTrace,
2345
- label,
2346
- labelBounds,
2347
- obstacles,
2348
- paddingBuffer,
2349
- detourCount
2350
- }) => {
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
2414
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/tryConnectPoints.ts
2512
2415
  var tryConnectPoints = (start, end) => {
2513
2416
  const candidates = [];
2514
2417
  if (start.x === end.x || start.y === end.y) {
@@ -2520,7 +2423,7 @@ var tryConnectPoints = (start, end) => {
2520
2423
  return candidates;
2521
2424
  };
2522
2425
 
2523
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/hasCollisionsWithLabels.ts
2426
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts
2524
2427
  var hasCollisionsWithLabels = (pathSegments, labels) => {
2525
2428
  for (let i = 0; i < pathSegments.length - 1; i++) {
2526
2429
  const p1 = pathSegments[i];
@@ -2534,7 +2437,7 @@ var hasCollisionsWithLabels = (pathSegments, labels) => {
2534
2437
  return false;
2535
2438
  };
2536
2439
 
2537
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/turnMinimization.ts
2440
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/turnMinimization.ts
2538
2441
  var minimizeTurns = ({
2539
2442
  path,
2540
2443
  obstacles,
@@ -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) {
@@ -2860,189 +2764,601 @@ var balanceLShapes = ({
2860
2764
  if (changesMade) {
2861
2765
  return newTraces;
2862
2766
  } else {
2863
- return null;
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
+ }
2864
3035
  }
3036
+ return candidateDetours.map((detourPoints) => [
3037
+ ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
3038
+ ...detourPoints,
3039
+ ...initialTrace.tracePath.slice(collidingSegIndex + 1)
3040
+ ]);
2865
3041
  };
2866
3042
 
2867
- // lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts
2868
- var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
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;
2869
3090
  problem;
2870
- traces;
2871
- netTempLabelPlacements;
2872
- netLabelPlacements;
2873
- updatedTraces;
2874
- mergedLabelNetIdMap;
2875
- detourCountByLabel;
2876
- PADDING_BUFFER = 0.1;
3091
+ obstacles;
3092
+ label;
2877
3093
  constructor(solverInput) {
2878
3094
  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 = [];
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;
2888
3118
  return;
2889
3119
  }
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);
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;
2900
3125
  }
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
- ]
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"
2931
3149
  });
2932
3150
  }
2933
- this.netTempLabelPlacements = finalPlacements;
3151
+ if (this.solvedTracePath) {
3152
+ graphics.lines.push({
3153
+ points: this.solvedTracePath,
3154
+ strokeColor: "green"
3155
+ });
3156
+ }
3157
+ return graphics;
3158
+ }
3159
+ };
3160
+
3161
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts
3162
+ var OverlapAvoidanceStepSolver = class extends BaseSolver {
3163
+ inputProblem;
3164
+ netLabelPlacements;
3165
+ mergedLabelNetIdMap;
3166
+ allTraces;
3167
+ modifiedTraces = [];
3168
+ detourCountByLabel = {};
3169
+ PADDING_BUFFER = 0.1;
3170
+ activeSubSolver = null;
3171
+ overlapQueue = [];
3172
+ recentlyFailed = /* @__PURE__ */ new Set();
3173
+ constructor(solverInput) {
3174
+ super();
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;
@@ -3299,7 +3615,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3299
3615
  longDistancePairSolver;
3300
3616
  traceOverlapShiftSolver;
3301
3617
  netLabelPlacementSolver;
3618
+ labelMergingSolver;
3302
3619
  traceLabelOverlapAvoidanceSolver;
3620
+ traceCleanupSolver;
3303
3621
  startTimeOfPhase;
3304
3622
  endTimeOfPhase;
3305
3623
  timeSpentOnPhase;
@@ -3405,14 +3723,21 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3405
3723
  netLabelPlacements
3406
3724
  }
3407
3725
  ];
3408
- },
3409
- {
3410
- onSolved: (instance) => {
3411
- if (instance.traceLabelOverlapAvoidanceSolver && instance.netLabelPlacementSolver) {
3412
- const { netLabelPlacements } = instance.traceLabelOverlapAvoidanceSolver.getOutput();
3413
- 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
+ )
3414
3739
  }
3415
- }
3740
+ ];
3416
3741
  }
3417
3742
  )
3418
3743
  ];
@@ -3498,14 +3823,17 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3498
3823
  return viz;
3499
3824
  })
3500
3825
  ];
3501
- if (visualizations.length === 1) return visualizations[0];
3502
- return {
3826
+ if (visualizations.length === 1) {
3827
+ return visualizations[0];
3828
+ }
3829
+ const finalGraphics = {
3503
3830
  points: visualizations.flatMap((v) => v.points || []),
3504
3831
  rects: visualizations.flatMap((v) => v.rects || []),
3505
3832
  lines: visualizations.flatMap((v) => v.lines || []),
3506
3833
  circles: visualizations.flatMap((v) => v.circles || []),
3507
3834
  texts: visualizations.flatMap((v) => v.texts || [])
3508
3835
  };
3836
+ return finalGraphics;
3509
3837
  }
3510
3838
  /**
3511
3839
  * A lightweight version of the visualize method that can be used to stream