@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.
@@ -52,8 +52,9 @@ export interface InlineNetLabelPlacement {
52
52
  pinIds: PinId[]
53
53
 
54
54
  /**
55
- * A generated single-ended trace stub. Present only for an eligible
56
- * single-pin net connection; routed point-to-point traces omit it.
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 direct connections that opted in via `allowInlineNetLabel`.
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
- /** Direct connections that opted in, still waiting to be processed */
106
- queuedDirectConnections: InputDirectConnection[]
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.queuedDirectConnections = this.inputProblem.directConnections.filter(
121
- (dc) => dc.allowInlineNetLabel && dc.netId,
122
- )
123
- this.queuedPortOnlyNetConnections = this.inputProblem.netConnections.filter(
124
- (nc) => nc.allowInlineNetLabel && nc.pinIds.length === 1 && nc.netId,
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 directConnection = this.queuedDirectConnections.shift()
151
- if (directConnection) {
152
- const placement = this.computeInlinePlacement(directConnection)
153
- if (placement) {
154
- this.inlineNetLabelPlacements.push(placement)
155
- }
156
- return
157
- }
158
-
159
- const portOnlyNetConnection = this.queuedPortOnlyNetConnections.shift()
160
- if (portOnlyNetConnection) {
161
- const placement = this.computePortOnlyInlinePlacement(
162
- portOnlyNetConnection,
163
- )
164
- if (placement) {
165
- this.inlineNetLabelPlacements.push(placement)
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 a conventional port-only anchored placement into an inline label
187
- * on a generated outward stub. The stub follows the pin's true facing
188
- * direction; an anchored label may finish in another direction after an
189
- * elbow, which is not the direction a terminal stub should leave the pin.
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 computePortOnlyInlinePlacement(
192
- netConnection: InputNetConnection,
204
+ private computeTerminalInlinePlacement(
205
+ connection: InlineEligibleConnection,
206
+ pinId: PinId,
193
207
  ): InlineNetLabelPlacement | null {
194
- const [pinId] = netConnection.pinIds
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 === netConnection.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
- netConnection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
224
+ connection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
212
225
  const width =
213
- netConnection.inlineNetLabelWidth ??
214
- netConnection.netLabelWidth ??
215
- estimateInlineNetLabelWidth(netConnection.netId, height)
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: netConnection.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
- directConnection: InputDirectConnection,
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(directConnection.pinIds)) ?? []
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
- directConnection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
315
+ connection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT
303
316
  const width =
304
- directConnection.inlineNetLabelWidth ??
305
- directConnection.netLabelWidth ??
306
- estimateInlineNetLabelWidth(directConnection.netId!, height)
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: directConnection.netId,
376
+ netId: connection.netId,
364
377
  mspPairId: trace.mspPairId,
365
- pinIds: [...directConnection.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
- directConnection,
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
- directConnection,
416
+ connection,
404
417
  width,
405
418
  height,
406
419
  offset,
407
420
  }: {
408
421
  trace: SolvedTracePath
409
- directConnection: InputDirectConnection
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: directConnection.netId,
512
+ netId: connection.netId,
500
513
  mspPairId: trace.mspPairId,
501
- pinIds: [...directConnection.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
- * An inline label is only emitted when the connection actually got routed.
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 single-pin net may be drawn as a short outward trace
71
- * stub with its net name placed inline. Multi-pin net connections retain the
72
- * regular anchored-label behavior.
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
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
6
  },
7
7
  "main": "dist/index.js",
8
- "version": "0.0.142",
8
+ "version": "0.0.144",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",
@@ -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
+ }