@tscircuit/schematic-trace-solver 0.0.48 → 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 (162) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +51 -1
  3. package/dist/index.js +730 -43
  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/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +16 -0
  11. package/package.json +1 -1
  12. package/site/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.page.tsx +2 -0
  13. package/site/TraceCleanupSolver/TraceCleanupSolver.page.tsx +23 -0
  14. package/site/TraceLabelOverlapAvoidanceSolver/MergedNetLabelObstacles.page.tsx +18 -0
  15. package/site/TraceLabelOverlapAvoidanceSolver/OverlapAvoidanceStepSolver.page.tsx +24 -0
  16. package/site/TraceLabelOverlapAvoidanceSolver/SingleOverlapSolver.page.tsx +10 -0
  17. package/site/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.page.tsx +146 -0
  18. package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView01.page.tsx +32 -0
  19. package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView02.page.tsx +32 -0
  20. package/site/TraceLabelOverlapAvoidanceSolver/renderComparisonView03.page.tsx +35 -0
  21. package/site/examples/example01.page.tsx +6 -0
  22. package/site/examples/example02.page.tsx +4 -180
  23. package/site/examples/example03.page.tsx +3 -212
  24. package/site/examples/example04.page.tsx +6 -0
  25. package/site/examples/example05.page.tsx +3 -179
  26. package/site/examples/example06.page.tsx +3 -57
  27. package/site/examples/example07.page.tsx +3 -132
  28. package/site/examples/example08.page.tsx +3 -91
  29. package/site/examples/example09.page.tsx +3 -414
  30. package/site/examples/example10.page.tsx +3 -75
  31. package/site/examples/example11.page.tsx +3 -116
  32. package/site/examples/example12.page.tsx +3 -94
  33. package/site/examples/example13.page.tsx +3 -216
  34. package/site/examples/example14.page.tsx +3 -207
  35. package/site/examples/example15.page.tsx +6 -0
  36. package/site/examples/example16.page.tsx +6 -0
  37. package/site/examples/example17.page.tsx +6 -0
  38. package/site/examples/example18.page.tsx +3 -178
  39. package/site/examples/example19.page.tsx +3 -166
  40. package/site/examples/example20.page.tsx +3 -100
  41. package/site/examples/example21.page.tsx +3 -174
  42. package/site/examples/example22.page.tsx +3 -105
  43. package/site/examples/example23.page.tsx +3 -134
  44. package/site/examples/example24.page.tsx +3 -125
  45. package/site/examples/example25.page.tsx +2 -0
  46. package/site/examples/example26.page.tsx +2 -0
  47. package/site/examples/example27.page.tsx +2 -0
  48. package/site/examples/example28.page.tsx +3 -58
  49. package/site/examples/example29.page.tsx +6 -0
  50. package/site/examples/example30.page.tsx +6 -0
  51. package/site/examples/example31.page.tsx +4 -0
  52. package/tests/assets/example01.json +109 -0
  53. package/tests/assets/example02.json +178 -0
  54. package/tests/assets/example03.json +210 -0
  55. package/tests/assets/example04.json +41 -0
  56. package/tests/assets/example05.json +175 -0
  57. package/tests/assets/example06.json +55 -0
  58. package/tests/assets/example07.json +130 -0
  59. package/tests/assets/example08.json +89 -0
  60. package/tests/assets/example09.json +412 -0
  61. package/tests/assets/example10.json +73 -0
  62. package/tests/assets/example11.json +114 -0
  63. package/tests/assets/example12.json +92 -0
  64. package/tests/assets/example13.json +214 -0
  65. package/tests/assets/example14.json +205 -0
  66. package/tests/assets/example15.json +618 -0
  67. package/tests/assets/example16.json +102 -0
  68. package/tests/assets/example17.json +160 -0
  69. package/tests/assets/example18.json +176 -0
  70. package/tests/assets/example19.json +164 -0
  71. package/tests/assets/example20.json +98 -0
  72. package/tests/assets/example21.json +172 -0
  73. package/tests/assets/example22.json +103 -0
  74. package/tests/assets/example23.json +132 -0
  75. package/tests/assets/example24.json +123 -0
  76. package/tests/assets/example25.json +146 -33
  77. package/tests/assets/example26.json +51 -1134
  78. package/tests/assets/example27.json +151 -91
  79. package/tests/assets/example28.json +56 -0
  80. package/tests/assets/example29.json +1206 -0
  81. package/tests/assets/example30.json +168 -0
  82. package/tests/assets/example31.json +46 -0
  83. package/tests/examples/__snapshots__/example01.snap.svg +12 -6
  84. package/tests/examples/__snapshots__/example02.snap.svg +16 -8
  85. package/tests/examples/__snapshots__/example03.snap.svg +36 -18
  86. package/tests/examples/__snapshots__/example04.snap.svg +8 -4
  87. package/tests/examples/__snapshots__/example05.snap.svg +4 -2
  88. package/tests/examples/__snapshots__/example06.snap.svg +4 -2
  89. package/tests/examples/__snapshots__/example07.snap.svg +20 -10
  90. package/tests/examples/__snapshots__/example08.snap.svg +16 -8
  91. package/tests/examples/__snapshots__/example09.snap.svg +80 -40
  92. package/tests/examples/__snapshots__/example10.snap.svg +16 -8
  93. package/tests/examples/__snapshots__/example11.snap.svg +24 -12
  94. package/tests/examples/__snapshots__/example12.snap.svg +16 -8
  95. package/tests/examples/__snapshots__/example13.snap.svg +32 -16
  96. package/tests/examples/__snapshots__/example14.snap.svg +115 -48
  97. package/tests/examples/__snapshots__/example15.snap.svg +36 -18
  98. package/tests/examples/__snapshots__/example16.snap.svg +16 -8
  99. package/tests/examples/__snapshots__/example17.snap.svg +16 -8
  100. package/tests/examples/__snapshots__/example18.snap.svg +20 -10
  101. package/tests/examples/__snapshots__/example19.snap.svg +16 -8
  102. package/tests/examples/__snapshots__/example20.snap.svg +43 -135
  103. package/tests/examples/__snapshots__/example21.snap.svg +34 -18
  104. package/tests/examples/__snapshots__/example22.snap.svg +37 -23
  105. package/tests/examples/__snapshots__/example23.snap.svg +62 -26
  106. package/tests/examples/__snapshots__/example24.snap.svg +50 -28
  107. package/tests/examples/__snapshots__/example25.snap.svg +175 -49
  108. package/tests/examples/__snapshots__/example26.snap.svg +16 -8
  109. package/tests/examples/__snapshots__/example27.snap.svg +24 -12
  110. package/tests/examples/__snapshots__/example28.snap.svg +24 -18
  111. package/tests/examples/__snapshots__/example29.snap.svg +184 -92
  112. package/tests/examples/__snapshots__/example30.snap.svg +48 -24
  113. package/tests/examples/__snapshots__/example31.snap.svg +88 -0
  114. package/tests/examples/example01.test.ts +2 -2
  115. package/tests/examples/example02.test.ts +2 -2
  116. package/tests/examples/example03.test.ts +2 -2
  117. package/tests/examples/example04.test.ts +2 -2
  118. package/tests/examples/example05.test.ts +2 -2
  119. package/tests/examples/example06.test.ts +2 -2
  120. package/tests/examples/example07.test.ts +2 -2
  121. package/tests/examples/example08.test.ts +2 -2
  122. package/tests/examples/example09.test.ts +2 -2
  123. package/tests/examples/example10.test.ts +2 -2
  124. package/tests/examples/{example11.test.tsx → example11.test.ts} +2 -2
  125. package/tests/examples/{example12.test.tsx → example12.test.ts} +2 -2
  126. package/tests/examples/{example13.test.tsx → example13.test.ts} +2 -2
  127. package/tests/examples/example14.test.ts +12 -0
  128. package/tests/examples/example15.test.ts +12 -0
  129. package/tests/examples/example16.test.ts +12 -0
  130. package/tests/examples/example17.test.ts +12 -0
  131. package/tests/examples/example18.test.ts +12 -0
  132. package/tests/examples/example19.test.ts +12 -0
  133. package/tests/examples/example20.test.ts +12 -0
  134. package/tests/examples/example21.test.ts +12 -0
  135. package/tests/examples/example22.test.ts +12 -0
  136. package/tests/examples/example23.test.ts +12 -0
  137. package/tests/examples/example24.test.ts +12 -0
  138. package/tests/examples/example25.test.ts +12 -0
  139. package/tests/examples/example26.test.ts +12 -0
  140. package/tests/examples/example27.test.ts +3 -232
  141. package/tests/examples/example28.test.ts +2 -2
  142. package/tests/examples/example29.test.ts +1 -2
  143. package/tests/examples/example30.test.ts +2 -2
  144. package/tests/examples/example31.test.ts +12 -0
  145. package/site/examples/example01-basic.page.tsx +0 -105
  146. package/site/examples/example04-single-symbol.page.tsx +0 -46
  147. package/site/examples/example15-rp2040-caps.page.tsx +0 -623
  148. package/site/examples/example16-core-repro51.page.tsx +0 -107
  149. package/site/examples/example17-straight-line-trace.page.tsx +0 -165
  150. package/tests/examples/example14.test.tsx +0 -11
  151. package/tests/examples/example15.test.tsx +0 -629
  152. package/tests/examples/example16.test.tsx +0 -113
  153. package/tests/examples/example17.test.tsx +0 -171
  154. package/tests/examples/example18.test.tsx +0 -187
  155. package/tests/examples/example19.test.tsx +0 -175
  156. package/tests/examples/example20.test.tsx +0 -190
  157. package/tests/examples/example21.test.tsx +0 -183
  158. package/tests/examples/example22.test.tsx +0 -109
  159. package/tests/examples/example23.test.tsx +0 -109
  160. package/tests/examples/example24.test.tsx +0 -114
  161. package/tests/examples/example25.test.tsx +0 -143
  162. package/tests/examples/example26.test.tsx +0 -134
@@ -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
+ }
@@ -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
+ }