@tscircuit/schematic-trace-solver 0.0.42 → 0.0.44

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 (38) hide show
  1. package/dist/index.d.ts +31 -29
  2. package/dist/index.js +981 -926
  3. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +20 -1
  4. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +133 -0
  5. package/lib/solvers/TraceCleanupSolver/balanceZShapes.ts +200 -0
  6. package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +76 -0
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +1 -21
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +1 -1
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +1 -1
  10. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +83 -0
  11. package/package.json +1 -1
  12. package/site/examples/example26.page.tsx +4 -0
  13. package/tests/assets/example26.json +1206 -0
  14. package/tests/examples/__snapshots__/example02.snap.svg +1 -1
  15. package/tests/examples/__snapshots__/example09.snap.svg +34 -34
  16. package/tests/examples/__snapshots__/example11.snap.svg +13 -13
  17. package/tests/examples/__snapshots__/example12.snap.svg +3 -3
  18. package/tests/examples/__snapshots__/example13.snap.svg +12 -12
  19. package/tests/examples/__snapshots__/example16.snap.svg +4 -4
  20. package/tests/examples/__snapshots__/example19.snap.svg +9 -9
  21. package/tests/examples/__snapshots__/example25.snap.svg +3 -3
  22. package/tests/examples/__snapshots__/example26.snap.svg +3 -3
  23. package/tests/examples/__snapshots__/example28.snap.svg +3 -3
  24. package/tests/examples/__snapshots__/example29.snap.svg +1208 -0
  25. package/tests/examples/example29.test.ts +13 -0
  26. package/tests/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solver → TraceCleanupSolver}/TraceCleanupSolver.test.ts +2 -2
  27. package/tests/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solver → TraceCleanupSolver}/__snapshots__/TraceCleanupSolver.snap.svg +1 -1
  28. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +1 -1
  29. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +1 -1
  30. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts +0 -132
  31. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/balanceLShapes.ts +0 -209
  32. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +0 -66
  33. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/countTurns.ts +0 -0
  34. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/hasCollisions.ts +0 -0
  35. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/hasCollisionsWithLabels.ts +0 -0
  36. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/simplifyPath.ts +0 -0
  37. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/tryConnectPoints.ts +0 -0
  38. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver → TraceCleanupSolver}/turnMinimization.ts +0 -0
@@ -19,7 +19,7 @@ import { correctPinsInsideChips } from "./correctPinsInsideChip"
19
19
  import { expandChipsToFitPins } from "./expandChipsToFitPins"
20
20
  import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePairSolver"
21
21
  import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver"
22
- import { TraceCleanupSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver"
22
+ import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver"
23
23
 
24
24
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
25
25
  solverName: string
@@ -188,6 +188,25 @@ export class SchematicTracePipelineSolver extends BaseSolver {
188
188
  ]
189
189
  },
190
190
  ),
191
+ definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
192
+ const prevSolverOutput =
193
+ instance.traceLabelOverlapAvoidanceSolver!.getOutput()
194
+ const traces = prevSolverOutput.traces
195
+
196
+ const labelMergingOutput =
197
+ instance.traceLabelOverlapAvoidanceSolver!.labelMergingSolver!.getOutput()
198
+
199
+ return [
200
+ {
201
+ inputProblem: instance.inputProblem,
202
+ allTraces: traces,
203
+ targetTraceIds: new Set(traces.map((t) => t.mspPairId)),
204
+ allLabelPlacements: labelMergingOutput.netLabelPlacements,
205
+ mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
206
+ paddingBuffer: 0.1,
207
+ },
208
+ ]
209
+ }),
191
210
  definePipelineStep(
192
211
  "netLabelPlacementSolver",
193
212
  NetLabelPlacementSolver,
@@ -0,0 +1,133 @@
1
+ import type { InputProblem } from "lib/types/InputProblem"
2
+ import type { GraphicsObject, Line } from "graphics-debug"
3
+ import { minimizeTurnsWithFilteredLabels } from "./minimizeTurnsWithFilteredLabels"
4
+ import { balanceZShapes } from "./balanceZShapes"
5
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
6
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
7
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
8
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
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
+ type PipelineStep = "minimizing_turns" | "balancing_l_shapes"
20
+
21
+ /**
22
+ * Cleans up traces by minimizing turns and balancing L-shapes to improve
23
+ * the overall aesthetics and readability of the schematic.
24
+ */
25
+ export class TraceCleanupSolver extends BaseSolver {
26
+ private input: TraceCleanupSolverInput
27
+ private outputTraces: SolvedTracePath[]
28
+ private traceIdQueue: string[]
29
+ private tracesMap: Map<string, SolvedTracePath>
30
+ private pipelineStep: PipelineStep = "minimizing_turns"
31
+ private activeTraceId: string | null = null // New property
32
+
33
+ constructor(solverInput: TraceCleanupSolverInput) {
34
+ super()
35
+ this.input = solverInput
36
+ this.outputTraces = [...solverInput.allTraces]
37
+ this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]))
38
+ this.traceIdQueue = Array.from(solverInput.targetTraceIds)
39
+ }
40
+
41
+ override _step() {
42
+ if (
43
+ this.pipelineStep === "minimizing_turns" &&
44
+ this.traceIdQueue.length === 0
45
+ ) {
46
+ this.pipelineStep = "balancing_l_shapes"
47
+ this.traceIdQueue = Array.from(this.input.targetTraceIds)
48
+ }
49
+
50
+ if (
51
+ this.pipelineStep === "balancing_l_shapes" &&
52
+ this.traceIdQueue.length === 0
53
+ ) {
54
+ this.solved = true
55
+ return
56
+ }
57
+
58
+ const targetMspConnectionPairId = this.traceIdQueue.shift()!
59
+ this.activeTraceId = targetMspConnectionPairId // Set active trace ID
60
+ const originalTrace = this.tracesMap.get(targetMspConnectionPairId)!
61
+
62
+ const { tracePath } = originalTrace
63
+
64
+ // Skip if the trace is a perfect rectangle
65
+ const is4PointRectangle = (path: typeof tracePath): boolean => {
66
+ if (path.length !== 4) return false
67
+ const [p0, p1, p2, p3] = path
68
+ // H-V-H "C" shape
69
+ const isHVHC =
70
+ p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x
71
+ // V-H-V "C" shape
72
+ const isVHVC =
73
+ p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y
74
+ return isHVHC || isVHVC
75
+ }
76
+
77
+ if (is4PointRectangle(tracePath)) {
78
+ return
79
+ }
80
+
81
+ const allTraces = Array.from(this.tracesMap.values())
82
+
83
+ let updatedTrace: SolvedTracePath
84
+
85
+ if (this.pipelineStep === "minimizing_turns") {
86
+ updatedTrace = minimizeTurnsWithFilteredLabels({
87
+ ...this.input,
88
+ targetMspConnectionPairId,
89
+ traces: allTraces,
90
+ })
91
+ } else {
92
+ updatedTrace = balanceZShapes({
93
+ ...this.input,
94
+ targetMspConnectionPairId,
95
+ traces: allTraces,
96
+ })
97
+ }
98
+
99
+ // Update the state in the central map
100
+ this.tracesMap.set(targetMspConnectionPairId, updatedTrace)
101
+
102
+ // Update the output for visualization
103
+ this.outputTraces = Array.from(this.tracesMap.values())
104
+ }
105
+
106
+ getOutput() {
107
+ return {
108
+ traces: this.outputTraces,
109
+ }
110
+ }
111
+
112
+ override visualize(): GraphicsObject {
113
+ const graphics = visualizeInputProblem(this.input.inputProblem, {
114
+ chipAlpha: 0.1,
115
+ connectionAlpha: 0.1,
116
+ })
117
+
118
+ if (!graphics.lines) graphics.lines = []
119
+ if (!graphics.points) graphics.points = []
120
+ if (!graphics.rects) graphics.rects = []
121
+ if (!graphics.circles) graphics.circles = []
122
+ if (!graphics.texts) graphics.texts = []
123
+
124
+ for (const trace of this.outputTraces) {
125
+ const line: Line = {
126
+ points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
127
+ strokeColor: trace.mspPairId === this.activeTraceId ? "red" : "blue", // Highlight active trace
128
+ }
129
+ graphics.lines!.push(line)
130
+ }
131
+ return graphics
132
+ }
133
+ }
@@ -0,0 +1,200 @@
1
+ import type { Point } from "graphics-debug"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+ import { simplifyPath } from "./simplifyPath"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
6
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
7
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
8
+
9
+ export const balanceZShapes = ({
10
+ targetMspConnectionPairId,
11
+ traces,
12
+ inputProblem,
13
+ allLabelPlacements,
14
+ mergedLabelNetIdMap,
15
+ paddingBuffer,
16
+ }: {
17
+ targetMspConnectionPairId: string
18
+ traces: SolvedTracePath[]
19
+ inputProblem: InputProblem
20
+ allLabelPlacements: NetLabelPlacement[]
21
+ mergedLabelNetIdMap: Record<string, Set<string>>
22
+ paddingBuffer: number
23
+ }): SolvedTracePath => {
24
+ const targetTrace = traces.find(
25
+ (t) => t.mspPairId === targetMspConnectionPairId,
26
+ )
27
+
28
+ if (!targetTrace) {
29
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`)
30
+ }
31
+
32
+ const TOLERANCE = 1e-5
33
+
34
+ const obstacleTraces = traces.filter(
35
+ (t) => t.mspPairId !== targetMspConnectionPairId,
36
+ )
37
+
38
+ const TRACE_WIDTH = 0.01
39
+ const traceObstacles = obstacleTraces.flatMap((trace, i) =>
40
+ trace.tracePath.slice(0, -1).map((p1, pi) => {
41
+ const p2 = trace.tracePath[pi + 1]!
42
+ return {
43
+ chipId: `trace-obstacle-${i}-${pi}`,
44
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
45
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
46
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
47
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2,
48
+ }
49
+ }),
50
+ )
51
+
52
+ const staticObstacles = getObstacleRects(inputProblem).map((obs) => ({
53
+ ...obs,
54
+ minX: obs.minX + TOLERANCE,
55
+ maxX: obs.maxX - TOLERANCE,
56
+ minY: obs.minY + TOLERANCE,
57
+ maxY: obs.maxY - TOLERANCE,
58
+ }))
59
+
60
+ const combinedObstacles = [...staticObstacles, ...traceObstacles]
61
+
62
+ const segmentIntersectsAnyRect = (
63
+ p1: Point,
64
+ p2: Point,
65
+ rects: any[],
66
+ ): boolean => {
67
+ for (const rect of rects) {
68
+ if (segmentIntersectsRect(p1, p2, rect)) {
69
+ return true
70
+ }
71
+ }
72
+ return false
73
+ }
74
+
75
+ const filteredLabels = allLabelPlacements.filter((label) => {
76
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId]
77
+ if (originalNetIds) {
78
+ return !originalNetIds.has(targetTrace.globalConnNetId)
79
+ }
80
+ return label.globalConnNetId !== targetTrace.globalConnNetId
81
+ })
82
+
83
+ const labelBounds = filteredLabels.map((nl) => ({
84
+ minX: nl.center.x - nl.width / 2 + TOLERANCE,
85
+ maxX: nl.center.x + nl.width / 2 - TOLERANCE,
86
+ minY: nl.center.y - nl.height / 2 + TOLERANCE,
87
+ maxY: nl.center.y + nl.height / 2 - TOLERANCE,
88
+ }))
89
+
90
+ const newPath = [...targetTrace.tracePath]
91
+
92
+ if (newPath.length < 4) {
93
+ return { ...targetTrace }
94
+ }
95
+
96
+ if (newPath.length === 4) {
97
+ const [p0, p1, p2, p3] = newPath
98
+ let p1New: Point
99
+ let p2New: Point
100
+
101
+ const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y
102
+
103
+ if (isHVHShape) {
104
+ const idealX = (p0.x + p3.x) / 2
105
+ p1New = { x: idealX, y: p1.y }
106
+ p2New = { x: idealX, y: p2.y }
107
+ } else {
108
+ const idealY = (p0.y + p3.y) / 2
109
+ p1New = { x: p1.x, y: idealY }
110
+ p2New = { x: p2.x, y: idealY }
111
+ }
112
+
113
+ const collides =
114
+ segmentIntersectsAnyRect(p0, p1New, combinedObstacles) ||
115
+ segmentIntersectsAnyRect(p1New, p2New, combinedObstacles) ||
116
+ segmentIntersectsAnyRect(p2New, p3, combinedObstacles) ||
117
+ segmentIntersectsAnyRect(p0, p1New, labelBounds) ||
118
+ segmentIntersectsAnyRect(p1New, p2New, labelBounds) ||
119
+ segmentIntersectsAnyRect(p2New, p3, labelBounds)
120
+
121
+ if (!collides) {
122
+ newPath[1] = p1New
123
+ newPath[2] = p2New
124
+ }
125
+
126
+ return { ...targetTrace, tracePath: simplifyPath(newPath) }
127
+ }
128
+
129
+ for (let i = 1; i < newPath.length - 4; i++) {
130
+ const p1 = newPath[i]!
131
+ const p2 = newPath[i + 1]!
132
+ const p3 = newPath[i + 2]!
133
+ const p4 = newPath[i + 3]!
134
+
135
+ const isHVHZShape = p1.y === p2.y && p2.x === p3.x && p3.y === p4.y
136
+ const isVHVZShape = p1.x === p2.x && p2.y === p3.y && p3.x === p4.x
137
+
138
+ const isCollinearHorizontal =
139
+ p1.y === p2.y && p2.y === p3.y && p3.y === p4.y
140
+ const isCollinearVertical = p1.x === p2.x && p2.x === p3.x && p3.x === p4.x
141
+ const isCollinear = isCollinearHorizontal || isCollinearVertical
142
+
143
+ let isSameDirection = false
144
+ if (isHVHZShape) {
145
+ isSameDirection = Math.sign(p2.x - p1.x) === Math.sign(p4.x - p3.x)
146
+ } else if (isVHVZShape) {
147
+ isSameDirection = Math.sign(p2.y - p1.y) === Math.sign(p4.y - p3.y)
148
+ }
149
+
150
+ const isValidZShape =
151
+ (isHVHZShape || isVHVZShape) && !isCollinear && isSameDirection
152
+
153
+ if (!isValidZShape) {
154
+ continue
155
+ }
156
+
157
+ let p2New: Point
158
+ let p3New: Point
159
+ const len1Original = isHVHZShape
160
+ ? Math.abs(p1.x - p2.x)
161
+ : Math.abs(p1.y - p2.y)
162
+ const len2Original = isHVHZShape
163
+ ? Math.abs(p3.x - p4.x)
164
+ : Math.abs(p3.y - p4.y)
165
+
166
+ if (Math.abs(len1Original - len2Original) < 0.001) {
167
+ continue
168
+ }
169
+
170
+ if (isHVHZShape) {
171
+ const idealX = (p1.x + p4.x) / 2
172
+ p2New = { x: idealX, y: p2.y }
173
+ p3New = { x: idealX, y: p3.y }
174
+ } else {
175
+ const idealY = (p1.y + p4.y) / 2
176
+ p2New = { x: p2.x, y: idealY }
177
+ p3New = { x: p3.x, y: idealY }
178
+ }
179
+
180
+ const collides =
181
+ segmentIntersectsAnyRect(p1, p2New, combinedObstacles) ||
182
+ segmentIntersectsAnyRect(p2New, p3New, combinedObstacles) ||
183
+ segmentIntersectsAnyRect(p3New, p4, combinedObstacles) ||
184
+ segmentIntersectsAnyRect(p1, p2New, labelBounds) ||
185
+ segmentIntersectsAnyRect(p2New, p3New, labelBounds) ||
186
+ segmentIntersectsAnyRect(p3New, p4, labelBounds)
187
+
188
+ if (!collides) {
189
+ newPath[i + 1] = p2New
190
+ newPath[i + 2] = p3New
191
+ i = 0
192
+ }
193
+ }
194
+
195
+ const finalSimplifiedPath = simplifyPath(newPath)
196
+ return {
197
+ ...targetTrace,
198
+ tracePath: finalSimplifiedPath,
199
+ }
200
+ }
@@ -0,0 +1,76 @@
1
+ import type { InputProblem } from "lib/types/InputProblem"
2
+ import { minimizeTurns } from "./turnMinimization"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
5
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
6
+
7
+ export const minimizeTurnsWithFilteredLabels = ({
8
+ targetMspConnectionPairId,
9
+ traces,
10
+ inputProblem,
11
+ allLabelPlacements,
12
+ mergedLabelNetIdMap,
13
+ paddingBuffer,
14
+ }: {
15
+ targetMspConnectionPairId: string
16
+ traces: SolvedTracePath[]
17
+ inputProblem: InputProblem
18
+ allLabelPlacements: NetLabelPlacement[]
19
+ mergedLabelNetIdMap: Record<string, Set<string>>
20
+ paddingBuffer: number
21
+ }): SolvedTracePath => {
22
+ const targetTrace = traces.find(
23
+ (t) => t.mspPairId === targetMspConnectionPairId,
24
+ )
25
+ if (!targetTrace) {
26
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`)
27
+ }
28
+
29
+ const obstacleTraces = traces.filter(
30
+ (t) => t.mspPairId !== targetMspConnectionPairId,
31
+ )
32
+
33
+ const TRACE_WIDTH = 0.01
34
+ const traceObstacles = obstacleTraces.flatMap((trace, i) =>
35
+ trace.tracePath.slice(0, -1).map((p1, pi) => {
36
+ const p2 = trace.tracePath[pi + 1]!
37
+ return {
38
+ chipId: `trace-obstacle-${i}-${pi}`,
39
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
40
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
41
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
42
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2,
43
+ }
44
+ }),
45
+ )
46
+
47
+ const staticObstacles = getObstacleRects(inputProblem)
48
+ const combinedObstacles = [...staticObstacles, ...traceObstacles]
49
+
50
+ const originalPath = targetTrace.tracePath
51
+ const filteredLabels = allLabelPlacements.filter((label) => {
52
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId]
53
+ if (originalNetIds) {
54
+ return !originalNetIds.has(targetTrace.globalConnNetId)
55
+ }
56
+ return label.globalConnNetId !== targetTrace.globalConnNetId
57
+ })
58
+
59
+ const labelBounds = filteredLabels.map((nl) => ({
60
+ minX: nl.center.x - nl.width / 2 - paddingBuffer,
61
+ maxX: nl.center.x + nl.width / 2 + paddingBuffer,
62
+ minY: nl.center.y - nl.height / 2 - paddingBuffer,
63
+ maxY: nl.center.y + nl.height / 2 + paddingBuffer,
64
+ }))
65
+
66
+ const newPath = minimizeTurns({
67
+ path: originalPath,
68
+ obstacles: combinedObstacles,
69
+ labelBounds,
70
+ })
71
+
72
+ return {
73
+ ...targetTrace,
74
+ tracePath: newPath,
75
+ }
76
+ }
@@ -5,7 +5,6 @@ import type { GraphicsObject } from "graphics-debug"
5
5
  import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
6
6
  import type { InputProblem } from "../../types/InputProblem"
7
7
  import { MergedNetLabelObstacleSolver } from "./sub-solvers/LabelMergingSolver/LabelMergingSolver"
8
- import { TraceCleanupSolver } from "./sub-solvers/TraceCleanupSolver/TraceCleanupSolver"
9
8
  import { getColorFromString } from "lib/utils/getColorFromString"
10
9
  import { OverlapAvoidanceStepSolver } from "./sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver"
11
10
 
@@ -27,7 +26,6 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
27
26
  // sub-solver instances
28
27
  labelMergingSolver?: MergedNetLabelObstacleSolver
29
28
  overlapAvoidanceSolver?: OverlapAvoidanceStepSolver
30
- traceCleanupSolver?: TraceCleanupSolver
31
29
  pipelineStepIndex = 0
32
30
 
33
31
  constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput) {
@@ -75,24 +73,6 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
75
73
  this.activeSubSolver = this.overlapAvoidanceSolver
76
74
  break
77
75
 
78
- case 2:
79
- this.traceCleanupSolver = new TraceCleanupSolver({
80
- inputProblem: this.inputProblem,
81
- allTraces: this.overlapAvoidanceSolver!.getOutput().allTraces,
82
- targetTraceIds: new Set(
83
- this.overlapAvoidanceSolver!.getOutput().modifiedTraces.map(
84
- (t) => t.mspPairId,
85
- ),
86
- ),
87
- allLabelPlacements:
88
- this.labelMergingSolver!.getOutput().netLabelPlacements,
89
- mergedLabelNetIdMap:
90
- this.labelMergingSolver!.getOutput().mergedLabelNetIdMap,
91
- paddingBuffer: 0.01,
92
- })
93
- this.activeSubSolver = this.traceCleanupSolver
94
- break
95
-
96
76
  default:
97
77
  this.solved = true
98
78
  break
@@ -101,7 +81,7 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
101
81
 
102
82
  getOutput() {
103
83
  return {
104
- traces: this.traceCleanupSolver?.getOutput().traces ?? this.traces,
84
+ traces: this.overlapAvoidanceSolver?.getOutput().allTraces ?? this.traces,
105
85
  netLabelPlacements:
106
86
  this.labelMergingSolver?.getOutput().netLabelPlacements ??
107
87
  this.netLabelPlacements,
@@ -5,8 +5,8 @@ import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacemen
5
5
  import type { InputProblem } from "lib/types/InputProblem"
6
6
  import { findTraceViolationZone } from "./violation"
7
7
  import { generateSnipAndReconnectCandidates } from "./trySnipAndReconnect"
8
- import { simplifyPath } from "./sub-solvers/TraceCleanupSolver/simplifyPath"
9
8
  import { generateFourPointDetourCandidates } from "./tryFourPointDetour"
9
+ import { simplifyPath } from "../TraceCleanupSolver/simplifyPath"
10
10
 
11
11
  export const generateRerouteCandidates = ({
12
12
  trace,
@@ -8,7 +8,7 @@ import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSol
8
8
  import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
9
9
  import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
10
10
  import { generateRerouteCandidates } from "../../rerouteCollidingTrace"
11
- import { simplifyPath } from "../TraceCleanupSolver/simplifyPath"
11
+ import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
12
12
 
13
13
  interface SingleOverlapSolverInput {
14
14
  trace: SolvedTracePath
@@ -42,6 +42,8 @@ export class TraceOverlapShiftSolver extends BaseSolver {
42
42
 
43
43
  correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath> = {}
44
44
 
45
+ cleanupPhase: "diagonals" | "done" | null = null
46
+
45
47
  constructor(params: {
46
48
  inputProblem: InputProblem
47
49
  inputTracePaths: Array<SolvedTracePath>
@@ -211,6 +213,62 @@ export class TraceOverlapShiftSolver extends BaseSolver {
211
213
  return null
212
214
  }
213
215
 
216
+ private findNextDiagonalSegment() {
217
+ const EPS = 2e-3
218
+ for (const mspPairId in this.correctedTraceMap) {
219
+ const tracePath = this.correctedTraceMap[mspPairId]!.tracePath
220
+ for (let i = 0; i < tracePath.length - 1; i++) {
221
+ const p1 = tracePath[i]!
222
+ const p2 = tracePath[i + 1]!
223
+
224
+ const isHorizontal = Math.abs(p1.y - p2.y) < EPS
225
+ const isVertical = Math.abs(p1.x - p2.x) < EPS
226
+
227
+ if (!isHorizontal && !isVertical) {
228
+ return { mspPairId, tracePath, i, p1, p2 }
229
+ }
230
+ }
231
+ }
232
+ return null
233
+ }
234
+
235
+ private findAndFixNextDiagonalSegment(): boolean {
236
+ const diagonalInfo = this.findNextDiagonalSegment()
237
+
238
+ if (!diagonalInfo) {
239
+ return false // No diagonal segments found
240
+ }
241
+
242
+ const { mspPairId, tracePath, i, p1, p2 } = diagonalInfo
243
+ const EPS = 2e-3
244
+
245
+ const p0 = i > 0 ? tracePath[i - 1] : null
246
+ const p3 = i + 2 < tracePath.length ? tracePath[i + 2] : null
247
+
248
+ const prevIsVertical = p0 ? Math.abs(p0.x - p1.x) < EPS : false
249
+ const prevIsHorizontal = p0 ? Math.abs(p0.y - p1.y) < EPS : false
250
+
251
+ const nextIsVertical = p3 ? Math.abs(p2.x - p3.x) < EPS : false
252
+ const nextIsHorizontal = p3 ? Math.abs(p2.y - p3.y) < EPS : false
253
+
254
+ const elbow1 = { x: p1.x, y: p2.y } // vertical from p1
255
+ const elbow2 = { x: p2.x, y: p1.y } // horizontal from p1
256
+
257
+ let score1 = 0
258
+ if (prevIsVertical) score1++
259
+ if (nextIsHorizontal) score1++
260
+
261
+ let score2 = 0
262
+ if (prevIsHorizontal) score2++
263
+ if (nextIsVertical) score2++
264
+
265
+ const elbowPoint = score1 < score2 ? elbow1 : elbow2
266
+
267
+ // Replace [p1, p2] with [p1, elbowPoint, p2]
268
+ tracePath.splice(i + 1, 0, elbowPoint)
269
+ return true // Fixed one diagonal, return true to re-evaluate in next step
270
+ }
271
+
214
272
  override _step() {
215
273
  if (this.activeSubSolver?.solved) {
216
274
  for (const [mspPairId, newTrace] of Object.entries(
@@ -231,6 +289,19 @@ export class TraceOverlapShiftSolver extends BaseSolver {
231
289
  const overlapIssue = this.findNextOverlapIssue()
232
290
 
233
291
  if (overlapIssue === null) {
292
+ if (this.cleanupPhase === null) {
293
+ this.cleanupPhase = "diagonals"
294
+ }
295
+
296
+ if (this.cleanupPhase === "diagonals") {
297
+ const fixedDiagonal = this.findAndFixNextDiagonalSegment()
298
+ if (!fixedDiagonal) {
299
+ this.cleanupPhase = "done"
300
+ this.solved = true
301
+ }
302
+ return
303
+ }
304
+ // If cleanupPhase is "done", then the solver is truly solved.
234
305
  this.solved = true
235
306
  return
236
307
  }
@@ -249,6 +320,7 @@ export class TraceOverlapShiftSolver extends BaseSolver {
249
320
  }
250
321
 
251
322
  const graphics = visualizeInputProblem(this.inputProblem)
323
+ graphics.circles = graphics.circles || []
252
324
 
253
325
  // Draw current corrected traces
254
326
  for (const trace of Object.values(this.correctedTraceMap)) {
@@ -258,6 +330,17 @@ export class TraceOverlapShiftSolver extends BaseSolver {
258
330
  })
259
331
  }
260
332
 
333
+ if (this.cleanupPhase === "diagonals") {
334
+ const diagonalInfo = this.findNextDiagonalSegment()
335
+ if (diagonalInfo) {
336
+ graphics.lines!.push({
337
+ points: [diagonalInfo.p1, diagonalInfo.p2],
338
+ strokeColor: "red",
339
+ strokeWidth: 0.05,
340
+ })
341
+ }
342
+ }
343
+
261
344
  return graphics
262
345
  }
263
346
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.42",
4
+ "version": "0.0.44",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/assets/example26.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />