@tscircuit/schematic-trace-solver 0.0.196 → 0.0.197

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 CHANGED
@@ -1451,6 +1451,7 @@ declare class NetLabelToTraceSolver extends BaseSolver {
1451
1451
  private isPortOnlyFallbackLabel;
1452
1452
  private isDirectConnectionLabel;
1453
1453
  private getMultiPinNetConnection;
1454
+ private getTwoPinNetConnection;
1454
1455
  private buildCandidatePairs;
1455
1456
  private buildRoutedComponentCandidates;
1456
1457
  private isSupersededConnectorTrace;
package/dist/index.js CHANGED
@@ -17900,6 +17900,23 @@ var getTraceRecoveryConnectivityMaps = (inputProblem) => {
17900
17900
  return { chipMap, pinMap };
17901
17901
  };
17902
17902
 
17903
+ // lib/solvers/NetLabelToTraceSolver/getSimpleElbowPath.ts
17904
+ var getSimpleElbowPath = (first, second) => {
17905
+ const firstDirection = dir(first._facingDirection);
17906
+ const secondDirection = dir(second._facingDirection);
17907
+ if (firstDirection.x * secondDirection.x || firstDirection.y * secondDirection.y)
17908
+ return void 0;
17909
+ const elbow = firstDirection.x ? { x: second.x, y: first.y } : { x: first.x, y: second.y };
17910
+ for (const [pin, direction] of [
17911
+ [first, firstDirection],
17912
+ [second, secondDirection]
17913
+ ]) {
17914
+ if ((elbow.x - pin.x) * direction.x + (elbow.y - pin.y) * direction.y <= 1e-6)
17915
+ return void 0;
17916
+ }
17917
+ return [{ x: first.x, y: first.y }, elbow, { x: second.x, y: second.y }];
17918
+ };
17919
+
17903
17920
  // lib/solvers/NetLabelToTraceSolver/reduceTraceCrossings.ts
17904
17921
  var EPS18 = 1e-9;
17905
17922
  var isHorizontal4 = (start, end) => Math.abs(start.y - end.y) < EPS18;
@@ -18074,6 +18091,11 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18074
18091
  (connection) => connection.isGround === false && connection.pinIds.length > 2 && connection.netId === label.netId && connection.pinIds.includes(pinId)
18075
18092
  );
18076
18093
  }
18094
+ getTwoPinNetConnection(label) {
18095
+ return this.inputProblem.netConnections.find(
18096
+ (connection) => !connection.isGround && connection.pinIds.length === 2 && connection.netId === label.netId && connection.pinIds.includes(label.pinIds[0])
18097
+ );
18098
+ }
18077
18099
  buildCandidatePairs() {
18078
18100
  const { netConnMap } = getConnectivityMapsFromInputProblem(
18079
18101
  this.inputProblem
@@ -18086,7 +18108,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18086
18108
  }
18087
18109
  const labelsByGlobalNet = /* @__PURE__ */ new Map();
18088
18110
  for (const label of this.input.netLabelPlacements) {
18089
- if (!this.isPortOnlyFallbackLabel(label, groundGlobalConnNetIds) || !this.isDirectConnectionLabel(label) && !this.getMultiPinNetConnection(label)) {
18111
+ if (!this.isPortOnlyFallbackLabel(label, groundGlobalConnNetIds) || !this.isDirectConnectionLabel(label) && !this.getMultiPinNetConnection(label) && !this.getTwoPinNetConnection(label)) {
18090
18112
  continue;
18091
18113
  }
18092
18114
  const labels = labelsByGlobalNet.get(label.globalConnNetId) ?? [];
@@ -18110,7 +18132,9 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18110
18132
  const bothLabelsBelongToDirectConnections = this.isDirectConnectionLabel(firstLabel) && this.isDirectConnectionLabel(secondLabel);
18111
18133
  const firstMultiPinNetConnection = this.getMultiPinNetConnection(firstLabel);
18112
18134
  const secondMultiPinNetConnection = this.getMultiPinNetConnection(secondLabel);
18113
- if (!bothLabelsBelongToDirectConnections && (!firstMultiPinNetConnection || firstMultiPinNetConnection !== secondMultiPinNetConnection || perpendicularOffset > MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET)) {
18135
+ const twoPinNetConnection = this.getTwoPinNetConnection(firstLabel);
18136
+ const simpleElbowPath = !bothLabelsBelongToDirectConnections && twoPinNetConnection && firstPin.chipId !== secondPin.chipId && twoPinNetConnection === this.getTwoPinNetConnection(secondLabel) ? getSimpleElbowPath(firstPin, secondPin) : void 0;
18137
+ if (!simpleElbowPath && !bothLabelsBelongToDirectConnections && (!firstMultiPinNetConnection || firstMultiPinNetConnection !== secondMultiPinNetConnection || perpendicularOffset > MAX_NAMED_NET_RECOVERY_PERPENDICULAR_OFFSET)) {
18114
18138
  continue;
18115
18139
  }
18116
18140
  if (arePinsInDifferentSchematicSections(
@@ -18136,7 +18160,8 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18136
18160
  perpendicularOffset,
18137
18161
  routeDistance: Math.abs(firstPin.x - secondPin.x) + Math.abs(firstPin.y - secondPin.y),
18138
18162
  key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
18139
- recoveryMode: "fallback_labels"
18163
+ recoveryMode: "fallback_labels",
18164
+ simpleElbowPath
18140
18165
  });
18141
18166
  }
18142
18167
  }
@@ -18298,6 +18323,16 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18298
18323
  (trace) => trace.globalConnNetId !== candidate.firstLabel.globalConnNetId
18299
18324
  );
18300
18325
  }
18326
+ if (candidate.simpleElbowPath) {
18327
+ tracePath = candidate.simpleElbowPath;
18328
+ if (findFirstCollision(tracePath, this.activeSubSolver.obstacles) || collisionTraces.reduce(
18329
+ (count, trace) => count + findPerpendicularPathCrossings(tracePath, trace.tracePath, {
18330
+ includeTerminalSegments: true
18331
+ }).length,
18332
+ 0
18333
+ ) > 1)
18334
+ return;
18335
+ }
18301
18336
  if (doesTraceRecoveryPathConflict(tracePath, collisionTraces) || this.routeIntersectsRemainingLabels(tracePath, candidate)) {
18302
18337
  return;
18303
18338
  }
@@ -27,6 +27,8 @@ import {
27
27
  type InlineNetLabelPlacement,
28
28
  visualizeInlineNetLabelOutput,
29
29
  } from "../InlineNetLabelSolver/InlineNetLabelSolver"
30
+ import { getSimpleElbowPath } from "./getSimpleElbowPath"
31
+ import { findPerpendicularPathCrossings } from "../TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles"
30
32
  import { reduceTraceCrossings } from "./reduceTraceCrossings"
31
33
 
32
34
  type GlobalConnNetId = NetLabelPlacement["globalConnNetId"]
@@ -43,6 +45,7 @@ interface CandidatePair {
43
45
  | "routed_components"
44
46
  | "routed_direct_connection"
45
47
  netConnectionPinIds?: PinId[]
48
+ simpleElbowPath?: Point[]
46
49
  }
47
50
 
48
51
  const RECOVERED_TRACE_PREFIX = "net-label-to-trace-"
@@ -153,6 +156,16 @@ export class NetLabelToTraceSolver extends BaseSolver {
153
156
  )
154
157
  }
155
158
 
159
+ private getTwoPinNetConnection(label: NetLabelPlacement) {
160
+ return this.inputProblem.netConnections.find(
161
+ (connection) =>
162
+ !connection.isGround &&
163
+ connection.pinIds.length === 2 &&
164
+ connection.netId === label.netId &&
165
+ connection.pinIds.includes(label.pinIds[0]!),
166
+ )
167
+ }
168
+
156
169
  private buildCandidatePairs() {
157
170
  const { netConnMap } = getConnectivityMapsFromInputProblem(
158
171
  this.inputProblem,
@@ -169,7 +182,8 @@ export class NetLabelToTraceSolver extends BaseSolver {
169
182
  if (
170
183
  !this.isPortOnlyFallbackLabel(label, groundGlobalConnNetIds) ||
171
184
  (!this.isDirectConnectionLabel(label) &&
172
- !this.getMultiPinNetConnection(label))
185
+ !this.getMultiPinNetConnection(label) &&
186
+ !this.getTwoPinNetConnection(label))
173
187
  ) {
174
188
  continue
175
189
  }
@@ -203,7 +217,16 @@ export class NetLabelToTraceSolver extends BaseSolver {
203
217
  this.getMultiPinNetConnection(firstLabel)
204
218
  const secondMultiPinNetConnection =
205
219
  this.getMultiPinNetConnection(secondLabel)
220
+ const twoPinNetConnection = this.getTwoPinNetConnection(firstLabel)
221
+ const simpleElbowPath =
222
+ !bothLabelsBelongToDirectConnections &&
223
+ twoPinNetConnection &&
224
+ firstPin.chipId !== secondPin.chipId &&
225
+ twoPinNetConnection === this.getTwoPinNetConnection(secondLabel)
226
+ ? getSimpleElbowPath(firstPin, secondPin)
227
+ : undefined
206
228
  if (
229
+ !simpleElbowPath &&
207
230
  !bothLabelsBelongToDirectConnections &&
208
231
  (!firstMultiPinNetConnection ||
209
232
  firstMultiPinNetConnection !== secondMultiPinNetConnection ||
@@ -242,6 +265,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
242
265
  Math.abs(firstPin.y - secondPin.y),
243
266
  key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
244
267
  recoveryMode: "fallback_labels",
268
+ simpleElbowPath,
245
269
  })
246
270
  }
247
271
  }
@@ -474,6 +498,23 @@ export class NetLabelToTraceSolver extends BaseSolver {
474
498
  trace.globalConnNetId !== candidate.firstLabel.globalConnNetId,
475
499
  )
476
500
  }
501
+ // Only recover a named two-pin elbow when the shortest, direction-respecting
502
+ // path is clear. Do not replace labels with obstacle detours or many hops.
503
+ if (candidate.simpleElbowPath) {
504
+ tracePath = candidate.simpleElbowPath
505
+ if (
506
+ findFirstCollision(tracePath, this.activeSubSolver!.obstacles) ||
507
+ collisionTraces.reduce(
508
+ (count, trace) =>
509
+ count +
510
+ findPerpendicularPathCrossings(tracePath!, trace.tracePath, {
511
+ includeTerminalSegments: true,
512
+ }).length,
513
+ 0,
514
+ ) > 1
515
+ )
516
+ return
517
+ }
477
518
  if (
478
519
  doesTraceRecoveryPathConflict(tracePath, collisionTraces) ||
479
520
  this.routeIntersectsRemainingLabels(tracePath, candidate)
@@ -0,0 +1,31 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { TraceRecoveryPin } from "lib/solvers/NetLabelTraceRecovery/getTraceRecoveryConnectivityMaps"
3
+ import { dir } from "lib/utils/dir"
4
+
5
+ /** The single elbow reached by extending both pins outwards, if it exists. */
6
+ export const getSimpleElbowPath = (
7
+ first: TraceRecoveryPin,
8
+ second: TraceRecoveryPin,
9
+ ): Point[] | undefined => {
10
+ const firstDirection = dir(first._facingDirection)
11
+ const secondDirection = dir(second._facingDirection)
12
+ if (
13
+ firstDirection.x * secondDirection.x ||
14
+ firstDirection.y * secondDirection.y
15
+ )
16
+ return undefined
17
+ const elbow = firstDirection.x
18
+ ? { x: second.x, y: first.y }
19
+ : { x: first.x, y: second.y }
20
+ for (const [pin, direction] of [
21
+ [first, firstDirection],
22
+ [second, secondDirection],
23
+ ] as const) {
24
+ if (
25
+ (elbow.x - pin.x) * direction.x + (elbow.y - pin.y) * direction.y <=
26
+ 1e-6
27
+ )
28
+ return undefined
29
+ }
30
+ return [{ x: first.x, y: first.y }, elbow, { x: second.x, y: second.y }]
31
+ }
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.196",
8
+ "version": "0.0.197",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",
@@ -0,0 +1,6 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/assets/enc-scl-hop.json"
3
+
4
+ export default () => (
5
+ <PipelineDebugger inputProblem={inputProblem as any} hideRatsNet />
6
+ )
@@ -0,0 +1,185 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "R_ENC_SDA",
5
+ "center": {
6
+ "x": 0.0,
7
+ "y": 1.8
8
+ },
9
+ "width": 0.16000000000000003,
10
+ "height": 0.4,
11
+ "pins": [
12
+ {
13
+ "pinId": "R_ENC_SDA.1",
14
+ "x": 0.0,
15
+ "y": 2.1,
16
+ "_facingDirection": "y+"
17
+ },
18
+ {
19
+ "pinId": "R_ENC_SDA.2",
20
+ "x": 0.0,
21
+ "y": 1.5,
22
+ "_facingDirection": "y-"
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "chipId": "R_ENC_SCL",
28
+ "center": {
29
+ "x": 2.0,
30
+ "y": 1.8
31
+ },
32
+ "width": 0.16000000000000003,
33
+ "height": 0.4,
34
+ "pins": [
35
+ {
36
+ "pinId": "R_ENC_SCL.1",
37
+ "x": 2.0,
38
+ "y": 2.1,
39
+ "_facingDirection": "y+"
40
+ },
41
+ {
42
+ "pinId": "R_ENC_SCL.2",
43
+ "x": 2.0,
44
+ "y": 1.5,
45
+ "_facingDirection": "y-"
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "chipId": "U_ENCODER",
51
+ "center": {
52
+ "x": 6.0,
53
+ "y": 0.0
54
+ },
55
+ "width": 1.7600000000000002,
56
+ "height": 1.6,
57
+ "pins": [
58
+ {
59
+ "pinId": "U_ENCODER.6",
60
+ "displayName": "SDA",
61
+ "x": 4.720000000000001,
62
+ "y": 0.1,
63
+ "_facingDirection": "x-"
64
+ },
65
+ {
66
+ "pinId": "U_ENCODER.7",
67
+ "displayName": "SCL",
68
+ "x": 4.720000000000001,
69
+ "y": -0.1,
70
+ "_facingDirection": "x-"
71
+ },
72
+ {
73
+ "pinId": "U_ENCODER.1",
74
+ "displayName": "VDD5V",
75
+ "x": 5.9,
76
+ "y": 1.2000000000000002,
77
+ "_facingDirection": "y+"
78
+ },
79
+ {
80
+ "pinId": "U_ENCODER.2",
81
+ "displayName": "VDD3V3",
82
+ "x": 6.1000000000000005,
83
+ "y": 1.2000000000000002,
84
+ "_facingDirection": "y+"
85
+ },
86
+ {
87
+ "pinId": "U_ENCODER.4",
88
+ "displayName": "GND",
89
+ "x": 5.9,
90
+ "y": -1.2000000000000002,
91
+ "_facingDirection": "y-"
92
+ },
93
+ {
94
+ "pinId": "U_ENCODER.8",
95
+ "displayName": "DIR",
96
+ "x": 6.1000000000000005,
97
+ "y": -1.2000000000000002,
98
+ "_facingDirection": "y-"
99
+ },
100
+ {
101
+ "pinId": "U_ENCODER.3",
102
+ "displayName": "OUT",
103
+ "x": 7.28,
104
+ "y": 0.1,
105
+ "_facingDirection": "x+"
106
+ },
107
+ {
108
+ "pinId": "U_ENCODER.5",
109
+ "displayName": "PGO",
110
+ "x": 7.28,
111
+ "y": -0.1,
112
+ "_facingDirection": "x+"
113
+ }
114
+ ]
115
+ }
116
+ ],
117
+ "directConnections": [
118
+ {
119
+ "netId": "ENC_SDA",
120
+ "netLabelText": "ENC_SDA",
121
+ "pinIds": [
122
+ "R_ENC_SDA.2",
123
+ "U_ENCODER.6"
124
+ ]
125
+ },
126
+ {
127
+ "pinIds": [
128
+ "R_ENC_SDA.1",
129
+ "R_ENC_SCL.1"
130
+ ],
131
+ "netId": "V3V3"
132
+ },
133
+ {
134
+ "pinIds": [
135
+ "U_ENCODER.1",
136
+ "U_ENCODER.2"
137
+ ],
138
+ "netId": "V3V3"
139
+ }
140
+ ],
141
+ "netConnections": [
142
+ {
143
+ "netId": "ENC_SDA",
144
+ "netLabelText": "ENC_SDA",
145
+ "pinIds": [
146
+ "R_ENC_SDA.2",
147
+ "U_ENCODER.6"
148
+ ]
149
+ },
150
+ {
151
+ "netId": "ENC_SCL",
152
+ "netLabelText": "ENC_SCL",
153
+ "pinIds": [
154
+ "R_ENC_SCL.2",
155
+ "U_ENCODER.7"
156
+ ]
157
+ },
158
+ {
159
+ "netId": "V3V3",
160
+ "netLabelText": "V3V3",
161
+ "pinIds": [
162
+ "R_ENC_SDA.1",
163
+ "R_ENC_SCL.1",
164
+ "U_ENCODER.1",
165
+ "U_ENCODER.2"
166
+ ]
167
+ },
168
+ {
169
+ "netId": "GND",
170
+ "netLabelText": "GND",
171
+ "pinIds": [
172
+ "U_ENCODER.4",
173
+ "U_ENCODER.8"
174
+ ]
175
+ }
176
+ ],
177
+ "availableNetLabelOrientations": {
178
+ "V3V3": [
179
+ "y+"
180
+ ],
181
+ "GND": [
182
+ "y-"
183
+ ]
184
+ }
185
+ }