@tscircuit/schematic-trace-solver 0.0.48 → 0.0.50

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 (168) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +128 -1
  3. package/dist/index.js +1450 -42
  4. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +682 -0
  5. package/lib/solvers/AvailableNetOrientationSolver/constants.ts +6 -0
  6. package/lib/solvers/AvailableNetOrientationSolver/geometry.ts +192 -0
  7. package/lib/solvers/AvailableNetOrientationSolver/traces.ts +43 -0
  8. package/lib/solvers/AvailableNetOrientationSolver/types.ts +44 -0
  9. package/lib/solvers/AvailableNetOrientationSolver/visualize.ts +123 -0
  10. package/lib/solvers/Example28Solver/Example28Solver.ts +182 -0
  11. package/lib/solvers/Example28Solver/geometry.ts +246 -0
  12. package/lib/solvers/Example28Solver/labelMovement.ts +82 -0
  13. package/lib/solvers/Example28Solver/reroute.ts +368 -0
  14. package/lib/solvers/Example28Solver/types.ts +47 -0
  15. package/lib/solvers/Example28Solver/visualize.ts +96 -0
  16. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +30 -0
  17. package/package.json +1 -1
  18. package/site/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.page.tsx +2 -0
  19. package/site/TraceCleanupSolver/TraceCleanupSolver.page.tsx +23 -0
  20. package/site/TraceLabelOverlapAvoidanceSolver/MergedNetLabelObstacles.page.tsx +18 -0
  21. package/site/TraceLabelOverlapAvoidanceSolver/OverlapAvoidanceStepSolver.page.tsx +24 -0
  22. package/site/TraceLabelOverlapAvoidanceSolver/SingleOverlapSolver.page.tsx +10 -0
  23. package/site/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.page.tsx +146 -0
  24. package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView01.page.tsx +32 -0
  25. package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView02.page.tsx +32 -0
  26. package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView03.page.tsx +35 -0
  27. package/site/examples/example01.page.tsx +6 -0
  28. package/site/examples/example02.page.tsx +4 -180
  29. package/site/examples/example03.page.tsx +3 -212
  30. package/site/examples/example04.page.tsx +6 -0
  31. package/site/examples/example05.page.tsx +3 -179
  32. package/site/examples/example06.page.tsx +3 -57
  33. package/site/examples/example07.page.tsx +3 -132
  34. package/site/examples/example08.page.tsx +3 -91
  35. package/site/examples/example09.page.tsx +3 -414
  36. package/site/examples/example10.page.tsx +3 -75
  37. package/site/examples/example11.page.tsx +3 -116
  38. package/site/examples/example12.page.tsx +3 -94
  39. package/site/examples/example13.page.tsx +3 -216
  40. package/site/examples/example14.page.tsx +3 -207
  41. package/site/examples/example15.page.tsx +6 -0
  42. package/site/examples/example16.page.tsx +6 -0
  43. package/site/examples/example17.page.tsx +6 -0
  44. package/site/examples/example18.page.tsx +3 -178
  45. package/site/examples/example19.page.tsx +3 -166
  46. package/site/examples/example20.page.tsx +3 -100
  47. package/site/examples/example21.page.tsx +3 -174
  48. package/site/examples/example22.page.tsx +3 -105
  49. package/site/examples/example23.page.tsx +3 -134
  50. package/site/examples/example24.page.tsx +3 -125
  51. package/site/examples/example25.page.tsx +2 -0
  52. package/site/examples/example26.page.tsx +2 -0
  53. package/site/examples/example27.page.tsx +2 -0
  54. package/site/examples/example28.page.tsx +3 -58
  55. package/site/examples/example29.page.tsx +6 -0
  56. package/site/examples/example30.page.tsx +6 -0
  57. package/site/examples/example31.page.tsx +4 -0
  58. package/tests/assets/example01.json +109 -0
  59. package/tests/assets/example02.json +178 -0
  60. package/tests/assets/example03.json +210 -0
  61. package/tests/assets/example04.json +41 -0
  62. package/tests/assets/example05.json +175 -0
  63. package/tests/assets/example06.json +55 -0
  64. package/tests/assets/example07.json +130 -0
  65. package/tests/assets/example08.json +89 -0
  66. package/tests/assets/example09.json +412 -0
  67. package/tests/assets/example10.json +73 -0
  68. package/tests/assets/example11.json +114 -0
  69. package/tests/assets/example12.json +92 -0
  70. package/tests/assets/example13.json +214 -0
  71. package/tests/assets/example14.json +205 -0
  72. package/tests/assets/example15.json +618 -0
  73. package/tests/assets/example16.json +102 -0
  74. package/tests/assets/example17.json +160 -0
  75. package/tests/assets/example18.json +176 -0
  76. package/tests/assets/example19.json +164 -0
  77. package/tests/assets/example20.json +98 -0
  78. package/tests/assets/example21.json +172 -0
  79. package/tests/assets/example22.json +103 -0
  80. package/tests/assets/example23.json +132 -0
  81. package/tests/assets/example24.json +123 -0
  82. package/tests/assets/example25.json +146 -33
  83. package/tests/assets/example26.json +51 -1134
  84. package/tests/assets/example27.json +151 -91
  85. package/tests/assets/example28.json +56 -0
  86. package/tests/assets/example29.json +1206 -0
  87. package/tests/assets/example30.json +168 -0
  88. package/tests/assets/example31.json +46 -0
  89. package/tests/examples/__snapshots__/example01.snap.svg +12 -6
  90. package/tests/examples/__snapshots__/example02.snap.svg +16 -8
  91. package/tests/examples/__snapshots__/example03.snap.svg +36 -18
  92. package/tests/examples/__snapshots__/example04.snap.svg +8 -4
  93. package/tests/examples/__snapshots__/example05.snap.svg +4 -2
  94. package/tests/examples/__snapshots__/example06.snap.svg +4 -2
  95. package/tests/examples/__snapshots__/example07.snap.svg +20 -10
  96. package/tests/examples/__snapshots__/example08.snap.svg +16 -8
  97. package/tests/examples/__snapshots__/example09.snap.svg +80 -40
  98. package/tests/examples/__snapshots__/example10.snap.svg +16 -8
  99. package/tests/examples/__snapshots__/example11.snap.svg +24 -12
  100. package/tests/examples/__snapshots__/example12.snap.svg +41 -30
  101. package/tests/examples/__snapshots__/example13.snap.svg +38 -16
  102. package/tests/examples/__snapshots__/example14.snap.svg +121 -48
  103. package/tests/examples/__snapshots__/example15.snap.svg +36 -18
  104. package/tests/examples/__snapshots__/example16.snap.svg +41 -27
  105. package/tests/examples/__snapshots__/example17.snap.svg +16 -8
  106. package/tests/examples/__snapshots__/example18.snap.svg +20 -10
  107. package/tests/examples/__snapshots__/example19.snap.svg +16 -8
  108. package/tests/examples/__snapshots__/example20.snap.svg +45 -131
  109. package/tests/examples/__snapshots__/example21.snap.svg +93 -65
  110. package/tests/examples/__snapshots__/example22.snap.svg +43 -23
  111. package/tests/examples/__snapshots__/example23.snap.svg +62 -26
  112. package/tests/examples/__snapshots__/example24.snap.svg +50 -28
  113. package/tests/examples/__snapshots__/example25.snap.svg +175 -49
  114. package/tests/examples/__snapshots__/example26.snap.svg +16 -8
  115. package/tests/examples/__snapshots__/example27.snap.svg +24 -12
  116. package/tests/examples/__snapshots__/example28.snap.svg +24 -18
  117. package/tests/examples/__snapshots__/example29.snap.svg +184 -92
  118. package/tests/examples/__snapshots__/example30.snap.svg +48 -24
  119. package/tests/examples/__snapshots__/example31.snap.svg +88 -0
  120. package/tests/examples/example01.test.ts +2 -2
  121. package/tests/examples/example02.test.ts +2 -2
  122. package/tests/examples/example03.test.ts +2 -2
  123. package/tests/examples/example04.test.ts +2 -2
  124. package/tests/examples/example05.test.ts +2 -2
  125. package/tests/examples/example06.test.ts +2 -2
  126. package/tests/examples/example07.test.ts +2 -2
  127. package/tests/examples/example08.test.ts +2 -2
  128. package/tests/examples/example09.test.ts +2 -2
  129. package/tests/examples/example10.test.ts +2 -2
  130. package/tests/examples/{example11.test.tsx → example11.test.ts} +2 -2
  131. package/tests/examples/{example12.test.tsx → example12.test.ts} +2 -2
  132. package/tests/examples/{example13.test.tsx → example13.test.ts} +2 -2
  133. package/tests/examples/example14.test.ts +12 -0
  134. package/tests/examples/example15.test.ts +12 -0
  135. package/tests/examples/example16.test.ts +12 -0
  136. package/tests/examples/example17.test.ts +12 -0
  137. package/tests/examples/example18.test.ts +12 -0
  138. package/tests/examples/example19.test.ts +12 -0
  139. package/tests/examples/example20.test.ts +12 -0
  140. package/tests/examples/example21.test.ts +12 -0
  141. package/tests/examples/example22.test.ts +12 -0
  142. package/tests/examples/example23.test.ts +12 -0
  143. package/tests/examples/example24.test.ts +12 -0
  144. package/tests/examples/example25.test.ts +12 -0
  145. package/tests/examples/example26.test.ts +12 -0
  146. package/tests/examples/example27.test.ts +3 -232
  147. package/tests/examples/example28.test.ts +2 -2
  148. package/tests/examples/example29.test.ts +1 -2
  149. package/tests/examples/example30.test.ts +2 -2
  150. package/tests/examples/example31.test.ts +12 -0
  151. package/site/examples/example01-basic.page.tsx +0 -105
  152. package/site/examples/example04-single-symbol.page.tsx +0 -46
  153. package/site/examples/example15-rp2040-caps.page.tsx +0 -623
  154. package/site/examples/example16-core-repro51.page.tsx +0 -107
  155. package/site/examples/example17-straight-line-trace.page.tsx +0 -165
  156. package/tests/examples/example14.test.tsx +0 -11
  157. package/tests/examples/example15.test.tsx +0 -629
  158. package/tests/examples/example16.test.tsx +0 -113
  159. package/tests/examples/example17.test.tsx +0 -171
  160. package/tests/examples/example18.test.tsx +0 -187
  161. package/tests/examples/example19.test.tsx +0 -175
  162. package/tests/examples/example20.test.tsx +0 -190
  163. package/tests/examples/example21.test.tsx +0 -183
  164. package/tests/examples/example22.test.tsx +0 -109
  165. package/tests/examples/example23.test.tsx +0 -109
  166. package/tests/examples/example24.test.tsx +0 -114
  167. package/tests/examples/example25.test.tsx +0 -143
  168. package/tests/examples/example26.test.tsx +0 -134
@@ -0,0 +1,246 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
3
+ import type { ChipObstacle, PathSegment, SegmentOrientation } from "./types"
4
+ import { EPS } from "./types"
5
+
6
+ export const getPathKey = (path: Point[]) =>
7
+ path.map((point) => `${point.x},${point.y}`).join(";")
8
+
9
+ export const getPathLength = (path: Point[]) => {
10
+ let length = 0
11
+ for (let i = 0; i < path.length - 1; i++) {
12
+ length +=
13
+ Math.abs(path[i + 1]!.x - path[i]!.x) +
14
+ Math.abs(path[i + 1]!.y - path[i]!.y)
15
+ }
16
+ return length
17
+ }
18
+
19
+ export const getDistance = (a: Point, b: Point) =>
20
+ Math.abs(a.x - b.x) + Math.abs(a.y - b.y)
21
+
22
+ export const isAxisAlignedSegment = (start: Point, end: Point) =>
23
+ Math.abs(start.x - end.x) < EPS || Math.abs(start.y - end.y) < EPS
24
+
25
+ export const getSegmentOrientation = (
26
+ start: Point,
27
+ end: Point,
28
+ ): SegmentOrientation =>
29
+ Math.abs(start.y - end.y) < EPS ? "horizontal" : "vertical"
30
+
31
+ export const projectPointToSegment = (
32
+ point: Point,
33
+ start: Point,
34
+ end: Point,
35
+ ): Point => {
36
+ if (Math.abs(start.x - end.x) < EPS) {
37
+ return {
38
+ x: start.x,
39
+ y: Math.min(
40
+ Math.max(point.y, Math.min(start.y, end.y)),
41
+ Math.max(start.y, end.y),
42
+ ),
43
+ }
44
+ }
45
+
46
+ if (Math.abs(start.y - end.y) < EPS) {
47
+ return {
48
+ x: Math.min(
49
+ Math.max(point.x, Math.min(start.x, end.x)),
50
+ Math.max(start.x, end.x),
51
+ ),
52
+ y: start.y,
53
+ }
54
+ }
55
+
56
+ return start
57
+ }
58
+
59
+ export const getPointToSegmentDistance = (
60
+ point: Point,
61
+ start: Point,
62
+ end: Point,
63
+ ) => getDistance(point, projectPointToSegment(point, start, end))
64
+
65
+ export const getSegments = (path: Point[]) => {
66
+ const segments: PathSegment[] = []
67
+
68
+ for (let i = 0; i < path.length - 1; i++) {
69
+ const start = path[i]!
70
+ const end = path[i + 1]!
71
+ if (!isAxisAlignedSegment(start, end)) continue
72
+ segments.push({
73
+ start,
74
+ end,
75
+ orientation: getSegmentOrientation(start, end),
76
+ })
77
+ }
78
+
79
+ return segments
80
+ }
81
+
82
+ export const findSegmentContainingPoint = (path: Point[], point: Point) => {
83
+ for (let i = 0; i < path.length - 1; i++) {
84
+ const start = path[i]!
85
+ const end = path[i + 1]!
86
+ if (!isAxisAlignedSegment(start, end)) continue
87
+ if (getPointToSegmentDistance(point, start, end) > 1e-6) continue
88
+
89
+ return {
90
+ index: i,
91
+ start,
92
+ end,
93
+ orientation: getSegmentOrientation(start, end),
94
+ }
95
+ }
96
+
97
+ return null
98
+ }
99
+
100
+ export const isPointWithinSegmentPrimaryRange = (
101
+ point: Point,
102
+ segment: PathSegment,
103
+ ) => {
104
+ if (segment.orientation === "horizontal") {
105
+ return (
106
+ point.x >= Math.min(segment.start.x, segment.end.x) - EPS &&
107
+ point.x <= Math.max(segment.start.x, segment.end.x) + EPS
108
+ )
109
+ }
110
+
111
+ return (
112
+ point.y >= Math.min(segment.start.y, segment.end.y) - EPS &&
113
+ point.y <= Math.max(segment.start.y, segment.end.y) + EPS
114
+ )
115
+ }
116
+
117
+ export const findPreferredReroutedSegment = (
118
+ path: Point[],
119
+ originalSegmentIndex: number,
120
+ originalSegmentCount: number,
121
+ orientation: SegmentOrientation,
122
+ anchorPoint: Point,
123
+ ) => {
124
+ const matchingSegments = getSegments(path).filter(
125
+ (segment) => segment.orientation === orientation,
126
+ )
127
+ if (matchingSegments.length === 0) return null
128
+
129
+ const containingSegments = matchingSegments.filter((segment) =>
130
+ isPointWithinSegmentPrimaryRange(anchorPoint, segment),
131
+ )
132
+ const candidateSegments =
133
+ containingSegments.length > 0 ? containingSegments : matchingSegments
134
+
135
+ const isNearStart = originalSegmentIndex < originalSegmentCount / 2
136
+ return isNearStart
137
+ ? candidateSegments[0]!
138
+ : candidateSegments[candidateSegments.length - 1]!
139
+ }
140
+
141
+ export const projectPointToPath = (point: Point, path: Point[]) => {
142
+ let bestPoint: Point | null = null
143
+ let bestDistance = Number.POSITIVE_INFINITY
144
+
145
+ for (let i = 0; i < path.length - 1; i++) {
146
+ const projectedPoint = projectPointToSegment(point, path[i]!, path[i + 1]!)
147
+ const distance = getDistance(point, projectedPoint)
148
+ if (distance < bestDistance) {
149
+ bestPoint = projectedPoint
150
+ bestDistance = distance
151
+ }
152
+ }
153
+
154
+ return bestPoint
155
+ }
156
+
157
+ export const segmentsIntersect = (
158
+ a1: Point,
159
+ a2: Point,
160
+ b1: Point,
161
+ b2: Point,
162
+ ) => {
163
+ const aVertical = Math.abs(a1.x - a2.x) < EPS
164
+ const bVertical = Math.abs(b1.x - b2.x) < EPS
165
+ const between = (value: number, p1: number, p2: number) =>
166
+ value >= Math.min(p1, p2) - EPS && value <= Math.max(p1, p2) + EPS
167
+
168
+ if (aVertical && bVertical) {
169
+ if (Math.abs(a1.x - b1.x) > EPS) return false
170
+ const overlap =
171
+ Math.min(Math.max(a1.y, a2.y), Math.max(b1.y, b2.y)) -
172
+ Math.max(Math.min(a1.y, a2.y), Math.min(b1.y, b2.y))
173
+ return overlap > EPS
174
+ }
175
+
176
+ if (!aVertical && !bVertical) {
177
+ if (Math.abs(a1.y - b1.y) > EPS) return false
178
+ const overlap =
179
+ Math.min(Math.max(a1.x, a2.x), Math.max(b1.x, b2.x)) -
180
+ Math.max(Math.min(a1.x, a2.x), Math.min(b1.x, b2.x))
181
+ return overlap > EPS
182
+ }
183
+
184
+ const verticalA = aVertical ? a1 : b1
185
+ const verticalB = aVertical ? a2 : b2
186
+ const horizontalA = aVertical ? b1 : a1
187
+ const horizontalB = aVertical ? b2 : a2
188
+
189
+ return (
190
+ between(verticalA.x, horizontalA.x, horizontalB.x) &&
191
+ between(horizontalA.y, verticalA.y, verticalB.y)
192
+ )
193
+ }
194
+
195
+ export const countPathIntersections = (pathA: Point[], pathB: Point[]) => {
196
+ let count = 0
197
+ for (let i = 0; i < pathA.length - 1; i++) {
198
+ for (let j = 0; j < pathB.length - 1; j++) {
199
+ if (
200
+ segmentsIntersect(pathA[i]!, pathA[i + 1]!, pathB[j]!, pathB[j + 1]!)
201
+ ) {
202
+ count++
203
+ }
204
+ }
205
+ }
206
+ return count
207
+ }
208
+
209
+ export const isPathCollidingWithChipInterior = (
210
+ path: Point[],
211
+ chipObstacles: ChipObstacle[],
212
+ ) => {
213
+ for (let i = 0; i < path.length - 1; i++) {
214
+ const start = path[i]!
215
+ const end = path[i + 1]!
216
+ for (const obstacle of chipObstacles) {
217
+ if (!segmentIntersectsRect(start, end, obstacle)) continue
218
+ if (segmentRunsAlongRectBoundary(start, end, obstacle)) continue
219
+ return true
220
+ }
221
+ }
222
+ return false
223
+ }
224
+
225
+ const segmentRunsAlongRectBoundary = (
226
+ start: Point,
227
+ end: Point,
228
+ rect: ChipObstacle,
229
+ ) => {
230
+ const isVertical = Math.abs(start.x - end.x) < EPS
231
+ const isHorizontal = Math.abs(start.y - end.y) < EPS
232
+
233
+ if (isVertical) {
234
+ return (
235
+ Math.abs(start.x - rect.minX) < EPS || Math.abs(start.x - rect.maxX) < EPS
236
+ )
237
+ }
238
+
239
+ if (isHorizontal) {
240
+ return (
241
+ Math.abs(start.y - rect.minY) < EPS || Math.abs(start.y - rect.maxY) < EPS
242
+ )
243
+ }
244
+
245
+ return false
246
+ }
@@ -0,0 +1,82 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import {
5
+ findPreferredReroutedSegment,
6
+ findSegmentContainingPoint,
7
+ projectPointToPath,
8
+ projectPointToSegment,
9
+ } from "./geometry"
10
+
11
+ export const moveAttachedLabelsToReroutedTrace = ({
12
+ trace,
13
+ originalTracePath,
14
+ reroutedTracePath,
15
+ netLabelPlacements,
16
+ }: {
17
+ trace: SolvedTracePath
18
+ originalTracePath: Point[]
19
+ reroutedTracePath: Point[]
20
+ netLabelPlacements: NetLabelPlacement[]
21
+ }) =>
22
+ netLabelPlacements.map((label) => {
23
+ if (!isLabelAttachedToTrace(label, trace)) return label
24
+
25
+ const movedAnchorPoint = getMovedAnchorPointForReroute(
26
+ label.anchorPoint,
27
+ originalTracePath,
28
+ reroutedTracePath,
29
+ )
30
+ if (!movedAnchorPoint) return label
31
+
32
+ const delta = {
33
+ x: movedAnchorPoint.x - label.anchorPoint.x,
34
+ y: movedAnchorPoint.y - label.anchorPoint.y,
35
+ }
36
+
37
+ return {
38
+ ...label,
39
+ anchorPoint: movedAnchorPoint,
40
+ center: {
41
+ x: label.center.x + delta.x,
42
+ y: label.center.y + delta.y,
43
+ },
44
+ }
45
+ })
46
+
47
+ const isLabelAttachedToTrace = (
48
+ label: NetLabelPlacement,
49
+ trace: SolvedTracePath,
50
+ ) =>
51
+ label.globalConnNetId === trace.globalConnNetId ||
52
+ label.mspConnectionPairIds.includes(trace.mspPairId)
53
+
54
+ const getMovedAnchorPointForReroute = (
55
+ anchorPoint: Point,
56
+ originalTracePath: Point[],
57
+ reroutedTracePath: Point[],
58
+ ) => {
59
+ const originalSegment = findSegmentContainingPoint(
60
+ originalTracePath,
61
+ anchorPoint,
62
+ )
63
+ if (!originalSegment) return null
64
+
65
+ const preferredSegment = findPreferredReroutedSegment(
66
+ reroutedTracePath,
67
+ originalSegment.index,
68
+ originalTracePath.length - 1,
69
+ originalSegment.orientation,
70
+ anchorPoint,
71
+ )
72
+
73
+ if (!preferredSegment) {
74
+ return projectPointToPath(anchorPoint, reroutedTracePath)
75
+ }
76
+
77
+ return projectPointToSegment(
78
+ anchorPoint,
79
+ preferredSegment.start,
80
+ preferredSegment.end,
81
+ )
82
+ }
@@ -0,0 +1,368 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
6
+ import { detectTraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
7
+ import { generateRerouteCandidates } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace"
8
+ import type { InputProblem } from "lib/types/InputProblem"
9
+ import { dir } from "lib/utils/dir"
10
+ import {
11
+ countPathIntersections,
12
+ getPathKey,
13
+ getPathLength,
14
+ isPathCollidingWithChipInterior,
15
+ } from "./geometry"
16
+ import type {
17
+ ChipObstacle,
18
+ RerouteCandidateResult,
19
+ TracePathScore,
20
+ } from "./types"
21
+
22
+ const LABEL_SIDE_CLEARANCE = 0.1
23
+ const LABEL_HUG_CLEARANCE = 0.001
24
+
25
+ export const findBestReroutePath = ({
26
+ trace,
27
+ obstacleLabel,
28
+ inputProblem,
29
+ outputTraces,
30
+ outputNetLabelPlacements,
31
+ chipObstacles,
32
+ }: {
33
+ trace: SolvedTracePath
34
+ obstacleLabel: NetLabelPlacement
35
+ inputProblem: InputProblem
36
+ outputTraces: SolvedTracePath[]
37
+ outputNetLabelPlacements: NetLabelPlacement[]
38
+ chipObstacles: ChipObstacle[]
39
+ }) => {
40
+ const candidateResults = generateRerouteCandidateResults({
41
+ trace,
42
+ label: obstacleLabel,
43
+ inputProblem,
44
+ outputTraces,
45
+ outputNetLabelPlacements,
46
+ chipObstacles,
47
+ })
48
+ const bestPath = selectBestReroutePath({
49
+ trace,
50
+ obstacleLabel,
51
+ outputTraces,
52
+ outputNetLabelPlacements,
53
+ candidateResults,
54
+ })
55
+
56
+ markSelectedCandidate(candidateResults, bestPath)
57
+
58
+ return { bestPath, candidateResults }
59
+ }
60
+
61
+ export const generateRerouteCandidateResults = ({
62
+ trace,
63
+ label,
64
+ inputProblem,
65
+ outputTraces,
66
+ outputNetLabelPlacements,
67
+ chipObstacles,
68
+ }: {
69
+ trace: SolvedTracePath
70
+ label: NetLabelPlacement
71
+ inputProblem: InputProblem
72
+ outputTraces: SolvedTracePath[]
73
+ outputNetLabelPlacements: NetLabelPlacement[]
74
+ chipObstacles: ChipObstacle[]
75
+ }) => {
76
+ const rawCandidates = generateCandidatePaths(trace, label, inputProblem)
77
+ const seen = new Set<string>()
78
+ const candidateResults: RerouteCandidateResult[] = []
79
+
80
+ for (const rawCandidate of rawCandidates) {
81
+ candidateResults.push(
82
+ createCandidateResult({
83
+ trace,
84
+ obstacleLabel: label,
85
+ path: simplifyPath(rawCandidate),
86
+ seen,
87
+ outputTraces,
88
+ outputNetLabelPlacements,
89
+ chipObstacles,
90
+ }),
91
+ )
92
+ }
93
+
94
+ return candidateResults
95
+ }
96
+
97
+ const generateCandidatePaths = (
98
+ trace: SolvedTracePath,
99
+ label: NetLabelPlacement,
100
+ inputProblem: InputProblem,
101
+ ) => [
102
+ ...generateLabelHugCandidates(trace, label),
103
+ ...generateRerouteCandidates({
104
+ trace,
105
+ label,
106
+ problem: inputProblem,
107
+ paddingBuffer: LABEL_SIDE_CLEARANCE,
108
+ detourCount: 0,
109
+ }),
110
+ ...generateEndpointDetourCandidates(trace, label),
111
+ ]
112
+
113
+ const createCandidateResult = ({
114
+ trace,
115
+ obstacleLabel,
116
+ path,
117
+ seen,
118
+ outputTraces,
119
+ outputNetLabelPlacements,
120
+ chipObstacles,
121
+ }: {
122
+ trace: SolvedTracePath
123
+ obstacleLabel: NetLabelPlacement
124
+ path: Point[]
125
+ seen: Set<string>
126
+ outputTraces: SolvedTracePath[]
127
+ outputNetLabelPlacements: NetLabelPlacement[]
128
+ chipObstacles: ChipObstacle[]
129
+ }): RerouteCandidateResult => {
130
+ const key = getPathKey(path)
131
+
132
+ if (seen.has(key)) {
133
+ return {
134
+ path,
135
+ status: "duplicate",
136
+ selected: false,
137
+ }
138
+ }
139
+
140
+ seen.add(key)
141
+
142
+ if (isPathCollidingWithChipInterior(path, chipObstacles)) {
143
+ return {
144
+ path,
145
+ status: "chip-collision",
146
+ selected: false,
147
+ }
148
+ }
149
+
150
+ return {
151
+ path,
152
+ score: scoreTracePath({
153
+ trace,
154
+ tracePath: path,
155
+ obstacleLabel,
156
+ outputTraces,
157
+ outputNetLabelPlacements,
158
+ }),
159
+ status: "valid",
160
+ selected: false,
161
+ }
162
+ }
163
+
164
+ const generateEndpointDetourCandidates = (
165
+ trace: SolvedTracePath,
166
+ label: NetLabelPlacement,
167
+ ): Point[][] => {
168
+ const start = trace.tracePath[0]
169
+ const end = trace.tracePath[trace.tracePath.length - 1]
170
+ if (!start || !end) return []
171
+
172
+ const bounds = getRectBounds(label.center, label.width, label.height)
173
+ const padding = LABEL_SIDE_CLEARANCE
174
+ const labelDirection = dir(label.orientation)
175
+ const labelSideX =
176
+ labelDirection.x < 0 ? bounds.minX - padding : bounds.maxX + padding
177
+ const oppositeSideX =
178
+ labelDirection.x < 0 ? bounds.maxX + padding : bounds.minX - padding
179
+ const topY = bounds.maxY + padding
180
+ const bottomY = bounds.minY - padding
181
+ const candidates: Point[][] = []
182
+
183
+ for (const sideX of [labelSideX, oppositeSideX]) {
184
+ candidates.push(
185
+ [
186
+ start,
187
+ { x: start.x, y: topY },
188
+ { x: sideX, y: topY },
189
+ { x: sideX, y: bottomY },
190
+ { x: end.x, y: bottomY },
191
+ end,
192
+ ],
193
+ [
194
+ start,
195
+ { x: start.x, y: bottomY },
196
+ { x: sideX, y: bottomY },
197
+ { x: sideX, y: topY },
198
+ { x: end.x, y: topY },
199
+ end,
200
+ ],
201
+ )
202
+ }
203
+
204
+ return candidates
205
+ }
206
+
207
+ const generateLabelHugCandidates = (
208
+ trace: SolvedTracePath,
209
+ label: NetLabelPlacement,
210
+ ): Point[][] => {
211
+ const start = trace.tracePath[0]
212
+ const end = trace.tracePath[trace.tracePath.length - 1]
213
+ if (!start || !end) return []
214
+
215
+ const labelDirection = dir(label.orientation)
216
+ if (labelDirection.x === 0) return []
217
+
218
+ const bounds = getRectBounds(label.center, label.width, label.height)
219
+ const sideX =
220
+ labelDirection.x < 0
221
+ ? bounds.minX - LABEL_SIDE_CLEARANCE
222
+ : bounds.maxX + LABEL_SIDE_CLEARANCE
223
+ const topY = bounds.maxY + LABEL_HUG_CLEARANCE
224
+ const bottomY = bounds.minY - LABEL_HUG_CLEARANCE
225
+ const startsAboveEnd = start.y >= end.y
226
+ const firstY = startsAboveEnd ? topY : bottomY
227
+ const secondY = startsAboveEnd ? bottomY : topY
228
+
229
+ return [
230
+ [
231
+ start,
232
+ { x: start.x, y: firstY },
233
+ { x: sideX, y: firstY },
234
+ { x: sideX, y: secondY },
235
+ { x: end.x, y: secondY },
236
+ end,
237
+ ],
238
+ [
239
+ start,
240
+ { x: start.x, y: secondY },
241
+ { x: sideX, y: secondY },
242
+ { x: sideX, y: firstY },
243
+ { x: end.x, y: firstY },
244
+ end,
245
+ ],
246
+ ]
247
+ }
248
+
249
+ const selectBestReroutePath = ({
250
+ trace,
251
+ obstacleLabel,
252
+ outputTraces,
253
+ outputNetLabelPlacements,
254
+ candidateResults,
255
+ }: {
256
+ trace: SolvedTracePath
257
+ obstacleLabel: NetLabelPlacement
258
+ outputTraces: SolvedTracePath[]
259
+ outputNetLabelPlacements: NetLabelPlacement[]
260
+ candidateResults: RerouteCandidateResult[]
261
+ }) => {
262
+ let bestPath: Point[] | null = null
263
+ let bestScore = scoreTracePath({
264
+ trace,
265
+ tracePath: trace.tracePath,
266
+ obstacleLabel,
267
+ outputTraces,
268
+ outputNetLabelPlacements,
269
+ })
270
+
271
+ for (const candidate of candidateResults) {
272
+ if (candidate.status !== "valid" || !candidate.score) continue
273
+ if (!isBetterScore(candidate.score, bestScore)) continue
274
+
275
+ bestScore = candidate.score
276
+ bestPath = candidate.path
277
+ }
278
+
279
+ return bestPath
280
+ }
281
+
282
+ const markSelectedCandidate = (
283
+ candidateResults: RerouteCandidateResult[],
284
+ bestPath: Point[] | null,
285
+ ) => {
286
+ if (!bestPath) return
287
+
288
+ for (const candidate of candidateResults) {
289
+ candidate.selected = candidate.path === bestPath
290
+ }
291
+ }
292
+
293
+ const scoreTracePath = ({
294
+ trace,
295
+ tracePath,
296
+ obstacleLabel,
297
+ outputTraces,
298
+ outputNetLabelPlacements,
299
+ }: {
300
+ trace: SolvedTracePath
301
+ tracePath: Point[]
302
+ obstacleLabel: NetLabelPlacement
303
+ outputTraces: SolvedTracePath[]
304
+ outputNetLabelPlacements: NetLabelPlacement[]
305
+ }): TracePathScore => {
306
+ const candidateTrace = { ...trace, tracePath }
307
+ return {
308
+ labelIntersections: detectTraceLabelOverlap({
309
+ traces: [candidateTrace],
310
+ netLabels: outputNetLabelPlacements,
311
+ }).length,
312
+ labelHugDistance: getLabelHugDistance(tracePath, obstacleLabel),
313
+ traceIntersections: countTraceIntersections(candidateTrace, outputTraces),
314
+ pathLength: getPathLength(tracePath),
315
+ }
316
+ }
317
+
318
+ const countTraceIntersections = (
319
+ trace: SolvedTracePath,
320
+ outputTraces: SolvedTracePath[],
321
+ ) => {
322
+ let count = 0
323
+ for (const otherTrace of outputTraces) {
324
+ if (otherTrace.mspPairId === trace.mspPairId) continue
325
+ count += countPathIntersections(trace.tracePath, otherTrace.tracePath)
326
+ }
327
+ return count
328
+ }
329
+
330
+ const isBetterScore = (score: TracePathScore, bestScore: TracePathScore) => {
331
+ if (score.labelIntersections !== bestScore.labelIntersections) {
332
+ return score.labelIntersections < bestScore.labelIntersections
333
+ }
334
+ if (score.labelHugDistance !== bestScore.labelHugDistance) {
335
+ return score.labelHugDistance < bestScore.labelHugDistance
336
+ }
337
+ if (score.traceIntersections !== bestScore.traceIntersections) {
338
+ return score.traceIntersections < bestScore.traceIntersections
339
+ }
340
+ return score.pathLength < bestScore.pathLength
341
+ }
342
+
343
+ const getLabelHugDistance = (
344
+ tracePath: Point[],
345
+ obstacleLabel: NetLabelPlacement,
346
+ ) => {
347
+ const bounds = getRectBounds(
348
+ obstacleLabel.center,
349
+ obstacleLabel.width,
350
+ obstacleLabel.height,
351
+ )
352
+ let distance = 0
353
+
354
+ for (const point of tracePath) {
355
+ distance += getPointDistanceFromRect(point, bounds)
356
+ }
357
+
358
+ return distance
359
+ }
360
+
361
+ const getPointDistanceFromRect = (
362
+ point: Point,
363
+ rect: { minX: number; minY: number; maxX: number; maxY: number },
364
+ ) => {
365
+ const dx = Math.max(rect.minX - point.x, 0, point.x - rect.maxX)
366
+ const dy = Math.max(rect.minY - point.y, 0, point.y - rect.maxY)
367
+ return dx + dy
368
+ }