@tscircuit/schematic-trace-solver 0.0.46 → 0.0.48

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 (21) hide show
  1. package/dist/index.d.ts +18 -16
  2. package/dist/index.js +228 -122
  3. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +2 -0
  4. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts +12 -0
  5. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +89 -61
  6. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +16 -5
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +27 -16
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/doesTraceStartOrEndInLabel.ts +58 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +13 -1
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +110 -60
  11. package/package.json +1 -1
  12. package/site/examples/example27.page.tsx +4 -0
  13. package/site/examples/example28.page.tsx +61 -0
  14. package/tests/assets/example27.json +168 -0
  15. package/tests/examples/__snapshots__/example03.snap.svg +67 -67
  16. package/tests/examples/__snapshots__/example16.snap.svg +3 -3
  17. package/tests/examples/__snapshots__/example28.snap.svg +23 -189
  18. package/tests/examples/__snapshots__/example30.snap.svg +258 -0
  19. package/tests/examples/example28.test.ts +2 -3
  20. package/tests/examples/example30.test.ts +12 -0
  21. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +9 -6
@@ -47,6 +47,14 @@ export function visualizeSingleNetLabelPlacementSolver(
47
47
  : c.status === "trace-collision"
48
48
  ? "orange"
49
49
  : "gray"
50
+ const candidateLabel =
51
+ c.status === "ok"
52
+ ? "status: ok(valid net label candidate)"
53
+ : c.status === "chip-collision"
54
+ ? "status: chip-collision"
55
+ : c.status === "trace-collision"
56
+ ? "status: trace-collision"
57
+ : "status: parallel-to-segment"
50
58
 
51
59
  graphics.rects!.push({
52
60
  center: {
@@ -57,12 +65,14 @@ export function visualizeSingleNetLabelPlacementSolver(
57
65
  height: c.height,
58
66
  fill,
59
67
  strokeColor: stroke,
68
+ label: `${candidateLabel}\norientation: ${c.orientation}`,
60
69
  } as any)
61
70
 
62
71
  graphics.points!.push({
63
72
  x: c.anchor.x,
64
73
  y: c.anchor.y,
65
74
  color: stroke,
75
+ label: `anchor\norientation: ${c.orientation}`,
66
76
  } as any)
67
77
  }
68
78
 
@@ -75,11 +85,13 @@ export function visualizeSingleNetLabelPlacementSolver(
75
85
  height: p.height,
76
86
  fill: "rgba(0, 128, 255, 0.35)",
77
87
  strokeColor: "blue",
88
+ label: `netId: ${p.netId}\nglobalConnNetId: ${p.globalConnNetId}`,
78
89
  } as any)
79
90
  graphics.points!.push({
80
91
  x: p.anchorPoint.x,
81
92
  y: p.anchorPoint.y,
82
93
  color: "blue",
94
+ label: `anchor\norientation: ${p.orientation}`,
83
95
  } as any)
84
96
  }
85
97
 
@@ -7,6 +7,7 @@ import type { InputProblem } from "../../types/InputProblem"
7
7
  import { MergedNetLabelObstacleSolver } from "./sub-solvers/LabelMergingSolver/LabelMergingSolver"
8
8
  import { getColorFromString } from "lib/utils/getColorFromString"
9
9
  import { OverlapAvoidanceStepSolver } from "./sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver"
10
+ import { detectTraceLabelOverlap } from "./detectTraceLabelOverlap"
10
11
 
11
12
  interface TraceLabelOverlapAvoidanceSolverInput {
12
13
  inputProblem: InputProblem
@@ -15,87 +16,110 @@ interface TraceLabelOverlapAvoidanceSolverInput {
15
16
  }
16
17
 
17
18
  /**
18
- * A pipeline solver responsible for resolving overlaps between schematic traces and net labels.
19
+ * Resolves overlaps between schematic traces and net labels using a two-phase,
20
+ * "fire-and-forget" dispatching strategy.
19
21
  *
20
- * This solver orchestrates a sequence of sub-solvers to achieve its goal:
21
- * 1. **MergedNetLabelObstacleSolver**: This solver first merges labels that are
22
- * close to each other to form larger "obstacle" groups. This simplifies the
23
- * problem by reducing the number of individual obstacles the traces need to avoid.
24
- * 2. **OverlapAvoidanceStepSolver**: This solver then takes the output of the merging
25
- * step and iteratively attempts to reroute traces to avoid the merged label obstacles.
26
- * It handles one overlap at a time, making it a step-by-step process.
22
+ * This solver operates in two distinct phases:
27
23
  *
28
- * The final output is a set of modified traces that have been rerouted to avoid
29
- * labels, and the set of merged labels that were used as obstacles.
24
+ * 1. **Dispatch Phase**: Iterates through traces, identifying clean ones and dispatching
25
+ * colliding ones to dedicated `OverlapAvoidanceStepSolver` instances.
30
26
  *
31
- * @param {TraceLabelOverlapAvoidanceSolverInput} solverInput - The input for the solver,
32
- * containing the initial traces, label placements, and the input problem definition.
27
+ * 2. **Execution Phase**: Steps through all dispatched sub-solvers until they complete.
28
+ *
29
+ * The final output combines clean traces with results from sub-solvers. A final
30
+ * `MergedNetLabelObstacleSolver` ensures pipeline compatibility.
33
31
  */
34
32
  export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
35
33
  inputProblem: InputProblem
36
- traces: SolvedTracePath[]
37
34
  netLabelPlacements: NetLabelPlacement[]
38
35
 
39
- // sub-solver instances
36
+ unprocessedTraces: SolvedTracePath[] = []
37
+ cleanTraces: SolvedTracePath[] = []
38
+ subSolvers: OverlapAvoidanceStepSolver[] = []
39
+ private phase: "searching_for_overlaps" | "fixing_overlaps" =
40
+ "searching_for_overlaps"
41
+ detourCounts: Map<string, number> = new Map()
42
+
40
43
  labelMergingSolver?: MergedNetLabelObstacleSolver
41
- overlapAvoidanceSolver?: OverlapAvoidanceStepSolver
42
- pipelineStepIndex = 0
43
44
 
44
45
  constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput) {
45
46
  super()
46
47
  this.inputProblem = solverInput.inputProblem
47
- this.traces = solverInput.traces
48
+ this.unprocessedTraces = [...solverInput.traces]
48
49
  this.netLabelPlacements = solverInput.netLabelPlacements
50
+ this.cleanTraces = []
51
+ this.subSolvers = []
49
52
  }
50
53
 
51
54
  override _step() {
52
- // If a sub-solver is active, step it and check for completion.
53
- if (this.activeSubSolver) {
54
- this.activeSubSolver.step()
55
-
56
- if (this.activeSubSolver.solved) {
57
- this.activeSubSolver = null
58
- this.pipelineStepIndex++
59
- } else if (this.activeSubSolver.failed) {
60
- this.failed = true // If any sub-solver fails, the whole thing fails
61
- this.activeSubSolver = null
55
+ if (this.phase === "searching_for_overlaps") {
56
+ if (this.unprocessedTraces.length === 0) {
57
+ console.log(
58
+ `Dispatch phase complete. Created ${this.subSolvers.length} sub-solvers.`,
59
+ )
60
+ this.phase = "fixing_overlaps"
61
+ return
62
62
  }
63
- return // Return to allow the sub-solver to run
64
- }
65
63
 
66
- // If no sub-solver is active, create the next one in the pipeline.
67
- switch (this.pipelineStepIndex) {
68
- case 0:
69
- this.labelMergingSolver = new MergedNetLabelObstacleSolver({
70
- netLabelPlacements: this.netLabelPlacements,
64
+ const currentTargetTrace = this.unprocessedTraces.shift()!
65
+
66
+ const localOverlaps = detectTraceLabelOverlap({
67
+ traces: [currentTargetTrace],
68
+ netLabels: this.netLabelPlacements,
69
+ })
70
+
71
+ if (localOverlaps.length === 0) {
72
+ this.cleanTraces.push(currentTargetTrace)
73
+ } else {
74
+ // Dispatch a new sub-solver for this dirty trace
75
+ const collidingLabels = localOverlaps.map((o) => o.label)
76
+ const labelMerger = new MergedNetLabelObstacleSolver({
77
+ netLabelPlacements: collidingLabels,
71
78
  inputProblem: this.inputProblem,
72
- traces: this.traces,
79
+ traces: [currentTargetTrace],
73
80
  })
74
- this.activeSubSolver = this.labelMergingSolver
75
- break
81
+ labelMerger.solve()
82
+ const mergingOutput = labelMerger.getOutput()
76
83
 
77
- case 1:
78
- this.overlapAvoidanceSolver = new OverlapAvoidanceStepSolver({
84
+ const subSolver = new OverlapAvoidanceStepSolver({
79
85
  inputProblem: this.inputProblem,
80
- traces: this.traces,
81
- initialNetLabelPlacements: this.netLabelPlacements, // The original, unfiltered list
82
- mergedNetLabelPlacements:
83
- this.labelMergingSolver!.getOutput().netLabelPlacements,
84
- mergedLabelNetIdMap:
85
- this.labelMergingSolver!.getOutput().mergedLabelNetIdMap,
86
+ traces: [currentTargetTrace],
87
+ initialNetLabelPlacements: this.netLabelPlacements,
88
+ mergedNetLabelPlacements: mergingOutput.netLabelPlacements,
89
+ mergedLabelNetIdMap: mergingOutput.mergedLabelNetIdMap,
90
+ detourCounts: this.detourCounts,
86
91
  })
87
- this.activeSubSolver = this.overlapAvoidanceSolver
88
- break
89
-
90
- default:
92
+ this.subSolvers.push(subSolver)
93
+ }
94
+ } else if (this.phase === "fixing_overlaps") {
95
+ if (this.subSolvers.every((s) => s.solved || s.failed)) {
96
+ console.log("All sub-solvers finished.")
97
+ // Final merge for pipeline compatibility
98
+ if (!this.labelMergingSolver) {
99
+ this.labelMergingSolver = new MergedNetLabelObstacleSolver({
100
+ netLabelPlacements: this.netLabelPlacements,
101
+ inputProblem: this.inputProblem,
102
+ traces: this.getOutput().traces,
103
+ })
104
+ this.labelMergingSolver.solve()
105
+ }
91
106
  this.solved = true
92
- break
107
+ return
108
+ }
109
+
110
+ // Step through all active sub-solvers
111
+ for (const solver of this.subSolvers) {
112
+ if (!solver.solved && !solver.failed) {
113
+ solver.step()
114
+ }
115
+ }
93
116
  }
94
117
  }
95
118
 
96
119
  getOutput() {
120
+ const solvedTraces = this.subSolvers.flatMap((s) => s.getOutput().allTraces)
97
121
  return {
98
- traces: this.overlapAvoidanceSolver?.getOutput().allTraces ?? this.traces,
122
+ traces: [...this.cleanTraces, ...solvedTraces],
99
123
  netLabelPlacements:
100
124
  this.labelMergingSolver?.getOutput().netLabelPlacements ??
101
125
  this.netLabelPlacements,
@@ -103,26 +127,30 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
103
127
  }
104
128
 
105
129
  override visualize(): GraphicsObject {
106
- if (this.activeSubSolver) {
107
- return this.activeSubSolver.visualize()
108
- }
109
-
110
- // When no sub-solver is active, show the current state of the pipeline
111
130
  const graphics = visualizeInputProblem(this.inputProblem)
112
131
  if (!graphics.lines) graphics.lines = []
113
132
  if (!graphics.rects) graphics.rects = []
114
133
 
115
- const output = this.getOutput()
116
-
117
- for (const trace of output.traces) {
134
+ // Show clean traces in purple
135
+ for (const trace of this.cleanTraces) {
118
136
  graphics.lines!.push({
119
137
  points: trace.tracePath,
120
138
  strokeColor: "purple",
121
139
  })
122
140
  }
123
141
 
124
- for (const label of output.netLabelPlacements) {
125
- const color = getColorFromString(label.globalConnNetId, 0.3)
142
+ // Delegate visualization to sub-solvers
143
+ for (const solver of this.subSolvers) {
144
+ const solverGraphics = solver.visualize()
145
+ graphics.lines!.push(...(solverGraphics.lines ?? []))
146
+ graphics.rects!.push(...(solverGraphics.rects ?? []))
147
+ // graphics.texts!.push(...(solverGraphics.texts ?? []))
148
+ graphics.points!.push(...(solverGraphics.points ?? []))
149
+ }
150
+
151
+ // Also show original labels
152
+ for (const label of this.netLabelPlacements) {
153
+ const color = getColorFromString(label.globalConnNetId, 0.3) // Make fill opaque
126
154
  graphics.rects!.push({
127
155
  center: label.center,
128
156
  width: label.width,
@@ -8,6 +8,18 @@ import { generateSnipAndReconnectCandidates } from "./trySnipAndReconnect"
8
8
  import { generateFourPointDetourCandidates } from "./tryFourPointDetour"
9
9
  import { simplifyPath } from "../TraceCleanupSolver/simplifyPath"
10
10
 
11
+ /**
12
+ * Generates a list of candidate rerouted paths for a given trace that is
13
+ * colliding with a net label.
14
+ *
15
+ * This function employs multiple strategies to propose alternative paths:
16
+ * 1. **Four-Point Detour:** Creates a rectangular detour around the label.
17
+ * 2. **Snip and Reconnect:** Attempts to remove the colliding segment and
18
+ * reconnect the trace around the obstacle.
19
+ *
20
+ * The candidates are generated with increasing padding based on `detourCount`
21
+ * to explore progressively wider detours.
22
+ */
11
23
  export const generateRerouteCandidates = ({
12
24
  trace,
13
25
  label,
@@ -26,13 +38,12 @@ export const generateRerouteCandidates = ({
26
38
  return [initialTrace.tracePath]
27
39
  }
28
40
 
29
- const labelPadding = paddingBuffer
30
41
  const labelBoundsRaw = getRectBounds(label.center, label.width, label.height)
31
42
  const labelBounds = {
32
- minX: labelBoundsRaw.minX - labelPadding,
33
- minY: labelBoundsRaw.minY - labelPadding,
34
- maxX: labelBoundsRaw.maxX + labelPadding,
35
- maxY: labelBoundsRaw.maxY + labelPadding,
43
+ minX: labelBoundsRaw.minX,
44
+ minY: labelBoundsRaw.minY,
45
+ maxX: labelBoundsRaw.maxX,
46
+ maxY: labelBoundsRaw.maxY,
36
47
  chipId: `netlabel-${label.netId}`,
37
48
  }
38
49
 
@@ -6,7 +6,7 @@ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/
6
6
  import type { InputProblem } from "lib/types/InputProblem"
7
7
  import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
8
8
  import { SingleOverlapSolver } from "../SingleOverlapSolver/SingleOverlapSolver"
9
- import { isPointInsideLabel } from "./isPointInsideLabel"
9
+ import { doesTraceStartOrEndInLabel } from "./doesTraceStartOrEndInLabel"
10
10
  import { visualizeDecomposition } from "./visualizeDecomposition"
11
11
 
12
12
  type Overlap = ReturnType<typeof detectTraceLabelOverlap>[0]
@@ -18,6 +18,7 @@ export interface OverlapCollectionSolverInput {
18
18
  initialNetLabelPlacements: NetLabelPlacement[]
19
19
  mergedNetLabelPlacements: NetLabelPlacement[]
20
20
  mergedLabelNetIdMap: Record<string, Set<string>>
21
+ detourCounts: Map<string, number>
21
22
  }
22
23
 
23
24
  /**
@@ -33,8 +34,8 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
33
34
  allTraces: SolvedTracePath[]
34
35
  modifiedTraces: SolvedTracePath[] = []
35
36
 
36
- private detourCountByLabel: Record<string, number> = {}
37
37
  private readonly PADDING_BUFFER = 0.1
38
+ private detourCounts: Map<string, number> = new Map()
38
39
 
39
40
  public override activeSubSolver: SingleOverlapSolver | null = null
40
41
  private overlapQueue: Overlap[] = []
@@ -50,6 +51,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
50
51
  this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements
51
52
  this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap
52
53
  this.allTraces = [...solverInput.traces]
54
+ this.detourCounts = solverInput.detourCounts
53
55
  }
54
56
 
55
57
  override _step() {
@@ -111,7 +113,6 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
111
113
  (t) => t.mspPairId === nextOverlap.trace.mspPairId,
112
114
  )!
113
115
  const labelToAvoid = nextOverlap.label
114
- const traceStartPoint = traceToFix.tracePath[0]
115
116
 
116
117
  const originalNetIds =
117
118
  this.mergedLabelNetIdMap[labelToAvoid.globalConnNetId]
@@ -136,15 +137,19 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
136
137
  }
137
138
 
138
139
  if (actualOverlapLabel) {
139
- const labelId = actualOverlapLabel.globalConnNetId
140
- const detourCount = this.detourCountByLabel[labelId] || 0
141
- this.detourCountByLabel[labelId] = detourCount + 1
140
+ const detourCount =
141
+ this.detourCounts.get(actualOverlapLabel.globalConnNetId) ?? 0
142
+ this.detourCounts.set(
143
+ actualOverlapLabel.globalConnNetId,
144
+ detourCount + 1,
145
+ )
146
+
142
147
  this.activeSubSolver = new SingleOverlapSolver({
143
148
  trace: traceToFix,
144
149
  label: actualOverlapLabel,
145
150
  problem: this.inputProblem,
146
151
  paddingBuffer: this.PADDING_BUFFER,
147
- detourCount,
152
+ detourCount: detourCount,
148
153
  })
149
154
  } else {
150
155
  const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
@@ -155,7 +160,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
155
160
 
156
161
  if (
157
162
  originalNetIds &&
158
- isPointInsideLabel({ point: traceStartPoint, label: labelToAvoid })
163
+ doesTraceStartOrEndInLabel({ trace: traceToFix, label: labelToAvoid })
159
164
  ) {
160
165
  const childLabels = this.initialNetLabelPlacements.filter((l) =>
161
166
  originalNetIds.has(l.globalConnNetId),
@@ -175,15 +180,18 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
175
180
  }
176
181
 
177
182
  if (actualOverlapLabel) {
178
- const labelId = actualOverlapLabel.globalConnNetId
179
- const detourCount = this.detourCountByLabel[labelId] || 0
180
- this.detourCountByLabel[labelId] = detourCount + 1
183
+ const detourCount =
184
+ this.detourCounts.get(actualOverlapLabel.globalConnNetId) ?? 0
185
+ this.detourCounts.set(
186
+ actualOverlapLabel.globalConnNetId,
187
+ detourCount + 1,
188
+ )
181
189
  this.activeSubSolver = new SingleOverlapSolver({
182
190
  trace: traceToFix,
183
191
  label: actualOverlapLabel,
184
192
  problem: this.inputProblem,
185
193
  paddingBuffer: this.PADDING_BUFFER,
186
- detourCount,
194
+ detourCount: detourCount,
187
195
  })
188
196
  } else {
189
197
  const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
@@ -192,15 +200,17 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
192
200
  return
193
201
  }
194
202
 
195
- const labelId = labelToAvoid.globalConnNetId
196
- const detourCount = this.detourCountByLabel[labelId] || 0
197
- this.detourCountByLabel[labelId] = detourCount + 1
203
+ // STRATEGY 3: Real collision between different nets.
204
+ // We must reroute around the entire merged label.
205
+ const detourCount =
206
+ this.detourCounts.get(labelToAvoid.globalConnNetId) ?? 0
207
+ this.detourCounts.set(labelToAvoid.globalConnNetId, detourCount + 1)
198
208
  this.activeSubSolver = new SingleOverlapSolver({
199
209
  trace: traceToFix,
200
210
  label: labelToAvoid,
201
211
  problem: this.inputProblem,
202
212
  paddingBuffer: this.PADDING_BUFFER,
203
- detourCount,
213
+ detourCount: detourCount,
204
214
  })
205
215
  }
206
216
  }
@@ -209,6 +219,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
209
219
  return {
210
220
  allTraces: this.allTraces,
211
221
  modifiedTraces: this.modifiedTraces,
222
+ detourCounts: this.detourCounts,
212
223
  }
213
224
  }
214
225
 
@@ -0,0 +1,58 @@
1
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
2
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
+ import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
4
+
5
+ interface Props {
6
+ trace: SolvedTracePath
7
+ label: NetLabelPlacement
8
+ }
9
+
10
+ /**
11
+ * Checks if the first or last segment of a given trace overlaps with a specified net label.
12
+ * This is useful for determining if a trace begins or ends within an obstacle.
13
+ */
14
+ export const doesTraceStartOrEndInLabel = ({ trace, label }: Props) => {
15
+ if (trace.tracePath.length < 2) {
16
+ return false // Not a valid trace with segments
17
+ }
18
+
19
+ // Create a mini-trace for the first segment
20
+ const firstSegmentTrace: SolvedTracePath = {
21
+ ...trace,
22
+ tracePath: [trace.tracePath[0], trace.tracePath[1]],
23
+ }
24
+
25
+ // Check for overlap with the first segment
26
+ const firstSegmentOverlap = detectTraceLabelOverlap({
27
+ traces: [firstSegmentTrace],
28
+ netLabels: [label],
29
+ })
30
+
31
+ if (firstSegmentOverlap.length > 0) {
32
+ return true
33
+ }
34
+
35
+ // If the trace has more than one segment, check the last one
36
+ if (trace.tracePath.length > 2) {
37
+ const lastPoint = trace.tracePath[trace.tracePath.length - 1]
38
+ const secondToLastPoint = trace.tracePath[trace.tracePath.length - 2]
39
+
40
+ // Create a mini-trace for the last segment
41
+ const lastSegmentTrace: SolvedTracePath = {
42
+ ...trace,
43
+ tracePath: [secondToLastPoint, lastPoint],
44
+ }
45
+
46
+ // Check for overlap with the last segment
47
+ const lastSegmentOverlap = detectTraceLabelOverlap({
48
+ traces: [lastSegmentTrace],
49
+ netLabels: [label],
50
+ })
51
+
52
+ if (lastSegmentOverlap.length > 0) {
53
+ return true
54
+ }
55
+ }
56
+
57
+ return false
58
+ }
@@ -18,6 +18,8 @@ interface SingleOverlapSolverInput {
18
18
  detourCount: number
19
19
  }
20
20
 
21
+ const MAX_TRIES = 5
22
+
21
23
  /**
22
24
  * This solver attempts to find a valid rerouting for a single trace that is
23
25
  * overlapping with a net label. It tries various candidate paths until it
@@ -30,6 +32,7 @@ export class SingleOverlapSolver extends BaseSolver {
30
32
  problem: InputProblem
31
33
  obstacles: ReturnType<typeof getObstacleRects>
32
34
  label: NetLabelPlacement
35
+ _tried: number = 0
33
36
 
34
37
  constructor(solverInput: SingleOverlapSolverInput) {
35
38
  super()
@@ -37,8 +40,14 @@ export class SingleOverlapSolver extends BaseSolver {
37
40
  this.problem = solverInput.problem
38
41
  this.label = solverInput.label
39
42
 
43
+ // Calculate an effective padding for this specific run based on the detourCount.
44
+ const effectivePadding =
45
+ solverInput.paddingBuffer +
46
+ solverInput.detourCount * solverInput.paddingBuffer
47
+
40
48
  const candidates = generateRerouteCandidates({
41
49
  ...solverInput,
50
+ paddingBuffer: effectivePadding, // Use the calculated, larger padding
42
51
  })
43
52
 
44
53
  const getPathLength = (pts: Point[]) => {
@@ -58,11 +67,13 @@ export class SingleOverlapSolver extends BaseSolver {
58
67
  }
59
68
 
60
69
  override _step() {
61
- if (this.queuedCandidatePaths.length === 0) {
70
+ // Failure conditions: no more candidates or exceeded max tries
71
+ if (this.queuedCandidatePaths.length === 0 || this._tried >= MAX_TRIES) {
62
72
  this.failed = true
63
73
  return
64
74
  }
65
75
 
76
+ this._tried++
66
77
  const nextCandidatePath = this.queuedCandidatePaths.shift()!
67
78
  const simplifiedPath = simplifyPath(nextCandidatePath)
68
79
 
@@ -70,6 +81,7 @@ export class SingleOverlapSolver extends BaseSolver {
70
81
  this.solvedTracePath = simplifiedPath
71
82
  this.solved = true
72
83
  }
84
+ // If the path collides, we simply do nothing and let the next step try another candidate.
73
85
  }
74
86
 
75
87
  override visualize(): GraphicsObject {