@tscircuit/schematic-trace-solver 0.0.40 → 0.0.42
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 +155 -9
- package/dist/index.js +998 -479
- package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +238 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +61 -26
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +94 -192
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +13 -24
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +205 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +144 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +117 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts +132 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{balanceLShapes.ts → sub-solvers/TraceCleanupSolver/balanceLShapes.ts} +12 -10
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisions.ts → sub-solvers/TraceCleanupSolver/hasCollisions.ts} +1 -1
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisionsWithLabels.ts → sub-solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts} +1 -1
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{minimizeTurnsWithFilteredLabels.ts → sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts} +6 -6
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{simplifyPath.ts → sub-solvers/TraceCleanupSolver/simplifyPath.ts} +1 -1
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{turnMinimization.ts → sub-solvers/TraceCleanupSolver/turnMinimization.ts} +0 -2
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +104 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +24 -154
- package/lib/utils/does-trace-overlap-with-existing-traces.ts +33 -0
- package/package.json +1 -1
- package/site/examples/example25.page.tsx +4 -0
- package/tests/assets/1.input.json +379 -0
- package/tests/assets/2.input.json +330 -0
- package/tests/assets/3.input.json +388 -0
- package/tests/assets/MergedNetLabelObstacles.test.input.json +154 -0
- package/tests/assets/OverlapAvoidanceStepSolver.test.input.json +682 -0
- package/tests/assets/SingleOverlapSolver.test.input.json +170 -0
- package/tests/assets/TraceCleanupSolver.test.input.json +216 -0
- package/tests/assets/TraceLabelOverlapAvoidanceSolver.test.input.json +424 -0
- package/tests/assets/example25.json +96 -0
- package/tests/examples/__snapshots__/example03.snap.svg +67 -76
- package/tests/examples/__snapshots__/example10.snap.svg +24 -30
- package/tests/examples/__snapshots__/example13.snap.svg +81 -84
- package/tests/examples/__snapshots__/example15.snap.svg +5 -8
- package/tests/examples/__snapshots__/example21.snap.svg +68 -74
- package/tests/examples/__snapshots__/example25.snap.svg +3 -3
- package/tests/examples/__snapshots__/example26.snap.svg +3 -3
- package/tests/examples/__snapshots__/example28.snap.svg +273 -0
- package/tests/examples/example28.test.ts +13 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.test.ts +18 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +243 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +223 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +205 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +184 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView01.test.ts +29 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView02.test.ts +27 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +39 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/MergedNetLabelObstacles.test.ts +14 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/OverlapAvoidanceStepSolver.test.ts +20 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/SingleOverlapSolver.test.ts +10 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/TraceCleanupSolver.test.ts +19 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +140 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +216 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +125 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
- /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{countTurns.ts → sub-solvers/TraceCleanupSolver/countTurns.ts} +0 -0
- /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{tryConnectPoints.ts → sub-solvers/TraceCleanupSolver/tryConnectPoints.ts} +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
6
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
|
+
import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
|
|
8
|
+
import { SingleOverlapSolver } from "../SingleOverlapSolver/SingleOverlapSolver"
|
|
9
|
+
|
|
10
|
+
type Overlap = ReturnType<typeof detectTraceLabelOverlap>[0]
|
|
11
|
+
|
|
12
|
+
// Define a type for the input of the internal overlap solver to avoid conflicts
|
|
13
|
+
interface OverlapCollectionSolverInput {
|
|
14
|
+
inputProblem: InputProblem
|
|
15
|
+
traces: SolvedTracePath[]
|
|
16
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
17
|
+
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* This is an internal solver that manages the step-by-step process of avoiding
|
|
22
|
+
* multiple overlaps. It follows the pattern of SchematicTraceLinesSolver.
|
|
23
|
+
*/
|
|
24
|
+
export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
25
|
+
inputProblem: InputProblem
|
|
26
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
27
|
+
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
28
|
+
|
|
29
|
+
allTraces: SolvedTracePath[]
|
|
30
|
+
modifiedTraces: SolvedTracePath[] = []
|
|
31
|
+
|
|
32
|
+
private detourCountByLabel: Record<string, number> = {}
|
|
33
|
+
private readonly PADDING_BUFFER = 0.1
|
|
34
|
+
|
|
35
|
+
public override activeSubSolver: SingleOverlapSolver | null = null
|
|
36
|
+
private overlapQueue: Overlap[] = []
|
|
37
|
+
private recentlyFailed: Set<string> = new Set()
|
|
38
|
+
|
|
39
|
+
constructor(solverInput: OverlapCollectionSolverInput) {
|
|
40
|
+
super()
|
|
41
|
+
this.inputProblem = solverInput.inputProblem
|
|
42
|
+
this.netLabelPlacements = solverInput.netLabelPlacements
|
|
43
|
+
this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap
|
|
44
|
+
this.allTraces = [...solverInput.traces]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override _step() {
|
|
48
|
+
if (this.activeSubSolver) {
|
|
49
|
+
this.activeSubSolver.step()
|
|
50
|
+
|
|
51
|
+
if (this.activeSubSolver.solved) {
|
|
52
|
+
const solvedPath = this.activeSubSolver.solvedTracePath
|
|
53
|
+
if (solvedPath) {
|
|
54
|
+
const traceIndex = this.allTraces.findIndex(
|
|
55
|
+
(t) => t.mspPairId === this.activeSubSolver!.initialTrace.mspPairId,
|
|
56
|
+
)
|
|
57
|
+
if (traceIndex !== -1) {
|
|
58
|
+
this.allTraces[traceIndex].tracePath = solvedPath
|
|
59
|
+
this.modifiedTraces.push(this.allTraces[traceIndex])
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
this.activeSubSolver = null
|
|
63
|
+
this.recentlyFailed.clear()
|
|
64
|
+
} else if (this.activeSubSolver.failed) {
|
|
65
|
+
const overlapId = `${this.activeSubSolver.initialTrace.mspPairId}-${this.activeSubSolver.label.globalConnNetId}`
|
|
66
|
+
this.recentlyFailed.add(overlapId)
|
|
67
|
+
this.activeSubSolver = null
|
|
68
|
+
}
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
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
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
if (overlaps.length === 0) {
|
|
84
|
+
this.solved = true
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const nonFailedOverlaps = overlaps.filter((o) => {
|
|
89
|
+
const overlapId = `${o.trace.mspPairId}-${o.label.globalConnNetId}`
|
|
90
|
+
return !this.recentlyFailed.has(overlapId)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
if (nonFailedOverlaps.length === 0) {
|
|
94
|
+
this.solved = true // No more progress can be made
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.overlapQueue = nonFailedOverlaps
|
|
99
|
+
|
|
100
|
+
const nextOverlap = this.overlapQueue.shift()
|
|
101
|
+
|
|
102
|
+
if (nextOverlap) {
|
|
103
|
+
const traceToFix = this.allTraces.find(
|
|
104
|
+
(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
|
+
})
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getOutput() {
|
|
123
|
+
return {
|
|
124
|
+
allTraces: this.allTraces,
|
|
125
|
+
modifiedTraces: this.modifiedTraces,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
override visualize(): GraphicsObject {
|
|
130
|
+
if (this.activeSubSolver) {
|
|
131
|
+
return this.activeSubSolver.visualize()
|
|
132
|
+
}
|
|
133
|
+
// When idle, show all the traces
|
|
134
|
+
const graphics = visualizeInputProblem(this.inputProblem)
|
|
135
|
+
if (!graphics.lines) graphics.lines = []
|
|
136
|
+
for (const trace of this.allTraces) {
|
|
137
|
+
graphics.lines!.push({
|
|
138
|
+
points: trace.tracePath,
|
|
139
|
+
strokeColor: "purple",
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
return graphics
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
|
+
import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
5
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
6
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
7
|
+
import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
8
|
+
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
9
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
10
|
+
import { generateRerouteCandidates } from "../../rerouteCollidingTrace"
|
|
11
|
+
import { simplifyPath } from "../TraceCleanupSolver/simplifyPath"
|
|
12
|
+
|
|
13
|
+
interface SingleOverlapSolverInput {
|
|
14
|
+
trace: SolvedTracePath
|
|
15
|
+
label: NetLabelPlacement
|
|
16
|
+
problem: InputProblem
|
|
17
|
+
paddingBuffer: number
|
|
18
|
+
detourCount: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* This solver attempts to find a valid rerouting for a single trace that is
|
|
23
|
+
* overlapping with a net label. It tries various candidate paths until it
|
|
24
|
+
* finds one that does not introduce new collisions.
|
|
25
|
+
*/
|
|
26
|
+
export class SingleOverlapSolver extends BaseSolver {
|
|
27
|
+
queuedCandidatePaths: Point[][]
|
|
28
|
+
solvedTracePath: Point[] | null = null
|
|
29
|
+
initialTrace: SolvedTracePath
|
|
30
|
+
problem: InputProblem
|
|
31
|
+
obstacles: ReturnType<typeof getObstacleRects>
|
|
32
|
+
label: NetLabelPlacement
|
|
33
|
+
|
|
34
|
+
constructor(solverInput: SingleOverlapSolverInput) {
|
|
35
|
+
super()
|
|
36
|
+
this.initialTrace = solverInput.trace
|
|
37
|
+
this.problem = solverInput.problem
|
|
38
|
+
this.label = solverInput.label
|
|
39
|
+
|
|
40
|
+
const candidates = generateRerouteCandidates({
|
|
41
|
+
...solverInput,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const getPathLength = (pts: Point[]) => {
|
|
45
|
+
let len = 0
|
|
46
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
47
|
+
const dx = pts[i + 1].x - pts[i].x
|
|
48
|
+
const dy = pts[i + 1].y - pts[i].y
|
|
49
|
+
len += Math.sqrt(dx * dx + dy * dy)
|
|
50
|
+
}
|
|
51
|
+
return len
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.queuedCandidatePaths = candidates.sort(
|
|
55
|
+
(a, b) => getPathLength(a) - getPathLength(b),
|
|
56
|
+
)
|
|
57
|
+
this.obstacles = getObstacleRects(this.problem)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override _step() {
|
|
61
|
+
if (this.queuedCandidatePaths.length === 0) {
|
|
62
|
+
this.failed = true
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const nextCandidatePath = this.queuedCandidatePaths.shift()!
|
|
67
|
+
const simplifiedPath = simplifyPath(nextCandidatePath)
|
|
68
|
+
|
|
69
|
+
if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles)) {
|
|
70
|
+
this.solvedTracePath = simplifiedPath
|
|
71
|
+
this.solved = true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override visualize(): GraphicsObject {
|
|
76
|
+
const graphics = visualizeInputProblem(this.problem, {
|
|
77
|
+
chipAlpha: 0.1,
|
|
78
|
+
connectionAlpha: 0.1,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if (!graphics.lines) graphics.lines = []
|
|
82
|
+
if (!graphics.rects) graphics.rects = []
|
|
83
|
+
|
|
84
|
+
// Draw initial trace
|
|
85
|
+
graphics.lines.push({
|
|
86
|
+
points: this.initialTrace.tracePath,
|
|
87
|
+
strokeColor: "red",
|
|
88
|
+
strokeDash: "4 4",
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// Draw label
|
|
92
|
+
graphics.rects.push({
|
|
93
|
+
center: this.label.center,
|
|
94
|
+
width: this.label.width,
|
|
95
|
+
height: this.label.height,
|
|
96
|
+
fill: "rgba(255, 0, 0, 0.2)",
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Draw next candidate
|
|
100
|
+
if (this.queuedCandidatePaths.length > 0) {
|
|
101
|
+
graphics.lines.push({
|
|
102
|
+
points: this.queuedCandidatePaths[0],
|
|
103
|
+
strokeColor: "orange",
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Draw solved path
|
|
108
|
+
if (this.solvedTracePath) {
|
|
109
|
+
graphics.lines.push({
|
|
110
|
+
points: this.solvedTracePath,
|
|
111
|
+
strokeColor: "green",
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return graphics
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
2
|
+
import type { GraphicsObject, Line } from "graphics-debug"
|
|
3
|
+
import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
4
|
+
import { minimizeTurnsWithFilteredLabels } from "./minimizeTurnsWithFilteredLabels"
|
|
5
|
+
import { balanceLShapes } from "./balanceLShapes"
|
|
6
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
7
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
8
|
+
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
9
|
+
|
|
10
|
+
interface TraceCleanupSolverInput {
|
|
11
|
+
inputProblem: InputProblem
|
|
12
|
+
allTraces: SolvedTracePath[]
|
|
13
|
+
targetTraceIds: Set<string>
|
|
14
|
+
allLabelPlacements: NetLabelPlacement[]
|
|
15
|
+
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
16
|
+
paddingBuffer: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Cleans up traces by minimizing turns and balancing L-shapes to improve
|
|
21
|
+
* the overall aesthetics and readability of the schematic.
|
|
22
|
+
*/
|
|
23
|
+
export class TraceCleanupSolver extends BaseSolver {
|
|
24
|
+
private input: TraceCleanupSolverInput
|
|
25
|
+
private outputTraces: SolvedTracePath[]
|
|
26
|
+
private pipelineStepIndex = 0
|
|
27
|
+
private tracesToProcess: SolvedTracePath[]
|
|
28
|
+
private minimizedTraces: SolvedTracePath[] | null = null
|
|
29
|
+
private balancedTraces: SolvedTracePath[] | null = null
|
|
30
|
+
|
|
31
|
+
constructor(solverInput: TraceCleanupSolverInput) {
|
|
32
|
+
super()
|
|
33
|
+
this.input = solverInput
|
|
34
|
+
this.outputTraces = [...solverInput.allTraces]
|
|
35
|
+
this.tracesToProcess = this.outputTraces.filter((t) =>
|
|
36
|
+
this.input.targetTraceIds.has(t.mspPairId),
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override _step() {
|
|
41
|
+
const {
|
|
42
|
+
targetTraceIds,
|
|
43
|
+
inputProblem,
|
|
44
|
+
allLabelPlacements,
|
|
45
|
+
mergedLabelNetIdMap,
|
|
46
|
+
paddingBuffer,
|
|
47
|
+
} = this.input
|
|
48
|
+
|
|
49
|
+
if (targetTraceIds.size === 0) {
|
|
50
|
+
this.solved = true
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
switch (this.pipelineStepIndex) {
|
|
55
|
+
case 0: {
|
|
56
|
+
// Step 0: Minimize turns
|
|
57
|
+
const minimizedTracesResult = minimizeTurnsWithFilteredLabels({
|
|
58
|
+
traces: this.tracesToProcess,
|
|
59
|
+
inputProblem,
|
|
60
|
+
allLabelPlacements,
|
|
61
|
+
mergedLabelNetIdMap,
|
|
62
|
+
paddingBuffer,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
this.minimizedTraces = minimizedTracesResult ?? this.tracesToProcess
|
|
66
|
+
|
|
67
|
+
const tracesMap = new Map(
|
|
68
|
+
this.input.allTraces.map((t) => [t.mspPairId, t]),
|
|
69
|
+
)
|
|
70
|
+
for (const trace of this.minimizedTraces) {
|
|
71
|
+
tracesMap.set(trace.mspPairId, trace)
|
|
72
|
+
}
|
|
73
|
+
this.outputTraces = Array.from(tracesMap.values())
|
|
74
|
+
this.pipelineStepIndex++
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
case 1: {
|
|
78
|
+
// Step 1: Balance L-shapes
|
|
79
|
+
const balancedTracesResult = balanceLShapes({
|
|
80
|
+
traces: this.minimizedTraces!,
|
|
81
|
+
inputProblem,
|
|
82
|
+
allLabelPlacements,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
this.balancedTraces = balancedTracesResult ?? this.minimizedTraces
|
|
86
|
+
|
|
87
|
+
const tracesMap = new Map(
|
|
88
|
+
this.input.allTraces.map((t) => [t.mspPairId, t]),
|
|
89
|
+
)
|
|
90
|
+
for (const trace of this.balancedTraces!) {
|
|
91
|
+
tracesMap.set(trace.mspPairId, trace)
|
|
92
|
+
}
|
|
93
|
+
this.outputTraces = Array.from(tracesMap.values())
|
|
94
|
+
this.pipelineStepIndex++
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
case 2: {
|
|
98
|
+
// Step 2: Done
|
|
99
|
+
this.solved = true
|
|
100
|
+
break
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getOutput() {
|
|
106
|
+
return {
|
|
107
|
+
traces: this.outputTraces,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
override visualize(): GraphicsObject {
|
|
112
|
+
const graphics = visualizeInputProblem(this.input.inputProblem, {
|
|
113
|
+
chipAlpha: 0.1,
|
|
114
|
+
connectionAlpha: 0.1,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
if (!graphics.lines) graphics.lines = []
|
|
118
|
+
if (!graphics.points) graphics.points = []
|
|
119
|
+
if (!graphics.rects) graphics.rects = []
|
|
120
|
+
if (!graphics.circles) graphics.circles = []
|
|
121
|
+
if (!graphics.texts) graphics.texts = []
|
|
122
|
+
|
|
123
|
+
for (const trace of this.outputTraces) {
|
|
124
|
+
const line: Line = {
|
|
125
|
+
points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
|
|
126
|
+
strokeColor: "blue",
|
|
127
|
+
}
|
|
128
|
+
graphics.lines!.push(line)
|
|
129
|
+
}
|
|
130
|
+
return graphics
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { Point } from "graphics-debug"
|
|
2
2
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
3
|
-
import type { NetLabelPlacement } from "
|
|
4
|
-
import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
5
|
-
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
6
|
-
import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
3
|
+
import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
7
4
|
import { simplifyPath } from "./simplifyPath"
|
|
5
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
6
|
+
import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
7
|
+
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
8
8
|
|
|
9
9
|
export const balanceLShapes = ({
|
|
10
10
|
traces,
|
|
11
|
-
|
|
11
|
+
inputProblem,
|
|
12
12
|
allLabelPlacements,
|
|
13
13
|
}: {
|
|
14
14
|
traces: SolvedTracePath[]
|
|
15
|
-
|
|
15
|
+
inputProblem: InputProblem
|
|
16
16
|
allLabelPlacements: NetLabelPlacement[]
|
|
17
17
|
}): SolvedTracePath[] | null => {
|
|
18
18
|
const TOLERANCE = 1e-5
|
|
@@ -46,7 +46,7 @@ export const balanceLShapes = ({
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const obstacles = getObstacleRects(
|
|
49
|
+
const obstacles = getObstacleRects(inputProblem).map((obs) => ({
|
|
50
50
|
...obs,
|
|
51
51
|
minX: obs.minX + TOLERANCE,
|
|
52
52
|
maxX: obs.maxX - TOLERANCE,
|
|
@@ -81,7 +81,7 @@ export const balanceLShapes = ({
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
const newTraces = traces.map((trace) => {
|
|
84
|
-
|
|
84
|
+
const newPath = [...trace.tracePath]
|
|
85
85
|
|
|
86
86
|
if (newPath.length < 4) {
|
|
87
87
|
return { ...trace }
|
|
@@ -94,7 +94,8 @@ export const balanceLShapes = ({
|
|
|
94
94
|
|
|
95
95
|
if (newPath.length === 4) {
|
|
96
96
|
const [p0, p1, p2, p3] = newPath
|
|
97
|
-
let p1New: Point
|
|
97
|
+
let p1New: Point
|
|
98
|
+
let p2New: Point
|
|
98
99
|
|
|
99
100
|
const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y
|
|
100
101
|
|
|
@@ -154,7 +155,8 @@ export const balanceLShapes = ({
|
|
|
154
155
|
continue
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
let p2New: Point
|
|
158
|
+
let p2New: Point
|
|
159
|
+
let p3New: Point
|
|
158
160
|
const len1Original = isHVHZShape
|
|
159
161
|
? Math.abs(p1.x - p2.x)
|
|
160
162
|
: Math.abs(p1.y - p2.y)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Point } from "graphics-debug"
|
|
2
|
-
import { segmentIntersectsRect } from "
|
|
2
|
+
import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
3
3
|
|
|
4
4
|
export const hasCollisions = (
|
|
5
5
|
pathSegments: Point[],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
-
import { segmentIntersectsRect } from "
|
|
2
|
+
import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
3
3
|
|
|
4
4
|
export const hasCollisionsWithLabels = (
|
|
5
5
|
pathSegments: Point[],
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
2
|
-
import type { NetLabelPlacement } from "
|
|
3
|
-
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
-
import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
2
|
+
import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
3
|
import { minimizeTurns } from "./turnMinimization"
|
|
4
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
6
6
|
|
|
7
7
|
export const minimizeTurnsWithFilteredLabels = ({
|
|
8
8
|
traces,
|
|
9
|
-
|
|
9
|
+
inputProblem,
|
|
10
10
|
allLabelPlacements,
|
|
11
11
|
mergedLabelNetIdMap,
|
|
12
12
|
paddingBuffer,
|
|
13
13
|
}: {
|
|
14
14
|
traces: SolvedTracePath[]
|
|
15
|
-
|
|
15
|
+
inputProblem: InputProblem
|
|
16
16
|
allLabelPlacements: NetLabelPlacement[]
|
|
17
17
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
18
18
|
paddingBuffer: number
|
|
19
19
|
}): SolvedTracePath[] | null => {
|
|
20
20
|
let changesMade = false
|
|
21
|
-
const obstacles = getObstacleRects(
|
|
21
|
+
const obstacles = getObstacleRects(inputProblem)
|
|
22
22
|
|
|
23
23
|
const newTraces = traces.map((trace) => {
|
|
24
24
|
const originalPath = trace.tracePath
|
|
@@ -2,7 +2,7 @@ import type { Point } from "graphics-debug"
|
|
|
2
2
|
import {
|
|
3
3
|
isHorizontal,
|
|
4
4
|
isVertical,
|
|
5
|
-
} from "
|
|
5
|
+
} from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
6
6
|
|
|
7
7
|
export const simplifyPath = (path: Point[]): Point[] => {
|
|
8
8
|
if (path.length < 3) return path
|
|
@@ -105,8 +105,6 @@ export const minimizeTurns = ({
|
|
|
105
105
|
|
|
106
106
|
if (!collidesWithObstacles && !collidesWithLabels) {
|
|
107
107
|
const newTurns = countTurns(testPath)
|
|
108
|
-
const turnsRemoved = stairEndIdx - startIdx - 1
|
|
109
|
-
|
|
110
108
|
optimizedPath = testPath
|
|
111
109
|
currentTurns = newTurns
|
|
112
110
|
improved = true
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Point } from "graphics-debug"
|
|
2
|
+
import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
+
import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
4
|
+
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import {
|
|
6
|
+
segmentIntersectsRect,
|
|
7
|
+
isVertical,
|
|
8
|
+
} from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
9
|
+
|
|
10
|
+
export const generateFourPointDetourCandidates = ({
|
|
11
|
+
initialTrace,
|
|
12
|
+
label,
|
|
13
|
+
labelBounds,
|
|
14
|
+
paddingBuffer,
|
|
15
|
+
detourCount,
|
|
16
|
+
}: {
|
|
17
|
+
initialTrace: SolvedTracePath
|
|
18
|
+
label: NetLabelPlacement
|
|
19
|
+
labelBounds: any
|
|
20
|
+
paddingBuffer: number
|
|
21
|
+
detourCount: number
|
|
22
|
+
}): Point[][] => {
|
|
23
|
+
let collidingSegIndex = -1
|
|
24
|
+
for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
|
|
25
|
+
if (
|
|
26
|
+
segmentIntersectsRect(
|
|
27
|
+
initialTrace.tracePath[i],
|
|
28
|
+
initialTrace.tracePath[i + 1],
|
|
29
|
+
labelBounds,
|
|
30
|
+
)
|
|
31
|
+
) {
|
|
32
|
+
collidingSegIndex = i
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (collidingSegIndex === -1) return []
|
|
38
|
+
|
|
39
|
+
const pA = initialTrace.tracePath[collidingSegIndex]
|
|
40
|
+
const pB = initialTrace.tracePath[collidingSegIndex + 1]
|
|
41
|
+
|
|
42
|
+
if (!pA || !pB) return []
|
|
43
|
+
|
|
44
|
+
const candidateDetours: Point[][] = []
|
|
45
|
+
const paddedLabelBounds = getRectBounds(
|
|
46
|
+
label.center,
|
|
47
|
+
label.width,
|
|
48
|
+
label.height,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const effectivePadding = paddingBuffer + detourCount * paddingBuffer
|
|
52
|
+
|
|
53
|
+
if (isVertical(pA, pB)) {
|
|
54
|
+
const xCandidates = [
|
|
55
|
+
paddedLabelBounds.maxX + effectivePadding,
|
|
56
|
+
paddedLabelBounds.minX - effectivePadding,
|
|
57
|
+
]
|
|
58
|
+
for (const newX of xCandidates) {
|
|
59
|
+
candidateDetours.push(
|
|
60
|
+
pB.y > pA.y
|
|
61
|
+
? [
|
|
62
|
+
{ x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
|
|
63
|
+
{ x: newX, y: paddedLabelBounds.minY - effectivePadding },
|
|
64
|
+
{ x: newX, y: paddedLabelBounds.maxY + effectivePadding },
|
|
65
|
+
{ x: pB.x, y: paddedLabelBounds.maxY + effectivePadding },
|
|
66
|
+
]
|
|
67
|
+
: [
|
|
68
|
+
{ x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
|
|
69
|
+
{ x: newX, y: paddedLabelBounds.maxY + effectivePadding },
|
|
70
|
+
{ x: newX, y: paddedLabelBounds.minY - effectivePadding },
|
|
71
|
+
{ x: pB.x, y: paddedLabelBounds.minY - effectivePadding },
|
|
72
|
+
],
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
const yCandidates = [
|
|
77
|
+
paddedLabelBounds.maxY + effectivePadding,
|
|
78
|
+
paddedLabelBounds.minY - effectivePadding,
|
|
79
|
+
]
|
|
80
|
+
for (const newY of yCandidates) {
|
|
81
|
+
candidateDetours.push(
|
|
82
|
+
pB.x > pA.x
|
|
83
|
+
? [
|
|
84
|
+
{ x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
|
|
85
|
+
{ x: paddedLabelBounds.minX - effectivePadding, y: newY },
|
|
86
|
+
{ x: paddedLabelBounds.maxX + effectivePadding, y: newY },
|
|
87
|
+
{ x: paddedLabelBounds.maxX + effectivePadding, y: pB.y },
|
|
88
|
+
]
|
|
89
|
+
: [
|
|
90
|
+
{ x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
|
|
91
|
+
{ x: paddedLabelBounds.maxX + effectivePadding, y: newY },
|
|
92
|
+
{ x: paddedLabelBounds.minX - effectivePadding, y: newY },
|
|
93
|
+
{ x: paddedLabelBounds.minX - effectivePadding, y: pB.y },
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return candidateDetours.map((detourPoints) => [
|
|
100
|
+
...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
|
|
101
|
+
...detourPoints,
|
|
102
|
+
...initialTrace.tracePath.slice(collidingSegIndex + 1),
|
|
103
|
+
])
|
|
104
|
+
}
|