@tscircuit/schematic-trace-solver 0.0.91 → 0.0.93
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.
- package/dist/index.d.ts +7 -1
- package/dist/index.js +150 -21
- package/lib/solvers/Example28Solver/doesPathRunAlongChipBoundary.ts +18 -0
- package/lib/solvers/Example28Solver/reroute.ts +29 -10
- package/lib/solvers/Example28Solver/types.ts +7 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +35 -23
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/calculateDirectShortPath.ts +161 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +4 -6
- package/tests/bug-reports/bug-report-20260707T230831Z/__snapshots__/bug-report-20260707T230831Z.snap.svg +46 -52
- package/tests/examples/__snapshots__/example03.snap.svg +44 -44
- package/tests/examples/__snapshots__/example32.snap.svg +3 -3
- package/tests/examples/__snapshots__/example42.snap.svg +1 -1
- package/tests/repros/__snapshots__/repro-missing-trace-netlabel.snap.svg +60 -0
- package/tests/repros/__snapshots__/repro-netlabel-overlap-trace.snap.svg +60 -0
- package/tests/repros/__snapshots__/repro-rectifier-trace-overlap.snap.svg +58 -0
- package/tests/repros/assets/repro-missing-trace-netlabel.input.json +155 -0
- package/tests/repros/assets/repro-netlabel-overlap-trace.input.json +115 -0
- package/tests/repros/assets/repro-rectifier-trace-overlap.input.json +137 -0
- package/tests/repros/repro-missing-trace-netlabel.test.ts +14 -0
- package/tests/repros/repro-netlabel-overlap-trace.test.ts +14 -0
- package/tests/repros/repro-rectifier-trace-overlap.test.ts +14 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
3
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
4
|
+
import { calculateElbow } from "calculate-elbow"
|
|
5
|
+
|
|
6
|
+
const MAX_SHORT_TRACE_DISTANCE = 0.15
|
|
7
|
+
const SHORT_TRACE_OVERSHOOT = MAX_SHORT_TRACE_DISTANCE / 7.5
|
|
8
|
+
const FALLBACK_ELBOW_MAX_OVERSHOOT = 0.2
|
|
9
|
+
|
|
10
|
+
export function segmentDirection(
|
|
11
|
+
from: Point,
|
|
12
|
+
to: Point,
|
|
13
|
+
): FacingDirection | null {
|
|
14
|
+
if (to.x > from.x) return "x+"
|
|
15
|
+
if (to.x < from.x) return "x-"
|
|
16
|
+
if (to.y > from.y) return "y+"
|
|
17
|
+
if (to.y < from.y) return "y-"
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function pathMatchesPinDirections({
|
|
22
|
+
path,
|
|
23
|
+
pin1,
|
|
24
|
+
pin2,
|
|
25
|
+
}: {
|
|
26
|
+
path: Point[]
|
|
27
|
+
pin1: MspConnectionPair["pins"][number]
|
|
28
|
+
pin2: MspConnectionPair["pins"][number]
|
|
29
|
+
}): boolean {
|
|
30
|
+
const firstDirection = segmentDirection(path[0]!, path[1]!)
|
|
31
|
+
const lastDirection = segmentDirection(
|
|
32
|
+
path[path.length - 1]!,
|
|
33
|
+
path[path.length - 2]!,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
firstDirection === pin1._facingDirection &&
|
|
38
|
+
lastDirection === pin2._facingDirection
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Calculates a short orthogonal (perpendicular) route when the source and destination pins
|
|
44
|
+
* are oriented at a 90-degree angle to one another (e.g., one faces X and the other faces Y).
|
|
45
|
+
* It creates a minimal step-out to avoid overlapping pin bodies.
|
|
46
|
+
*/
|
|
47
|
+
export function calculateShortOrthogonalRoute(
|
|
48
|
+
pin1: MspConnectionPair["pins"][number],
|
|
49
|
+
pin2: MspConnectionPair["pins"][number],
|
|
50
|
+
): Point[] | null {
|
|
51
|
+
// If pins share an axis, an orthogonal 3-segment route is unnecessary
|
|
52
|
+
if (pin1.x === pin2.x || pin1.y === pin2.y) return null
|
|
53
|
+
|
|
54
|
+
const firstDir = pin1._facingDirection
|
|
55
|
+
const lastDir = pin2._facingDirection
|
|
56
|
+
|
|
57
|
+
const start = { x: pin1.x, y: pin1.y }
|
|
58
|
+
const end = { x: pin2.x, y: pin2.y }
|
|
59
|
+
|
|
60
|
+
let path: Point[] | null = null
|
|
61
|
+
|
|
62
|
+
// Handle case where pin1 faces vertically and pin2 faces horizontally
|
|
63
|
+
if (firstDir?.startsWith("y") && lastDir?.startsWith("x")) {
|
|
64
|
+
let yOffset = -SHORT_TRACE_OVERSHOOT
|
|
65
|
+
if (firstDir === "y+") {
|
|
66
|
+
yOffset = SHORT_TRACE_OVERSHOOT
|
|
67
|
+
}
|
|
68
|
+
const routeY = start.y + yOffset
|
|
69
|
+
const routeX = (start.x + end.x) / 2
|
|
70
|
+
path = [
|
|
71
|
+
start,
|
|
72
|
+
{ x: start.x, y: routeY },
|
|
73
|
+
{ x: routeX, y: routeY },
|
|
74
|
+
{ x: routeX, y: end.y },
|
|
75
|
+
end,
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
// Handle case where pin1 faces horizontally and pin2 faces vertically
|
|
79
|
+
else if (firstDir?.startsWith("x") && lastDir?.startsWith("y")) {
|
|
80
|
+
let xOffset = -SHORT_TRACE_OVERSHOOT
|
|
81
|
+
if (firstDir === "x+") {
|
|
82
|
+
xOffset = SHORT_TRACE_OVERSHOOT
|
|
83
|
+
}
|
|
84
|
+
const routeX = start.x + xOffset
|
|
85
|
+
const routeY = (start.y + end.y) / 2
|
|
86
|
+
path = [
|
|
87
|
+
start,
|
|
88
|
+
{ x: routeX, y: start.y },
|
|
89
|
+
{ x: routeX, y: routeY },
|
|
90
|
+
{ x: end.x, y: routeY },
|
|
91
|
+
end,
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!path) return null
|
|
96
|
+
|
|
97
|
+
if (pathMatchesPinDirections({ path, pin1, pin2 })) {
|
|
98
|
+
return path
|
|
99
|
+
}
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Attempts to calculate a direct route between two pins if they are very close together.
|
|
105
|
+
* This prevents complex routing logic from taking over for trivial connections.
|
|
106
|
+
*/
|
|
107
|
+
export function calculateDirectShortPath(
|
|
108
|
+
pin1: MspConnectionPair["pins"][number],
|
|
109
|
+
pin2: MspConnectionPair["pins"][number],
|
|
110
|
+
): Point[] | null {
|
|
111
|
+
const routingDistance = Math.abs(pin1.x - pin2.x) + Math.abs(pin1.y - pin2.y)
|
|
112
|
+
|
|
113
|
+
// If the distance is too large, fallback to the standard complex routing solver
|
|
114
|
+
if (routingDistance > MAX_SHORT_TRACE_DISTANCE) return null
|
|
115
|
+
|
|
116
|
+
const start = { x: pin1.x, y: pin1.y }
|
|
117
|
+
const end = { x: pin2.x, y: pin2.y }
|
|
118
|
+
|
|
119
|
+
// First, try a simple orthogonal route if the pins are facing perpendicular directions
|
|
120
|
+
const orthogonalRoute = calculateShortOrthogonalRoute(pin1, pin2)
|
|
121
|
+
if (orthogonalRoute) return orthogonalRoute
|
|
122
|
+
|
|
123
|
+
let candidatePaths: Point[][] = []
|
|
124
|
+
|
|
125
|
+
// If the pins are completely offset (neither perfectly aligned horizontally nor vertically),
|
|
126
|
+
// we evaluate standard two-segment L-shaped routes.
|
|
127
|
+
if (pin1.x !== pin2.x && pin1.y !== pin2.y) {
|
|
128
|
+
candidatePaths = [
|
|
129
|
+
[start, { x: pin2.x, y: pin1.y }, end],
|
|
130
|
+
[start, { x: pin1.x, y: pin2.y }, end],
|
|
131
|
+
]
|
|
132
|
+
} else {
|
|
133
|
+
// If they are perfectly aligned, try a straight line
|
|
134
|
+
candidatePaths = [[start, end]]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Return the first candidate that matches the facing direction requirements of the pins
|
|
138
|
+
for (const path of candidatePaths) {
|
|
139
|
+
if (pathMatchesPinDirections({ path, pin1, pin2 })) return path
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// If simple paths fail, compute a slightly overshoot-based elbow routing as a final short-trace fallback
|
|
143
|
+
return calculateElbow(
|
|
144
|
+
{
|
|
145
|
+
x: pin1.x,
|
|
146
|
+
y: pin1.y,
|
|
147
|
+
facingDirection: pin1._facingDirection!,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
x: pin2.x,
|
|
151
|
+
y: pin2.y,
|
|
152
|
+
facingDirection: pin2._facingDirection!,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
overshoot: Math.min(
|
|
156
|
+
FALLBACK_ELBOW_MAX_OVERSHOOT,
|
|
157
|
+
Math.max(SHORT_TRACE_OVERSHOOT, routingDistance / 4),
|
|
158
|
+
),
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
}
|