@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.
- package/.github/workflows/bun-formatcheck.yml +1 -1
- package/.github/workflows/bun-pver-release.yml +1 -1
- package/.github/workflows/bun-test.yml +1 -1
- package/.github/workflows/bun-typecheck.yml +1 -1
- package/dist/index.d.ts +35 -9
- package/dist/index.js +543 -186
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +92 -50
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts +26 -11
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +16 -5
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +95 -75
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/filterLabelsAtTraceEdges.ts +74 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/groupLabelsByChipAndOrientation.ts +45 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/mergeLabelGroup.ts +71 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +162 -28
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/doesTraceStartOrEndInLabel.ts +58 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/isPointInsideLabel.ts +23 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/visualizeDecomposition.ts +54 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +13 -1
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +110 -60
- package/package.json +1 -1
- package/site/examples/example27.page.tsx +4 -0
- package/tests/assets/example27.json +168 -0
- package/tests/examples/__snapshots__/example03.snap.svg +67 -67
- package/tests/examples/__snapshots__/example16.snap.svg +3 -3
- package/tests/examples/__snapshots__/example30.snap.svg +258 -0
- package/tests/examples/example30.test.ts +12 -0
- 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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|