@tscircuit/schematic-trace-solver 0.0.41 → 0.0.42

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 (46) hide show
  1. package/dist/index.d.ts +118 -7
  2. package/dist/index.js +760 -432
  3. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +30 -17
  4. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +94 -192
  5. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +13 -24
  6. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +205 -0
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +144 -0
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +117 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts +132 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{balanceLShapes.ts → sub-solvers/TraceCleanupSolver/balanceLShapes.ts} +12 -10
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisions.ts → sub-solvers/TraceCleanupSolver/hasCollisions.ts} +1 -1
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisionsWithLabels.ts → sub-solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts} +1 -1
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{minimizeTurnsWithFilteredLabels.ts → sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts} +6 -6
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{simplifyPath.ts → sub-solvers/TraceCleanupSolver/simplifyPath.ts} +1 -1
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{turnMinimization.ts → sub-solvers/TraceCleanupSolver/turnMinimization.ts} +0 -2
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +104 -0
  17. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +24 -154
  18. package/package.json +1 -1
  19. package/tests/assets/1.input.json +379 -0
  20. package/tests/assets/2.input.json +330 -0
  21. package/tests/assets/3.input.json +388 -0
  22. package/tests/assets/MergedNetLabelObstacles.test.input.json +154 -0
  23. package/tests/assets/OverlapAvoidanceStepSolver.test.input.json +682 -0
  24. package/tests/assets/SingleOverlapSolver.test.input.json +170 -0
  25. package/tests/assets/TraceCleanupSolver.test.input.json +216 -0
  26. package/tests/assets/TraceLabelOverlapAvoidanceSolver.test.input.json +424 -0
  27. package/tests/examples/__snapshots__/example25.snap.svg +3 -3
  28. package/tests/examples/__snapshots__/example26.snap.svg +3 -3
  29. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.test.ts +18 -0
  30. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +243 -0
  31. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +223 -0
  32. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +205 -0
  33. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +184 -0
  34. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView01.test.ts +29 -0
  35. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView02.test.ts +27 -0
  36. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +39 -0
  37. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/MergedNetLabelObstacles.test.ts +14 -0
  38. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/OverlapAvoidanceStepSolver.test.ts +20 -0
  39. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/SingleOverlapSolver.test.ts +10 -0
  40. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/TraceCleanupSolver.test.ts +19 -0
  41. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +140 -0
  42. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +216 -0
  43. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +125 -0
  44. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
  45. /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{countTurns.ts → sub-solvers/TraceCleanupSolver/countTurns.ts} +0 -0
  46. /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{tryConnectPoints.ts → sub-solvers/TraceCleanupSolver/tryConnectPoints.ts} +0 -0
@@ -1,24 +1,24 @@
1
1
  import type { InputProblem } from "lib/types/InputProblem"
2
- import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
3
- import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
- import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
2
+ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
5
3
  import { minimizeTurns } from "./turnMinimization"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
6
6
 
7
7
  export const minimizeTurnsWithFilteredLabels = ({
8
8
  traces,
9
- problem,
9
+ inputProblem,
10
10
  allLabelPlacements,
11
11
  mergedLabelNetIdMap,
12
12
  paddingBuffer,
13
13
  }: {
14
14
  traces: SolvedTracePath[]
15
- problem: InputProblem
15
+ inputProblem: InputProblem
16
16
  allLabelPlacements: NetLabelPlacement[]
17
17
  mergedLabelNetIdMap: Record<string, Set<string>>
18
18
  paddingBuffer: number
19
19
  }): SolvedTracePath[] | null => {
20
20
  let changesMade = false
21
- const obstacles = getObstacleRects(problem)
21
+ const obstacles = getObstacleRects(inputProblem)
22
22
 
23
23
  const newTraces = traces.map((trace) => {
24
24
  const originalPath = trace.tracePath
@@ -2,7 +2,7 @@ import type { Point } from "graphics-debug"
2
2
  import {
3
3
  isHorizontal,
4
4
  isVertical,
5
- } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
5
+ } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
6
6
 
7
7
  export const simplifyPath = (path: Point[]): Point[] => {
8
8
  if (path.length < 3) return path
@@ -105,8 +105,6 @@ export const minimizeTurns = ({
105
105
 
106
106
  if (!collidesWithObstacles && !collidesWithLabels) {
107
107
  const newTurns = countTurns(testPath)
108
- const turnsRemoved = stairEndIdx - startIdx - 1
109
-
110
108
  optimizedPath = testPath
111
109
  currentTurns = newTurns
112
110
  improved = true
@@ -0,0 +1,104 @@
1
+ import type { Point } from "graphics-debug"
2
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
4
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import {
6
+ segmentIntersectsRect,
7
+ isVertical,
8
+ } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
9
+
10
+ export const generateFourPointDetourCandidates = ({
11
+ initialTrace,
12
+ label,
13
+ labelBounds,
14
+ paddingBuffer,
15
+ detourCount,
16
+ }: {
17
+ initialTrace: SolvedTracePath
18
+ label: NetLabelPlacement
19
+ labelBounds: any
20
+ paddingBuffer: number
21
+ detourCount: number
22
+ }): Point[][] => {
23
+ let collidingSegIndex = -1
24
+ for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
25
+ if (
26
+ segmentIntersectsRect(
27
+ initialTrace.tracePath[i],
28
+ initialTrace.tracePath[i + 1],
29
+ labelBounds,
30
+ )
31
+ ) {
32
+ collidingSegIndex = i
33
+ break
34
+ }
35
+ }
36
+
37
+ if (collidingSegIndex === -1) return []
38
+
39
+ const pA = initialTrace.tracePath[collidingSegIndex]
40
+ const pB = initialTrace.tracePath[collidingSegIndex + 1]
41
+
42
+ if (!pA || !pB) return []
43
+
44
+ const candidateDetours: Point[][] = []
45
+ const paddedLabelBounds = getRectBounds(
46
+ label.center,
47
+ label.width,
48
+ label.height,
49
+ )
50
+
51
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer
52
+
53
+ if (isVertical(pA, pB)) {
54
+ const xCandidates = [
55
+ paddedLabelBounds.maxX + effectivePadding,
56
+ paddedLabelBounds.minX - effectivePadding,
57
+ ]
58
+ for (const newX of xCandidates) {
59
+ candidateDetours.push(
60
+ pB.y > pA.y
61
+ ? [
62
+ { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
63
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
64
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
65
+ { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding },
66
+ ]
67
+ : [
68
+ { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
69
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
70
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
71
+ { x: pB.x, y: paddedLabelBounds.minY - effectivePadding },
72
+ ],
73
+ )
74
+ }
75
+ } else {
76
+ const yCandidates = [
77
+ paddedLabelBounds.maxY + effectivePadding,
78
+ paddedLabelBounds.minY - effectivePadding,
79
+ ]
80
+ for (const newY of yCandidates) {
81
+ candidateDetours.push(
82
+ pB.x > pA.x
83
+ ? [
84
+ { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
85
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
86
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
87
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y },
88
+ ]
89
+ : [
90
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
91
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
92
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
93
+ { x: paddedLabelBounds.minX - effectivePadding, y: pB.y },
94
+ ],
95
+ )
96
+ }
97
+ }
98
+
99
+ return candidateDetours.map((detourPoints) => [
100
+ ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
101
+ ...detourPoints,
102
+ ...initialTrace.tracePath.slice(collidingSegIndex + 1),
103
+ ])
104
+ }
@@ -1,32 +1,26 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
- import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
3
2
  import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
- import {
5
- isPathCollidingWithObstacles,
6
- isVertical,
7
- segmentIntersectsRect,
8
- } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
9
- import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
10
- import { simplifyPath } from "./simplifyPath"
11
3
 
12
- export const trySnipAndReconnect = ({
4
+ export const generateSnipAndReconnectCandidates = ({
13
5
  initialTrace,
14
6
  firstInsideIndex,
15
7
  lastInsideIndex,
16
8
  labelBounds,
17
- obstacles,
9
+ paddingBuffer,
10
+ detourCount,
18
11
  }: {
19
12
  initialTrace: SolvedTracePath
20
13
  firstInsideIndex: number
21
14
  lastInsideIndex: number
22
15
  labelBounds: any
23
- obstacles: any[]
24
- }): SolvedTracePath | null => {
16
+ paddingBuffer: number
17
+ detourCount: number
18
+ }): Point[][] => {
25
19
  if (
26
20
  firstInsideIndex <= 0 ||
27
21
  lastInsideIndex >= initialTrace.tracePath.length - 1
28
22
  ) {
29
- return null
23
+ return []
30
24
  }
31
25
 
32
26
  const entryPoint = initialTrace.tracePath[firstInsideIndex - 1]
@@ -35,32 +29,19 @@ export const trySnipAndReconnect = ({
35
29
  const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex)
36
30
  const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1)
37
31
 
38
- const candidateDetours: Point[][] = []
32
+ const allCandidateDetours: Point[][] = []
39
33
 
34
+ // Candidate type 1: simple elbow
40
35
  if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
41
- candidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }])
42
- candidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }])
36
+ allCandidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }])
37
+ allCandidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }])
43
38
  } else if (entryPoint.x === exitPoint.x || entryPoint.y === exitPoint.y) {
44
- const newPath = [...pathToEntry, ...pathFromExit]
45
- const simplified = simplifyPath(newPath)
46
- if (!isPathCollidingWithObstacles(simplified, obstacles)) {
47
- return { ...initialTrace, tracePath: simplified }
48
- }
39
+ // Candidate type 2: direct connection (if points are aligned)
40
+ allCandidateDetours.push([]) // No detour points needed
49
41
  }
50
42
 
51
- for (let i = 0; i < candidateDetours.length; i++) {
52
- const detour = candidateDetours[i]
53
- const newFullPath = [...pathToEntry, ...detour, ...pathFromExit]
54
- const simplified = simplifyPath(newFullPath)
55
-
56
- if (!isPathCollidingWithObstacles(simplified, obstacles)) {
57
- return { ...initialTrace, tracePath: simplified }
58
- }
59
- }
60
-
61
- candidateDetours.length = 0
62
-
63
- const buffer = 0.1
43
+ // Candidate type 3: routing around the label bounds
44
+ const buffer = paddingBuffer + detourCount * paddingBuffer
64
45
  const leftX = labelBounds.minX - buffer
65
46
  const rightX = labelBounds.maxX + buffer
66
47
  const topY = labelBounds.maxY + buffer
@@ -71,7 +52,7 @@ export const trySnipAndReconnect = ({
71
52
  entryPoint.x < labelBounds.maxX &&
72
53
  exitPoint.x < labelBounds.maxX
73
54
  ) {
74
- candidateDetours.push([
55
+ allCandidateDetours.push([
75
56
  { x: leftX, y: entryPoint.y },
76
57
  { x: leftX, y: exitPoint.y },
77
58
  ])
@@ -82,7 +63,7 @@ export const trySnipAndReconnect = ({
82
63
  entryPoint.x > labelBounds.minX &&
83
64
  exitPoint.x > labelBounds.minX
84
65
  ) {
85
- candidateDetours.push([
66
+ allCandidateDetours.push([
86
67
  { x: rightX, y: entryPoint.y },
87
68
  { x: rightX, y: exitPoint.y },
88
69
  ])
@@ -93,7 +74,7 @@ export const trySnipAndReconnect = ({
93
74
  entryPoint.y > labelBounds.minY &&
94
75
  exitPoint.y > labelBounds.minY
95
76
  ) {
96
- candidateDetours.push([
77
+ allCandidateDetours.push([
97
78
  { x: entryPoint.x, y: topY },
98
79
  { x: exitPoint.x, y: topY },
99
80
  ])
@@ -104,126 +85,15 @@ export const trySnipAndReconnect = ({
104
85
  entryPoint.y < labelBounds.maxY &&
105
86
  exitPoint.y < labelBounds.maxY
106
87
  ) {
107
- candidateDetours.push([
88
+ allCandidateDetours.push([
108
89
  { x: entryPoint.x, y: bottomY },
109
90
  { x: exitPoint.x, y: bottomY },
110
91
  ])
111
92
  }
112
93
 
113
- for (let i = 0; i < candidateDetours.length; i++) {
114
- const detour = candidateDetours[i]
115
- const newFullPath = [...pathToEntry, ...detour, ...pathFromExit]
116
- const simplified = simplifyPath(newFullPath)
117
-
118
- if (!isPathCollidingWithObstacles(simplified, obstacles)) {
119
- return { ...initialTrace, tracePath: simplified }
120
- }
121
- }
122
-
123
- return null
124
- }
125
-
126
- export const tryFourPointDetour = ({
127
- initialTrace,
128
- label,
129
- labelBounds,
130
- obstacles,
131
- paddingBuffer,
132
- detourCount,
133
- }: {
134
- initialTrace: SolvedTracePath
135
- label: NetLabelPlacement
136
- labelBounds: any
137
- obstacles: any[]
138
- paddingBuffer: number
139
- detourCount: number
140
- }): SolvedTracePath | null => {
141
- let collidingSegIndex = -1
142
- for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
143
- if (
144
- segmentIntersectsRect(
145
- initialTrace.tracePath[i],
146
- initialTrace.tracePath[i + 1],
147
- labelBounds,
148
- )
149
- ) {
150
- collidingSegIndex = i
151
- break
152
- }
153
- }
154
-
155
- if (collidingSegIndex === -1) return initialTrace
156
-
157
- const pA = initialTrace.tracePath[collidingSegIndex]
158
- const pB = initialTrace.tracePath[collidingSegIndex + 1]
159
-
160
- if (!pA || !pB) return null
161
-
162
- const candidateDetours: Point[][] = []
163
- const paddedLabelBounds = getRectBounds(
164
- label.center,
165
- label.width,
166
- label.height,
167
- )
168
-
169
- const effectivePadding = paddingBuffer + detourCount * paddingBuffer
170
-
171
- if (isVertical(pA, pB)) {
172
- const xCandidates = [
173
- paddedLabelBounds.maxX + effectivePadding,
174
- paddedLabelBounds.minX - effectivePadding,
175
- ]
176
- for (const newX of xCandidates) {
177
- candidateDetours.push(
178
- pB.y > pA.y
179
- ? [
180
- { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
181
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
182
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
183
- { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding },
184
- ]
185
- : [
186
- { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
187
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
188
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
189
- { x: pB.x, y: paddedLabelBounds.minY - effectivePadding },
190
- ],
191
- )
192
- }
193
- } else {
194
- const yCandidates = [
195
- paddedLabelBounds.maxY + effectivePadding,
196
- paddedLabelBounds.minY - effectivePadding,
197
- ]
198
- for (const newY of yCandidates) {
199
- candidateDetours.push(
200
- pB.x > pA.x
201
- ? [
202
- { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
203
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
204
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
205
- { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y },
206
- ]
207
- : [
208
- { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
209
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
210
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
211
- { x: paddedLabelBounds.minX - effectivePadding, y: pB.y },
212
- ],
213
- )
214
- }
215
- }
216
-
217
- for (const detourPoints of candidateDetours) {
218
- const finalPath = [
219
- ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
220
- ...detourPoints,
221
- ...initialTrace.tracePath.slice(collidingSegIndex + 1),
222
- ]
223
- const simplifiedFinalPath = simplifyPath(finalPath)
224
- if (!isPathCollidingWithObstacles(simplifiedFinalPath, obstacles)) {
225
- return { ...initialTrace, tracePath: simplifiedFinalPath }
226
- }
227
- }
228
- return null
94
+ return allCandidateDetours.map((detour) => [
95
+ ...pathToEntry,
96
+ ...detour,
97
+ ...pathFromExit,
98
+ ])
229
99
  }
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.41",
4
+ "version": "0.0.42",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",