@tscircuit/schematic-trace-solver 0.0.36 → 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 (36) hide show
  1. package/dist/index.d.ts +26 -1
  2. package/dist/index.js +936 -23
  3. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +15 -3
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +17 -0
  5. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts +31 -12
  6. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +40 -0
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +247 -0
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts +207 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/countTurns.ts +18 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts +38 -0
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/hasCollisions.ts +26 -0
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/index.ts +1 -0
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +73 -0
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/simplifyPath.ts +41 -0
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +229 -0
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/turnMinimization.ts +305 -0
  17. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts +25 -0
  18. package/package.json +1 -1
  19. package/site/examples/example20.page.tsx +103 -0
  20. package/site/examples/example21.page.tsx +177 -0
  21. package/site/examples/example22.page.tsx +108 -0
  22. package/site/examples/example23.page.tsx +137 -0
  23. package/site/examples/example24.page.tsx +128 -0
  24. package/tests/examples/__snapshots__/example16.snap.svg +27 -27
  25. package/tests/examples/__snapshots__/example21.snap.svg +272 -0
  26. package/tests/examples/__snapshots__/example22.snap.svg +137 -0
  27. package/tests/examples/__snapshots__/example23.snap.svg +137 -0
  28. package/tests/examples/__snapshots__/example24.snap.svg +143 -0
  29. package/tests/examples/__snapshots__/example25.snap.svg +165 -0
  30. package/tests/examples/__snapshots__/example26.snap.svg +157 -0
  31. package/tests/examples/example21.test.tsx +183 -0
  32. package/tests/examples/example22.test.tsx +109 -0
  33. package/tests/examples/example23.test.tsx +109 -0
  34. package/tests/examples/example24.test.tsx +114 -0
  35. package/tests/examples/example25.test.tsx +143 -0
  36. package/tests/examples/example26.test.tsx +134 -0
@@ -0,0 +1,38 @@
1
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
2
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import { segmentIntersectsRect } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
4
+ import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
5
+
6
+ export interface TraceLabelOverlap {
7
+ trace: SolvedTracePath
8
+ label: NetLabelPlacement
9
+ }
10
+
11
+ export const detectTraceLabelOverlap = (
12
+ traces: SolvedTracePath[],
13
+ netLabels: NetLabelPlacement[],
14
+ ): TraceLabelOverlap[] => {
15
+ const overlaps: TraceLabelOverlap[] = []
16
+
17
+ for (const trace of traces) {
18
+ for (const label of netLabels) {
19
+ const labelBounds = getRectBounds(label.center, label.width, label.height)
20
+
21
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
22
+ const p1 = trace.tracePath[i]
23
+ const p2 = trace.tracePath[i + 1]
24
+
25
+ if (segmentIntersectsRect(p1, p2, labelBounds)) {
26
+ // Check if the trace and label belong to the same net.
27
+ // If so, it's a self-attachment, not a collision.
28
+ if (trace.globalConnNetId === label.globalConnNetId) {
29
+ break // Move to the next label
30
+ }
31
+ overlaps.push({ trace, label })
32
+ break // Move to the next label
33
+ }
34
+ }
35
+ }
36
+ }
37
+ return overlaps
38
+ }
@@ -0,0 +1,26 @@
1
+ import type { Point } from "graphics-debug"
2
+ import {
3
+ isVertical,
4
+ isHorizontal,
5
+ segmentIntersectsRect,
6
+ } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
7
+
8
+ export const hasCollisions = (
9
+ pathSegments: Point[],
10
+ obstacles: any[],
11
+ ): boolean => {
12
+ // Check each segment of the path
13
+ for (let i = 0; i < pathSegments.length - 1; i++) {
14
+ const p1 = pathSegments[i]
15
+ const p2 = pathSegments[i + 1]
16
+
17
+ // Check collision with each obstacle
18
+ for (const obstacle of obstacles) {
19
+ if (segmentIntersectsRect(p1, p2, obstacle)) {
20
+ return true
21
+ }
22
+ }
23
+ }
24
+
25
+ return false
26
+ }
@@ -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
+ }