@tscircuit/schematic-trace-solver 0.0.45 → 0.0.46

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.
@@ -17,7 +17,7 @@ jobs:
17
17
  - name: Setup bun
18
18
  uses: oven-sh/setup-bun@v2
19
19
  with:
20
- bun-version: latest
20
+ bun-version: 1.3.1
21
21
 
22
22
  - name: Install dependencies
23
23
  run: bun install
@@ -17,7 +17,7 @@ jobs:
17
17
  - name: Setup bun
18
18
  uses: oven-sh/setup-bun@v2
19
19
  with:
20
- bun-version: latest
20
+ bun-version: 1.3.1
21
21
  - uses: actions/setup-node@v3
22
22
  with:
23
23
  node-version: 20
@@ -19,7 +19,7 @@ jobs:
19
19
  - name: Setup bun
20
20
  uses: oven-sh/setup-bun@v2
21
21
  with:
22
- bun-version: latest
22
+ bun-version: 1.3.1
23
23
 
24
24
  - name: Install dependencies
25
25
  run: bun install
@@ -17,7 +17,7 @@ jobs:
17
17
  - name: Setup bun
18
18
  uses: oven-sh/setup-bun@v2
19
19
  with:
20
- bun-version: latest
20
+ bun-version: 1.3.1
21
21
 
22
22
  - name: Install dependencies
23
23
  run: bun i
package/dist/index.d.ts CHANGED
@@ -396,13 +396,20 @@ interface LabelMergingSolverOutput {
396
396
  }
397
397
  /**
398
398
  * Merges multiple net labels into a single, larger label if they are on the
399
- * same side of the same chip, reducing schematic clutter.
399
+ * same side of the same chip and physically adjacent.
400
400
  */
401
401
  declare class MergedNetLabelObstacleSolver extends BaseSolver {
402
402
  private input;
403
403
  private output;
404
404
  private inputProblem;
405
405
  private traces;
406
+ private pipelineStep;
407
+ private filteredLabels;
408
+ private labelGroups;
409
+ private groupKeysToProcess;
410
+ private finalPlacements;
411
+ private mergedLabelNetIdMap;
412
+ private activeMergingGroupKey;
406
413
  constructor(solverInput: LabelMergingSolverInput);
407
414
  _step(): void;
408
415
  getOutput(): LabelMergingSolverOutput;
@@ -436,7 +443,8 @@ declare class SingleOverlapSolver extends BaseSolver {
436
443
  interface OverlapCollectionSolverInput {
437
444
  inputProblem: InputProblem;
438
445
  traces: SolvedTracePath[];
439
- netLabelPlacements: NetLabelPlacement[];
446
+ initialNetLabelPlacements: NetLabelPlacement[];
447
+ mergedNetLabelPlacements: NetLabelPlacement[];
440
448
  mergedLabelNetIdMap: Record<string, Set<string>>;
441
449
  }
442
450
  /**
@@ -445,7 +453,8 @@ interface OverlapCollectionSolverInput {
445
453
  */
446
454
  declare class OverlapAvoidanceStepSolver extends BaseSolver {
447
455
  inputProblem: InputProblem;
448
- netLabelPlacements: NetLabelPlacement[];
456
+ initialNetLabelPlacements: NetLabelPlacement[];
457
+ mergedNetLabelPlacements: NetLabelPlacement[];
449
458
  mergedLabelNetIdMap: Record<string, Set<string>>;
450
459
  allTraces: SolvedTracePath[];
451
460
  modifiedTraces: SolvedTracePath[];
@@ -454,6 +463,8 @@ declare class OverlapAvoidanceStepSolver extends BaseSolver {
454
463
  activeSubSolver: SingleOverlapSolver | null;
455
464
  private overlapQueue;
456
465
  private recentlyFailed;
466
+ private currentlyProcessingOverlap;
467
+ private decomposedChildLabels;
457
468
  constructor(solverInput: OverlapCollectionSolverInput);
458
469
  _step(): void;
459
470
  getOutput(): {
@@ -469,8 +480,21 @@ interface TraceLabelOverlapAvoidanceSolverInput {
469
480
  netLabelPlacements: NetLabelPlacement[];
470
481
  }
471
482
  /**
472
- * This solver is a pipeline that runs a series of sub-solvers to resolve
473
- * trace-label overlaps and clean up the resulting traces.
483
+ * A pipeline solver responsible for resolving overlaps between schematic traces and net labels.
484
+ *
485
+ * This solver orchestrates a sequence of sub-solvers to achieve its goal:
486
+ * 1. **MergedNetLabelObstacleSolver**: This solver first merges labels that are
487
+ * close to each other to form larger "obstacle" groups. This simplifies the
488
+ * problem by reducing the number of individual obstacles the traces need to avoid.
489
+ * 2. **OverlapAvoidanceStepSolver**: This solver then takes the output of the merging
490
+ * step and iteratively attempts to reroute traces to avoid the merged label obstacles.
491
+ * It handles one overlap at a time, making it a step-by-step process.
492
+ *
493
+ * The final output is a set of modified traces that have been rerouted to avoid
494
+ * labels, and the set of merged labels that were used as obstacles.
495
+ *
496
+ * @param {TraceLabelOverlapAvoidanceSolverInput} solverInput - The input for the solver,
497
+ * containing the initial traces, label placements, and the input problem definition.
474
498
  */
475
499
  declare class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
476
500
  inputProblem: InputProblem;
package/dist/index.js CHANGED
@@ -2260,12 +2260,134 @@ var NetLabelPlacementSolver = class extends BaseSolver {
2260
2260
  }
2261
2261
  };
2262
2262
 
2263
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/groupLabelsByChipAndOrientation.ts
2264
+ var groupLabelsByChipAndOrientation = ({
2265
+ labels,
2266
+ chips
2267
+ }) => {
2268
+ const groupedLabels = {};
2269
+ for (const label of labels) {
2270
+ if (label.pinIds.length === 0) {
2271
+ continue;
2272
+ }
2273
+ const chipId = label.pinIds[0].split(".")[0];
2274
+ if (!chipId) {
2275
+ continue;
2276
+ }
2277
+ const key = `${chipId}-${label.orientation}`;
2278
+ if (!groupedLabels[key]) {
2279
+ groupedLabels[key] = [];
2280
+ }
2281
+ groupedLabels[key].push(label);
2282
+ }
2283
+ return groupedLabels;
2284
+ };
2285
+
2286
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/mergeLabelGroup.ts
2287
+ var mergeLabelGroup = (group, groupKey) => {
2288
+ if (group.length === 0) {
2289
+ throw new Error("Cannot merge an empty group of labels.");
2290
+ }
2291
+ let minX = Infinity;
2292
+ let minY = Infinity;
2293
+ let maxX = -Infinity;
2294
+ let maxY = -Infinity;
2295
+ const allPinIds = /* @__PURE__ */ new Set();
2296
+ const allMspConnectionPairIds = /* @__PURE__ */ new Set();
2297
+ const originalNetIds = /* @__PURE__ */ new Set();
2298
+ for (const label of group) {
2299
+ const bounds = getRectBounds(label.center, label.width, label.height);
2300
+ minX = Math.min(minX, bounds.minX);
2301
+ minY = Math.min(minY, bounds.minY);
2302
+ maxX = Math.max(maxX, bounds.maxX);
2303
+ maxY = Math.max(maxY, bounds.maxY);
2304
+ label.pinIds.forEach((id) => allPinIds.add(id));
2305
+ label.mspConnectionPairIds.forEach((id) => allMspConnectionPairIds.add(id));
2306
+ originalNetIds.add(label.globalConnNetId);
2307
+ }
2308
+ const newWidth = maxX - minX;
2309
+ const newHeight = maxY - minY;
2310
+ const newCenter = { x: minX + newWidth / 2, y: minY + newHeight / 2 };
2311
+ const template = group[0];
2312
+ const syntheticId = `merged-group-${groupKey}`;
2313
+ const mergedLabel = {
2314
+ ...template,
2315
+ // Copy common properties
2316
+ globalConnNetId: syntheticId,
2317
+ center: newCenter,
2318
+ width: newWidth,
2319
+ height: newHeight,
2320
+ pinIds: Array.from(allPinIds),
2321
+ mspConnectionPairIds: Array.from(allMspConnectionPairIds)
2322
+ };
2323
+ return {
2324
+ mergedLabel,
2325
+ originalNetIds
2326
+ };
2327
+ };
2328
+
2329
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/filterLabelsAtTraceEdges.ts
2330
+ import { distance } from "@tscircuit/math-utils";
2331
+ var filterLabelsAtTraceEdges = ({
2332
+ labels,
2333
+ traces,
2334
+ distanceThreshold = 0.5
2335
+ // Example threshold
2336
+ }) => {
2337
+ const tracesByNetId = /* @__PURE__ */ new Map();
2338
+ if (!traces) {
2339
+ throw new Error("No traces provided to filterLabelsAtTraceEdges");
2340
+ }
2341
+ for (const trace of traces) {
2342
+ if (!trace.globalConnNetId) continue;
2343
+ if (!tracesByNetId.has(trace.globalConnNetId)) {
2344
+ tracesByNetId.set(trace.globalConnNetId, []);
2345
+ }
2346
+ tracesByNetId.get(trace.globalConnNetId).push(trace);
2347
+ }
2348
+ const filteredLabels = [];
2349
+ for (const label of labels) {
2350
+ if (label.mspConnectionPairIds.length === 0) {
2351
+ filteredLabels.push(label);
2352
+ continue;
2353
+ }
2354
+ const relevantTraces = tracesByNetId.get(label.globalConnNetId);
2355
+ let isNearTraceEdge = false;
2356
+ if (!relevantTraces || relevantTraces.length === 0) {
2357
+ continue;
2358
+ }
2359
+ for (const trace of relevantTraces) {
2360
+ if (trace.tracePath.length === 0) continue;
2361
+ const startPoint = trace.tracePath[0];
2362
+ const endPoint = trace.tracePath[trace.tracePath.length - 1];
2363
+ const startDist = distance(label.center, startPoint);
2364
+ const endDist = distance(label.center, endPoint);
2365
+ if (startDist <= distanceThreshold || endDist <= distanceThreshold) {
2366
+ isNearTraceEdge = true;
2367
+ break;
2368
+ }
2369
+ }
2370
+ if (isNearTraceEdge) {
2371
+ filteredLabels.push(label);
2372
+ }
2373
+ }
2374
+ return filteredLabels;
2375
+ };
2376
+
2263
2377
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts
2264
2378
  var MergedNetLabelObstacleSolver = class extends BaseSolver {
2265
2379
  input;
2266
2380
  output;
2267
2381
  inputProblem;
2268
2382
  traces;
2383
+ // State for the new pipeline
2384
+ pipelineStep = "filtering_labels";
2385
+ filteredLabels = [];
2386
+ labelGroups = {};
2387
+ groupKeysToProcess = [];
2388
+ finalPlacements = [];
2389
+ mergedLabelNetIdMap = {};
2390
+ activeMergingGroupKey = null;
2269
2391
  constructor(solverInput) {
2270
2392
  super();
2271
2393
  this.input = solverInput;
@@ -2277,67 +2399,58 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
2277
2399
  };
2278
2400
  }
2279
2401
  _step() {
2280
- const originalLabels = this.input.netLabelPlacements;
2281
- const mergedLabelNetIdMap = {};
2282
- if (!originalLabels || originalLabels.length === 0) {
2283
- this.output = {
2284
- netLabelPlacements: [],
2285
- mergedLabelNetIdMap: {}
2286
- };
2287
- this.solved = true;
2288
- return;
2289
- }
2290
- const labelGroups = {};
2291
- for (const p of originalLabels) {
2292
- if (p.pinIds.length === 0) continue;
2293
- const chipId = p.pinIds[0].split(".")[0];
2294
- if (!chipId) continue;
2295
- const key = `${chipId}-${p.orientation}`;
2296
- if (!(key in labelGroups)) {
2297
- labelGroups[key] = [];
2298
- }
2299
- labelGroups[key].push(p);
2300
- }
2301
- const finalPlacements = [];
2302
- for (const [key, group] of Object.entries(labelGroups)) {
2303
- if (group.length <= 1) {
2304
- finalPlacements.push(...group);
2305
- continue;
2306
- }
2307
- let minX = Infinity;
2308
- let minY = Infinity;
2309
- let maxX = -Infinity;
2310
- let maxY = -Infinity;
2311
- for (const p of group) {
2312
- const bounds = getRectBounds(p.center, p.width, p.height);
2313
- minX = Math.min(minX, bounds.minX);
2314
- minY = Math.min(minY, bounds.minY);
2315
- maxX = Math.max(maxX, bounds.maxX);
2316
- maxY = Math.max(maxY, bounds.maxY);
2317
- }
2318
- const newWidth = maxX - minX;
2319
- const newHeight = maxY - minY;
2320
- const template = group[0];
2321
- const syntheticId = `merged-group-${key}`;
2322
- const originalNetIds = new Set(group.map((p) => p.globalConnNetId));
2323
- mergedLabelNetIdMap[syntheticId] = originalNetIds;
2324
- finalPlacements.push({
2325
- ...template,
2326
- globalConnNetId: syntheticId,
2327
- width: newWidth,
2328
- height: newHeight,
2329
- center: { x: minX + newWidth / 2, y: minY + newHeight / 2 },
2330
- pinIds: [...new Set(group.flatMap((p) => p.pinIds))],
2331
- mspConnectionPairIds: [
2332
- ...new Set(group.flatMap((p) => p.mspConnectionPairIds))
2333
- ]
2334
- });
2402
+ switch (this.pipelineStep) {
2403
+ case "filtering_labels":
2404
+ this.filteredLabels = filterLabelsAtTraceEdges({
2405
+ labels: this.input.netLabelPlacements,
2406
+ traces: this.traces
2407
+ });
2408
+ this.pipelineStep = "grouping_labels";
2409
+ break;
2410
+ case "grouping_labels":
2411
+ this.labelGroups = groupLabelsByChipAndOrientation({
2412
+ labels: this.filteredLabels,
2413
+ chips: this.inputProblem.chips
2414
+ });
2415
+ this.groupKeysToProcess = Object.keys(this.labelGroups);
2416
+ this.pipelineStep = "merging_groups";
2417
+ break;
2418
+ case "merging_groups":
2419
+ if (this.groupKeysToProcess.length === 0) {
2420
+ this.pipelineStep = "finalizing";
2421
+ this.activeMergingGroupKey = null;
2422
+ break;
2423
+ }
2424
+ const groupKey = this.groupKeysToProcess.pop();
2425
+ this.activeMergingGroupKey = groupKey;
2426
+ const group = this.labelGroups[groupKey];
2427
+ if (group.length > 1) {
2428
+ const { mergedLabel, originalNetIds } = mergeLabelGroup(
2429
+ group,
2430
+ groupKey
2431
+ );
2432
+ this.finalPlacements.push(mergedLabel);
2433
+ this.mergedLabelNetIdMap[mergedLabel.globalConnNetId] = originalNetIds;
2434
+ } else {
2435
+ this.finalPlacements.push(...group);
2436
+ }
2437
+ break;
2438
+ case "finalizing":
2439
+ const processedOriginalIds = new Set(
2440
+ this.finalPlacements.flatMap(
2441
+ (p) => this.mergedLabelNetIdMap[p.globalConnNetId] ? [...this.mergedLabelNetIdMap[p.globalConnNetId]] : [p.globalConnNetId]
2442
+ )
2443
+ );
2444
+ const unprocessedLabels = this.input.netLabelPlacements.filter(
2445
+ (l) => !processedOriginalIds.has(l.globalConnNetId)
2446
+ );
2447
+ this.output = {
2448
+ netLabelPlacements: [...this.finalPlacements, ...unprocessedLabels],
2449
+ mergedLabelNetIdMap: this.mergedLabelNetIdMap
2450
+ };
2451
+ this.solved = true;
2452
+ break;
2335
2453
  }
2336
- this.output = {
2337
- netLabelPlacements: finalPlacements,
2338
- mergedLabelNetIdMap
2339
- };
2340
- this.solved = true;
2341
2454
  }
2342
2455
  getOutput() {
2343
2456
  return this.output;
@@ -2362,6 +2475,19 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
2362
2475
  };
2363
2476
  graphics.lines.push(line);
2364
2477
  }
2478
+ if (this.activeMergingGroupKey && this.labelGroups[this.activeMergingGroupKey]) {
2479
+ const activeGroup = this.labelGroups[this.activeMergingGroupKey];
2480
+ for (const label of activeGroup) {
2481
+ graphics.rects.push({
2482
+ center: label.center,
2483
+ width: label.width,
2484
+ height: label.height,
2485
+ fill: "rgba(255, 165, 0, 0.5)",
2486
+ // Orange highlight
2487
+ stroke: "orange"
2488
+ });
2489
+ }
2490
+ }
2365
2491
  for (const finalLabel of this.output.netLabelPlacements) {
2366
2492
  const isMerged = finalLabel.globalConnNetId.startsWith("merged-group-");
2367
2493
  const color = getColorFromString(finalLabel.globalConnNetId);
@@ -2371,7 +2497,6 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
2371
2497
  width: finalLabel.width,
2372
2498
  height: finalLabel.height,
2373
2499
  fill: color.replace(/, 1\)/, ", 0.2)"),
2374
- // semi-transparent
2375
2500
  stroke: color,
2376
2501
  label: finalLabel.globalConnNetId
2377
2502
  });
@@ -2417,14 +2542,17 @@ var MergedNetLabelObstacleSolver = class extends BaseSolver {
2417
2542
  };
2418
2543
 
2419
2544
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts
2420
- var detectTraceLabelOverlap = (traces, netLabels) => {
2545
+ var detectTraceLabelOverlap = ({
2546
+ traces,
2547
+ netLabels = []
2548
+ }) => {
2421
2549
  const overlaps = [];
2422
2550
  for (const trace of traces) {
2423
2551
  for (const label of netLabels) {
2424
2552
  const labelBounds = getRectBounds(label.center, label.width, label.height);
2425
- for (let i = 0; i < trace.tracePath.length - 1; i++) {
2426
- const p1 = trace.tracePath[i];
2427
- const p2 = trace.tracePath[i + 1];
2553
+ for (let j = 0; j < trace.tracePath.length - 1; j++) {
2554
+ const p1 = trace.tracePath[j];
2555
+ const p2 = trace.tracePath[j + 1];
2428
2556
  if (segmentIntersectsRect2(p1, p2, labelBounds)) {
2429
2557
  if (trace.globalConnNetId === label.globalConnNetId) {
2430
2558
  break;
@@ -2738,10 +2866,45 @@ var SingleOverlapSolver = class extends BaseSolver {
2738
2866
  }
2739
2867
  };
2740
2868
 
2869
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/isPointInsideLabel.ts
2870
+ var isPointInsideLabel = ({
2871
+ point,
2872
+ label
2873
+ }) => {
2874
+ const bounds = getRectBounds(label.center, label.width, label.height);
2875
+ return point.x >= bounds.minX && point.x <= bounds.maxX && point.y >= bounds.minY && point.y <= bounds.maxY;
2876
+ };
2877
+
2878
+ // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/visualizeDecomposition.ts
2879
+ var visualizeDecomposition = (params) => {
2880
+ const { decomposedChildLabels, collidingTrace, mergedLabel, graphics } = params;
2881
+ if (!graphics.rects) graphics.rects = [];
2882
+ if (!graphics.texts) graphics.texts = [];
2883
+ for (const childLabel of decomposedChildLabels) {
2884
+ const isOwnLabel = childLabel.globalConnNetId === collidingTrace.globalConnNetId;
2885
+ graphics.rects.push({
2886
+ center: childLabel.center,
2887
+ width: childLabel.width,
2888
+ height: childLabel.height,
2889
+ fill: isOwnLabel ? "green" : "red"
2890
+ // Green for own label, red for others
2891
+ });
2892
+ }
2893
+ graphics.texts.push({
2894
+ x: mergedLabel.center.x,
2895
+ y: mergedLabel.center.y + mergedLabel.height / 2 + 0.5,
2896
+ text: `DECOMPOSITION: Trace ${collidingTrace.mspPairId} vs Merged Label ${mergedLabel.globalConnNetId}`,
2897
+ fontSize: 0.3,
2898
+ color: "blue"
2899
+ });
2900
+ return graphics;
2901
+ };
2902
+
2741
2903
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts
2742
2904
  var OverlapAvoidanceStepSolver = class extends BaseSolver {
2743
2905
  inputProblem;
2744
- netLabelPlacements;
2906
+ initialNetLabelPlacements;
2907
+ mergedNetLabelPlacements;
2745
2908
  mergedLabelNetIdMap;
2746
2909
  allTraces;
2747
2910
  modifiedTraces = [];
@@ -2750,14 +2913,19 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
2750
2913
  activeSubSolver = null;
2751
2914
  overlapQueue = [];
2752
2915
  recentlyFailed = /* @__PURE__ */ new Set();
2916
+ currentlyProcessingOverlap = null;
2917
+ decomposedChildLabels = null;
2753
2918
  constructor(solverInput) {
2754
2919
  super();
2755
2920
  this.inputProblem = solverInput.inputProblem;
2756
- this.netLabelPlacements = solverInput.netLabelPlacements;
2921
+ this.initialNetLabelPlacements = solverInput.initialNetLabelPlacements;
2922
+ this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements;
2757
2923
  this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap;
2758
2924
  this.allTraces = [...solverInput.traces];
2759
2925
  }
2760
2926
  _step() {
2927
+ this.currentlyProcessingOverlap = null;
2928
+ this.decomposedChildLabels = null;
2761
2929
  if (this.activeSubSolver) {
2762
2930
  this.activeSubSolver.step();
2763
2931
  if (this.activeSubSolver.solved) {
@@ -2777,18 +2945,13 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
2777
2945
  const overlapId = `${this.activeSubSolver.initialTrace.mspPairId}-${this.activeSubSolver.label.globalConnNetId}`;
2778
2946
  this.recentlyFailed.add(overlapId);
2779
2947
  this.activeSubSolver = null;
2948
+ } else {
2780
2949
  }
2781
2950
  return;
2782
2951
  }
2783
- const overlaps = detectTraceLabelOverlap(
2784
- this.allTraces,
2785
- this.netLabelPlacements
2786
- ).filter((o) => {
2787
- const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId];
2788
- if (originalNetIds) {
2789
- return !originalNetIds.has(o.trace.globalConnNetId);
2790
- }
2791
- return o.trace.globalConnNetId !== o.label.globalConnNetId;
2952
+ const overlaps = detectTraceLabelOverlap({
2953
+ traces: this.allTraces,
2954
+ netLabels: this.mergedNetLabelPlacements
2792
2955
  });
2793
2956
  if (overlaps.length === 0) {
2794
2957
  this.solved = true;
@@ -2804,22 +2967,91 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
2804
2967
  }
2805
2968
  this.overlapQueue = nonFailedOverlaps;
2806
2969
  const nextOverlap = this.overlapQueue.shift();
2970
+ this.currentlyProcessingOverlap = nextOverlap ?? null;
2807
2971
  if (nextOverlap) {
2808
2972
  const traceToFix = this.allTraces.find(
2809
2973
  (t) => t.mspPairId === nextOverlap.trace.mspPairId
2810
2974
  );
2811
- if (traceToFix) {
2812
- const labelId = nextOverlap.label.globalConnNetId;
2813
- const detourCount = this.detourCountByLabel[labelId] || 0;
2814
- this.detourCountByLabel[labelId] = detourCount + 1;
2815
- this.activeSubSolver = new SingleOverlapSolver({
2816
- trace: traceToFix,
2817
- label: nextOverlap.label,
2818
- problem: this.inputProblem,
2819
- paddingBuffer: this.PADDING_BUFFER,
2820
- detourCount
2821
- });
2975
+ const labelToAvoid = nextOverlap.label;
2976
+ const traceStartPoint = traceToFix.tracePath[0];
2977
+ const originalNetIds = this.mergedLabelNetIdMap[labelToAvoid.globalConnNetId];
2978
+ const isSelfOverlap = originalNetIds?.has(traceToFix.globalConnNetId);
2979
+ if (isSelfOverlap) {
2980
+ const childLabels = this.initialNetLabelPlacements.filter(
2981
+ (l) => originalNetIds.has(l.globalConnNetId)
2982
+ );
2983
+ this.decomposedChildLabels = childLabels;
2984
+ let actualOverlapLabel = null;
2985
+ for (const childLabel of childLabels) {
2986
+ const overlapsWithChild = detectTraceLabelOverlap({
2987
+ traces: [traceToFix],
2988
+ netLabels: [childLabel]
2989
+ });
2990
+ if (overlapsWithChild.length > 0) {
2991
+ actualOverlapLabel = childLabel;
2992
+ break;
2993
+ }
2994
+ }
2995
+ if (actualOverlapLabel) {
2996
+ const labelId2 = actualOverlapLabel.globalConnNetId;
2997
+ const detourCount2 = this.detourCountByLabel[labelId2] || 0;
2998
+ this.detourCountByLabel[labelId2] = detourCount2 + 1;
2999
+ this.activeSubSolver = new SingleOverlapSolver({
3000
+ trace: traceToFix,
3001
+ label: actualOverlapLabel,
3002
+ problem: this.inputProblem,
3003
+ paddingBuffer: this.PADDING_BUFFER,
3004
+ detourCount: detourCount2
3005
+ });
3006
+ } else {
3007
+ const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
3008
+ this.recentlyFailed.add(overlapId);
3009
+ }
3010
+ return;
2822
3011
  }
3012
+ if (originalNetIds && isPointInsideLabel({ point: traceStartPoint, label: labelToAvoid })) {
3013
+ const childLabels = this.initialNetLabelPlacements.filter(
3014
+ (l) => originalNetIds.has(l.globalConnNetId)
3015
+ );
3016
+ this.decomposedChildLabels = childLabels;
3017
+ let actualOverlapLabel = null;
3018
+ for (const childLabel of childLabels) {
3019
+ const overlapsWithChild = detectTraceLabelOverlap({
3020
+ traces: [traceToFix],
3021
+ netLabels: [childLabel]
3022
+ });
3023
+ if (overlapsWithChild.length > 0) {
3024
+ actualOverlapLabel = childLabel;
3025
+ break;
3026
+ }
3027
+ }
3028
+ if (actualOverlapLabel) {
3029
+ const labelId2 = actualOverlapLabel.globalConnNetId;
3030
+ const detourCount2 = this.detourCountByLabel[labelId2] || 0;
3031
+ this.detourCountByLabel[labelId2] = detourCount2 + 1;
3032
+ this.activeSubSolver = new SingleOverlapSolver({
3033
+ trace: traceToFix,
3034
+ label: actualOverlapLabel,
3035
+ problem: this.inputProblem,
3036
+ paddingBuffer: this.PADDING_BUFFER,
3037
+ detourCount: detourCount2
3038
+ });
3039
+ } else {
3040
+ const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`;
3041
+ this.recentlyFailed.add(overlapId);
3042
+ }
3043
+ return;
3044
+ }
3045
+ const labelId = labelToAvoid.globalConnNetId;
3046
+ const detourCount = this.detourCountByLabel[labelId] || 0;
3047
+ this.detourCountByLabel[labelId] = detourCount + 1;
3048
+ this.activeSubSolver = new SingleOverlapSolver({
3049
+ trace: traceToFix,
3050
+ label: labelToAvoid,
3051
+ problem: this.inputProblem,
3052
+ paddingBuffer: this.PADDING_BUFFER,
3053
+ detourCount
3054
+ });
2823
3055
  }
2824
3056
  }
2825
3057
  getOutput() {
@@ -2840,6 +3072,37 @@ var OverlapAvoidanceStepSolver = class extends BaseSolver {
2840
3072
  strokeColor: "purple"
2841
3073
  });
2842
3074
  }
3075
+ if (this.currentlyProcessingOverlap) {
3076
+ const { trace, label } = this.currentlyProcessingOverlap;
3077
+ graphics.lines.push({
3078
+ points: trace.tracePath,
3079
+ strokeColor: "red"
3080
+ });
3081
+ if (this.decomposedChildLabels) {
3082
+ visualizeDecomposition({
3083
+ decomposedChildLabels: this.decomposedChildLabels,
3084
+ collidingTrace: trace,
3085
+ mergedLabel: label,
3086
+ graphics
3087
+ });
3088
+ } else {
3089
+ if (!graphics.rects) graphics.rects = [];
3090
+ graphics.rects.push({
3091
+ center: label.center,
3092
+ width: label.width,
3093
+ height: label.height,
3094
+ fill: "yellow"
3095
+ });
3096
+ if (!graphics.texts) graphics.texts = [];
3097
+ graphics.texts.push({
3098
+ x: label.center.x,
3099
+ y: label.center.y + label.height / 2 + 0.5,
3100
+ text: `COLLISION: Trace ${trace.mspPairId} vs Label ${label.globalConnNetId}`,
3101
+ fontSize: 0.3,
3102
+ color: "red"
3103
+ });
3104
+ }
3105
+ }
2843
3106
  return graphics;
2844
3107
  }
2845
3108
  };
@@ -2884,7 +3147,9 @@ var TraceLabelOverlapAvoidanceSolver = class extends BaseSolver {
2884
3147
  this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
2885
3148
  inputProblem: this.inputProblem,
2886
3149
  traces: this.traces,
2887
- netLabelPlacements: this.labelMergingSolver.getOutput().netLabelPlacements,
3150
+ initialNetLabelPlacements: this.netLabelPlacements,
3151
+ // The original, unfiltered list
3152
+ mergedNetLabelPlacements: this.labelMergingSolver.getOutput().netLabelPlacements,
2888
3153
  mergedLabelNetIdMap: this.labelMergingSolver.getOutput().mergedLabelNetIdMap
2889
3154
  });
2890
3155
  this.activeSubSolver = this.overlapAvoidanceSolver;
@@ -3006,7 +3271,7 @@ function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
3006
3271
 
3007
3272
  // lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
3008
3273
  var NEAREST_NEIGHBOR_COUNT = 3;
3009
- var distance = (p1, p2) => {
3274
+ var distance2 = (p1, p2) => {
3010
3275
  return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
3011
3276
  };
3012
3277
  var LongDistancePairSolver = class extends BaseSolver {
@@ -3047,7 +3312,7 @@ var LongDistancePairSolver = class extends BaseSolver {
3047
3312
  return [
3048
3313
  {
3049
3314
  pin: targetPin,
3050
- distance: distance(sourcePin, targetPin)
3315
+ distance: distance2(sourcePin, targetPin)
3051
3316
  }
3052
3317
  ];
3053
3318
  }).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);