@tscircuit/schematic-trace-solver 0.0.125 → 0.0.126
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/dist/index.d.ts +45 -1
- package/dist/index.js +427 -35
- package/lib/solvers/NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver.ts +15 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +69 -11
- package/lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts +216 -0
- package/lib/solvers/TraceElbowTransitionSimplificationSolver/generateElbowTransitionSimplificationCandidates.ts +186 -0
- package/lib/solvers/TraceElbowTransitionSimplificationSolver/types.ts +10 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +3 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +16 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +56 -13
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260806T093501Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +3 -3
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +6 -6
- package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +135 -0
- package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.json +505 -0
- package/tests/bug-reports/bug-report-20260806T093501Z/bug-report-20260806T093501Z.test.ts +66 -0
- package/tests/examples/__snapshots__/example33.snap.svg +1 -1
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
|
|
13
13
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
14
14
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
15
|
+
import type { CompletedTraceReroute } from "lib/solvers/TraceElbowTransitionSimplificationSolver/types"
|
|
15
16
|
|
|
16
17
|
const ON_PATH_EPS = 1e-6
|
|
17
18
|
|
|
@@ -172,6 +173,7 @@ export class NetLabelTraceCollisionSolver extends BaseSolver {
|
|
|
172
173
|
|
|
173
174
|
outputTraces: SolvedTracePath[]
|
|
174
175
|
outputNetLabelPlacements: NetLabelPlacement[]
|
|
176
|
+
completedReroutes: CompletedTraceReroute[] = []
|
|
175
177
|
|
|
176
178
|
override activeSubSolver: SingleOverlapSolver | null = null
|
|
177
179
|
private recentlyFailed = new Set<string>()
|
|
@@ -208,6 +210,17 @@ export class NetLabelTraceCollisionSolver extends BaseSolver {
|
|
|
208
210
|
if (this.activeSubSolver.solved) {
|
|
209
211
|
const solvedPath = this.activeSubSolver.solvedTracePath
|
|
210
212
|
if (solvedPath) {
|
|
213
|
+
this.completedReroutes.push({
|
|
214
|
+
initialTrace: {
|
|
215
|
+
...this.activeSubSolver.initialTrace,
|
|
216
|
+
tracePath: this.activeSubSolver.initialTrace.tracePath.map(
|
|
217
|
+
(point) => ({ ...point }),
|
|
218
|
+
),
|
|
219
|
+
},
|
|
220
|
+
reroutedTracePath: solvedPath.map((point) => ({ ...point })),
|
|
221
|
+
label: this.activeSubSolver.label,
|
|
222
|
+
detourCount: this.activeSubSolver.detourCount,
|
|
223
|
+
})
|
|
211
224
|
const idx = this.outputTraces.findIndex(
|
|
212
225
|
(t) => t.mspPairId === this.activeSubSolver!.initialTrace.mspPairId,
|
|
213
226
|
)
|
|
@@ -282,6 +295,7 @@ export class NetLabelTraceCollisionSolver extends BaseSolver {
|
|
|
282
295
|
paddingBuffer: PADDING_BUFFER,
|
|
283
296
|
detourCount,
|
|
284
297
|
tracesToAvoidOverlapping: this.outputTraces,
|
|
298
|
+
netLabelPlacements: this.outputNetLabelPlacements,
|
|
285
299
|
})
|
|
286
300
|
}
|
|
287
301
|
|
|
@@ -319,6 +333,7 @@ export class NetLabelTraceCollisionSolver extends BaseSolver {
|
|
|
319
333
|
getOutput() {
|
|
320
334
|
return {
|
|
321
335
|
traces: this.outputTraces,
|
|
336
|
+
completedReroutes: this.completedReroutes,
|
|
322
337
|
netLabelPlacements: this.outputNetLabelPlacements,
|
|
323
338
|
}
|
|
324
339
|
}
|
|
@@ -30,6 +30,7 @@ import { NetLabelTraceCollisionSolver } from "../NetLabelTraceCollisionSolver/Ne
|
|
|
30
30
|
import { NetLabelNetLabelCollisionSolver } from "../NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver"
|
|
31
31
|
import { UnroutedTraceRecoverySolver } from "../UnroutedTraceRecoverySolver/UnroutedTraceRecoverySolver"
|
|
32
32
|
import { SameNetJunctionAlignmentSolver } from "../SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver"
|
|
33
|
+
import { TraceElbowTransitionSimplificationSolver } from "../TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver"
|
|
33
34
|
|
|
34
35
|
type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
|
|
35
36
|
solverName: string
|
|
@@ -88,6 +89,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
88
89
|
netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
|
|
89
90
|
traceCleanupSolver2?: TraceCleanupSolver
|
|
90
91
|
netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver
|
|
92
|
+
traceElbowTransitionSimplificationSolver?: TraceElbowTransitionSimplificationSolver
|
|
93
|
+
preAlignmentTraceElbowTransitionSimplificationSolver?: TraceElbowTransitionSimplificationSolver
|
|
94
|
+
finalTraceElbowTransitionSimplificationSolver?: TraceElbowTransitionSimplificationSolver
|
|
91
95
|
sameNetJunctionAlignmentSolver?: SameNetJunctionAlignmentSolver
|
|
92
96
|
|
|
93
97
|
startTimeOfPhase: Record<string, number>
|
|
@@ -224,9 +228,27 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
224
228
|
]
|
|
225
229
|
},
|
|
226
230
|
),
|
|
231
|
+
definePipelineStep(
|
|
232
|
+
"traceElbowTransitionSimplificationSolver",
|
|
233
|
+
TraceElbowTransitionSimplificationSolver,
|
|
234
|
+
(instance) => {
|
|
235
|
+
const overlapAvoidanceOutput =
|
|
236
|
+
instance.traceLabelOverlapAvoidanceSolver!.getOutput()
|
|
237
|
+
return [
|
|
238
|
+
{
|
|
239
|
+
inputProblem: instance.inputProblem,
|
|
240
|
+
traces: overlapAvoidanceOutput.traces,
|
|
241
|
+
completedReroutes: overlapAvoidanceOutput.completedReroutes,
|
|
242
|
+
netLabelPlacements:
|
|
243
|
+
instance.traceLabelOverlapAvoidanceSolver!.netLabelPlacements,
|
|
244
|
+
paddingBuffer: 0.1,
|
|
245
|
+
},
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
),
|
|
227
249
|
definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
|
|
228
250
|
const prevSolverOutput =
|
|
229
|
-
instance.
|
|
251
|
+
instance.traceElbowTransitionSimplificationSolver!.getOutput()
|
|
230
252
|
const traces = prevSolverOutput.traces
|
|
231
253
|
|
|
232
254
|
const labelMergingOutput =
|
|
@@ -348,12 +370,29 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
348
370
|
},
|
|
349
371
|
],
|
|
350
372
|
),
|
|
373
|
+
definePipelineStep(
|
|
374
|
+
"preAlignmentTraceElbowTransitionSimplificationSolver",
|
|
375
|
+
TraceElbowTransitionSimplificationSolver,
|
|
376
|
+
(instance) => {
|
|
377
|
+
const collisionOutput =
|
|
378
|
+
instance.preAlignmentNetLabelTraceCollisionSolver!.getOutput()
|
|
379
|
+
return [
|
|
380
|
+
{
|
|
381
|
+
inputProblem: instance.inputProblem,
|
|
382
|
+
traces: collisionOutput.traces,
|
|
383
|
+
completedReroutes: collisionOutput.completedReroutes,
|
|
384
|
+
netLabelPlacements: collisionOutput.netLabelPlacements,
|
|
385
|
+
paddingBuffer: 0.1,
|
|
386
|
+
},
|
|
387
|
+
]
|
|
388
|
+
},
|
|
389
|
+
),
|
|
351
390
|
definePipelineStep(
|
|
352
391
|
"traceCleanupSolver2",
|
|
353
392
|
TraceCleanupSolver,
|
|
354
393
|
(instance) => {
|
|
355
394
|
const collisionOutput =
|
|
356
|
-
instance.
|
|
395
|
+
instance.preAlignmentTraceElbowTransitionSimplificationSolver!.getOutput()
|
|
357
396
|
const labelMergingOutput =
|
|
358
397
|
instance.traceLabelOverlapAvoidanceSolver!.labelMergingSolver!.getOutput()
|
|
359
398
|
|
|
@@ -416,18 +455,37 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
416
455
|
]
|
|
417
456
|
},
|
|
418
457
|
),
|
|
458
|
+
definePipelineStep(
|
|
459
|
+
"finalTraceElbowTransitionSimplificationSolver",
|
|
460
|
+
TraceElbowTransitionSimplificationSolver,
|
|
461
|
+
(instance) => {
|
|
462
|
+
const collisionOutput =
|
|
463
|
+
instance.netLabelTraceCollisionSolver!.getOutput()
|
|
464
|
+
return [
|
|
465
|
+
{
|
|
466
|
+
inputProblem: instance.inputProblem,
|
|
467
|
+
traces: collisionOutput.traces,
|
|
468
|
+
completedReroutes: collisionOutput.completedReroutes,
|
|
469
|
+
netLabelPlacements: collisionOutput.netLabelPlacements,
|
|
470
|
+
paddingBuffer: 0.1,
|
|
471
|
+
},
|
|
472
|
+
]
|
|
473
|
+
},
|
|
474
|
+
),
|
|
419
475
|
definePipelineStep(
|
|
420
476
|
"netLabelNetLabelCollisionSolver",
|
|
421
477
|
NetLabelNetLabelCollisionSolver,
|
|
422
|
-
(instance) =>
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
instance.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
478
|
+
(instance) => {
|
|
479
|
+
const simplificationOutput =
|
|
480
|
+
instance.finalTraceElbowTransitionSimplificationSolver!.getOutput()
|
|
481
|
+
return [
|
|
482
|
+
{
|
|
483
|
+
inputProblem: instance.inputProblem,
|
|
484
|
+
traces: simplificationOutput.traces,
|
|
485
|
+
netLabelPlacements: simplificationOutput.netLabelPlacements,
|
|
486
|
+
},
|
|
487
|
+
]
|
|
488
|
+
},
|
|
431
489
|
),
|
|
432
490
|
definePipelineStep(
|
|
433
491
|
"sameNetJunctionAlignmentSolver",
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
4
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
6
|
+
import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
7
|
+
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
8
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
9
|
+
import { preservesLabelAnchors } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/preservesLabelAnchors"
|
|
10
|
+
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
11
|
+
import { detectTraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
|
|
12
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
13
|
+
import { doesPathCoincideWithTraces } from "lib/utils/doesPathCoincideWithTraces"
|
|
14
|
+
import type { CompletedTraceReroute } from "./types"
|
|
15
|
+
import { generateElbowTransitionSimplificationCandidates } from "./generateElbowTransitionSimplificationCandidates"
|
|
16
|
+
|
|
17
|
+
interface TraceElbowTransitionSimplificationSolverInput {
|
|
18
|
+
inputProblem: InputProblem
|
|
19
|
+
traces: SolvedTracePath[]
|
|
20
|
+
completedReroutes: CompletedTraceReroute[]
|
|
21
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
22
|
+
paddingBuffer: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const PATH_LENGTH_EPSILON = 1e-9
|
|
26
|
+
|
|
27
|
+
const getPathLength = (points: Point[]) =>
|
|
28
|
+
points.slice(1).reduce((length, point, pointIndex) => {
|
|
29
|
+
const previousPoint = points[pointIndex]!
|
|
30
|
+
return (
|
|
31
|
+
length +
|
|
32
|
+
Math.abs(point.x - previousPoint.x) +
|
|
33
|
+
Math.abs(point.y - previousPoint.y)
|
|
34
|
+
)
|
|
35
|
+
}, 0)
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Post-processes traces after trace/label overlap avoidance. It removes
|
|
39
|
+
* redundant elbow transitions and, when needed, shifts a simplified elbow
|
|
40
|
+
* around a rendered label. Every replacement is revalidated against labels,
|
|
41
|
+
* components, other-net traces, and label anchors.
|
|
42
|
+
*/
|
|
43
|
+
export class TraceElbowTransitionSimplificationSolver extends BaseSolver {
|
|
44
|
+
private input: TraceElbowTransitionSimplificationSolverInput
|
|
45
|
+
private outputTraces: SolvedTracePath[]
|
|
46
|
+
private traceIdQueue: string[]
|
|
47
|
+
private obstacles: ReturnType<typeof getObstacleRects>
|
|
48
|
+
|
|
49
|
+
constructor(input: TraceElbowTransitionSimplificationSolverInput) {
|
|
50
|
+
super()
|
|
51
|
+
this.input = input
|
|
52
|
+
this.outputTraces = [...input.traces]
|
|
53
|
+
const reroutedTraceIds = new Set(
|
|
54
|
+
input.completedReroutes.map(
|
|
55
|
+
(completedReroute) => completedReroute.initialTrace.mspPairId,
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
this.traceIdQueue = input.traces
|
|
59
|
+
.map((trace) => trace.mspPairId)
|
|
60
|
+
.filter((traceId) => reroutedTraceIds.has(traceId))
|
|
61
|
+
this.obstacles = getObstacleRects(input.inputProblem)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override _step() {
|
|
65
|
+
const traceId = this.traceIdQueue.shift()
|
|
66
|
+
if (!traceId) {
|
|
67
|
+
this.solved = true
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const traceIndex = this.outputTraces.findIndex(
|
|
72
|
+
(trace) => trace.mspPairId === traceId,
|
|
73
|
+
)
|
|
74
|
+
const trace = this.outputTraces[traceIndex]!
|
|
75
|
+
const tracePath = simplifyPath(trace.tracePath)
|
|
76
|
+
const initialOverlaps = detectTraceLabelOverlap({
|
|
77
|
+
traces: [{ ...trace, tracePath }],
|
|
78
|
+
netLabels: this.input.netLabelPlacements,
|
|
79
|
+
})
|
|
80
|
+
const otherNetTraces = this.outputTraces.filter(
|
|
81
|
+
(otherTrace) =>
|
|
82
|
+
otherTrace.mspPairId !== trace.mspPairId &&
|
|
83
|
+
otherTrace.globalConnNetId !== trace.globalConnNetId,
|
|
84
|
+
)
|
|
85
|
+
const candidateByPath = new Map<string, Point[]>()
|
|
86
|
+
const completedReroutes = this.input.completedReroutes.filter(
|
|
87
|
+
(completedReroute) =>
|
|
88
|
+
completedReroute.initialTrace.mspPairId === trace.mspPairId,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
for (const completedReroute of completedReroutes) {
|
|
92
|
+
const initialReroutePath = simplifyPath(
|
|
93
|
+
completedReroute.initialTrace.tracePath,
|
|
94
|
+
)
|
|
95
|
+
const reroutedPath = simplifyPath(completedReroute.reroutedTracePath)
|
|
96
|
+
const initialRerouteOverlapCount = detectTraceLabelOverlap({
|
|
97
|
+
traces: [
|
|
98
|
+
{ ...completedReroute.initialTrace, tracePath: initialReroutePath },
|
|
99
|
+
],
|
|
100
|
+
netLabels: this.input.netLabelPlacements,
|
|
101
|
+
}).length
|
|
102
|
+
|
|
103
|
+
const candidates = generateElbowTransitionSimplificationCandidates({
|
|
104
|
+
trace: completedReroute.initialTrace,
|
|
105
|
+
label: completedReroute.label,
|
|
106
|
+
netLabelPlacements: this.input.netLabelPlacements,
|
|
107
|
+
paddingBuffer: this.input.paddingBuffer,
|
|
108
|
+
detourCount: completedReroute.detourCount,
|
|
109
|
+
})
|
|
110
|
+
for (const candidate of candidates) {
|
|
111
|
+
const simplifiedCandidate = simplifyPath(candidate)
|
|
112
|
+
const candidateTrace = {
|
|
113
|
+
...completedReroute.initialTrace,
|
|
114
|
+
tracePath: simplifiedCandidate,
|
|
115
|
+
}
|
|
116
|
+
const candidateOverlapCount = detectTraceLabelOverlap({
|
|
117
|
+
traces: [candidateTrace],
|
|
118
|
+
netLabels: this.input.netLabelPlacements,
|
|
119
|
+
}).length
|
|
120
|
+
const isSimplerEquivalentReroute =
|
|
121
|
+
candidateOverlapCount < initialRerouteOverlapCount &&
|
|
122
|
+
Math.abs(
|
|
123
|
+
getPathLength(simplifiedCandidate) - getPathLength(reroutedPath),
|
|
124
|
+
) < PATH_LENGTH_EPSILON &&
|
|
125
|
+
simplifiedCandidate.length < reroutedPath.length &&
|
|
126
|
+
preservesLabelAnchors(
|
|
127
|
+
this.input.netLabelPlacements,
|
|
128
|
+
[completedReroute.initialTrace],
|
|
129
|
+
[candidateTrace],
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if (isSimplerEquivalentReroute) {
|
|
133
|
+
candidateByPath.set(
|
|
134
|
+
simplifiedCandidate
|
|
135
|
+
.map((point) => `${point.x},${point.y}`)
|
|
136
|
+
.join(";"),
|
|
137
|
+
simplifiedCandidate,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const initialOverlapIds = new Set(
|
|
144
|
+
initialOverlaps.map(
|
|
145
|
+
({ label }) => `${label.globalConnNetId}:${label.netId}`,
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
const initialPathLength = getPathLength(tracePath)
|
|
149
|
+
|
|
150
|
+
const validCandidates = [...candidateByPath.values()].filter(
|
|
151
|
+
(candidatePath) => {
|
|
152
|
+
const candidateTrace = { ...trace, tracePath: candidatePath }
|
|
153
|
+
const candidateOverlaps = detectTraceLabelOverlap({
|
|
154
|
+
traces: [candidateTrace],
|
|
155
|
+
netLabels: this.input.netLabelPlacements,
|
|
156
|
+
})
|
|
157
|
+
const candidateOnlyKeepsExistingOverlaps = candidateOverlaps.every(
|
|
158
|
+
({ label }) =>
|
|
159
|
+
initialOverlapIds.has(`${label.globalConnNetId}:${label.netId}`),
|
|
160
|
+
)
|
|
161
|
+
const reducesCollisions =
|
|
162
|
+
candidateOverlaps.length < initialOverlaps.length
|
|
163
|
+
const simplifiesGeometry =
|
|
164
|
+
candidateOverlaps.length === initialOverlaps.length &&
|
|
165
|
+
getPathLength(candidatePath) <=
|
|
166
|
+
initialPathLength + PATH_LENGTH_EPSILON &&
|
|
167
|
+
candidatePath.length < tracePath.length
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
candidateOnlyKeepsExistingOverlaps &&
|
|
171
|
+
(reducesCollisions || simplifiesGeometry) &&
|
|
172
|
+
preservesLabelAnchors(
|
|
173
|
+
this.input.netLabelPlacements,
|
|
174
|
+
[trace],
|
|
175
|
+
[candidateTrace],
|
|
176
|
+
) &&
|
|
177
|
+
!isPathCollidingWithObstacles(candidatePath, this.obstacles) &&
|
|
178
|
+
!doesPathCoincideWithTraces(candidatePath, otherNetTraces)
|
|
179
|
+
)
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const getOverlapCount = (candidatePath: Point[]) =>
|
|
184
|
+
detectTraceLabelOverlap({
|
|
185
|
+
traces: [{ ...trace, tracePath: candidatePath }],
|
|
186
|
+
netLabels: this.input.netLabelPlacements,
|
|
187
|
+
}).length
|
|
188
|
+
validCandidates.sort((a, b) => {
|
|
189
|
+
const overlapDifference = getOverlapCount(a) - getOverlapCount(b)
|
|
190
|
+
if (overlapDifference !== 0) return overlapDifference
|
|
191
|
+
return getPathLength(a) - getPathLength(b) || a.length - b.length
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const bestCandidate = validCandidates[0]
|
|
195
|
+
if (!bestCandidate) return
|
|
196
|
+
|
|
197
|
+
this.outputTraces[traceIndex] = { ...trace, tracePath: bestCandidate }
|
|
198
|
+
this.stats.simplifiedTraceCount = (this.stats.simplifiedTraceCount ?? 0) + 1
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
getOutput() {
|
|
202
|
+
return {
|
|
203
|
+
traces: this.outputTraces,
|
|
204
|
+
netLabelPlacements: this.input.netLabelPlacements,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
override visualize(): GraphicsObject {
|
|
209
|
+
const graphics = visualizeInputProblem(this.input.inputProblem)
|
|
210
|
+
graphics.lines ??= []
|
|
211
|
+
for (const trace of this.outputTraces) {
|
|
212
|
+
graphics.lines.push({ points: trace.tracePath, strokeColor: "purple" })
|
|
213
|
+
}
|
|
214
|
+
return graphics
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
4
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import {
|
|
6
|
+
isHorizontal,
|
|
7
|
+
isVertical,
|
|
8
|
+
segmentIntersectsRect,
|
|
9
|
+
} from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
10
|
+
import { shiftSegmentOrth } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps"
|
|
11
|
+
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
12
|
+
import { detectTraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
|
|
13
|
+
|
|
14
|
+
export const isSimpleFiveSegmentElbow = (path: Point[]): boolean => {
|
|
15
|
+
const simplifiedPath = simplifyPath(path)
|
|
16
|
+
if (simplifiedPath.length !== 6) return false
|
|
17
|
+
|
|
18
|
+
const segmentIsHorizontal = simplifiedPath
|
|
19
|
+
.slice(0, -1)
|
|
20
|
+
.map((point, index) => isHorizontal(point, simplifiedPath[index + 1]!))
|
|
21
|
+
|
|
22
|
+
return segmentIsHorizontal.every(
|
|
23
|
+
(isHorizontalSegment, index) =>
|
|
24
|
+
index === 0 || isHorizontalSegment !== segmentIsHorizontal[index - 1],
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const generateSegmentShiftCandidates = ({
|
|
29
|
+
trace,
|
|
30
|
+
label,
|
|
31
|
+
paddingBuffer,
|
|
32
|
+
detourCount,
|
|
33
|
+
}: {
|
|
34
|
+
trace: SolvedTracePath
|
|
35
|
+
label: NetLabelPlacement
|
|
36
|
+
paddingBuffer: number
|
|
37
|
+
detourCount: number
|
|
38
|
+
}): Point[][] => {
|
|
39
|
+
if (trace.globalConnNetId === label.globalConnNetId) return []
|
|
40
|
+
|
|
41
|
+
const path = simplifyPath(trace.tracePath)
|
|
42
|
+
if (!isSimpleFiveSegmentElbow(path)) return []
|
|
43
|
+
|
|
44
|
+
const labelBounds = getRectBounds(label.center, label.width, label.height)
|
|
45
|
+
const effectivePadding = paddingBuffer + detourCount * paddingBuffer
|
|
46
|
+
const paddedLabelBounds = {
|
|
47
|
+
minX: labelBounds.minX - effectivePadding,
|
|
48
|
+
maxX: labelBounds.maxX + effectivePadding,
|
|
49
|
+
minY: labelBounds.minY - effectivePadding,
|
|
50
|
+
maxY: labelBounds.maxY + effectivePadding,
|
|
51
|
+
}
|
|
52
|
+
const candidates: Point[][] = []
|
|
53
|
+
|
|
54
|
+
for (let segmentIndex = 1; segmentIndex < path.length - 2; segmentIndex++) {
|
|
55
|
+
const segmentStart = path[segmentIndex]!
|
|
56
|
+
const segmentEnd = path[segmentIndex + 1]!
|
|
57
|
+
if (!segmentIntersectsRect(segmentStart, segmentEnd, labelBounds)) continue
|
|
58
|
+
|
|
59
|
+
const isHorizontalSegment = isHorizontal(segmentStart, segmentEnd)
|
|
60
|
+
const isVerticalSegment = isVertical(segmentStart, segmentEnd)
|
|
61
|
+
if (!isHorizontalSegment && !isVerticalSegment) continue
|
|
62
|
+
|
|
63
|
+
const axis = isHorizontalSegment ? "y" : "x"
|
|
64
|
+
const coordinates = isHorizontalSegment
|
|
65
|
+
? [paddedLabelBounds.minY, paddedLabelBounds.maxY]
|
|
66
|
+
: [paddedLabelBounds.minX, paddedLabelBounds.maxX]
|
|
67
|
+
|
|
68
|
+
for (const coordinate of coordinates) {
|
|
69
|
+
const shiftedPath = shiftSegmentOrth(path, segmentIndex, axis, coordinate)
|
|
70
|
+
if (shiftedPath) candidates.push(shiftedPath)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return candidates
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const generateTransitionShiftCandidates = ({
|
|
78
|
+
trace,
|
|
79
|
+
label,
|
|
80
|
+
paddingBuffer,
|
|
81
|
+
detourCount,
|
|
82
|
+
}: {
|
|
83
|
+
trace: SolvedTracePath
|
|
84
|
+
label: NetLabelPlacement
|
|
85
|
+
paddingBuffer: number
|
|
86
|
+
detourCount: number
|
|
87
|
+
}): Point[][] => {
|
|
88
|
+
const path = simplifyPath(trace.tracePath)
|
|
89
|
+
if (!isSimpleFiveSegmentElbow(path)) return []
|
|
90
|
+
|
|
91
|
+
const labelBounds = getRectBounds(label.center, label.width, label.height)
|
|
92
|
+
const effectivePadding = paddingBuffer + detourCount * paddingBuffer
|
|
93
|
+
const paddedLabelBounds = {
|
|
94
|
+
minX: labelBounds.minX - effectivePadding,
|
|
95
|
+
maxX: labelBounds.maxX + effectivePadding,
|
|
96
|
+
minY: labelBounds.minY - effectivePadding,
|
|
97
|
+
maxY: labelBounds.maxY + effectivePadding,
|
|
98
|
+
}
|
|
99
|
+
const start = path[0]!
|
|
100
|
+
const end = path[path.length - 1]!
|
|
101
|
+
const middleSegmentIndex = 2
|
|
102
|
+
const candidates: Point[][] = []
|
|
103
|
+
|
|
104
|
+
for (let segmentIndex = 0; segmentIndex < path.length - 1; segmentIndex++) {
|
|
105
|
+
const segmentStart = path[segmentIndex]!
|
|
106
|
+
const segmentEnd = path[segmentIndex + 1]!
|
|
107
|
+
if (!segmentIntersectsRect(segmentStart, segmentEnd, labelBounds)) continue
|
|
108
|
+
if (Math.abs(segmentIndex - middleSegmentIndex) !== 1) continue
|
|
109
|
+
|
|
110
|
+
const isHorizontalSegment = isHorizontal(segmentStart, segmentEnd)
|
|
111
|
+
const isVerticalSegment = isVertical(segmentStart, segmentEnd)
|
|
112
|
+
if (!isHorizontalSegment && !isVerticalSegment) continue
|
|
113
|
+
|
|
114
|
+
const axis = isHorizontalSegment ? "x" : "y"
|
|
115
|
+
const min = isHorizontalSegment
|
|
116
|
+
? paddedLabelBounds.minX
|
|
117
|
+
: paddedLabelBounds.minY
|
|
118
|
+
const max = isHorizontalSegment
|
|
119
|
+
? paddedLabelBounds.maxX
|
|
120
|
+
: paddedLabelBounds.maxY
|
|
121
|
+
const startCoordinate = isHorizontalSegment ? start.x : start.y
|
|
122
|
+
const endCoordinate = isHorizontalSegment ? end.x : end.y
|
|
123
|
+
const corridorCoordinates: number[] = []
|
|
124
|
+
|
|
125
|
+
if (startCoordinate < min) {
|
|
126
|
+
corridorCoordinates.push((startCoordinate + min) / 2)
|
|
127
|
+
} else if (startCoordinate > max) {
|
|
128
|
+
corridorCoordinates.push((startCoordinate + max) / 2)
|
|
129
|
+
}
|
|
130
|
+
if (endCoordinate < min) {
|
|
131
|
+
corridorCoordinates.push((endCoordinate + min) / 2)
|
|
132
|
+
} else if (endCoordinate > max) {
|
|
133
|
+
corridorCoordinates.push((endCoordinate + max) / 2)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const coordinate of new Set(corridorCoordinates)) {
|
|
137
|
+
const shiftedPath = shiftSegmentOrth(
|
|
138
|
+
path,
|
|
139
|
+
middleSegmentIndex,
|
|
140
|
+
axis,
|
|
141
|
+
coordinate,
|
|
142
|
+
)
|
|
143
|
+
if (shiftedPath) candidates.push(shiftedPath)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return candidates
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const generateElbowTransitionSimplificationCandidates = ({
|
|
151
|
+
trace,
|
|
152
|
+
label,
|
|
153
|
+
netLabelPlacements,
|
|
154
|
+
paddingBuffer,
|
|
155
|
+
detourCount,
|
|
156
|
+
}: {
|
|
157
|
+
trace: SolvedTracePath
|
|
158
|
+
label: NetLabelPlacement
|
|
159
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
160
|
+
paddingBuffer: number
|
|
161
|
+
detourCount: number
|
|
162
|
+
}): Point[][] => {
|
|
163
|
+
const transitionCandidates = generateTransitionShiftCandidates({
|
|
164
|
+
trace,
|
|
165
|
+
label,
|
|
166
|
+
paddingBuffer,
|
|
167
|
+
detourCount,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
return transitionCandidates.flatMap((tracePath) => {
|
|
171
|
+
const shiftedTrace = { ...trace, tracePath }
|
|
172
|
+
const shiftedOverlaps = detectTraceLabelOverlap({
|
|
173
|
+
traces: [shiftedTrace],
|
|
174
|
+
netLabels: netLabelPlacements,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
return shiftedOverlaps.flatMap(({ label: shiftedLabel }) =>
|
|
178
|
+
generateSegmentShiftCandidates({
|
|
179
|
+
trace: shiftedTrace,
|
|
180
|
+
label: shiftedLabel,
|
|
181
|
+
paddingBuffer,
|
|
182
|
+
detourCount,
|
|
183
|
+
}),
|
|
184
|
+
)
|
|
185
|
+
})
|
|
186
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
+
|
|
5
|
+
export interface CompletedTraceReroute {
|
|
6
|
+
initialTrace: SolvedTracePath
|
|
7
|
+
reroutedTracePath: Point[]
|
|
8
|
+
label: NetLabelPlacement
|
|
9
|
+
detourCount: number
|
|
10
|
+
}
|
|
@@ -122,6 +122,9 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
|
122
122
|
const solvedTraces = this.subSolvers.flatMap((s) => s.getOutput().allTraces)
|
|
123
123
|
return {
|
|
124
124
|
traces: [...this.cleanTraces, ...solvedTraces],
|
|
125
|
+
completedReroutes: this.subSolvers.flatMap(
|
|
126
|
+
(solver) => solver.completedReroutes,
|
|
127
|
+
),
|
|
125
128
|
netLabelPlacements:
|
|
126
129
|
this.labelMergingSolver?.getOutput().netLabelPlacements ??
|
|
127
130
|
this.netLabelPlacements,
|
|
@@ -8,6 +8,7 @@ import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
|
|
|
8
8
|
import { SingleOverlapSolver } from "../SingleOverlapSolver/SingleOverlapSolver"
|
|
9
9
|
import { doesTraceStartOrEndInLabel } from "./doesTraceStartOrEndInLabel"
|
|
10
10
|
import { visualizeDecomposition } from "./visualizeDecomposition"
|
|
11
|
+
import type { CompletedTraceReroute } from "lib/solvers/TraceElbowTransitionSimplificationSolver/types"
|
|
11
12
|
|
|
12
13
|
type Overlap = ReturnType<typeof detectTraceLabelOverlap>[0]
|
|
13
14
|
|
|
@@ -35,6 +36,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
35
36
|
allTraces: SolvedTracePath[]
|
|
36
37
|
tracesToAvoidOverlapping: SolvedTracePath[]
|
|
37
38
|
modifiedTraces: SolvedTracePath[] = []
|
|
39
|
+
completedReroutes: CompletedTraceReroute[] = []
|
|
38
40
|
|
|
39
41
|
private readonly PADDING_BUFFER = 0.1
|
|
40
42
|
private detourCounts: Map<string, number> = new Map()
|
|
@@ -66,6 +68,17 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
66
68
|
if (this.activeSubSolver.solved) {
|
|
67
69
|
const solvedPath = this.activeSubSolver.solvedTracePath
|
|
68
70
|
if (solvedPath) {
|
|
71
|
+
this.completedReroutes.push({
|
|
72
|
+
initialTrace: {
|
|
73
|
+
...this.activeSubSolver.initialTrace,
|
|
74
|
+
tracePath: this.activeSubSolver.initialTrace.tracePath.map(
|
|
75
|
+
(point) => ({ ...point }),
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
reroutedTracePath: solvedPath.map((point) => ({ ...point })),
|
|
79
|
+
label: this.activeSubSolver.label,
|
|
80
|
+
detourCount: this.activeSubSolver.detourCount,
|
|
81
|
+
})
|
|
69
82
|
const traceIndex = this.allTraces.findIndex(
|
|
70
83
|
(t) => t.mspPairId === this.activeSubSolver!.initialTrace.mspPairId,
|
|
71
84
|
)
|
|
@@ -154,6 +167,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
154
167
|
paddingBuffer: this.PADDING_BUFFER,
|
|
155
168
|
detourCount: detourCount,
|
|
156
169
|
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
170
|
+
netLabelPlacements: this.initialNetLabelPlacements,
|
|
157
171
|
})
|
|
158
172
|
} else {
|
|
159
173
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
|
|
@@ -197,6 +211,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
197
211
|
paddingBuffer: this.PADDING_BUFFER,
|
|
198
212
|
detourCount: detourCount,
|
|
199
213
|
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
214
|
+
netLabelPlacements: this.initialNetLabelPlacements,
|
|
200
215
|
})
|
|
201
216
|
} else {
|
|
202
217
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
|
|
@@ -217,6 +232,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
217
232
|
paddingBuffer: this.PADDING_BUFFER,
|
|
218
233
|
detourCount: detourCount,
|
|
219
234
|
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
235
|
+
netLabelPlacements: this.initialNetLabelPlacements,
|
|
220
236
|
})
|
|
221
237
|
}
|
|
222
238
|
}
|