@tscircuit/schematic-trace-solver 0.0.142 → 0.0.144
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 +21 -16
- package/dist/index.js +111 -62
- package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +92 -16
- package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +70 -57
- package/lib/types/InputProblem.ts +7 -4
- package/package.json +1 -1
- package/tests/assets/inline-net-label-two-pin-net.json +67 -0
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +3 -3
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +3 -3
- package/tests/repros/repro-tida010076-page02.test.ts +19 -0
- package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +23 -1
- package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label-two-pin-net.snap.svg +57 -0
- package/tests/solvers/InlineNetLabelSolver/inline-net-label-two-pin-net.test.ts +38 -0
- package/tests/solvers/InlineNetLabelSolver/two-pin-terminal-stubs-atomic.test.ts +78 -0
|
@@ -52,8 +52,9 @@ export interface InlineNetLabelPlacement {
|
|
|
52
52
|
pinIds: PinId[]
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* A generated single-ended trace stub. Present
|
|
56
|
-
*
|
|
55
|
+
* A generated single-ended trace stub. Present for a single-pin net or for
|
|
56
|
+
* one endpoint of an unrouted two-pin net; routed point-to-point traces omit
|
|
57
|
+
* it.
|
|
57
58
|
*/
|
|
58
59
|
stubTracePath?: [Point, Point]
|
|
59
60
|
|
|
@@ -85,12 +86,15 @@ interface InlineNetLabelSolverInput {
|
|
|
85
86
|
netLabelPlacements: NetLabelPlacement[]
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
type InlineEligibleConnection = InputDirectConnection | InputNetConnection
|
|
90
|
+
|
|
88
91
|
const getPinPairKey = (pinIds: readonly string[]) =>
|
|
89
92
|
[...pinIds].sort().join("::")
|
|
90
93
|
|
|
91
94
|
/**
|
|
92
95
|
* Places "inline net labels" - net names drawn alongside the trace they belong
|
|
93
|
-
* to - for
|
|
96
|
+
* to - for one- or two-pin connections that opted in via
|
|
97
|
+
* `allowInlineNetLabel`.
|
|
94
98
|
*
|
|
95
99
|
* Any regular (anchored) net label placement for the same net is dropped, so a
|
|
96
100
|
* net is never labeled twice.
|
|
@@ -102,11 +106,8 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
102
106
|
|
|
103
107
|
inlineNetLabelPlacements: InlineNetLabelPlacement[] = []
|
|
104
108
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
/** Single-pin net connections that opted in, still waiting to be processed */
|
|
109
|
-
queuedPortOnlyNetConnections: InputNetConnection[]
|
|
109
|
+
/** Eligible one- or two-pin connections still waiting to be processed. */
|
|
110
|
+
queuedConnections: InlineEligibleConnection[]
|
|
110
111
|
|
|
111
112
|
private tracesByPinPairKey: Map<string, SolvedTracePath[]>
|
|
112
113
|
private hasAlignedPortOnlyStubs = false
|
|
@@ -117,12 +118,18 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
117
118
|
this.traces = input.traces
|
|
118
119
|
this.inputNetLabelPlacements = input.netLabelPlacements
|
|
119
120
|
|
|
120
|
-
this.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
this.queuedConnections = [
|
|
122
|
+
...this.inputProblem.directConnections.filter(
|
|
123
|
+
(connection) => connection.allowInlineNetLabel && connection.netId,
|
|
124
|
+
),
|
|
125
|
+
...this.inputProblem.netConnections.filter(
|
|
126
|
+
(connection) =>
|
|
127
|
+
connection.allowInlineNetLabel &&
|
|
128
|
+
connection.pinIds.length >= 1 &&
|
|
129
|
+
connection.pinIds.length <= 2 &&
|
|
130
|
+
connection.netId,
|
|
131
|
+
),
|
|
132
|
+
]
|
|
126
133
|
|
|
127
134
|
this.tracesByPinPairKey = new Map()
|
|
128
135
|
for (const trace of this.traces) {
|
|
@@ -147,22 +154,28 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
override _step() {
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
const connection = this.queuedConnections.shift()
|
|
158
|
+
if (connection) {
|
|
159
|
+
const routedTraces =
|
|
160
|
+
connection.pinIds.length === 2
|
|
161
|
+
? (this.tracesByPinPairKey.get(getPinPairKey(connection.pinIds)) ??
|
|
162
|
+
[])
|
|
163
|
+
: []
|
|
164
|
+
|
|
165
|
+
if (routedTraces.length > 0) {
|
|
166
|
+
const placement = this.computeInlinePlacement(connection)
|
|
167
|
+
if (placement) this.inlineNetLabelPlacements.push(placement)
|
|
168
|
+
} else {
|
|
169
|
+
// A one-pin net has one conventional endpoint label, while a skipped
|
|
170
|
+
// two-pin route has one at each endpoint. Convert a two-pin pair
|
|
171
|
+
// atomically so a collision at one endpoint cannot leave mixed inline
|
|
172
|
+
// and anchored representations for the same net.
|
|
173
|
+
const terminalPlacements = connection.pinIds.map((pinId) =>
|
|
174
|
+
this.computeTerminalInlinePlacement(connection, pinId),
|
|
175
|
+
)
|
|
176
|
+
if (terminalPlacements.every((placement) => placement !== null)) {
|
|
177
|
+
this.inlineNetLabelPlacements.push(...terminalPlacements)
|
|
178
|
+
}
|
|
166
179
|
}
|
|
167
180
|
return
|
|
168
181
|
}
|
|
@@ -183,20 +196,20 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
/**
|
|
186
|
-
* Converts
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
199
|
+
* Converts one conventional endpoint placement into an inline label on a
|
|
200
|
+
* generated outward stub. The stub follows the pin's true facing direction;
|
|
201
|
+
* an anchored label may finish in another direction after an elbow, which is
|
|
202
|
+
* not the direction a terminal stub should leave the pin.
|
|
190
203
|
*/
|
|
191
|
-
private
|
|
192
|
-
|
|
204
|
+
private computeTerminalInlinePlacement(
|
|
205
|
+
connection: InlineEligibleConnection,
|
|
206
|
+
pinId: PinId,
|
|
193
207
|
): InlineNetLabelPlacement | null {
|
|
194
|
-
|
|
195
|
-
if (!pinId) return null
|
|
208
|
+
if (!connection.netId) return null
|
|
196
209
|
|
|
197
210
|
const anchoredPlacement = this.inputNetLabelPlacements.find(
|
|
198
211
|
(placement) =>
|
|
199
|
-
placement.netId ===
|
|
212
|
+
placement.netId === connection.netId &&
|
|
200
213
|
placement.pinIds.length === 1 &&
|
|
201
214
|
placement.pinIds[0] === pinId,
|
|
202
215
|
)
|
|
@@ -208,11 +221,11 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
208
221
|
const inputPin = inputChip?.pins.find((pin) => pin.pinId === pinId)
|
|
209
222
|
|
|
210
223
|
const height =
|
|
211
|
-
|
|
224
|
+
connection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
|
|
212
225
|
const width =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
estimateInlineNetLabelWidth(
|
|
226
|
+
connection.inlineNetLabelWidth ??
|
|
227
|
+
connection.netLabelWidth ??
|
|
228
|
+
estimateInlineNetLabelWidth(connection.netId, height)
|
|
216
229
|
|
|
217
230
|
// Leave a small wire tail at both ends of the text so it unmistakably
|
|
218
231
|
// reads as a label on a trace rather than free-standing text.
|
|
@@ -273,7 +286,7 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
273
286
|
|
|
274
287
|
return {
|
|
275
288
|
globalConnNetId: anchoredPlacement.globalConnNetId,
|
|
276
|
-
netId:
|
|
289
|
+
netId: connection.netId,
|
|
277
290
|
pinIds: [pinId],
|
|
278
291
|
stubTracePath: [start, end],
|
|
279
292
|
axis,
|
|
@@ -286,12 +299,12 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
286
299
|
}
|
|
287
300
|
|
|
288
301
|
private computeInlinePlacement(
|
|
289
|
-
|
|
302
|
+
connection: InlineEligibleConnection,
|
|
290
303
|
): InlineNetLabelPlacement | null {
|
|
291
304
|
// Only connections the router actually drew a trace for can carry an inline
|
|
292
305
|
// label - there's nothing to run parallel to otherwise.
|
|
293
306
|
const traces =
|
|
294
|
-
this.tracesByPinPairKey.get(getPinPairKey(
|
|
307
|
+
this.tracesByPinPairKey.get(getPinPairKey(connection.pinIds)) ?? []
|
|
295
308
|
if (traces.length === 0) return null
|
|
296
309
|
|
|
297
310
|
const trace = traces[0]!
|
|
@@ -299,11 +312,11 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
299
312
|
if (segments.length === 0) return null
|
|
300
313
|
|
|
301
314
|
const height =
|
|
302
|
-
|
|
315
|
+
connection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
|
|
303
316
|
const width =
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
estimateInlineNetLabelWidth(
|
|
317
|
+
connection.inlineNetLabelWidth ??
|
|
318
|
+
connection.netLabelWidth ??
|
|
319
|
+
estimateInlineNetLabelWidth(connection.netId!, height)
|
|
307
320
|
|
|
308
321
|
const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN
|
|
309
322
|
|
|
@@ -360,9 +373,9 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
360
373
|
|
|
361
374
|
return {
|
|
362
375
|
globalConnNetId: trace.globalConnNetId,
|
|
363
|
-
netId:
|
|
376
|
+
netId: connection.netId,
|
|
364
377
|
mspPairId: trace.mspPairId,
|
|
365
|
-
pinIds: [...
|
|
378
|
+
pinIds: [...connection.pinIds],
|
|
366
379
|
axis: segment.axis,
|
|
367
380
|
anchorPoint,
|
|
368
381
|
center,
|
|
@@ -380,7 +393,7 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
380
393
|
// endpoints.
|
|
381
394
|
const spanPlacement = this.computeSpanPlacement({
|
|
382
395
|
trace,
|
|
383
|
-
|
|
396
|
+
connection,
|
|
384
397
|
width,
|
|
385
398
|
height,
|
|
386
399
|
offset,
|
|
@@ -400,13 +413,13 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
400
413
|
*/
|
|
401
414
|
private computeSpanPlacement({
|
|
402
415
|
trace,
|
|
403
|
-
|
|
416
|
+
connection,
|
|
404
417
|
width,
|
|
405
418
|
height,
|
|
406
419
|
offset,
|
|
407
420
|
}: {
|
|
408
421
|
trace: SolvedTracePath
|
|
409
|
-
|
|
422
|
+
connection: InlineEligibleConnection
|
|
410
423
|
width: number
|
|
411
424
|
height: number
|
|
412
425
|
offset: number
|
|
@@ -496,9 +509,9 @@ export class InlineNetLabelSolver extends BaseSolver {
|
|
|
496
509
|
|
|
497
510
|
return {
|
|
498
511
|
globalConnNetId: trace.globalConnNetId,
|
|
499
|
-
netId:
|
|
512
|
+
netId: connection.netId,
|
|
500
513
|
mspPairId: trace.mspPairId,
|
|
501
|
-
pinIds: [...
|
|
514
|
+
pinIds: [...connection.pinIds],
|
|
502
515
|
axis,
|
|
503
516
|
anchorPoint,
|
|
504
517
|
center,
|
|
@@ -43,7 +43,9 @@ export interface InputDirectConnection {
|
|
|
43
43
|
*
|
|
44
44
|
* Only set this for connections whose net name is worth showing on the wire -
|
|
45
45
|
* the solver trusts the caller (e.g. @tscircuit/core) to make that decision.
|
|
46
|
-
*
|
|
46
|
+
* When the connection is routed, the label is placed along the trace. If
|
|
47
|
+
* routing is intentionally skipped, both endpoint labels may instead become
|
|
48
|
+
* outward inline stubs.
|
|
47
49
|
*/
|
|
48
50
|
allowInlineNetLabel?: boolean
|
|
49
51
|
|
|
@@ -67,9 +69,10 @@ export interface InputNetConnection {
|
|
|
67
69
|
netLabelHeight?: number
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
|
-
* When true, a named
|
|
71
|
-
*
|
|
72
|
-
*
|
|
72
|
+
* When true, a named one- or two-pin net may use inline labels. A single-pin
|
|
73
|
+
* net gets an outward stub. A routed two-pin net gets one label along its
|
|
74
|
+
* trace; when that route is intentionally skipped, both endpoints get
|
|
75
|
+
* outward stubs. Nets with more than two pins retain anchored labels.
|
|
73
76
|
*/
|
|
74
77
|
allowInlineNetLabel?: boolean
|
|
75
78
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"chips": [
|
|
3
|
+
{
|
|
4
|
+
"chipId": "U1",
|
|
5
|
+
"center": { "x": -1, "y": 1 },
|
|
6
|
+
"width": 1,
|
|
7
|
+
"height": 1,
|
|
8
|
+
"sectionId": "routed",
|
|
9
|
+
"pins": [
|
|
10
|
+
{ "pinId": "U1.1", "x": -0.5, "y": 1, "_facingDirection": "x+" }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"chipId": "U2",
|
|
15
|
+
"center": { "x": 1, "y": 1 },
|
|
16
|
+
"width": 1,
|
|
17
|
+
"height": 1,
|
|
18
|
+
"sectionId": "routed",
|
|
19
|
+
"pins": [
|
|
20
|
+
{ "pinId": "U2.1", "x": 0.5, "y": 1, "_facingDirection": "x-" }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"chipId": "U3",
|
|
25
|
+
"center": { "x": -2, "y": -1 },
|
|
26
|
+
"width": 1,
|
|
27
|
+
"height": 1,
|
|
28
|
+
"sectionId": "left",
|
|
29
|
+
"pins": [
|
|
30
|
+
{ "pinId": "U3.1", "x": -1.5, "y": -1, "_facingDirection": "x+" }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"chipId": "U4",
|
|
35
|
+
"center": { "x": 2, "y": -1 },
|
|
36
|
+
"width": 1,
|
|
37
|
+
"height": 1,
|
|
38
|
+
"sectionId": "right",
|
|
39
|
+
"pins": [
|
|
40
|
+
{ "pinId": "U4.1", "x": 1.5, "y": -1, "_facingDirection": "x-" }
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"directConnections": [],
|
|
45
|
+
"netConnections": [
|
|
46
|
+
{
|
|
47
|
+
"netId": "NET_ROUTED",
|
|
48
|
+
"pinIds": ["U1.1", "U2.1"],
|
|
49
|
+
"netLabelWidth": 0.9,
|
|
50
|
+
"allowInlineNetLabel": true,
|
|
51
|
+
"inlineNetLabelWidth": 0.9,
|
|
52
|
+
"inlineNetLabelHeight": 0.12
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"netId": "NET_SECTIONED",
|
|
56
|
+
"pinIds": ["U3.1", "U4.1"],
|
|
57
|
+
"netLabelWidth": 1.1,
|
|
58
|
+
"allowInlineNetLabel": true,
|
|
59
|
+
"inlineNetLabelWidth": 1.1,
|
|
60
|
+
"inlineNetLabelHeight": 0.12
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"availableNetLabelOrientations": {
|
|
64
|
+
"NET_ROUTED": ["x-", "x+"],
|
|
65
|
+
"NET_SECTIONED": ["x-", "x+"]
|
|
66
|
+
}
|
|
67
|
+
}
|