@tscircuit/schematic-trace-solver 0.0.47 → 0.0.49

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 (164) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +51 -1
  3. package/dist/index.js +750 -49
  4. package/lib/solvers/Example28Solver/Example28Solver.ts +182 -0
  5. package/lib/solvers/Example28Solver/geometry.ts +246 -0
  6. package/lib/solvers/Example28Solver/labelMovement.ts +82 -0
  7. package/lib/solvers/Example28Solver/reroute.ts +368 -0
  8. package/lib/solvers/Example28Solver/types.ts +47 -0
  9. package/lib/solvers/Example28Solver/visualize.ts +96 -0
  10. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +2 -0
  11. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver_visualize.ts +12 -0
  12. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +16 -0
  13. package/package.json +1 -1
  14. package/site/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.page.tsx +2 -0
  15. package/site/TraceCleanupSolver/TraceCleanupSolver.page.tsx +23 -0
  16. package/site/TraceLabelOverlapAvoidanceSolver/MergedNetLabelObstacles.page.tsx +18 -0
  17. package/site/TraceLabelOverlapAvoidanceSolver/OverlapAvoidanceStepSolver.page.tsx +24 -0
  18. package/site/TraceLabelOverlapAvoidanceSolver/SingleOverlapSolver.page.tsx +10 -0
  19. package/site/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.page.tsx +146 -0
  20. package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView01.page.tsx +32 -0
  21. package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView02.page.tsx +32 -0
  22. package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView03.page.tsx +35 -0
  23. package/site/examples/example01.page.tsx +6 -0
  24. package/site/examples/example02.page.tsx +4 -180
  25. package/site/examples/example03.page.tsx +3 -212
  26. package/site/examples/example04.page.tsx +6 -0
  27. package/site/examples/example05.page.tsx +3 -179
  28. package/site/examples/example06.page.tsx +3 -57
  29. package/site/examples/example07.page.tsx +3 -132
  30. package/site/examples/example08.page.tsx +3 -91
  31. package/site/examples/example09.page.tsx +3 -414
  32. package/site/examples/example10.page.tsx +3 -75
  33. package/site/examples/example11.page.tsx +3 -116
  34. package/site/examples/example12.page.tsx +3 -94
  35. package/site/examples/example13.page.tsx +3 -216
  36. package/site/examples/example14.page.tsx +3 -207
  37. package/site/examples/example15.page.tsx +6 -0
  38. package/site/examples/example16.page.tsx +6 -0
  39. package/site/examples/example17.page.tsx +6 -0
  40. package/site/examples/example18.page.tsx +3 -178
  41. package/site/examples/example19.page.tsx +3 -166
  42. package/site/examples/example20.page.tsx +3 -100
  43. package/site/examples/example21.page.tsx +3 -174
  44. package/site/examples/example22.page.tsx +3 -105
  45. package/site/examples/example23.page.tsx +3 -134
  46. package/site/examples/example24.page.tsx +3 -125
  47. package/site/examples/example25.page.tsx +2 -0
  48. package/site/examples/example26.page.tsx +2 -0
  49. package/site/examples/example27.page.tsx +2 -0
  50. package/site/examples/example28.page.tsx +6 -0
  51. package/site/examples/example29.page.tsx +6 -0
  52. package/site/examples/example30.page.tsx +6 -0
  53. package/site/examples/example31.page.tsx +4 -0
  54. package/tests/assets/example01.json +109 -0
  55. package/tests/assets/example02.json +178 -0
  56. package/tests/assets/example03.json +210 -0
  57. package/tests/assets/example04.json +41 -0
  58. package/tests/assets/example05.json +175 -0
  59. package/tests/assets/example06.json +55 -0
  60. package/tests/assets/example07.json +130 -0
  61. package/tests/assets/example08.json +89 -0
  62. package/tests/assets/example09.json +412 -0
  63. package/tests/assets/example10.json +73 -0
  64. package/tests/assets/example11.json +114 -0
  65. package/tests/assets/example12.json +92 -0
  66. package/tests/assets/example13.json +214 -0
  67. package/tests/assets/example14.json +205 -0
  68. package/tests/assets/example15.json +618 -0
  69. package/tests/assets/example16.json +102 -0
  70. package/tests/assets/example17.json +160 -0
  71. package/tests/assets/example18.json +176 -0
  72. package/tests/assets/example19.json +164 -0
  73. package/tests/assets/example20.json +98 -0
  74. package/tests/assets/example21.json +172 -0
  75. package/tests/assets/example22.json +103 -0
  76. package/tests/assets/example23.json +132 -0
  77. package/tests/assets/example24.json +123 -0
  78. package/tests/assets/example25.json +146 -33
  79. package/tests/assets/example26.json +51 -1134
  80. package/tests/assets/example27.json +151 -91
  81. package/tests/assets/example28.json +56 -0
  82. package/tests/assets/example29.json +1206 -0
  83. package/tests/assets/example30.json +168 -0
  84. package/tests/assets/example31.json +46 -0
  85. package/tests/examples/__snapshots__/example01.snap.svg +12 -6
  86. package/tests/examples/__snapshots__/example02.snap.svg +16 -8
  87. package/tests/examples/__snapshots__/example03.snap.svg +36 -18
  88. package/tests/examples/__snapshots__/example04.snap.svg +8 -4
  89. package/tests/examples/__snapshots__/example05.snap.svg +4 -2
  90. package/tests/examples/__snapshots__/example06.snap.svg +4 -2
  91. package/tests/examples/__snapshots__/example07.snap.svg +20 -10
  92. package/tests/examples/__snapshots__/example08.snap.svg +16 -8
  93. package/tests/examples/__snapshots__/example09.snap.svg +80 -40
  94. package/tests/examples/__snapshots__/example10.snap.svg +16 -8
  95. package/tests/examples/__snapshots__/example11.snap.svg +24 -12
  96. package/tests/examples/__snapshots__/example12.snap.svg +16 -8
  97. package/tests/examples/__snapshots__/example13.snap.svg +32 -16
  98. package/tests/examples/__snapshots__/example14.snap.svg +115 -48
  99. package/tests/examples/__snapshots__/example15.snap.svg +36 -18
  100. package/tests/examples/__snapshots__/example16.snap.svg +16 -8
  101. package/tests/examples/__snapshots__/example17.snap.svg +16 -8
  102. package/tests/examples/__snapshots__/example18.snap.svg +20 -10
  103. package/tests/examples/__snapshots__/example19.snap.svg +16 -8
  104. package/tests/examples/__snapshots__/example20.snap.svg +43 -135
  105. package/tests/examples/__snapshots__/example21.snap.svg +34 -18
  106. package/tests/examples/__snapshots__/example22.snap.svg +37 -23
  107. package/tests/examples/__snapshots__/example23.snap.svg +62 -26
  108. package/tests/examples/__snapshots__/example24.snap.svg +50 -28
  109. package/tests/examples/__snapshots__/example25.snap.svg +175 -49
  110. package/tests/examples/__snapshots__/example26.snap.svg +16 -8
  111. package/tests/examples/__snapshots__/example27.snap.svg +24 -12
  112. package/tests/examples/__snapshots__/example28.snap.svg +29 -189
  113. package/tests/examples/__snapshots__/example29.snap.svg +184 -92
  114. package/tests/examples/__snapshots__/example30.snap.svg +48 -24
  115. package/tests/examples/__snapshots__/example31.snap.svg +88 -0
  116. package/tests/examples/example01.test.ts +2 -2
  117. package/tests/examples/example02.test.ts +2 -2
  118. package/tests/examples/example03.test.ts +2 -2
  119. package/tests/examples/example04.test.ts +2 -2
  120. package/tests/examples/example05.test.ts +2 -2
  121. package/tests/examples/example06.test.ts +2 -2
  122. package/tests/examples/example07.test.ts +2 -2
  123. package/tests/examples/example08.test.ts +2 -2
  124. package/tests/examples/example09.test.ts +2 -2
  125. package/tests/examples/example10.test.ts +2 -2
  126. package/tests/examples/{example11.test.tsx → example11.test.ts} +2 -2
  127. package/tests/examples/{example12.test.tsx → example12.test.ts} +2 -2
  128. package/tests/examples/{example13.test.tsx → example13.test.ts} +2 -2
  129. package/tests/examples/example14.test.ts +12 -0
  130. package/tests/examples/example15.test.ts +12 -0
  131. package/tests/examples/example16.test.ts +12 -0
  132. package/tests/examples/example17.test.ts +12 -0
  133. package/tests/examples/example18.test.ts +12 -0
  134. package/tests/examples/example19.test.ts +12 -0
  135. package/tests/examples/example20.test.ts +12 -0
  136. package/tests/examples/example21.test.ts +12 -0
  137. package/tests/examples/example22.test.ts +12 -0
  138. package/tests/examples/example23.test.ts +12 -0
  139. package/tests/examples/example24.test.ts +12 -0
  140. package/tests/examples/example25.test.ts +12 -0
  141. package/tests/examples/example26.test.ts +12 -0
  142. package/tests/examples/example27.test.ts +3 -232
  143. package/tests/examples/example28.test.ts +1 -2
  144. package/tests/examples/example29.test.ts +1 -2
  145. package/tests/examples/example30.test.ts +2 -2
  146. package/tests/examples/example31.test.ts +12 -0
  147. package/site/examples/example01-basic.page.tsx +0 -105
  148. package/site/examples/example04-single-symbol.page.tsx +0 -46
  149. package/site/examples/example15-rp2040-caps.page.tsx +0 -623
  150. package/site/examples/example16-core-repro51.page.tsx +0 -107
  151. package/site/examples/example17-straight-line-trace.page.tsx +0 -165
  152. package/tests/examples/example14.test.tsx +0 -11
  153. package/tests/examples/example15.test.tsx +0 -629
  154. package/tests/examples/example16.test.tsx +0 -113
  155. package/tests/examples/example17.test.tsx +0 -171
  156. package/tests/examples/example18.test.tsx +0 -187
  157. package/tests/examples/example19.test.tsx +0 -175
  158. package/tests/examples/example20.test.tsx +0 -190
  159. package/tests/examples/example21.test.tsx +0 -183
  160. package/tests/examples/example22.test.tsx +0 -109
  161. package/tests/examples/example23.test.tsx +0 -109
  162. package/tests/examples/example24.test.tsx +0 -114
  163. package/tests/examples/example25.test.tsx +0 -143
  164. package/tests/examples/example26.test.tsx +0 -134
@@ -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
+ }
@@ -0,0 +1,47 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import type { TraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
6
+ import type { InputProblem } from "lib/types/InputProblem"
7
+
8
+ export interface Example28SolverParams {
9
+ inputProblem: InputProblem
10
+ traces: SolvedTracePath[]
11
+ netLabelPlacements: NetLabelPlacement[]
12
+ }
13
+
14
+ export type TracePathScore = {
15
+ labelIntersections: number
16
+ labelHugDistance: number
17
+ traceIntersections: number
18
+ pathLength: number
19
+ }
20
+
21
+ export type SegmentOrientation = "horizontal" | "vertical"
22
+
23
+ export type PathSegment = {
24
+ start: Point
25
+ end: Point
26
+ orientation: SegmentOrientation
27
+ }
28
+
29
+ export type ChipObstacle = ReturnType<typeof getObstacleRects>[number]
30
+
31
+ export type RerouteCandidateResult = {
32
+ path: Point[]
33
+ score?: TracePathScore
34
+ status: "valid" | "duplicate" | "chip-collision"
35
+ selected: boolean
36
+ }
37
+
38
+ export type Example28VisualizationState = {
39
+ inputProblem: InputProblem
40
+ outputTraces: SolvedTracePath[]
41
+ outputNetLabelPlacements: NetLabelPlacement[]
42
+ solved: boolean
43
+ currentOverlap: TraceLabelOverlap | null
44
+ currentCandidateResults: RerouteCandidateResult[]
45
+ }
46
+
47
+ export const EPS = 1e-9
@@ -0,0 +1,96 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
3
+ import { getColorFromString } from "lib/utils/getColorFromString"
4
+ import type {
5
+ Example28VisualizationState,
6
+ RerouteCandidateResult,
7
+ } from "./types"
8
+
9
+ const CANDIDATE_SELECTED_COLOR = "blue"
10
+ const CANDIDATE_REJECTED_COLOR = "red"
11
+
12
+ export const visualizeExample28Solver = (
13
+ state: Example28VisualizationState,
14
+ ): GraphicsObject => {
15
+ const graphics = visualizeInputProblem(state.inputProblem)
16
+
17
+ drawTraces(graphics, state)
18
+ drawNetLabels(graphics, state)
19
+
20
+ if (!state.solved) {
21
+ drawCurrentCandidates(graphics, state.currentCandidateResults)
22
+ drawCurrentOverlap(graphics, state)
23
+ }
24
+
25
+ return graphics
26
+ }
27
+
28
+ const drawTraces = (
29
+ graphics: GraphicsObject,
30
+ state: Example28VisualizationState,
31
+ ) => {
32
+ for (const trace of state.outputTraces) {
33
+ graphics.lines!.push({
34
+ points: trace.tracePath,
35
+ strokeColor: "purple",
36
+ } as any)
37
+ }
38
+ }
39
+
40
+ const drawNetLabels = (
41
+ graphics: GraphicsObject,
42
+ state: Example28VisualizationState,
43
+ ) => {
44
+ for (const label of state.outputNetLabelPlacements) {
45
+ const color = getColorFromString(label.globalConnNetId, 0.35)
46
+ graphics.rects!.push({
47
+ center: label.center,
48
+ width: label.width,
49
+ height: label.height,
50
+ fill: color,
51
+ strokeColor: getColorFromString(label.globalConnNetId, 0.9),
52
+ label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
53
+ } as any)
54
+ graphics.points!.push({
55
+ ...label.anchorPoint,
56
+ color: getColorFromString(label.globalConnNetId, 0.9),
57
+ label: `anchorPoint\norientation: ${label.orientation}`,
58
+ } as any)
59
+ }
60
+ }
61
+
62
+ const drawCurrentCandidates = (
63
+ graphics: GraphicsObject,
64
+ candidateResults: RerouteCandidateResult[],
65
+ ) => {
66
+ for (const candidate of candidateResults) {
67
+ const scoreLabel = candidate.score
68
+ ? `\nlabel intersections: ${candidate.score.labelIntersections}\ntrace intersections: ${candidate.score.traceIntersections}`
69
+ : ""
70
+ graphics.lines!.push({
71
+ points: candidate.path,
72
+ strokeColor: candidate.selected
73
+ ? CANDIDATE_SELECTED_COLOR
74
+ : CANDIDATE_REJECTED_COLOR,
75
+ strokeDash: candidate.selected ? undefined : "4 2",
76
+ label: `${candidate.selected ? "selected" : candidate.status} reroute${scoreLabel}`,
77
+ } as any)
78
+ }
79
+ }
80
+
81
+ const drawCurrentOverlap = (
82
+ graphics: GraphicsObject,
83
+ state: Example28VisualizationState,
84
+ ) => {
85
+ if (!state.currentOverlap) return
86
+
87
+ const label = state.currentOverlap.label
88
+ graphics.rects!.push({
89
+ center: label.center,
90
+ width: label.width,
91
+ height: label.height,
92
+ fill: "rgba(255, 0, 0, 0.2)",
93
+ strokeColor: CANDIDATE_REJECTED_COLOR,
94
+ label: `overlap target\n${label.netId ?? label.globalConnNetId}`,
95
+ } as any)
96
+ }
@@ -350,11 +350,13 @@ export class NetLabelPlacementSolver extends BaseSolver {
350
350
  height: p.height,
351
351
  fill: getColorFromString(p.globalConnNetId, 0.35),
352
352
  strokeColor: getColorFromString(p.globalConnNetId, 0.9),
353
+ label: `netId: ${p.netId}\nglobalConnNetId: ${p.globalConnNetId}`,
353
354
  } as any)
354
355
  graphics.points!.push({
355
356
  x: p.anchorPoint.x,
356
357
  y: p.anchorPoint.y,
357
358
  color: getColorFromString(p.globalConnNetId, 0.9),
359
+ label: `anchorPoint\norientation: ${p.orientation}`,
358
360
  } as any)
359
361
  }
360
362
 
@@ -47,6 +47,14 @@ export function visualizeSingleNetLabelPlacementSolver(
47
47
  : c.status === "trace-collision"
48
48
  ? "orange"
49
49
  : "gray"
50
+ const candidateLabel =
51
+ c.status === "ok"
52
+ ? "status: ok(valid net label candidate)"
53
+ : c.status === "chip-collision"
54
+ ? "status: chip-collision"
55
+ : c.status === "trace-collision"
56
+ ? "status: trace-collision"
57
+ : "status: parallel-to-segment"
50
58
 
51
59
  graphics.rects!.push({
52
60
  center: {
@@ -57,12 +65,14 @@ export function visualizeSingleNetLabelPlacementSolver(
57
65
  height: c.height,
58
66
  fill,
59
67
  strokeColor: stroke,
68
+ label: `${candidateLabel}\norientation: ${c.orientation}`,
60
69
  } as any)
61
70
 
62
71
  graphics.points!.push({
63
72
  x: c.anchor.x,
64
73
  y: c.anchor.y,
65
74
  color: stroke,
75
+ label: `anchor\norientation: ${c.orientation}`,
66
76
  } as any)
67
77
  }
68
78
 
@@ -75,11 +85,13 @@ export function visualizeSingleNetLabelPlacementSolver(
75
85
  height: p.height,
76
86
  fill: "rgba(0, 128, 255, 0.35)",
77
87
  strokeColor: "blue",
88
+ label: `netId: ${p.netId}\nglobalConnNetId: ${p.globalConnNetId}`,
78
89
  } as any)
79
90
  graphics.points!.push({
80
91
  x: p.anchorPoint.x,
81
92
  y: p.anchorPoint.y,
82
93
  color: "blue",
94
+ label: `anchor\norientation: ${p.orientation}`,
83
95
  } as any)
84
96
  }
85
97
 
@@ -20,6 +20,7 @@ import { expandChipsToFitPins } from "./expandChipsToFitPins"
20
20
  import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePairSolver"
21
21
  import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver"
22
22
  import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver"
23
+ import { Example28Solver } from "../Example28Solver/Example28Solver"
23
24
 
24
25
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
25
26
  solverName: string
@@ -69,6 +70,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
69
70
  labelMergingSolver?: MergedNetLabelObstacleSolver
70
71
  traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver
71
72
  traceCleanupSolver?: TraceCleanupSolver
73
+ example28Solver?: Example28Solver
72
74
 
73
75
  startTimeOfPhase: Record<string, number>
74
76
  endTimeOfPhase: Record<string, number>
@@ -224,6 +226,20 @@ export class SchematicTracePipelineSolver extends BaseSolver {
224
226
  ]
225
227
  },
226
228
  ),
229
+ definePipelineStep("example28Solver", Example28Solver, (instance) => {
230
+ const traces =
231
+ instance.traceCleanupSolver?.getOutput().traces ??
232
+ instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces
233
+
234
+ return [
235
+ {
236
+ inputProblem: instance.inputProblem,
237
+ traces,
238
+ netLabelPlacements:
239
+ instance.netLabelPlacementSolver!.netLabelPlacements,
240
+ },
241
+ ]
242
+ }),
227
243
  ]
228
244
 
229
245
  constructor(inputProblem: InputProblem) {
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.47",
4
+ "version": "0.0.49",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,2 @@
1
+ export { inputProblem } from "./SchematicTraceSingleLineSolver2_01-example17.page"
2
+ export { default } from "./SchematicTraceSingleLineSolver2_01-example17.page"
@@ -0,0 +1,23 @@
1
+ import { useMemo } from "react"
2
+ import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
3
+ import { TraceCleanupSolver } from "lib/solvers/TraceCleanupSolver/TraceCleanupSolver"
4
+ import inputData from "../../tests/assets/TraceCleanupSolver.test.input.json"
5
+
6
+ export default () => {
7
+ const solver = useMemo(
8
+ () =>
9
+ new TraceCleanupSolver({
10
+ ...inputData,
11
+ targetTraceIds: new Set(inputData.targetTraceIds),
12
+ mergedLabelNetIdMap: Object.fromEntries(
13
+ Object.entries(inputData.mergedLabelNetIdMap).map(([k, v]) => [
14
+ k,
15
+ new Set(v as any),
16
+ ]),
17
+ ),
18
+ } as any),
19
+ [],
20
+ )
21
+
22
+ return <GenericSolverDebugger solver={solver} />
23
+ }
@@ -0,0 +1,18 @@
1
+ import { useMemo } from "react"
2
+ import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
3
+ import { MergedNetLabelObstacleSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver"
4
+ import inputData from "../../tests/assets/MergedNetLabelObstacles.test.input.json"
5
+
6
+ export default () => {
7
+ const solver = useMemo(
8
+ () =>
9
+ new MergedNetLabelObstacleSolver({
10
+ netLabelPlacements: inputData.netLabelPlacements as any,
11
+ inputProblem: inputData.inputProblem as any,
12
+ traces: inputData.traces as any,
13
+ }),
14
+ [],
15
+ )
16
+
17
+ return <GenericSolverDebugger solver={solver} />
18
+ }
@@ -0,0 +1,24 @@
1
+ import { useMemo } from "react"
2
+ import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
3
+ import { OverlapAvoidanceStepSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver"
4
+ import inputData from "../../tests/assets/OverlapAvoidanceStepSolver.test.input.json"
5
+
6
+ export default () => {
7
+ const solver = useMemo(
8
+ () =>
9
+ new OverlapAvoidanceStepSolver({
10
+ inputProblem: inputData.problem as any,
11
+ traces: inputData.traces as any,
12
+ netLabelPlacements: inputData.netLabelPlacements as any,
13
+ mergedLabelNetIdMap: Object.fromEntries(
14
+ Object.entries(inputData.mergedLabelNetIdMap).map(([k, v]) => [
15
+ k,
16
+ new Set(v as any),
17
+ ]),
18
+ ),
19
+ } as any),
20
+ [],
21
+ )
22
+
23
+ return <GenericSolverDebugger solver={solver} />
24
+ }
@@ -0,0 +1,10 @@
1
+ import { useMemo } from "react"
2
+ import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
3
+ import { SingleOverlapSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver"
4
+ import inputData from "../../tests/assets/SingleOverlapSolver.test.input.json"
5
+
6
+ export default () => {
7
+ const solver = useMemo(() => new SingleOverlapSolver(inputData as any), [])
8
+
9
+ return <GenericSolverDebugger solver={solver} />
10
+ }