@tscircuit/schematic-trace-solver 0.0.195 → 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
@@ -13253,6 +13253,16 @@ var pathCollidesWithObstacles = ({
13253
13253
  const lastPathPin = connectionPair.pins.find(
13254
13254
  (pin) => pointsAreEqual(pin, lastPathPoint)
13255
13255
  );
13256
+ for (const [pin, neighbor] of [
13257
+ [firstPathPin, path[1]],
13258
+ [lastPathPin, path[path.length - 2]]
13259
+ ]) {
13260
+ if (!pin?._facingDirection) continue;
13261
+ const outward = dir(pin._facingDirection);
13262
+ if ((neighbor.x - pin.x) * outward.x + (neighbor.y - pin.y) * outward.y < -COORDINATE_TOLERANCE) {
13263
+ return true;
13264
+ }
13265
+ }
13256
13266
  const pathConnectsPairPins = firstPathPin !== void 0 && lastPathPin !== void 0;
13257
13267
  const firstChipObstacle = obstacles.find(
13258
13268
  (obstacle) => obstacle.kind === "chip" && obstacle.chipId === firstPathPin?.chipId
@@ -17890,6 +17900,23 @@ var getTraceRecoveryConnectivityMaps = (inputProblem) => {
17890
17900
  return { chipMap, pinMap };
17891
17901
  };
17892
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
+
17893
17920
  // lib/solvers/NetLabelToTraceSolver/reduceTraceCrossings.ts
17894
17921
  var EPS18 = 1e-9;
17895
17922
  var isHorizontal4 = (start, end) => Math.abs(start.y - end.y) < EPS18;
@@ -18064,6 +18091,11 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18064
18091
  (connection) => connection.isGround === false && connection.pinIds.length > 2 && connection.netId === label.netId && connection.pinIds.includes(pinId)
18065
18092
  );
18066
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
+ }
18067
18099
  buildCandidatePairs() {
18068
18100
  const { netConnMap } = getConnectivityMapsFromInputProblem(
18069
18101
  this.inputProblem
@@ -18076,7 +18108,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18076
18108
  }
18077
18109
  const labelsByGlobalNet = /* @__PURE__ */ new Map();
18078
18110
  for (const label of this.input.netLabelPlacements) {
18079
- 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)) {
18080
18112
  continue;
18081
18113
  }
18082
18114
  const labels = labelsByGlobalNet.get(label.globalConnNetId) ?? [];
@@ -18100,7 +18132,9 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18100
18132
  const bothLabelsBelongToDirectConnections = this.isDirectConnectionLabel(firstLabel) && this.isDirectConnectionLabel(secondLabel);
18101
18133
  const firstMultiPinNetConnection = this.getMultiPinNetConnection(firstLabel);
18102
18134
  const secondMultiPinNetConnection = this.getMultiPinNetConnection(secondLabel);
18103
- 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)) {
18104
18138
  continue;
18105
18139
  }
18106
18140
  if (arePinsInDifferentSchematicSections(
@@ -18126,7 +18160,8 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18126
18160
  perpendicularOffset,
18127
18161
  routeDistance: Math.abs(firstPin.x - secondPin.x) + Math.abs(firstPin.y - secondPin.y),
18128
18162
  key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
18129
- recoveryMode: "fallback_labels"
18163
+ recoveryMode: "fallback_labels",
18164
+ simpleElbowPath
18130
18165
  });
18131
18166
  }
18132
18167
  }
@@ -18288,6 +18323,16 @@ var NetLabelToTraceSolver = class extends BaseSolver {
18288
18323
  (trace) => trace.globalConnNetId !== candidate.firstLabel.globalConnNetId
18289
18324
  );
18290
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
+ }
18291
18336
  if (doesTraceRecoveryPathConflict(tracePath, collisionTraces) || this.routeIntersectsRemainingLabels(tracePath, candidate)) {
18292
18337
  return;
18293
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
+ }
@@ -20,7 +20,7 @@ import {
20
20
  } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
21
21
  import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
22
22
  import type { InputProblem, PinId } from "lib/types/InputProblem"
23
- import type { FacingDirection } from "lib/utils/dir"
23
+ import { dir, type FacingDirection } from "lib/utils/dir"
24
24
 
25
25
  const ROUTE_CLEARANCE = 0.2
26
26
  const COORDINATE_TOLERANCE = 1e-9
@@ -306,6 +306,20 @@ const pathCollidesWithObstacles = ({
306
306
  const lastPathPin = connectionPair.pins.find((pin) =>
307
307
  pointsAreEqual(pin, lastPathPoint),
308
308
  )
309
+ // Endpoint exemptions must not allow a route through the back of a component.
310
+ for (const [pin, neighbor] of [
311
+ [firstPathPin, path[1]!],
312
+ [lastPathPin, path[path.length - 2]!],
313
+ ] as const) {
314
+ if (!pin?._facingDirection) continue
315
+ const outward = dir(pin._facingDirection)
316
+ if (
317
+ (neighbor.x - pin.x) * outward.x + (neighbor.y - pin.y) * outward.y <
318
+ -COORDINATE_TOLERANCE
319
+ ) {
320
+ return true
321
+ }
322
+ }
309
323
  const pathConnectsPairPins =
310
324
  firstPathPin !== undefined && lastPathPin !== undefined
311
325
  const firstChipObstacle = obstacles.find(
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.195",
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
+ }