@tscircuit/schematic-trace-solver 0.0.46 → 0.0.47
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 +18 -16
- package/dist/index.js +208 -116
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +89 -61
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +16 -5
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +27 -16
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/doesTraceStartOrEndInLabel.ts +58 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +13 -1
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +110 -60
- package/package.json +1 -1
- package/site/examples/example27.page.tsx +4 -0
- package/tests/assets/example27.json +168 -0
- package/tests/examples/__snapshots__/example03.snap.svg +67 -67
- package/tests/examples/__snapshots__/example16.snap.svg +3 -3
- package/tests/examples/__snapshots__/example30.snap.svg +258 -0
- package/tests/examples/example30.test.ts +12 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +9 -6
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { Point } from "graphics-debug"
|
|
2
2
|
import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
|
-
import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
|
|
4
3
|
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
-
import {
|
|
6
|
-
segmentIntersectsRect,
|
|
7
|
-
isVertical,
|
|
8
|
-
} from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
4
|
+
import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
9
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Generates candidate four-point detour paths for a trace colliding with a label obstacle.
|
|
8
|
+
* It uses a two-strategy approach: a simpler one for individual labels and a more robust one
|
|
9
|
+
* for merged labels, finding precise entry and exit points. Detours are U-shaped, respecting
|
|
10
|
+
* the trace's direction to prevent loops.
|
|
11
|
+
*/
|
|
10
12
|
export const generateFourPointDetourCandidates = ({
|
|
11
13
|
initialTrace,
|
|
12
14
|
label,
|
|
@@ -16,89 +18,137 @@ export const generateFourPointDetourCandidates = ({
|
|
|
16
18
|
}: {
|
|
17
19
|
initialTrace: SolvedTracePath
|
|
18
20
|
label: NetLabelPlacement
|
|
19
|
-
labelBounds:
|
|
21
|
+
labelBounds: { minX: number; maxX: number; minY: number; maxY: number }
|
|
20
22
|
paddingBuffer: number
|
|
21
23
|
detourCount: number
|
|
22
24
|
}): Point[][] => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
)
|
|
31
|
-
) {
|
|
32
|
-
collidingSegIndex = i
|
|
33
|
-
break
|
|
34
|
-
}
|
|
25
|
+
const isMergedLabel = label.globalConnNetId.startsWith("merged-group-")
|
|
26
|
+
const effectivePadding = paddingBuffer + detourCount * paddingBuffer
|
|
27
|
+
const paddedLabelBounds = {
|
|
28
|
+
minX: labelBounds.minX - effectivePadding,
|
|
29
|
+
maxX: labelBounds.maxX + effectivePadding,
|
|
30
|
+
minY: labelBounds.minY - effectivePadding,
|
|
31
|
+
maxY: labelBounds.maxY + effectivePadding,
|
|
35
32
|
}
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
let entryPoint: Point, exitPoint: Point
|
|
35
|
+
let entryIndex: number, exitIndex: number
|
|
38
36
|
|
|
39
|
-
const
|
|
40
|
-
|
|
37
|
+
const isPointInRect = (p: Point) =>
|
|
38
|
+
p.x >= paddedLabelBounds.minX &&
|
|
39
|
+
p.x <= paddedLabelBounds.maxX &&
|
|
40
|
+
p.y >= paddedLabelBounds.minY &&
|
|
41
|
+
p.y <= paddedLabelBounds.maxY
|
|
41
42
|
|
|
42
|
-
if (
|
|
43
|
+
if (isMergedLabel) {
|
|
44
|
+
// STRATEGY 2: Merged Label - find first point before and after the entire crossing
|
|
45
|
+
let firstInsideIndex = -1
|
|
46
|
+
for (let i = 0; i < initialTrace.tracePath.length; i++) {
|
|
47
|
+
if (isPointInRect(initialTrace.tracePath[i])) {
|
|
48
|
+
firstInsideIndex = i
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
}
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
const paddedLabelBounds = getRectBounds(
|
|
46
|
-
label.center,
|
|
47
|
-
label.width,
|
|
48
|
-
label.height,
|
|
49
|
-
)
|
|
53
|
+
if (firstInsideIndex === -1) return [] // No points inside, shouldn't be called
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
entryIndex = Math.max(0, firstInsideIndex - 1)
|
|
56
|
+
entryPoint = initialTrace.tracePath[entryIndex]
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
let firstOutsideIndex = -1
|
|
59
|
+
for (let i = firstInsideIndex; i < initialTrace.tracePath.length; i++) {
|
|
60
|
+
if (!isPointInRect(initialTrace.tracePath[i])) {
|
|
61
|
+
firstOutsideIndex = i
|
|
62
|
+
break
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (firstOutsideIndex === -1) {
|
|
67
|
+
// Trace ends inside the box, use the last point as exit
|
|
68
|
+
exitIndex = initialTrace.tracePath.length - 1
|
|
69
|
+
} else {
|
|
70
|
+
exitIndex = firstOutsideIndex
|
|
71
|
+
}
|
|
72
|
+
exitPoint = initialTrace.tracePath[exitIndex]
|
|
73
|
+
} else {
|
|
74
|
+
// STRATEGY 1: Single Label - find the first intersecting segment
|
|
75
|
+
let collidingSegIndex = -1
|
|
76
|
+
for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
|
|
77
|
+
if (
|
|
78
|
+
segmentIntersectsRect(
|
|
79
|
+
initialTrace.tracePath[i],
|
|
80
|
+
initialTrace.tracePath[i + 1],
|
|
81
|
+
{ ...paddedLabelBounds, chipId: "temp-obstacle" },
|
|
82
|
+
)
|
|
83
|
+
) {
|
|
84
|
+
collidingSegIndex = i
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (collidingSegIndex === -1) return []
|
|
90
|
+
|
|
91
|
+
entryIndex = collidingSegIndex
|
|
92
|
+
exitIndex = collidingSegIndex + 1
|
|
93
|
+
entryPoint = initialTrace.tracePath[entryIndex]
|
|
94
|
+
exitPoint = initialTrace.tracePath[exitIndex]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!entryPoint || !exitPoint || entryIndex >= exitIndex) return []
|
|
98
|
+
|
|
99
|
+
const candidateDetours: Point[][] = []
|
|
100
|
+
const dx = exitPoint.x - entryPoint.x
|
|
101
|
+
const dy = exitPoint.y - entryPoint.y
|
|
102
|
+
|
|
103
|
+
if (Math.abs(dx) > Math.abs(dy)) {
|
|
104
|
+
// More horizontal
|
|
105
|
+
const yCandidates = [paddedLabelBounds.maxY, paddedLabelBounds.minY]
|
|
106
|
+
for (const newY of yCandidates) {
|
|
59
107
|
candidateDetours.push(
|
|
60
|
-
|
|
108
|
+
dx > 0 // Left-to-right
|
|
61
109
|
? [
|
|
62
|
-
{ x:
|
|
63
|
-
{ x:
|
|
64
|
-
{ x:
|
|
65
|
-
{ x:
|
|
110
|
+
{ x: paddedLabelBounds.minX, y: entryPoint.y },
|
|
111
|
+
{ x: paddedLabelBounds.minX, y: newY },
|
|
112
|
+
{ x: paddedLabelBounds.maxX, y: newY },
|
|
113
|
+
{ x: paddedLabelBounds.maxX, y: exitPoint.y },
|
|
66
114
|
]
|
|
67
115
|
: [
|
|
68
|
-
|
|
69
|
-
{ x:
|
|
70
|
-
{ x:
|
|
71
|
-
{ x:
|
|
116
|
+
// Right-to-left
|
|
117
|
+
{ x: paddedLabelBounds.maxX, y: entryPoint.y },
|
|
118
|
+
{ x: paddedLabelBounds.maxX, y: newY },
|
|
119
|
+
{ x: paddedLabelBounds.minX, y: newY },
|
|
120
|
+
{ x: paddedLabelBounds.minX, y: exitPoint.y },
|
|
72
121
|
],
|
|
73
122
|
)
|
|
74
123
|
}
|
|
75
124
|
} else {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
]
|
|
80
|
-
for (const newY of yCandidates) {
|
|
125
|
+
// More vertical
|
|
126
|
+
const xCandidates = [paddedLabelBounds.maxX, paddedLabelBounds.minX]
|
|
127
|
+
for (const newX of xCandidates) {
|
|
81
128
|
candidateDetours.push(
|
|
82
|
-
|
|
129
|
+
dy > 0 // Top-to-bottom
|
|
83
130
|
? [
|
|
84
|
-
{ x:
|
|
85
|
-
{ x:
|
|
86
|
-
{ x:
|
|
87
|
-
{ x:
|
|
131
|
+
{ x: entryPoint.x, y: paddedLabelBounds.minY },
|
|
132
|
+
{ x: newX, y: paddedLabelBounds.minY },
|
|
133
|
+
{ x: newX, y: paddedLabelBounds.maxY },
|
|
134
|
+
{ x: exitPoint.x, y: paddedLabelBounds.maxY },
|
|
88
135
|
]
|
|
89
136
|
: [
|
|
90
|
-
|
|
91
|
-
{ x:
|
|
92
|
-
{ x:
|
|
93
|
-
{ x:
|
|
137
|
+
// Bottom-to-top
|
|
138
|
+
{ x: entryPoint.x, y: paddedLabelBounds.maxY },
|
|
139
|
+
{ x: newX, y: paddedLabelBounds.maxY },
|
|
140
|
+
{ x: newX, y: paddedLabelBounds.minY },
|
|
141
|
+
{ x: exitPoint.x, y: paddedLabelBounds.minY },
|
|
94
142
|
],
|
|
95
143
|
)
|
|
96
144
|
}
|
|
97
145
|
}
|
|
98
146
|
|
|
99
147
|
return candidateDetours.map((detourPoints) => [
|
|
100
|
-
...initialTrace.tracePath.slice(0,
|
|
148
|
+
...initialTrace.tracePath.slice(0, entryIndex),
|
|
149
|
+
entryPoint,
|
|
101
150
|
...detourPoints,
|
|
102
|
-
|
|
151
|
+
exitPoint,
|
|
152
|
+
...initialTrace.tracePath.slice(exitIndex + 1),
|
|
103
153
|
])
|
|
104
154
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
{
|
|
2
|
+
"chips": [
|
|
3
|
+
{
|
|
4
|
+
"chipId": "schematic_component_0",
|
|
5
|
+
"center": {
|
|
6
|
+
"x": 0,
|
|
7
|
+
"y": 0
|
|
8
|
+
},
|
|
9
|
+
"width": 0.4,
|
|
10
|
+
"height": 2.5999999999999996,
|
|
11
|
+
"pins": [
|
|
12
|
+
{
|
|
13
|
+
"pinId": "P.1",
|
|
14
|
+
"x": -0.6000000000000001,
|
|
15
|
+
"y": 1.0999999999999999
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"pinId": "P.2",
|
|
19
|
+
"x": -0.6000000000000001,
|
|
20
|
+
"y": 0.8999999999999999
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"pinId": "P.3",
|
|
24
|
+
"x": -0.6000000000000001,
|
|
25
|
+
"y": 0.6999999999999998
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"pinId": "P.4",
|
|
29
|
+
"x": -0.6000000000000001,
|
|
30
|
+
"y": 0.4999999999999998
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"pinId": "P.5",
|
|
34
|
+
"x": -0.6000000000000001,
|
|
35
|
+
"y": 0.2999999999999998
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"pinId": "P.6",
|
|
39
|
+
"x": -0.6000000000000001,
|
|
40
|
+
"y": 0.09999999999999987
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"pinId": "P.7",
|
|
44
|
+
"x": -0.6000000000000001,
|
|
45
|
+
"y": -0.10000000000000009
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"pinId": "P.8",
|
|
49
|
+
"x": -0.6000000000000001,
|
|
50
|
+
"y": -0.30000000000000004
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"pinId": "P.9",
|
|
54
|
+
"x": -0.6000000000000001,
|
|
55
|
+
"y": -0.5
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"pinId": "P.10",
|
|
59
|
+
"x": -0.6000000000000001,
|
|
60
|
+
"y": -0.7
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"pinId": "P.11",
|
|
64
|
+
"x": -0.6000000000000001,
|
|
65
|
+
"y": -0.8999999999999999
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"pinId": "P.12",
|
|
69
|
+
"x": -0.6000000000000001,
|
|
70
|
+
"y": -1.0999999999999999
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"pinId": "P.13",
|
|
74
|
+
"x": 0.6000000000000001,
|
|
75
|
+
"y": -1.0999999999999999
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"pinId": "P.14",
|
|
79
|
+
"x": 0.6000000000000001,
|
|
80
|
+
"y": -0.8999999999999999
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"pinId": "P.15",
|
|
84
|
+
"x": 0.6000000000000001,
|
|
85
|
+
"y": -0.6999999999999998
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"pinId": "P.16",
|
|
89
|
+
"x": 0.6000000000000001,
|
|
90
|
+
"y": -0.4999999999999998
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"pinId": "P.17",
|
|
94
|
+
"x": 0.6000000000000001,
|
|
95
|
+
"y": -0.2999999999999998
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"pinId": "P.18",
|
|
99
|
+
"x": 0.6000000000000001,
|
|
100
|
+
"y": -0.09999999999999987
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"pinId": "P.19",
|
|
104
|
+
"x": 0.6000000000000001,
|
|
105
|
+
"y": 0.10000000000000009
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"pinId": "P.20",
|
|
109
|
+
"x": 0.6000000000000001,
|
|
110
|
+
"y": 0.30000000000000004
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"pinId": "P.21",
|
|
114
|
+
"x": 0.6000000000000001,
|
|
115
|
+
"y": 0.5
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"pinId": "P.22",
|
|
119
|
+
"x": 0.6000000000000001,
|
|
120
|
+
"y": 0.7
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"pinId": "P.23",
|
|
124
|
+
"x": 0.6000000000000001,
|
|
125
|
+
"y": 0.8999999999999999
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"pinId": "P.24",
|
|
129
|
+
"x": 0.6000000000000001,
|
|
130
|
+
"y": 1.0999999999999999
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"directConnections": [
|
|
136
|
+
{
|
|
137
|
+
"pinIds": ["P.1", "P.18"],
|
|
138
|
+
"netId": "P.pin1 to P.pin18"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"pinIds": ["P.2", "P.17"],
|
|
142
|
+
"netId": "P.pin2 to P.pin17"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"pinIds": ["P.3", "P.16"],
|
|
146
|
+
"netId": "P.pin3 to P.pin16"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"pinIds": ["P.4", "P.15"],
|
|
150
|
+
"netId": "P.pin4 to P.pin15"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"pinIds": ["P.5", "P.14"],
|
|
154
|
+
"netId": "P.pin5 to P.pin14"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"pinIds": ["P.6", "P.13"],
|
|
158
|
+
"netId": "P.pin6 to P.pin13"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"pinIds": ["P.7", "P.19"],
|
|
162
|
+
"netId": "P.pin7 to P.pin19"
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
"netConnections": [],
|
|
166
|
+
"availableNetLabelOrientations": {},
|
|
167
|
+
"maxMspPairDistance": 2.4
|
|
168
|
+
}
|