@tscircuit/schematic-trace-solver 0.0.37 → 0.0.38

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 (32) hide show
  1. package/dist/index.d.ts +26 -1
  2. package/dist/index.js +927 -20
  3. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +17 -0
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts +31 -12
  5. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +40 -0
  6. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +247 -0
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts +207 -0
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/countTurns.ts +18 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts +38 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/hasCollisions.ts +26 -0
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/index.ts +1 -0
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +73 -0
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/simplifyPath.ts +41 -0
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +229 -0
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/turnMinimization.ts +305 -0
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts +25 -0
  17. package/package.json +1 -1
  18. package/site/examples/example20.page.tsx +103 -0
  19. package/site/examples/example22.page.tsx +108 -0
  20. package/site/examples/example23.page.tsx +137 -0
  21. package/site/examples/example24.page.tsx +128 -0
  22. package/tests/examples/__snapshots__/example16.snap.svg +27 -27
  23. package/tests/examples/__snapshots__/example22.snap.svg +137 -0
  24. package/tests/examples/__snapshots__/example23.snap.svg +137 -0
  25. package/tests/examples/__snapshots__/example24.snap.svg +143 -0
  26. package/tests/examples/__snapshots__/example25.snap.svg +165 -0
  27. package/tests/examples/__snapshots__/example26.snap.svg +157 -0
  28. package/tests/examples/example22.test.tsx +109 -0
  29. package/tests/examples/example23.test.tsx +109 -0
  30. package/tests/examples/example24.test.tsx +114 -0
  31. package/tests/examples/example25.test.tsx +143 -0
  32. package/tests/examples/example26.test.tsx +134 -0
@@ -0,0 +1 @@
1
+ export * from "./TraceLabelOverlapAvoidanceSolver"
@@ -0,0 +1,73 @@
1
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
2
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
4
+ import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
5
+ import type { InputProblem } from "lib/types/InputProblem"
6
+ import { findTraceViolationZone } from "./violation"
7
+ import { tryFourPointDetour, trySnipAndReconnect } from "./trySnipAndReconnect"
8
+ import { simplifyPath } from "./simplifyPath"
9
+
10
+ export const rerouteCollidingTrace = ({
11
+ trace,
12
+ label,
13
+ problem,
14
+ paddingBuffer,
15
+ detourCount,
16
+ }: {
17
+ trace: SolvedTracePath
18
+ label: NetLabelPlacement
19
+ problem: InputProblem
20
+ paddingBuffer: number
21
+ detourCount: number
22
+ }): SolvedTracePath => {
23
+ const initialTrace = { ...trace, tracePath: simplifyPath(trace.tracePath) }
24
+
25
+ if (trace.globalConnNetId === label.globalConnNetId) {
26
+ return initialTrace
27
+ }
28
+
29
+ const obstacles = getObstacleRects(problem)
30
+ const labelPadding = paddingBuffer
31
+ const labelBoundsRaw = getRectBounds(label.center, label.width, label.height)
32
+ const labelBounds = {
33
+ minX: labelBoundsRaw.minX - labelPadding,
34
+ minY: labelBoundsRaw.minY - labelPadding,
35
+ maxX: labelBoundsRaw.maxX + labelPadding,
36
+ maxY: labelBoundsRaw.maxY + labelPadding,
37
+ chipId: `netlabel-${label.netId}`,
38
+ }
39
+
40
+ const fourPointResult = tryFourPointDetour({
41
+ initialTrace,
42
+ label,
43
+ labelBounds,
44
+ obstacles,
45
+ paddingBuffer,
46
+ detourCount,
47
+ })
48
+ if (fourPointResult) {
49
+ initialTrace.tracePath = fourPointResult.tracePath
50
+ }
51
+ const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone(
52
+ initialTrace.tracePath,
53
+ labelBounds,
54
+ )
55
+
56
+ const snipReconnectResult = trySnipAndReconnect({
57
+ initialTrace,
58
+ firstInsideIndex,
59
+ lastInsideIndex,
60
+ labelBounds,
61
+ obstacles,
62
+ })
63
+
64
+ if (snipReconnectResult) {
65
+ return snipReconnectResult
66
+ }
67
+
68
+ if (fourPointResult) {
69
+ return fourPointResult
70
+ }
71
+
72
+ return initialTrace
73
+ }
@@ -0,0 +1,41 @@
1
+ import type { Point } from "graphics-debug"
2
+ import {
3
+ isHorizontal,
4
+ isVertical,
5
+ } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
6
+
7
+ export const simplifyPath = (path: Point[]): Point[] => {
8
+ if (path.length < 3) return path
9
+ const newPath: Point[] = [path[0]]
10
+ for (let i = 1; i < path.length - 1; i++) {
11
+ const p1 = newPath[newPath.length - 1]
12
+ const p2 = path[i]
13
+ const p3 = path[i + 1]
14
+ if (
15
+ (isVertical(p1, p2) && isVertical(p2, p3)) ||
16
+ (isHorizontal(p1, p2) && isHorizontal(p2, p3))
17
+ ) {
18
+ continue
19
+ }
20
+ newPath.push(p2)
21
+ }
22
+ newPath.push(path[path.length - 1])
23
+
24
+ if (newPath.length < 3) return newPath
25
+ const finalPath: Point[] = [newPath[0]]
26
+ for (let i = 1; i < newPath.length - 1; i++) {
27
+ const p1 = finalPath[finalPath.length - 1]
28
+ const p2 = newPath[i]
29
+ const p3 = newPath[i + 1]
30
+ if (
31
+ (isVertical(p1, p2) && isVertical(p2, p3)) ||
32
+ (isHorizontal(p1, p2) && isHorizontal(p2, p3))
33
+ ) {
34
+ continue
35
+ }
36
+ finalPath.push(p2)
37
+ }
38
+ finalPath.push(newPath[newPath.length - 1])
39
+
40
+ return finalPath
41
+ }
@@ -0,0 +1,229 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
3
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import {
5
+ isPathCollidingWithObstacles,
6
+ isVertical,
7
+ segmentIntersectsRect,
8
+ } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
9
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
10
+ import { simplifyPath } from "./simplifyPath"
11
+
12
+ export const trySnipAndReconnect = ({
13
+ initialTrace,
14
+ firstInsideIndex,
15
+ lastInsideIndex,
16
+ labelBounds,
17
+ obstacles,
18
+ }: {
19
+ initialTrace: SolvedTracePath
20
+ firstInsideIndex: number
21
+ lastInsideIndex: number
22
+ labelBounds: any
23
+ obstacles: any[]
24
+ }): SolvedTracePath | null => {
25
+ if (
26
+ firstInsideIndex <= 0 ||
27
+ lastInsideIndex >= initialTrace.tracePath.length - 1
28
+ ) {
29
+ return null
30
+ }
31
+
32
+ const entryPoint = initialTrace.tracePath[firstInsideIndex - 1]
33
+ const exitPoint = initialTrace.tracePath[lastInsideIndex + 1]
34
+
35
+ const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex)
36
+ const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1)
37
+
38
+ const candidateDetours: Point[][] = []
39
+
40
+ if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
41
+ candidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }])
42
+ candidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }])
43
+ } else if (entryPoint.x === exitPoint.x || entryPoint.y === exitPoint.y) {
44
+ const newPath = [...pathToEntry, ...pathFromExit]
45
+ const simplified = simplifyPath(newPath)
46
+ if (!isPathCollidingWithObstacles(simplified, obstacles)) {
47
+ return { ...initialTrace, tracePath: simplified }
48
+ }
49
+ }
50
+
51
+ for (let i = 0; i < candidateDetours.length; i++) {
52
+ const detour = candidateDetours[i]
53
+ const newFullPath = [...pathToEntry, ...detour, ...pathFromExit]
54
+ const simplified = simplifyPath(newFullPath)
55
+
56
+ if (!isPathCollidingWithObstacles(simplified, obstacles)) {
57
+ return { ...initialTrace, tracePath: simplified }
58
+ }
59
+ }
60
+
61
+ candidateDetours.length = 0
62
+
63
+ const buffer = 0.1
64
+ const leftX = labelBounds.minX - buffer
65
+ const rightX = labelBounds.maxX + buffer
66
+ const topY = labelBounds.maxY + buffer
67
+ const bottomY = labelBounds.minY - buffer
68
+
69
+ if (
70
+ (entryPoint.x <= labelBounds.minX || exitPoint.x <= labelBounds.minX) &&
71
+ entryPoint.x < labelBounds.maxX &&
72
+ exitPoint.x < labelBounds.maxX
73
+ ) {
74
+ candidateDetours.push([
75
+ { x: leftX, y: entryPoint.y },
76
+ { x: leftX, y: exitPoint.y },
77
+ ])
78
+ }
79
+
80
+ if (
81
+ (entryPoint.x >= labelBounds.maxX || exitPoint.x >= labelBounds.maxX) &&
82
+ entryPoint.x > labelBounds.minX &&
83
+ exitPoint.x > labelBounds.minX
84
+ ) {
85
+ candidateDetours.push([
86
+ { x: rightX, y: entryPoint.y },
87
+ { x: rightX, y: exitPoint.y },
88
+ ])
89
+ }
90
+
91
+ if (
92
+ (entryPoint.y >= labelBounds.maxY || exitPoint.y >= labelBounds.maxY) &&
93
+ entryPoint.y > labelBounds.minY &&
94
+ exitPoint.y > labelBounds.minY
95
+ ) {
96
+ candidateDetours.push([
97
+ { x: entryPoint.x, y: topY },
98
+ { x: exitPoint.x, y: topY },
99
+ ])
100
+ }
101
+
102
+ if (
103
+ (entryPoint.y <= labelBounds.minY || exitPoint.y <= labelBounds.minY) &&
104
+ entryPoint.y < labelBounds.maxY &&
105
+ exitPoint.y < labelBounds.maxY
106
+ ) {
107
+ candidateDetours.push([
108
+ { x: entryPoint.x, y: bottomY },
109
+ { x: exitPoint.x, y: bottomY },
110
+ ])
111
+ }
112
+
113
+ for (let i = 0; i < candidateDetours.length; i++) {
114
+ const detour = candidateDetours[i]
115
+ const newFullPath = [...pathToEntry, ...detour, ...pathFromExit]
116
+ const simplified = simplifyPath(newFullPath)
117
+
118
+ if (!isPathCollidingWithObstacles(simplified, obstacles)) {
119
+ return { ...initialTrace, tracePath: simplified }
120
+ }
121
+ }
122
+
123
+ return null
124
+ }
125
+
126
+ export const tryFourPointDetour = ({
127
+ initialTrace,
128
+ label,
129
+ labelBounds,
130
+ obstacles,
131
+ paddingBuffer,
132
+ detourCount,
133
+ }: {
134
+ initialTrace: SolvedTracePath
135
+ label: NetLabelPlacement
136
+ labelBounds: any
137
+ obstacles: any[]
138
+ paddingBuffer: number
139
+ detourCount: number
140
+ }): SolvedTracePath | null => {
141
+ let collidingSegIndex = -1
142
+ for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
143
+ if (
144
+ segmentIntersectsRect(
145
+ initialTrace.tracePath[i],
146
+ initialTrace.tracePath[i + 1],
147
+ labelBounds,
148
+ )
149
+ ) {
150
+ collidingSegIndex = i
151
+ break
152
+ }
153
+ }
154
+
155
+ if (collidingSegIndex === -1) return initialTrace
156
+
157
+ const pA = initialTrace.tracePath[collidingSegIndex]
158
+ const pB = initialTrace.tracePath[collidingSegIndex + 1]
159
+
160
+ if (!pA || !pB) return null
161
+
162
+ const candidateDetours: Point[][] = []
163
+ const paddedLabelBounds = getRectBounds(
164
+ label.center,
165
+ label.width,
166
+ label.height,
167
+ )
168
+
169
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer
170
+
171
+ if (isVertical(pA, pB)) {
172
+ const xCandidates = [
173
+ paddedLabelBounds.maxX + effectivePadding,
174
+ paddedLabelBounds.minX - effectivePadding,
175
+ ]
176
+ for (const newX of xCandidates) {
177
+ candidateDetours.push(
178
+ pB.y > pA.y
179
+ ? [
180
+ { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
181
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
182
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
183
+ { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding },
184
+ ]
185
+ : [
186
+ { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
187
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
188
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
189
+ { x: pB.x, y: paddedLabelBounds.minY - effectivePadding },
190
+ ],
191
+ )
192
+ }
193
+ } else {
194
+ const yCandidates = [
195
+ paddedLabelBounds.maxY + effectivePadding,
196
+ paddedLabelBounds.minY - effectivePadding,
197
+ ]
198
+ for (const newY of yCandidates) {
199
+ candidateDetours.push(
200
+ pB.x > pA.x
201
+ ? [
202
+ { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
203
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
204
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
205
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y },
206
+ ]
207
+ : [
208
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
209
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
210
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
211
+ { x: paddedLabelBounds.minX - effectivePadding, y: pB.y },
212
+ ],
213
+ )
214
+ }
215
+ }
216
+
217
+ for (const detourPoints of candidateDetours) {
218
+ const finalPath = [
219
+ ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
220
+ ...detourPoints,
221
+ ...initialTrace.tracePath.slice(collidingSegIndex + 1),
222
+ ]
223
+ const simplifiedFinalPath = simplifyPath(finalPath)
224
+ if (!isPathCollidingWithObstacles(simplifiedFinalPath, obstacles)) {
225
+ return { ...initialTrace, tracePath: simplifiedFinalPath }
226
+ }
227
+ }
228
+ return null
229
+ }
@@ -0,0 +1,305 @@
1
+ import type { Point } from "graphics-debug"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
5
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
+ import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
7
+ import { hasCollisions } from "./hasCollisions"
8
+ import { countTurns } from "./countTurns"
9
+ import { simplifyPath } from "./simplifyPath"
10
+
11
+ const minimizeTurns = ({
12
+ path,
13
+ obstacles,
14
+ labelBounds,
15
+ }: {
16
+ path: Point[]
17
+ obstacles: any[]
18
+ labelBounds: any[]
19
+ }): Point[] => {
20
+ if (path.length <= 2) {
21
+ return path
22
+ }
23
+
24
+ const hasCollisionsWithLabels = (
25
+ pathSegments: Point[],
26
+ labels: any[],
27
+ ): boolean => {
28
+ for (let i = 0; i < pathSegments.length - 1; i++) {
29
+ const p1 = pathSegments[i]
30
+ const p2 = pathSegments[i + 1]
31
+
32
+ for (const label of labels) {
33
+ if (segmentIntersectsRect(p1, p2, label)) {
34
+ return true
35
+ }
36
+ }
37
+ }
38
+ return false
39
+ }
40
+
41
+ const tryConnectPoints = (start: Point, end: Point): Point[][] => {
42
+ const candidates: Point[][] = []
43
+
44
+ if (start.x === end.x || start.y === end.y) {
45
+ candidates.push([start, end])
46
+ } else {
47
+ candidates.push([start, { x: end.x, y: start.y }, end])
48
+ candidates.push([start, { x: start.x, y: end.y }, end])
49
+ }
50
+
51
+ return candidates
52
+ }
53
+
54
+ const recognizeStairStepPattern = (
55
+ pathToCheck: Point[],
56
+ startIdx: number,
57
+ ): number => {
58
+ if (startIdx >= pathToCheck.length - 3) return -1
59
+
60
+ let endIdx = startIdx
61
+ let isStairStep = true
62
+
63
+ for (
64
+ let i = startIdx;
65
+ i < pathToCheck.length - 2 && i < startIdx + 10;
66
+ i++
67
+ ) {
68
+ if (i + 2 >= pathToCheck.length) break
69
+
70
+ const p1 = pathToCheck[i]
71
+ const p2 = pathToCheck[i + 1]
72
+ const p3 = pathToCheck[i + 2]
73
+
74
+ const seg1Vertical = p1.x === p2.x
75
+ const seg2Vertical = p2.x === p3.x
76
+
77
+ if (seg1Vertical === seg2Vertical) {
78
+ break
79
+ }
80
+
81
+ const seg1Direction = seg1Vertical
82
+ ? Math.sign(p2.y - p1.y)
83
+ : Math.sign(p2.x - p1.x)
84
+
85
+ if (i > startIdx) {
86
+ const prevP = pathToCheck[i - 1]
87
+ const prevSegVertical = prevP.x === p1.x
88
+ const prevDirection = prevSegVertical
89
+ ? Math.sign(p1.y - prevP.y)
90
+ : Math.sign(p1.x - prevP.x)
91
+
92
+ if (
93
+ (seg1Vertical &&
94
+ prevSegVertical &&
95
+ seg1Direction !== prevDirection) ||
96
+ (!seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection)
97
+ ) {
98
+ isStairStep = false
99
+ break
100
+ }
101
+ }
102
+
103
+ endIdx = i + 2
104
+ }
105
+
106
+ return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1
107
+ }
108
+
109
+ let optimizedPath = [...path]
110
+ let currentTurns = countTurns(optimizedPath)
111
+ let improved = true
112
+
113
+ while (improved) {
114
+ improved = false
115
+
116
+ // First try to identify and replace stair-step patterns
117
+ for (let startIdx = 0; startIdx < optimizedPath.length - 3; startIdx++) {
118
+ const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx)
119
+
120
+ if (stairEndIdx > 0) {
121
+ const startPoint = optimizedPath[startIdx]
122
+ const endPoint = optimizedPath[stairEndIdx]
123
+
124
+ const connectionOptions = tryConnectPoints(startPoint, endPoint)
125
+
126
+ for (const connection of connectionOptions) {
127
+ const testPath = [
128
+ ...optimizedPath.slice(0, startIdx + 1),
129
+ ...connection.slice(1, -1),
130
+ ...optimizedPath.slice(stairEndIdx),
131
+ ]
132
+
133
+ const collidesWithObstacles = hasCollisions(connection, obstacles)
134
+ const collidesWithLabels = hasCollisionsWithLabels(
135
+ connection,
136
+ labelBounds,
137
+ )
138
+
139
+ if (!collidesWithObstacles && !collidesWithLabels) {
140
+ const newTurns = countTurns(testPath)
141
+ const turnsRemoved = stairEndIdx - startIdx - 1
142
+
143
+ optimizedPath = testPath
144
+ currentTurns = newTurns
145
+ improved = true
146
+ break
147
+ }
148
+ }
149
+
150
+ if (improved) break
151
+ }
152
+ }
153
+
154
+ // If no stair-step optimization worked, try regular point removal
155
+ if (!improved) {
156
+ for (let startIdx = 0; startIdx < optimizedPath.length - 2; startIdx++) {
157
+ const maxRemove = Math.min(
158
+ optimizedPath.length - startIdx - 2,
159
+ optimizedPath.length - 2,
160
+ )
161
+
162
+ for (let removeCount = 1; removeCount <= maxRemove; removeCount++) {
163
+ const endIdx = startIdx + removeCount + 1
164
+
165
+ if (endIdx >= optimizedPath.length) continue
166
+
167
+ const startPoint = optimizedPath[startIdx]
168
+ const endPoint = optimizedPath[endIdx]
169
+
170
+ const connectionOptions = tryConnectPoints(startPoint, endPoint)
171
+
172
+ for (const connection of connectionOptions) {
173
+ const testPath = [
174
+ ...optimizedPath.slice(0, startIdx + 1),
175
+ ...connection.slice(1, -1),
176
+ ...optimizedPath.slice(endIdx),
177
+ ]
178
+
179
+ const connectionSegments = connection
180
+ const collidesWithObstacles = hasCollisions(
181
+ connectionSegments,
182
+ obstacles,
183
+ )
184
+ const collidesWithLabels = hasCollisionsWithLabels(
185
+ connectionSegments,
186
+ labelBounds,
187
+ )
188
+
189
+ if (!collidesWithObstacles && !collidesWithLabels) {
190
+ const newTurns = countTurns(testPath)
191
+
192
+ if (
193
+ newTurns < currentTurns ||
194
+ (newTurns === currentTurns &&
195
+ testPath.length < optimizedPath.length)
196
+ ) {
197
+ optimizedPath = testPath
198
+ currentTurns = newTurns
199
+ improved = true
200
+ break
201
+ }
202
+ }
203
+ }
204
+
205
+ if (improved) break
206
+ }
207
+ if (improved) break
208
+ }
209
+ }
210
+
211
+ if (!improved) {
212
+ for (let i = 0; i < optimizedPath.length - 2; i++) {
213
+ const p1 = optimizedPath[i]
214
+ const p2 = optimizedPath[i + 1]
215
+ const p3 = optimizedPath[i + 2]
216
+
217
+ const allVertical = p1.x === p2.x && p2.x === p3.x
218
+ const allHorizontal = p1.y === p2.y && p2.y === p3.y
219
+
220
+ if (allVertical || allHorizontal) {
221
+ const testPath = [
222
+ ...optimizedPath.slice(0, i + 1),
223
+ ...optimizedPath.slice(i + 2),
224
+ ]
225
+
226
+ const collidesWithObstacles = hasCollisions([p1, p3], obstacles)
227
+ const collidesWithLabels = hasCollisionsWithLabels(
228
+ [p1, p3],
229
+ labelBounds,
230
+ )
231
+
232
+ if (!collidesWithObstacles && !collidesWithLabels) {
233
+ optimizedPath = testPath
234
+ improved = true
235
+ break
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
241
+
242
+ const finalSimplifiedPath = simplifyPath(optimizedPath)
243
+ return finalSimplifiedPath
244
+ }
245
+
246
+ export const minimizeTurnsWithFilteredLabels = ({
247
+ traces,
248
+ problem,
249
+ allLabelPlacements,
250
+ mergedLabelNetIdMap,
251
+ paddingBuffer,
252
+ }: {
253
+ traces: SolvedTracePath[]
254
+ problem: InputProblem
255
+ allLabelPlacements: NetLabelPlacement[]
256
+ mergedLabelNetIdMap: Map<string, Set<string>>
257
+ paddingBuffer: number
258
+ }): SolvedTracePath[] | null => {
259
+ let changesMade = false
260
+ const obstacles = getObstacleRects(problem)
261
+
262
+ const newTraces = traces.map((trace) => {
263
+ const originalPath = trace.tracePath
264
+ const filteredLabels = allLabelPlacements.filter((label) => {
265
+ const originalNetIds = mergedLabelNetIdMap.get(label.globalConnNetId)
266
+ if (originalNetIds) {
267
+ return !originalNetIds.has(trace.globalConnNetId)
268
+ }
269
+ return label.globalConnNetId !== trace.globalConnNetId
270
+ })
271
+
272
+ const labelBounds = filteredLabels.map((nl) => ({
273
+ minX: nl.center.x - nl.width / 2 - paddingBuffer,
274
+ maxX: nl.center.x + nl.width / 2 + paddingBuffer,
275
+ minY: nl.center.y - nl.height / 2 - paddingBuffer,
276
+ maxY: nl.center.y + nl.height / 2 + paddingBuffer,
277
+ }))
278
+
279
+ const newPath = minimizeTurns({
280
+ path: originalPath,
281
+ obstacles,
282
+ labelBounds,
283
+ })
284
+
285
+ if (
286
+ newPath.length !== originalPath.length ||
287
+ newPath.some(
288
+ (p, i) => p.x !== originalPath[i].x || p.y !== originalPath[i].y,
289
+ )
290
+ ) {
291
+ changesMade = true
292
+ }
293
+
294
+ return {
295
+ ...trace,
296
+ tracePath: newPath,
297
+ }
298
+ })
299
+
300
+ if (changesMade) {
301
+ return newTraces
302
+ } else {
303
+ return null
304
+ }
305
+ }
@@ -0,0 +1,25 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+
3
+ export const findTraceViolationZone = (
4
+ path: Point[],
5
+ labelBounds: { minX: number; maxX: number; minY: number; maxY: number },
6
+ ) => {
7
+ const isPointInside = (p: Point) =>
8
+ p.x > labelBounds.minX &&
9
+ p.x < labelBounds.maxX &&
10
+ p.y > labelBounds.minY &&
11
+ p.y < labelBounds.maxY
12
+
13
+ let firstInsideIndex = -1
14
+ let lastInsideIndex = -1
15
+
16
+ for (let i = 0; i < path.length; i++) {
17
+ if (isPointInside(path[i])) {
18
+ if (firstInsideIndex === -1) {
19
+ firstInsideIndex = i
20
+ }
21
+ lastInsideIndex = i
22
+ }
23
+ }
24
+ return { firstInsideIndex, lastInsideIndex }
25
+ }
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.37",
4
+ "version": "0.0.38",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",