@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,192 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
+ import type { InputProblem } from "lib/types/InputProblem"
4
+ import type { FacingDirection } from "lib/utils/dir"
5
+ import { EPS, TRACE_BOUNDARY_TOLERANCE } from "./constants"
6
+ import type { Bounds, ChipSide } from "./types"
7
+
8
+ export const isYOrientation = (
9
+ orientation: FacingDirection,
10
+ ): orientation is "y+" | "y-" => orientation === "y+" || orientation === "y-"
11
+
12
+ export const isXOrientation = (
13
+ orientation: FacingDirection,
14
+ ): orientation is "x+" | "x-" => orientation === "x+" || orientation === "x-"
15
+
16
+ export const rectsOverlap = (a: Bounds, b: Bounds) =>
17
+ Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS &&
18
+ Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS
19
+
20
+ export const rangesOverlap = (
21
+ minA: number,
22
+ maxA: number,
23
+ minB: number,
24
+ maxB: number,
25
+ ) => Math.min(maxA, maxB) - Math.max(minA, minB) > EPS
26
+
27
+ export const traceCrossesBoundsInterior = (
28
+ bounds: Bounds,
29
+ traceMap: Record<string, SolvedTracePath>,
30
+ ) => {
31
+ for (const trace of Object.values(traceMap)) {
32
+ const points = trace.tracePath
33
+ for (let i = 0; i < points.length - 1; i++) {
34
+ if (segmentCrossesBoundsInterior(points[i]!, points[i + 1]!, bounds)) {
35
+ return true
36
+ }
37
+ }
38
+ }
39
+
40
+ return false
41
+ }
42
+
43
+ const segmentCrossesBoundsInterior = (p1: Point, p2: Point, bounds: Bounds) => {
44
+ const interiorBounds = {
45
+ minX: bounds.minX + TRACE_BOUNDARY_TOLERANCE,
46
+ minY: bounds.minY + TRACE_BOUNDARY_TOLERANCE,
47
+ maxX: bounds.maxX - TRACE_BOUNDARY_TOLERANCE,
48
+ maxY: bounds.maxY - TRACE_BOUNDARY_TOLERANCE,
49
+ }
50
+
51
+ if (
52
+ interiorBounds.minX >= interiorBounds.maxX ||
53
+ interiorBounds.minY >= interiorBounds.maxY
54
+ ) {
55
+ return false
56
+ }
57
+
58
+ if (sameX(p1, p2)) {
59
+ if (
60
+ p1.x <= interiorBounds.minX + EPS ||
61
+ p1.x >= interiorBounds.maxX - EPS
62
+ ) {
63
+ return false
64
+ }
65
+ return rangesOverlap(
66
+ Math.min(p1.y, p2.y),
67
+ Math.max(p1.y, p2.y),
68
+ interiorBounds.minY,
69
+ interiorBounds.maxY,
70
+ )
71
+ }
72
+
73
+ if (sameY(p1, p2)) {
74
+ if (
75
+ p1.y <= interiorBounds.minY + EPS ||
76
+ p1.y >= interiorBounds.maxY - EPS
77
+ ) {
78
+ return false
79
+ }
80
+ return rangesOverlap(
81
+ Math.min(p1.x, p2.x),
82
+ Math.max(p1.x, p2.x),
83
+ interiorBounds.minX,
84
+ interiorBounds.maxX,
85
+ )
86
+ }
87
+
88
+ return false
89
+ }
90
+
91
+ export const tracePathCrossesAnyTrace = (
92
+ tracePath: Point[],
93
+ traceMap: Record<string, SolvedTracePath>,
94
+ ) => {
95
+ for (const trace of Object.values(traceMap)) {
96
+ const points = trace.tracePath
97
+ for (let i = 0; i < tracePath.length - 1; i++) {
98
+ for (let j = 0; j < points.length - 1; j++) {
99
+ if (
100
+ segmentsStrictlyCross(
101
+ tracePath[i]!,
102
+ tracePath[i + 1]!,
103
+ points[j]!,
104
+ points[j + 1]!,
105
+ )
106
+ ) {
107
+ return true
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ return false
114
+ }
115
+
116
+ const segmentsStrictlyCross = (a1: Point, a2: Point, b1: Point, b2: Point) => {
117
+ if (sameX(a1, a2) && sameY(b1, b2)) {
118
+ return (
119
+ a1.x > Math.min(b1.x, b2.x) + EPS &&
120
+ a1.x < Math.max(b1.x, b2.x) - EPS &&
121
+ b1.y > Math.min(a1.y, a2.y) + EPS &&
122
+ b1.y < Math.max(a1.y, a2.y) - EPS
123
+ )
124
+ }
125
+
126
+ if (sameY(a1, a2) && sameX(b1, b2)) {
127
+ return (
128
+ b1.x > Math.min(a1.x, a2.x) + EPS &&
129
+ b1.x < Math.max(a1.x, a2.x) - EPS &&
130
+ a1.y > Math.min(b1.y, b2.y) + EPS &&
131
+ a1.y < Math.max(b1.y, b2.y) - EPS
132
+ )
133
+ }
134
+
135
+ return false
136
+ }
137
+
138
+ export const getSideDistances = (point: Point, bounds: Bounds) =>
139
+ [
140
+ ["left", Math.abs(point.x - bounds.minX)] as const,
141
+ ["right", Math.abs(point.x - bounds.maxX)] as const,
142
+ ["bottom", Math.abs(point.y - bounds.minY)] as const,
143
+ ["top", Math.abs(point.y - bounds.maxY)] as const,
144
+ ] satisfies Array<readonly [ChipSide, number]>
145
+
146
+ export const getConnectorTracePath = (
147
+ source: Point,
148
+ target: Point,
149
+ orientation: FacingDirection,
150
+ ) =>
151
+ simplifyOrthogonalPath(
152
+ isYOrientation(orientation)
153
+ ? [source, { x: target.x, y: source.y }, target]
154
+ : [source, { x: source.x, y: target.y }, target],
155
+ )
156
+
157
+ const simplifyOrthogonalPath = (path: Point[]) => {
158
+ const deduped = path.filter(
159
+ (point, index) => index === 0 || !pointsEqual(point, path[index - 1]!),
160
+ )
161
+ if (deduped.length < 3) return deduped
162
+
163
+ const simplified: Point[] = [deduped[0]!]
164
+ for (let i = 1; i < deduped.length - 1; i++) {
165
+ const prev = simplified[simplified.length - 1]!
166
+ const point = deduped[i]!
167
+ const next = deduped[i + 1]!
168
+ if (
169
+ (sameX(prev, point) && sameX(point, next)) ||
170
+ (sameY(prev, point) && sameY(point, next))
171
+ ) {
172
+ continue
173
+ }
174
+ simplified.push(point)
175
+ }
176
+ simplified.push(deduped[deduped.length - 1]!)
177
+ return simplified
178
+ }
179
+
180
+ const pointsEqual = (a: Point, b: Point) => sameX(a, b) && sameY(a, b)
181
+
182
+ const sameX = (a: Point, b: Point) => Math.abs(a.x - b.x) <= EPS
183
+
184
+ const sameY = (a: Point, b: Point) => Math.abs(a.y - b.y) <= EPS
185
+
186
+ export const getMaxSearchDistance = (inputProblem: InputProblem) => {
187
+ const maxChipWidth = Math.max(
188
+ ...inputProblem.chips.map((chip) => chip.width),
189
+ 1,
190
+ )
191
+ return maxChipWidth * 3
192
+ }
@@ -0,0 +1,43 @@
1
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
2
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
+ import type { InputPin, InputProblem } from "lib/types/InputProblem"
4
+ import type { CandidateLabel } from "./types"
5
+
6
+ export const getPinMap = (inputProblem: InputProblem) => {
7
+ const pinMap: Record<string, InputPin & { chipId: string }> = {}
8
+ for (const chip of inputProblem.chips) {
9
+ for (const pin of chip.pins) {
10
+ pinMap[pin.pinId] = { ...pin, chipId: chip.chipId }
11
+ }
12
+ }
13
+ return pinMap
14
+ }
15
+
16
+ export const getTracePins = (
17
+ label: NetLabelPlacement,
18
+ pinMap: Record<string, InputPin & { chipId: string }>,
19
+ ): SolvedTracePath["pins"] => {
20
+ const pins = label.pinIds.flatMap((pinId) => {
21
+ const pin = pinMap[pinId]
22
+ return pin ? [pin] : []
23
+ })
24
+
25
+ if (pins.length >= 2) return [pins[0]!, pins[1]!]
26
+ if (pins.length === 1) return [pins[0]!, pins[0]!]
27
+
28
+ const syntheticPin = {
29
+ pinId: `${label.globalConnNetId}-netlabel-anchor`,
30
+ x: label.anchorPoint.x,
31
+ y: label.anchorPoint.y,
32
+ chipId: "available-net-orientation",
33
+ }
34
+ return [syntheticPin, syntheticPin]
35
+ }
36
+
37
+ export const toNetLabelPlacementPatch = (candidate: CandidateLabel) => ({
38
+ orientation: candidate.orientation,
39
+ anchorPoint: candidate.anchorPoint,
40
+ center: candidate.center,
41
+ width: candidate.width,
42
+ height: candidate.height,
43
+ })
@@ -0,0 +1,44 @@
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 type { InputProblem } from "lib/types/InputProblem"
5
+ import type { FacingDirection } from "lib/utils/dir"
6
+
7
+ export interface AvailableNetOrientationSolverParams {
8
+ inputProblem: InputProblem
9
+ traces: SolvedTracePath[]
10
+ netLabelPlacements: NetLabelPlacement[]
11
+ }
12
+
13
+ export type Bounds = {
14
+ minX: number
15
+ minY: number
16
+ maxX: number
17
+ maxY: number
18
+ }
19
+
20
+ export type ChipSide = "left" | "right" | "top" | "bottom"
21
+
22
+ export type CandidateLabel = {
23
+ orientation: FacingDirection
24
+ anchorPoint: Point
25
+ center: Point
26
+ width: number
27
+ height: number
28
+ }
29
+
30
+ export type CandidateStatus =
31
+ | "valid"
32
+ | "chip-collision"
33
+ | "trace-collision"
34
+ | "netlabel-collision"
35
+
36
+ export type CandidatePhase = "rotate" | "shift"
37
+
38
+ export type EvaluatedCandidate = CandidateLabel & {
39
+ status: CandidateStatus
40
+ selected: boolean
41
+ phase: CandidatePhase
42
+ distance?: number
43
+ outwardDistance?: number
44
+ }
@@ -0,0 +1,123 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
5
+ import type { InputProblem } from "lib/types/InputProblem"
6
+ import { getColorFromString } from "lib/utils/getColorFromString"
7
+ import { CANDIDATE_REJECTED_COLOR, CANDIDATE_SELECTED_COLOR } from "./constants"
8
+ import type { EvaluatedCandidate } from "./types"
9
+
10
+ export const visualizeAvailableNetOrientationSolver = (params: {
11
+ inputProblem: InputProblem
12
+ traces: SolvedTracePath[]
13
+ outputNetLabelPlacements: NetLabelPlacement[]
14
+ currentLabel: NetLabelPlacement | null
15
+ currentCandidateResults: EvaluatedCandidate[]
16
+ solved: boolean
17
+ }): GraphicsObject => {
18
+ const graphics = visualizeInputProblem(params.inputProblem)
19
+ ensureGraphicsArrays(graphics)
20
+
21
+ drawTraces(graphics, params.traces)
22
+ drawNetLabels(graphics, params.outputNetLabelPlacements)
23
+ drawCurrentLabel(graphics, params.currentLabel, params.solved)
24
+ drawCurrentCandidates(graphics, params.currentCandidateResults, params.solved)
25
+
26
+ return graphics
27
+ }
28
+
29
+ const drawTraces = (graphics: GraphicsObject, traces: SolvedTracePath[]) => {
30
+ for (const trace of traces) {
31
+ graphics.lines!.push({
32
+ points: trace.tracePath,
33
+ strokeColor: "purple",
34
+ } as any)
35
+ }
36
+ }
37
+
38
+ const drawNetLabels = (
39
+ graphics: GraphicsObject,
40
+ labels: NetLabelPlacement[],
41
+ ) => {
42
+ for (const label of labels) {
43
+ graphics.rects!.push({
44
+ center: label.center,
45
+ width: label.width,
46
+ height: label.height,
47
+ fill: getColorFromString(label.globalConnNetId, 0.35),
48
+ strokeColor: getColorFromString(label.globalConnNetId, 0.9),
49
+ label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
50
+ } as any)
51
+ graphics.points!.push({
52
+ x: label.anchorPoint.x,
53
+ y: label.anchorPoint.y,
54
+ color: getColorFromString(label.globalConnNetId, 0.9),
55
+ label: `anchorPoint\norientation: ${label.orientation}`,
56
+ } as any)
57
+ }
58
+ }
59
+
60
+ const drawCurrentLabel = (
61
+ graphics: GraphicsObject,
62
+ currentLabel: NetLabelPlacement | null,
63
+ solved: boolean,
64
+ ) => {
65
+ if (!currentLabel || solved) return
66
+
67
+ graphics.rects!.push({
68
+ center: currentLabel.center,
69
+ width: currentLabel.width,
70
+ height: currentLabel.height,
71
+ fill: "rgba(255, 0, 0, 0.2)",
72
+ strokeColor: CANDIDATE_REJECTED_COLOR,
73
+ label: `available orientation target\n${currentLabel.netId ?? currentLabel.globalConnNetId}`,
74
+ } as any)
75
+ }
76
+
77
+ const drawCurrentCandidates = (
78
+ graphics: GraphicsObject,
79
+ candidates: EvaluatedCandidate[],
80
+ solved: boolean,
81
+ ) => {
82
+ if (solved) return
83
+
84
+ for (const candidate of candidates) {
85
+ const color = candidate.selected
86
+ ? CANDIDATE_SELECTED_COLOR
87
+ : CANDIDATE_REJECTED_COLOR
88
+ const distanceLabel =
89
+ candidate.distance === undefined
90
+ ? ""
91
+ : `\ndistance: ${candidate.distance.toFixed(3)}`
92
+ const outwardDistanceLabel =
93
+ candidate.outwardDistance === undefined || candidate.outwardDistance === 0
94
+ ? ""
95
+ : `\noutward distance: ${candidate.outwardDistance.toFixed(3)}`
96
+
97
+ graphics.rects!.push({
98
+ center: candidate.center,
99
+ width: candidate.width,
100
+ height: candidate.height,
101
+ fill: candidate.selected
102
+ ? "rgba(0, 0, 255, 0.2)"
103
+ : "rgba(255, 0, 0, 0.15)",
104
+ strokeColor: color,
105
+ strokeDash: candidate.selected ? undefined : "4 2",
106
+ label: `${candidate.selected ? "selected" : candidate.status} available orientation\nphase: ${candidate.phase}\norientation: ${candidate.orientation}${distanceLabel}${outwardDistanceLabel}`,
107
+ } as any)
108
+ graphics.points!.push({
109
+ x: candidate.anchorPoint.x,
110
+ y: candidate.anchorPoint.y,
111
+ color,
112
+ label: `candidate anchor\n${candidate.phase}\n${candidate.orientation}`,
113
+ } as any)
114
+ }
115
+ }
116
+
117
+ const ensureGraphicsArrays = (graphics: GraphicsObject) => {
118
+ if (!graphics.lines) graphics.lines = []
119
+ if (!graphics.points) graphics.points = []
120
+ if (!graphics.rects) graphics.rects = []
121
+ if (!graphics.circles) graphics.circles = []
122
+ if (!graphics.texts) graphics.texts = []
123
+ }
@@ -0,0 +1,182 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
3
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
5
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
+ import {
7
+ detectTraceLabelOverlap,
8
+ type TraceLabelOverlap,
9
+ } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
10
+ import type { InputProblem } from "lib/types/InputProblem"
11
+ import { moveAttachedLabelsToReroutedTrace } from "./labelMovement"
12
+ import { findBestReroutePath } from "./reroute"
13
+ import type { Example28SolverParams, RerouteCandidateResult } from "./types"
14
+ import { visualizeExample28Solver } from "./visualize"
15
+
16
+ export class Example28Solver extends BaseSolver {
17
+ inputProblem: InputProblem
18
+ traces: SolvedTracePath[]
19
+ netLabelPlacements: NetLabelPlacement[]
20
+
21
+ outputTraces: SolvedTracePath[]
22
+ outputNetLabelPlacements: NetLabelPlacement[]
23
+ queuedOverlaps: TraceLabelOverlap[] = []
24
+ currentOverlap: TraceLabelOverlap | null = null
25
+ currentCandidateResults: RerouteCandidateResult[] = []
26
+
27
+ private chipObstacles: ReturnType<typeof getObstacleRects>
28
+
29
+ constructor(params: Example28SolverParams) {
30
+ super()
31
+ this.inputProblem = params.inputProblem
32
+ this.traces = params.traces
33
+ this.netLabelPlacements = params.netLabelPlacements
34
+ this.outputTraces = [...params.traces]
35
+ this.outputNetLabelPlacements = [...params.netLabelPlacements]
36
+ this.chipObstacles = getObstacleRects(params.inputProblem)
37
+ this.initializeQueuedOverlaps()
38
+ this.currentOverlap = this.queuedOverlaps[0] ?? null
39
+ }
40
+
41
+ override getConstructorParams(): ConstructorParameters<
42
+ typeof Example28Solver
43
+ >[0] {
44
+ return {
45
+ inputProblem: this.inputProblem,
46
+ traces: this.traces,
47
+ netLabelPlacements: this.netLabelPlacements,
48
+ }
49
+ }
50
+
51
+ override _step() {
52
+ const overlap = this.queuedOverlaps.shift()
53
+ if (!overlap) {
54
+ this.finish()
55
+ return
56
+ }
57
+
58
+ if (!this.isOverlapStillPresent(overlap)) {
59
+ this.currentOverlap = overlap
60
+ this.currentCandidateResults = []
61
+ return
62
+ }
63
+
64
+ this.rerouteOverlappingTrace(overlap)
65
+ }
66
+
67
+ private finish() {
68
+ this.currentOverlap = null
69
+ this.currentCandidateResults = []
70
+ this.solved = true
71
+ }
72
+
73
+ private initializeQueuedOverlaps() {
74
+ const queuedOverlaps: TraceLabelOverlap[] = []
75
+ const seenPairs = new Set<string>()
76
+
77
+ for (const overlap of this.getTraceLabelOverlaps()) {
78
+ if (!this.shouldRerouteOverlap(overlap)) continue
79
+
80
+ const key = this.getOverlapKey(overlap)
81
+ if (seenPairs.has(key)) continue
82
+
83
+ seenPairs.add(key)
84
+ queuedOverlaps.push(overlap)
85
+ }
86
+
87
+ this.queuedOverlaps = queuedOverlaps
88
+ }
89
+
90
+ private getTraceLabelOverlaps() {
91
+ return detectTraceLabelOverlap({
92
+ traces: this.outputTraces,
93
+ netLabels: this.outputNetLabelPlacements,
94
+ })
95
+ }
96
+
97
+ private shouldRerouteOverlap(overlap: TraceLabelOverlap) {
98
+ return (
99
+ this.isXFacingLabel(overlap.label) &&
100
+ !this.hasExplicitOrientationConstraint(overlap.label)
101
+ )
102
+ }
103
+
104
+ private isXFacingLabel(label: NetLabelPlacement) {
105
+ return label.orientation === "x+" || label.orientation === "x-"
106
+ }
107
+
108
+ private hasExplicitOrientationConstraint(label: NetLabelPlacement) {
109
+ const effectiveNetId = label.netId ?? label.globalConnNetId
110
+ return Object.hasOwn(
111
+ this.inputProblem.availableNetLabelOrientations,
112
+ effectiveNetId,
113
+ )
114
+ }
115
+
116
+ private getOverlapKey(overlap: TraceLabelOverlap) {
117
+ const labelNetId = overlap.label.netId ?? overlap.label.globalConnNetId
118
+ return `${overlap.trace.mspPairId}:${labelNetId}:${overlap.label.anchorPoint.x}:${overlap.label.anchorPoint.y}`
119
+ }
120
+
121
+ private isOverlapStillPresent(overlap: TraceLabelOverlap) {
122
+ const currentTrace = this.getCurrentTrace(overlap.trace)
123
+ if (!currentTrace) return false
124
+
125
+ return (
126
+ detectTraceLabelOverlap({
127
+ traces: [currentTrace],
128
+ netLabels: [overlap.label],
129
+ }).length > 0
130
+ )
131
+ }
132
+
133
+ private getCurrentTrace(trace: SolvedTracePath) {
134
+ return this.outputTraces.find((t) => t.mspPairId === trace.mspPairId)
135
+ }
136
+
137
+ private rerouteOverlappingTrace(overlap: TraceLabelOverlap) {
138
+ this.currentOverlap = overlap
139
+ this.currentCandidateResults = []
140
+
141
+ const traceIndex = this.outputTraces.findIndex(
142
+ (trace) => trace.mspPairId === overlap.trace.mspPairId,
143
+ )
144
+ if (traceIndex === -1) return
145
+
146
+ const currentTrace = this.outputTraces[traceIndex]!
147
+ const rerouteResult = findBestReroutePath({
148
+ trace: currentTrace,
149
+ obstacleLabel: overlap.label,
150
+ inputProblem: this.inputProblem,
151
+ outputTraces: this.outputTraces,
152
+ outputNetLabelPlacements: this.outputNetLabelPlacements,
153
+ chipObstacles: this.chipObstacles,
154
+ })
155
+ this.currentCandidateResults = rerouteResult.candidateResults
156
+
157
+ if (!rerouteResult.bestPath) return
158
+
159
+ const originalTracePath = currentTrace.tracePath
160
+ this.outputTraces[traceIndex] = {
161
+ ...currentTrace,
162
+ tracePath: rerouteResult.bestPath,
163
+ }
164
+ this.outputNetLabelPlacements = moveAttachedLabelsToReroutedTrace({
165
+ trace: currentTrace,
166
+ originalTracePath,
167
+ reroutedTracePath: rerouteResult.bestPath,
168
+ netLabelPlacements: this.outputNetLabelPlacements,
169
+ })
170
+ }
171
+
172
+ override visualize(): GraphicsObject {
173
+ return visualizeExample28Solver({
174
+ inputProblem: this.inputProblem,
175
+ outputTraces: this.outputTraces,
176
+ outputNetLabelPlacements: this.outputNetLabelPlacements,
177
+ solved: this.solved,
178
+ currentOverlap: this.currentOverlap,
179
+ currentCandidateResults: this.currentCandidateResults,
180
+ })
181
+ }
182
+ }