@tscircuit/schematic-trace-solver 0.0.45 → 0.0.47

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 (27) hide show
  1. package/.github/workflows/bun-formatcheck.yml +1 -1
  2. package/.github/workflows/bun-pver-release.yml +1 -1
  3. package/.github/workflows/bun-test.yml +1 -1
  4. package/.github/workflows/bun-typecheck.yml +1 -1
  5. package/dist/index.d.ts +35 -9
  6. package/dist/index.js +543 -186
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +92 -50
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts +26 -11
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +16 -5
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +95 -75
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/filterLabelsAtTraceEdges.ts +74 -0
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/groupLabelsByChipAndOrientation.ts +45 -0
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/mergeLabelGroup.ts +71 -0
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +162 -28
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/doesTraceStartOrEndInLabel.ts +58 -0
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/isPointInsideLabel.ts +23 -0
  17. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/visualizeDecomposition.ts +54 -0
  18. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +13 -1
  19. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +110 -60
  20. package/package.json +1 -1
  21. package/site/examples/example27.page.tsx +4 -0
  22. package/tests/assets/example27.json +168 -0
  23. package/tests/examples/__snapshots__/example03.snap.svg +67 -67
  24. package/tests/examples/__snapshots__/example16.snap.svg +3 -3
  25. package/tests/examples/__snapshots__/example30.snap.svg +258 -0
  26. package/tests/examples/example30.test.ts +12 -0
  27. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +4 -1
@@ -0,0 +1,71 @@
1
+ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
2
+ import { getRectBounds } from "../../../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
3
+ import type { Point } from "graphics-debug" // Assuming Point is from graphics-debug or similar
4
+
5
+ /**
6
+ * Merges a group of NetLabelPlacement objects into a single, larger NetLabelPlacement.
7
+ * It calculates a new bounding box that encompasses all labels in the group,
8
+ * aggregates unique pin and MSP connection pair IDs, and creates a synthetic
9
+ * merged label.
10
+ *
11
+ * @param group An array of NetLabelPlacement objects to be merged. Assumes the group is not empty.
12
+ * @param groupKey A string key representing the group (e.g., "chip1-left"), used for generating the merged label's globalConnNetId.
13
+ * @returns An object containing the newly created merged NetLabelPlacement and a Set of the original globalConnNetIds that were merged.
14
+ * @throws Error if the input group is empty.
15
+ */
16
+ export const mergeLabelGroup = (
17
+ group: NetLabelPlacement[],
18
+ groupKey: string,
19
+ ): {
20
+ mergedLabel: NetLabelPlacement
21
+ originalNetIds: Set<string>
22
+ } => {
23
+ if (group.length === 0) {
24
+ throw new Error("Cannot merge an empty group of labels.")
25
+ }
26
+
27
+ let minX = Infinity
28
+ let minY = Infinity
29
+ let maxX = -Infinity
30
+ let maxY = -Infinity
31
+
32
+ const allPinIds = new Set<string>()
33
+ const allMspConnectionPairIds = new Set<string>()
34
+ const originalNetIds = new Set<string>()
35
+
36
+ for (const label of group) {
37
+ const bounds = getRectBounds(label.center, label.width, label.height)
38
+ minX = Math.min(minX, bounds.minX)
39
+ minY = Math.min(minY, bounds.minY)
40
+ maxX = Math.max(maxX, bounds.maxX)
41
+ maxY = Math.max(maxY, bounds.maxY)
42
+
43
+ label.pinIds.forEach((id) => allPinIds.add(id))
44
+ label.mspConnectionPairIds.forEach((id) => allMspConnectionPairIds.add(id))
45
+ originalNetIds.add(label.globalConnNetId)
46
+ }
47
+
48
+ const newWidth = maxX - minX
49
+ const newHeight = maxY - minY
50
+ const newCenter: Point = { x: minX + newWidth / 2, y: minY + newHeight / 2 }
51
+
52
+ // Use the first label as a template for properties that are consistent across the group
53
+ // like orientation.
54
+ const template = group[0]!
55
+ const syntheticId = `merged-group-${groupKey}`
56
+
57
+ const mergedLabel: NetLabelPlacement = {
58
+ ...template, // Copy common properties
59
+ globalConnNetId: syntheticId,
60
+ center: newCenter,
61
+ width: newWidth,
62
+ height: newHeight,
63
+ pinIds: Array.from(allPinIds),
64
+ mspConnectionPairIds: Array.from(allMspConnectionPairIds),
65
+ }
66
+
67
+ return {
68
+ mergedLabel,
69
+ originalNetIds,
70
+ }
71
+ }
@@ -6,15 +6,19 @@ 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 { doesTraceStartOrEndInLabel } from "./doesTraceStartOrEndInLabel"
10
+ import { visualizeDecomposition } from "./visualizeDecomposition"
9
11
 
10
12
  type Overlap = ReturnType<typeof detectTraceLabelOverlap>[0]
11
13
 
12
14
  // Define a type for the input of the internal overlap solver to avoid conflicts
13
- interface OverlapCollectionSolverInput {
15
+ export interface OverlapCollectionSolverInput {
14
16
  inputProblem: InputProblem
15
17
  traces: SolvedTracePath[]
16
- netLabelPlacements: NetLabelPlacement[]
18
+ initialNetLabelPlacements: NetLabelPlacement[]
19
+ mergedNetLabelPlacements: NetLabelPlacement[]
17
20
  mergedLabelNetIdMap: Record<string, Set<string>>
21
+ detourCounts: Map<string, number>
18
22
  }
19
23
 
20
24
  /**
@@ -23,28 +27,36 @@ interface OverlapCollectionSolverInput {
23
27
  */
24
28
  export class OverlapAvoidanceStepSolver extends BaseSolver {
25
29
  inputProblem: InputProblem
26
- netLabelPlacements: NetLabelPlacement[]
30
+ initialNetLabelPlacements: NetLabelPlacement[]
31
+ mergedNetLabelPlacements: NetLabelPlacement[]
27
32
  mergedLabelNetIdMap: Record<string, Set<string>>
28
33
 
29
34
  allTraces: SolvedTracePath[]
30
35
  modifiedTraces: SolvedTracePath[] = []
31
36
 
32
- private detourCountByLabel: Record<string, number> = {}
33
37
  private readonly PADDING_BUFFER = 0.1
38
+ private detourCounts: Map<string, number> = new Map()
34
39
 
35
40
  public override activeSubSolver: SingleOverlapSolver | null = null
36
41
  private overlapQueue: Overlap[] = []
37
42
  private recentlyFailed: Set<string> = new Set()
38
43
 
44
+ private currentlyProcessingOverlap: Overlap | null = null
45
+ private decomposedChildLabels: NetLabelPlacement[] | null = null
46
+
39
47
  constructor(solverInput: OverlapCollectionSolverInput) {
40
48
  super()
41
49
  this.inputProblem = solverInput.inputProblem
42
- this.netLabelPlacements = solverInput.netLabelPlacements
50
+ this.initialNetLabelPlacements = solverInput.initialNetLabelPlacements
51
+ this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements
43
52
  this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap
44
53
  this.allTraces = [...solverInput.traces]
54
+ this.detourCounts = solverInput.detourCounts
45
55
  }
46
56
 
47
57
  override _step() {
58
+ this.currentlyProcessingOverlap = null
59
+ this.decomposedChildLabels = null
48
60
  if (this.activeSubSolver) {
49
61
  this.activeSubSolver.step()
50
62
 
@@ -65,19 +77,14 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
65
77
  const overlapId = `${this.activeSubSolver.initialTrace.mspPairId}-${this.activeSubSolver.label.globalConnNetId}`
66
78
  this.recentlyFailed.add(overlapId)
67
79
  this.activeSubSolver = null
80
+ } else {
68
81
  }
69
82
  return
70
83
  }
71
84
 
72
- const overlaps = detectTraceLabelOverlap(
73
- this.allTraces,
74
- this.netLabelPlacements,
75
- ).filter((o) => {
76
- const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId]
77
- if (originalNetIds) {
78
- return !originalNetIds.has(o.trace.globalConnNetId)
79
- }
80
- return o.trace.globalConnNetId !== o.label.globalConnNetId
85
+ const overlaps = detectTraceLabelOverlap({
86
+ traces: this.allTraces,
87
+ netLabels: this.mergedNetLabelPlacements,
81
88
  })
82
89
 
83
90
  if (overlaps.length === 0) {
@@ -85,6 +92,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
85
92
  return
86
93
  }
87
94
 
95
+ // Filter out overlaps that have recently failed to avoid infinite loops
88
96
  const nonFailedOverlaps = overlaps.filter((o) => {
89
97
  const overlapId = `${o.trace.mspPairId}-${o.label.globalConnNetId}`
90
98
  return !this.recentlyFailed.has(overlapId)
@@ -98,24 +106,112 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
98
106
  this.overlapQueue = nonFailedOverlaps
99
107
 
100
108
  const nextOverlap = this.overlapQueue.shift()
109
+ this.currentlyProcessingOverlap = nextOverlap ?? null
101
110
 
102
111
  if (nextOverlap) {
103
112
  const traceToFix = this.allTraces.find(
104
113
  (t) => t.mspPairId === nextOverlap.trace.mspPairId,
105
- )
106
- if (traceToFix) {
107
- const labelId = nextOverlap.label.globalConnNetId
108
- const detourCount = this.detourCountByLabel[labelId] || 0
109
- this.detourCountByLabel[labelId] = detourCount + 1
110
-
111
- this.activeSubSolver = new SingleOverlapSolver({
112
- trace: traceToFix,
113
- label: nextOverlap.label,
114
- problem: this.inputProblem,
115
- paddingBuffer: this.PADDING_BUFFER,
116
- detourCount,
117
- })
114
+ )!
115
+ const labelToAvoid = nextOverlap.label
116
+
117
+ const originalNetIds =
118
+ this.mergedLabelNetIdMap[labelToAvoid.globalConnNetId]
119
+ const isSelfOverlap = originalNetIds?.has(traceToFix.globalConnNetId)
120
+
121
+ if (isSelfOverlap) {
122
+ const childLabels = this.initialNetLabelPlacements.filter((l) =>
123
+ originalNetIds.has(l.globalConnNetId),
124
+ )
125
+ this.decomposedChildLabels = childLabels
126
+ let actualOverlapLabel: NetLabelPlacement | null = null
127
+
128
+ for (const childLabel of childLabels) {
129
+ const overlapsWithChild = detectTraceLabelOverlap({
130
+ traces: [traceToFix],
131
+ netLabels: [childLabel],
132
+ })
133
+ if (overlapsWithChild.length > 0) {
134
+ actualOverlapLabel = childLabel
135
+ break
136
+ }
137
+ }
138
+
139
+ if (actualOverlapLabel) {
140
+ const detourCount =
141
+ this.detourCounts.get(actualOverlapLabel.globalConnNetId) ?? 0
142
+ this.detourCounts.set(
143
+ actualOverlapLabel.globalConnNetId,
144
+ detourCount + 1,
145
+ )
146
+
147
+ this.activeSubSolver = new SingleOverlapSolver({
148
+ trace: traceToFix,
149
+ label: actualOverlapLabel,
150
+ problem: this.inputProblem,
151
+ paddingBuffer: this.PADDING_BUFFER,
152
+ detourCount: detourCount,
153
+ })
154
+ } else {
155
+ const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
156
+ this.recentlyFailed.add(overlapId)
157
+ }
158
+ return
118
159
  }
160
+
161
+ if (
162
+ originalNetIds &&
163
+ doesTraceStartOrEndInLabel({ trace: traceToFix, label: labelToAvoid })
164
+ ) {
165
+ const childLabels = this.initialNetLabelPlacements.filter((l) =>
166
+ originalNetIds.has(l.globalConnNetId),
167
+ )
168
+ this.decomposedChildLabels = childLabels
169
+ let actualOverlapLabel: NetLabelPlacement | null = null
170
+
171
+ for (const childLabel of childLabels) {
172
+ const overlapsWithChild = detectTraceLabelOverlap({
173
+ traces: [traceToFix],
174
+ netLabels: [childLabel],
175
+ })
176
+ if (overlapsWithChild.length > 0) {
177
+ actualOverlapLabel = childLabel
178
+ break
179
+ }
180
+ }
181
+
182
+ if (actualOverlapLabel) {
183
+ const detourCount =
184
+ this.detourCounts.get(actualOverlapLabel.globalConnNetId) ?? 0
185
+ this.detourCounts.set(
186
+ actualOverlapLabel.globalConnNetId,
187
+ detourCount + 1,
188
+ )
189
+ this.activeSubSolver = new SingleOverlapSolver({
190
+ trace: traceToFix,
191
+ label: actualOverlapLabel,
192
+ problem: this.inputProblem,
193
+ paddingBuffer: this.PADDING_BUFFER,
194
+ detourCount: detourCount,
195
+ })
196
+ } else {
197
+ const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
198
+ this.recentlyFailed.add(overlapId)
199
+ }
200
+ return
201
+ }
202
+
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)
208
+ this.activeSubSolver = new SingleOverlapSolver({
209
+ trace: traceToFix,
210
+ label: labelToAvoid,
211
+ problem: this.inputProblem,
212
+ paddingBuffer: this.PADDING_BUFFER,
213
+ detourCount: detourCount,
214
+ })
119
215
  }
120
216
  }
121
217
 
@@ -123,6 +219,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
123
219
  return {
124
220
  allTraces: this.allTraces,
125
221
  modifiedTraces: this.modifiedTraces,
222
+ detourCounts: this.detourCounts,
126
223
  }
127
224
  }
128
225
 
@@ -130,7 +227,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
130
227
  if (this.activeSubSolver) {
131
228
  return this.activeSubSolver.visualize()
132
229
  }
133
- // When idle, show all the traces
230
+
134
231
  const graphics = visualizeInputProblem(this.inputProblem)
135
232
  if (!graphics.lines) graphics.lines = []
136
233
  for (const trace of this.allTraces) {
@@ -139,6 +236,43 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
139
236
  strokeColor: "purple",
140
237
  })
141
238
  }
239
+
240
+ if (this.currentlyProcessingOverlap) {
241
+ const { trace, label } = this.currentlyProcessingOverlap
242
+
243
+ // Highlight the colliding trace
244
+ graphics.lines!.push({
245
+ points: trace.tracePath,
246
+ strokeColor: "red",
247
+ })
248
+
249
+ if (this.decomposedChildLabels) {
250
+ visualizeDecomposition({
251
+ decomposedChildLabels: this.decomposedChildLabels,
252
+ collidingTrace: trace,
253
+ mergedLabel: label,
254
+ graphics,
255
+ })
256
+ } else {
257
+ // Standard case: highlight the entire label
258
+ if (!graphics.rects) graphics.rects = []
259
+ graphics.rects.push({
260
+ center: label.center,
261
+ width: label.width,
262
+ height: label.height,
263
+ fill: "yellow",
264
+ })
265
+ if (!graphics.texts) graphics.texts = []
266
+ graphics.texts.push({
267
+ x: label.center.x,
268
+ y: label.center.y + label.height / 2 + 0.5,
269
+ text: `COLLISION: Trace ${trace.mspPairId} vs Label ${label.globalConnNetId}`,
270
+ fontSize: 0.3,
271
+ color: "red",
272
+ })
273
+ }
274
+ }
275
+
142
276
  return graphics
143
277
  }
144
278
  }
@@ -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
+ }
@@ -0,0 +1,23 @@
1
+ import type { Point } from "graphics-debug"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
4
+
5
+ /**
6
+ * Checks if a point is inside the bounding box of a net label.
7
+ */
8
+ export const isPointInsideLabel = ({
9
+ point,
10
+ label,
11
+ }: {
12
+ point: Point
13
+ label: NetLabelPlacement
14
+ }): boolean => {
15
+ const bounds = getRectBounds(label.center, label.width, label.height)
16
+
17
+ return (
18
+ point.x >= bounds.minX &&
19
+ point.x <= bounds.maxX &&
20
+ point.y >= bounds.minY &&
21
+ point.y <= bounds.maxY
22
+ )
23
+ }
@@ -0,0 +1,54 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+
5
+ interface VisualizeDecompositionParams {
6
+ decomposedChildLabels: NetLabelPlacement[]
7
+ collidingTrace: SolvedTracePath
8
+ mergedLabel: NetLabelPlacement
9
+ graphics: GraphicsObject
10
+ }
11
+
12
+ /**
13
+ * Visualizes the decomposition of a merged label during overlap avoidance.
14
+ * It draws individual child labels with distinct colors based on their relation
15
+ * to the colliding trace and adds a textual report to the graphics.
16
+ *
17
+ * @param {VisualizeDecompositionParams} params - The parameters for visualization.
18
+ * @param {NetLabelPlacement[]} params.decomposedChildLabels - The individual labels that make up the merged group.
19
+ * @param {SolvedTracePath} params.collidingTrace - The trace that is currently colliding with the merged label.
20
+ * @param {NetLabelPlacement} params.mergedLabel - The original merged label that is being decomposed.
21
+ * @param {GraphicsObject} params.graphics - The graphics object to which the visualization elements will be added.
22
+ * @returns {GraphicsObject} The modified graphics object with decomposition visualization elements.
23
+ */
24
+ export const visualizeDecomposition = (
25
+ params: VisualizeDecompositionParams,
26
+ ): GraphicsObject => {
27
+ const { decomposedChildLabels, collidingTrace, mergedLabel, graphics } =
28
+ params
29
+
30
+ if (!graphics.rects) graphics.rects = []
31
+ if (!graphics.texts) graphics.texts = []
32
+
33
+ for (const childLabel of decomposedChildLabels) {
34
+ const isOwnLabel =
35
+ childLabel.globalConnNetId === collidingTrace.globalConnNetId
36
+
37
+ graphics.rects.push({
38
+ center: childLabel.center,
39
+ width: childLabel.width,
40
+ height: childLabel.height,
41
+ fill: isOwnLabel ? "green" : "red", // Green for own label, red for others
42
+ })
43
+ }
44
+
45
+ graphics.texts.push({
46
+ x: mergedLabel.center.x,
47
+ y: mergedLabel.center.y + mergedLabel.height / 2 + 0.5,
48
+ text: `DECOMPOSITION: Trace ${collidingTrace.mspPairId} vs Merged Label ${mergedLabel.globalConnNetId}`,
49
+ fontSize: 0.3,
50
+ color: "blue",
51
+ })
52
+
53
+ return graphics
54
+ }
@@ -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 {