@tscircuit/schematic-trace-solver 0.0.36 → 0.0.38

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 (36) hide show
  1. package/dist/index.d.ts +26 -1
  2. package/dist/index.js +936 -23
  3. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +15 -3
  4. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions.ts +17 -0
  5. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/pathOps.ts +31 -12
  6. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +40 -0
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +247 -0
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts +207 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/countTurns.ts +18 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap.ts +38 -0
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/hasCollisions.ts +26 -0
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/index.ts +1 -0
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +73 -0
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/simplifyPath.ts +41 -0
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +229 -0
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/turnMinimization.ts +305 -0
  17. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts +25 -0
  18. package/package.json +1 -1
  19. package/site/examples/example20.page.tsx +103 -0
  20. package/site/examples/example21.page.tsx +177 -0
  21. package/site/examples/example22.page.tsx +108 -0
  22. package/site/examples/example23.page.tsx +137 -0
  23. package/site/examples/example24.page.tsx +128 -0
  24. package/tests/examples/__snapshots__/example16.snap.svg +27 -27
  25. package/tests/examples/__snapshots__/example21.snap.svg +272 -0
  26. package/tests/examples/__snapshots__/example22.snap.svg +137 -0
  27. package/tests/examples/__snapshots__/example23.snap.svg +137 -0
  28. package/tests/examples/__snapshots__/example24.snap.svg +143 -0
  29. package/tests/examples/__snapshots__/example25.snap.svg +165 -0
  30. package/tests/examples/__snapshots__/example26.snap.svg +157 -0
  31. package/tests/examples/example21.test.tsx +183 -0
  32. package/tests/examples/example22.test.tsx +109 -0
  33. package/tests/examples/example23.test.tsx +109 -0
  34. package/tests/examples/example24.test.tsx +114 -0
  35. package/tests/examples/example25.test.tsx +143 -0
  36. package/tests/examples/example26.test.tsx +134 -0
@@ -0,0 +1,305 @@
1
+ import type { Point } from "graphics-debug"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
5
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
+ import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
7
+ import { hasCollisions } from "./hasCollisions"
8
+ import { countTurns } from "./countTurns"
9
+ import { simplifyPath } from "./simplifyPath"
10
+
11
+ const minimizeTurns = ({
12
+ path,
13
+ obstacles,
14
+ labelBounds,
15
+ }: {
16
+ path: Point[]
17
+ obstacles: any[]
18
+ labelBounds: any[]
19
+ }): Point[] => {
20
+ if (path.length <= 2) {
21
+ return path
22
+ }
23
+
24
+ const hasCollisionsWithLabels = (
25
+ pathSegments: Point[],
26
+ labels: any[],
27
+ ): boolean => {
28
+ for (let i = 0; i < pathSegments.length - 1; i++) {
29
+ const p1 = pathSegments[i]
30
+ const p2 = pathSegments[i + 1]
31
+
32
+ for (const label of labels) {
33
+ if (segmentIntersectsRect(p1, p2, label)) {
34
+ return true
35
+ }
36
+ }
37
+ }
38
+ return false
39
+ }
40
+
41
+ const tryConnectPoints = (start: Point, end: Point): Point[][] => {
42
+ const candidates: Point[][] = []
43
+
44
+ if (start.x === end.x || start.y === end.y) {
45
+ candidates.push([start, end])
46
+ } else {
47
+ candidates.push([start, { x: end.x, y: start.y }, end])
48
+ candidates.push([start, { x: start.x, y: end.y }, end])
49
+ }
50
+
51
+ return candidates
52
+ }
53
+
54
+ const recognizeStairStepPattern = (
55
+ pathToCheck: Point[],
56
+ startIdx: number,
57
+ ): number => {
58
+ if (startIdx >= pathToCheck.length - 3) return -1
59
+
60
+ let endIdx = startIdx
61
+ let isStairStep = true
62
+
63
+ for (
64
+ let i = startIdx;
65
+ i < pathToCheck.length - 2 && i < startIdx + 10;
66
+ i++
67
+ ) {
68
+ if (i + 2 >= pathToCheck.length) break
69
+
70
+ const p1 = pathToCheck[i]
71
+ const p2 = pathToCheck[i + 1]
72
+ const p3 = pathToCheck[i + 2]
73
+
74
+ const seg1Vertical = p1.x === p2.x
75
+ const seg2Vertical = p2.x === p3.x
76
+
77
+ if (seg1Vertical === seg2Vertical) {
78
+ break
79
+ }
80
+
81
+ const seg1Direction = seg1Vertical
82
+ ? Math.sign(p2.y - p1.y)
83
+ : Math.sign(p2.x - p1.x)
84
+
85
+ if (i > startIdx) {
86
+ const prevP = pathToCheck[i - 1]
87
+ const prevSegVertical = prevP.x === p1.x
88
+ const prevDirection = prevSegVertical
89
+ ? Math.sign(p1.y - prevP.y)
90
+ : Math.sign(p1.x - prevP.x)
91
+
92
+ if (
93
+ (seg1Vertical &&
94
+ prevSegVertical &&
95
+ seg1Direction !== prevDirection) ||
96
+ (!seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection)
97
+ ) {
98
+ isStairStep = false
99
+ break
100
+ }
101
+ }
102
+
103
+ endIdx = i + 2
104
+ }
105
+
106
+ return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1
107
+ }
108
+
109
+ let optimizedPath = [...path]
110
+ let currentTurns = countTurns(optimizedPath)
111
+ let improved = true
112
+
113
+ while (improved) {
114
+ improved = false
115
+
116
+ // First try to identify and replace stair-step patterns
117
+ for (let startIdx = 0; startIdx < optimizedPath.length - 3; startIdx++) {
118
+ const stairEndIdx = recognizeStairStepPattern(optimizedPath, startIdx)
119
+
120
+ if (stairEndIdx > 0) {
121
+ const startPoint = optimizedPath[startIdx]
122
+ const endPoint = optimizedPath[stairEndIdx]
123
+
124
+ const connectionOptions = tryConnectPoints(startPoint, endPoint)
125
+
126
+ for (const connection of connectionOptions) {
127
+ const testPath = [
128
+ ...optimizedPath.slice(0, startIdx + 1),
129
+ ...connection.slice(1, -1),
130
+ ...optimizedPath.slice(stairEndIdx),
131
+ ]
132
+
133
+ const collidesWithObstacles = hasCollisions(connection, obstacles)
134
+ const collidesWithLabels = hasCollisionsWithLabels(
135
+ connection,
136
+ labelBounds,
137
+ )
138
+
139
+ if (!collidesWithObstacles && !collidesWithLabels) {
140
+ const newTurns = countTurns(testPath)
141
+ const turnsRemoved = stairEndIdx - startIdx - 1
142
+
143
+ optimizedPath = testPath
144
+ currentTurns = newTurns
145
+ improved = true
146
+ break
147
+ }
148
+ }
149
+
150
+ if (improved) break
151
+ }
152
+ }
153
+
154
+ // If no stair-step optimization worked, try regular point removal
155
+ if (!improved) {
156
+ for (let startIdx = 0; startIdx < optimizedPath.length - 2; startIdx++) {
157
+ const maxRemove = Math.min(
158
+ optimizedPath.length - startIdx - 2,
159
+ optimizedPath.length - 2,
160
+ )
161
+
162
+ for (let removeCount = 1; removeCount <= maxRemove; removeCount++) {
163
+ const endIdx = startIdx + removeCount + 1
164
+
165
+ if (endIdx >= optimizedPath.length) continue
166
+
167
+ const startPoint = optimizedPath[startIdx]
168
+ const endPoint = optimizedPath[endIdx]
169
+
170
+ const connectionOptions = tryConnectPoints(startPoint, endPoint)
171
+
172
+ for (const connection of connectionOptions) {
173
+ const testPath = [
174
+ ...optimizedPath.slice(0, startIdx + 1),
175
+ ...connection.slice(1, -1),
176
+ ...optimizedPath.slice(endIdx),
177
+ ]
178
+
179
+ const connectionSegments = connection
180
+ const collidesWithObstacles = hasCollisions(
181
+ connectionSegments,
182
+ obstacles,
183
+ )
184
+ const collidesWithLabels = hasCollisionsWithLabels(
185
+ connectionSegments,
186
+ labelBounds,
187
+ )
188
+
189
+ if (!collidesWithObstacles && !collidesWithLabels) {
190
+ const newTurns = countTurns(testPath)
191
+
192
+ if (
193
+ newTurns < currentTurns ||
194
+ (newTurns === currentTurns &&
195
+ testPath.length < optimizedPath.length)
196
+ ) {
197
+ optimizedPath = testPath
198
+ currentTurns = newTurns
199
+ improved = true
200
+ break
201
+ }
202
+ }
203
+ }
204
+
205
+ if (improved) break
206
+ }
207
+ if (improved) break
208
+ }
209
+ }
210
+
211
+ if (!improved) {
212
+ for (let i = 0; i < optimizedPath.length - 2; i++) {
213
+ const p1 = optimizedPath[i]
214
+ const p2 = optimizedPath[i + 1]
215
+ const p3 = optimizedPath[i + 2]
216
+
217
+ const allVertical = p1.x === p2.x && p2.x === p3.x
218
+ const allHorizontal = p1.y === p2.y && p2.y === p3.y
219
+
220
+ if (allVertical || allHorizontal) {
221
+ const testPath = [
222
+ ...optimizedPath.slice(0, i + 1),
223
+ ...optimizedPath.slice(i + 2),
224
+ ]
225
+
226
+ const collidesWithObstacles = hasCollisions([p1, p3], obstacles)
227
+ const collidesWithLabels = hasCollisionsWithLabels(
228
+ [p1, p3],
229
+ labelBounds,
230
+ )
231
+
232
+ if (!collidesWithObstacles && !collidesWithLabels) {
233
+ optimizedPath = testPath
234
+ improved = true
235
+ break
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
241
+
242
+ const finalSimplifiedPath = simplifyPath(optimizedPath)
243
+ return finalSimplifiedPath
244
+ }
245
+
246
+ export const minimizeTurnsWithFilteredLabels = ({
247
+ traces,
248
+ problem,
249
+ allLabelPlacements,
250
+ mergedLabelNetIdMap,
251
+ paddingBuffer,
252
+ }: {
253
+ traces: SolvedTracePath[]
254
+ problem: InputProblem
255
+ allLabelPlacements: NetLabelPlacement[]
256
+ mergedLabelNetIdMap: Map<string, Set<string>>
257
+ paddingBuffer: number
258
+ }): SolvedTracePath[] | null => {
259
+ let changesMade = false
260
+ const obstacles = getObstacleRects(problem)
261
+
262
+ const newTraces = traces.map((trace) => {
263
+ const originalPath = trace.tracePath
264
+ const filteredLabels = allLabelPlacements.filter((label) => {
265
+ const originalNetIds = mergedLabelNetIdMap.get(label.globalConnNetId)
266
+ if (originalNetIds) {
267
+ return !originalNetIds.has(trace.globalConnNetId)
268
+ }
269
+ return label.globalConnNetId !== trace.globalConnNetId
270
+ })
271
+
272
+ const labelBounds = filteredLabels.map((nl) => ({
273
+ minX: nl.center.x - nl.width / 2 - paddingBuffer,
274
+ maxX: nl.center.x + nl.width / 2 + paddingBuffer,
275
+ minY: nl.center.y - nl.height / 2 - paddingBuffer,
276
+ maxY: nl.center.y + nl.height / 2 + paddingBuffer,
277
+ }))
278
+
279
+ const newPath = minimizeTurns({
280
+ path: originalPath,
281
+ obstacles,
282
+ labelBounds,
283
+ })
284
+
285
+ if (
286
+ newPath.length !== originalPath.length ||
287
+ newPath.some(
288
+ (p, i) => p.x !== originalPath[i].x || p.y !== originalPath[i].y,
289
+ )
290
+ ) {
291
+ changesMade = true
292
+ }
293
+
294
+ return {
295
+ ...trace,
296
+ tracePath: newPath,
297
+ }
298
+ })
299
+
300
+ if (changesMade) {
301
+ return newTraces
302
+ } else {
303
+ return null
304
+ }
305
+ }
@@ -0,0 +1,25 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+
3
+ export const findTraceViolationZone = (
4
+ path: Point[],
5
+ labelBounds: { minX: number; maxX: number; minY: number; maxY: number },
6
+ ) => {
7
+ const isPointInside = (p: Point) =>
8
+ p.x > labelBounds.minX &&
9
+ p.x < labelBounds.maxX &&
10
+ p.y > labelBounds.minY &&
11
+ p.y < labelBounds.maxY
12
+
13
+ let firstInsideIndex = -1
14
+ let lastInsideIndex = -1
15
+
16
+ for (let i = 0; i < path.length; i++) {
17
+ if (isPointInside(path[i])) {
18
+ if (firstInsideIndex === -1) {
19
+ firstInsideIndex = i
20
+ }
21
+ lastInsideIndex = i
22
+ }
23
+ }
24
+ return { firstInsideIndex, lastInsideIndex }
25
+ }
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.36",
4
+ "version": "0.0.38",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,103 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+
4
+ const inputProblem: InputProblem = {
5
+ chips: [
6
+ {
7
+ chipId: "schematic_component_0",
8
+ center: {
9
+ x: 0,
10
+ y: 0,
11
+ },
12
+ width: 2.4000000000000004,
13
+ height: 1,
14
+ pins: [
15
+ {
16
+ pinId: "U1.1",
17
+ x: 1.2000000000000002,
18
+ y: -0.30000000000000004,
19
+ },
20
+ {
21
+ pinId: "U1.2",
22
+ x: -1.2000000000000002,
23
+ y: -0.30000000000000004,
24
+ },
25
+ {
26
+ pinId: "U1.3",
27
+ x: 1.2000000000000002,
28
+ y: 0.09999999999999998,
29
+ },
30
+ {
31
+ pinId: "U1.4",
32
+ x: -1.2000000000000002,
33
+ y: 0.30000000000000004,
34
+ },
35
+ {
36
+ pinId: "U1.5",
37
+ x: -1.2000000000000002,
38
+ y: 0.10000000000000003,
39
+ },
40
+ {
41
+ pinId: "U1.6",
42
+ x: -1.2000000000000002,
43
+ y: -0.09999999999999998,
44
+ },
45
+ {
46
+ pinId: "U1.7",
47
+ x: 1.2000000000000002,
48
+ y: -0.10000000000000003,
49
+ },
50
+ {
51
+ pinId: "U1.8",
52
+ x: 1.2000000000000002,
53
+ y: 0.30000000000000004,
54
+ },
55
+ ],
56
+ },
57
+ {
58
+ chipId: "schematic_component_1",
59
+ center: {
60
+ x: 2.7,
61
+ y: 1.9049999999999998,
62
+ },
63
+ width: 2.2,
64
+ height: 0.8,
65
+ pins: [
66
+ {
67
+ pinId: "J1.1",
68
+ x: 1.6,
69
+ y: 2.105,
70
+ },
71
+ {
72
+ pinId: "J1.2",
73
+ x: 1.6,
74
+ y: 1.9049999999999998,
75
+ },
76
+ {
77
+ pinId: "J1.3",
78
+ x: 1.6,
79
+ y: 1.7049999999999998,
80
+ },
81
+ ],
82
+ },
83
+ ],
84
+ directConnections: [],
85
+ netConnections: [
86
+ {
87
+ netId: "GND",
88
+ pinIds: ["U1.1", "J1.3"],
89
+ },
90
+ {
91
+ netId: "VCC",
92
+ pinIds: ["U1.8", "J1.1"],
93
+ },
94
+ ],
95
+ availableNetLabelOrientations: {
96
+ VCC: ["y+"],
97
+ OUT: ["x-", "x+"],
98
+ GND: ["y-"],
99
+ },
100
+ maxMspPairDistance: 2.4,
101
+ }
102
+
103
+ export default () => <PipelineDebugger inputProblem={inputProblem} />
@@ -0,0 +1,177 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+
4
+ const inputProblem: InputProblem = {
5
+ chips: [
6
+ {
7
+ chipId: "schematic_component_0",
8
+ center: {
9
+ x: 0,
10
+ y: 0,
11
+ },
12
+ width: 2,
13
+ height: 2,
14
+ pins: [
15
+ {
16
+ pinId: "CORNERS.1",
17
+ x: -1.4,
18
+ y: -0.6,
19
+ },
20
+ {
21
+ pinId: "CORNERS.2",
22
+ x: -1.4,
23
+ y: 0.6,
24
+ },
25
+ {
26
+ pinId: "CORNERS.3",
27
+ x: 1.4,
28
+ y: 0.6,
29
+ },
30
+ {
31
+ pinId: "CORNERS.4",
32
+ x: 1.4,
33
+ y: -0.6,
34
+ },
35
+ ],
36
+ },
37
+ {
38
+ chipId: "schematic_component_1",
39
+ center: {
40
+ x: 4.785,
41
+ y: -0.3999999999999999,
42
+ },
43
+ width: 1.2000000000000002,
44
+ height: 1,
45
+ pins: [
46
+ {
47
+ pinId: "U100.1",
48
+ x: 3.785,
49
+ y: -0.29999999999999993,
50
+ },
51
+ {
52
+ pinId: "U100.2",
53
+ x: 4.785,
54
+ y: -1.2999999999999998,
55
+ },
56
+ {
57
+ pinId: "U100.3",
58
+ x: 3.785,
59
+ y: -0.4999999999999999,
60
+ },
61
+ {
62
+ pinId: "U100.5",
63
+ x: 5.785,
64
+ y: -0.3999999999999999,
65
+ },
66
+ ],
67
+ },
68
+ {
69
+ chipId: "schematic_component_2",
70
+ center: {
71
+ x: 6.7,
72
+ y: -0.9499999999999993,
73
+ },
74
+ width: 0.53,
75
+ height: 1.06,
76
+ pins: [
77
+ {
78
+ pinId: "C101.1",
79
+ x: 6.7,
80
+ y: -0.39999999999999925,
81
+ },
82
+ {
83
+ pinId: "C101.2",
84
+ x: 6.7,
85
+ y: -1.4999999999999993,
86
+ },
87
+ ],
88
+ },
89
+ {
90
+ chipId: "schematic_component_3",
91
+ center: {
92
+ x: 2.8699999999999997,
93
+ y: -1.75,
94
+ },
95
+ width: 0.53,
96
+ height: 1.06,
97
+ pins: [
98
+ {
99
+ pinId: "C100.1",
100
+ x: 2.8699999999999997,
101
+ y: -1.2,
102
+ },
103
+ {
104
+ pinId: "C100.2",
105
+ x: 2.8699999999999997,
106
+ y: -2.3,
107
+ },
108
+ ],
109
+ },
110
+ {
111
+ chipId: "schematic_component_4",
112
+ center: {
113
+ x: 2.9752723250000006,
114
+ y: 0.050000000000000266,
115
+ },
116
+ width: 0.3194553499999995,
117
+ height: 1.06,
118
+ pins: [
119
+ {
120
+ pinId: "R100.1",
121
+ x: 2.9752723250000006,
122
+ y: 0.6000000000000001,
123
+ },
124
+ {
125
+ pinId: "R100.2",
126
+ x: 2.9752723250000006,
127
+ y: -0.4999999999999998,
128
+ },
129
+ ],
130
+ },
131
+ ],
132
+ directConnections: [
133
+ {
134
+ pinIds: ["C101.1", "U100.5"],
135
+ netId: "group > capacitor.C101 > port.pin1 to U100.VOUT",
136
+ },
137
+ {
138
+ pinIds: ["C100.1", "U100.1"],
139
+ netId: "group > capacitor.C100 > port.pin1 to U100.VIN",
140
+ },
141
+ {
142
+ pinIds: ["R100.2", "U100.3"],
143
+ netId: "group > resistor.R100 > port.pin2 to U100.EN",
144
+ },
145
+ ],
146
+ netConnections: [
147
+ {
148
+ netId: "GND",
149
+ pinIds: ["CORNERS.1", "CORNERS.4", "U100.2", "C101.2", "C100.2"],
150
+ netLabelWidth: 0.3,
151
+ },
152
+ {
153
+ netId: "VIN",
154
+ pinIds: ["CORNERS.2", "U100.1", "C100.1", "R100.1"],
155
+ netLabelWidth: 0.3,
156
+ },
157
+ {
158
+ netId: "VOUT",
159
+ pinIds: ["CORNERS.3", "U100.5", "C101.1"],
160
+ netLabelWidth: 0.4,
161
+ },
162
+ {
163
+ netId: "LDO_EN",
164
+ pinIds: ["U100.3", "R100.2"],
165
+ netLabelWidth: 0.6,
166
+ },
167
+ ],
168
+ availableNetLabelOrientations: {
169
+ GND: ["y-"],
170
+ VIN: ["y+"],
171
+ VOUT: ["y+"],
172
+ LDO_EN: ["x-", "x+"],
173
+ },
174
+ maxMspPairDistance: 2.4,
175
+ } as InputProblem
176
+
177
+ export default () => <PipelineDebugger inputProblem={inputProblem} />