@tscircuit/schematic-trace-solver 0.0.170 → 0.0.172

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.
Files changed (20) hide show
  1. package/dist/index.js +52 -14
  2. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +6 -1
  3. package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +68 -14
  4. package/package.json +1 -1
  5. package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +10 -10
  6. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +10 -10
  7. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +10 -10
  8. package/tests/repros/__snapshots__/repro-battery-management-bq24073.snap.svg +191 -0
  9. package/tests/repros/__snapshots__/repro-hdc3020-ground-label-alignment.snap.svg +110 -0
  10. package/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg +11 -29
  11. package/tests/repros/__snapshots__/repro-tida-01389-hbridge.snap.svg +216 -0
  12. package/tests/repros/__snapshots__/repro-tmp1075-ground-label-overlap.snap.svg +166 -0
  13. package/tests/repros/assets/repro-battery-management-bq24073.input.json +721 -0
  14. package/tests/repros/assets/repro-hdc3020-ground-label-alignment.input.json +282 -0
  15. package/tests/repros/assets/repro-tida-01389-hbridge.input.json +780 -0
  16. package/tests/repros/assets/repro-tmp1075-ground-label-overlap.input.json +305 -0
  17. package/tests/repros/repro-battery-management-bq24073.test.ts +62 -0
  18. package/tests/repros/repro-hdc3020-ground-label-alignment.test.ts +25 -0
  19. package/tests/repros/repro-tida-01389-hbridge.test.ts +23 -0
  20. package/tests/repros/repro-tmp1075-ground-label-overlap.test.ts +18 -0
package/dist/index.js CHANGED
@@ -10049,7 +10049,9 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
10049
10049
  changed = true;
10050
10050
  }
10051
10051
  }
10052
- if (connectedPinIds.size <= 3) return null;
10052
+ if (connectedPinIds.size < 3 || connectedPinIds.size === 3 && !this.hasPortOnlyLabelOnSameNet(label)) {
10053
+ return null;
10054
+ }
10053
10055
  const pins = [...connectedPinIds].map((pinId) => this.pinMap[pinId]);
10054
10056
  const direction = dir(orientation);
10055
10057
  const pinX = pins[0].x;
@@ -15623,24 +15625,42 @@ var NetLabelToTraceSolver = class extends BaseSolver {
15623
15625
  }
15624
15626
  }
15625
15627
  }
15626
- candidates.push(...this.buildRoutedComponentCandidates());
15628
+ candidates.push(
15629
+ ...this.buildRoutedComponentCandidates(groundGlobalConnNetIds)
15630
+ );
15627
15631
  candidates.sort(
15628
15632
  (first, second) => first.perpendicularOffset - second.perpendicularOffset || first.routeDistance - second.routeDistance || first.key.localeCompare(second.key)
15629
15633
  );
15630
15634
  return candidates;
15631
15635
  }
15632
- buildRoutedComponentCandidates() {
15636
+ buildRoutedComponentCandidates(groundGlobalConnNetIds) {
15633
15637
  const { netConnMap } = getConnectivityMapsFromInputProblem(
15634
15638
  this.inputProblem
15635
15639
  );
15636
15640
  const candidates = [];
15637
- for (const connection of this.inputProblem.netConnections) {
15638
- if (connection.pinIds.length <= 2 || connection.isGround !== false)
15641
+ const directConnections = new Set(
15642
+ this.inputProblem.directConnections
15643
+ );
15644
+ for (const connection of [
15645
+ ...this.inputProblem.netConnections.filter(
15646
+ (connection2) => connection2.pinIds.length > 2 && connection2.isGround === false
15647
+ ),
15648
+ ...this.inputProblem.directConnections
15649
+ ]) {
15650
+ const isDirectConnection = directConnections.has(connection);
15651
+ const globalConnNetId = netConnMap.getNetConnectedToId(
15652
+ connection.pinIds[0]
15653
+ );
15654
+ if (!globalConnNetId || isDirectConnection && groundGlobalConnNetIds.has(globalConnNetId))
15639
15655
  continue;
15640
- const globalConnNetId = netConnMap.getNetConnectedToId(connection.netId);
15641
- if (!globalConnNetId) continue;
15656
+ let connectionPinIds = connection.pinIds;
15657
+ if (isDirectConnection) {
15658
+ connectionPinIds = [...this.pinMap.keys()].filter(
15659
+ (pinId) => netConnMap.getNetConnectedToId(pinId) === globalConnNetId
15660
+ );
15661
+ }
15642
15662
  const traceConnectedPinComponents = getTraceConnectedPinComponents({
15643
- pinIds: connection.pinIds,
15663
+ pinIds: connectionPinIds,
15644
15664
  traces: this.outputTraces.filter(
15645
15665
  (trace) => trace.globalConnNetId === globalConnNetId
15646
15666
  )
@@ -15649,6 +15669,8 @@ var NetLabelToTraceSolver = class extends BaseSolver {
15649
15669
  for (let secondIndex = firstIndex + 1; secondIndex < traceConnectedPinComponents.length; secondIndex++) {
15650
15670
  const firstComponent = traceConnectedPinComponents[firstIndex];
15651
15671
  const secondComponent = traceConnectedPinComponents[secondIndex];
15672
+ if (isDirectConnection && !(firstComponent.pinIds.includes(connection.pinIds[0]) && secondComponent.pinIds.includes(connection.pinIds[1]) || firstComponent.pinIds.includes(connection.pinIds[1]) && secondComponent.pinIds.includes(connection.pinIds[0])))
15673
+ continue;
15652
15674
  const firstLabel = this.input.netLabelPlacements.find(
15653
15675
  (label) => label.globalConnNetId === globalConnNetId && label.mspConnectionPairIds.length > 0 && label.pinIds.some(
15654
15676
  (pinId) => firstComponent.pinIds.includes(pinId)
@@ -15668,11 +15690,23 @@ var NetLabelToTraceSolver = class extends BaseSolver {
15668
15690
  continue;
15669
15691
  }
15670
15692
  let bestCandidate;
15671
- for (const firstPinId of firstComponent.pinIds) {
15672
- for (const secondPinId of secondComponent.pinIds) {
15693
+ let firstPinIds = firstComponent.pinIds;
15694
+ let secondPinIds = secondComponent.pinIds;
15695
+ if (isDirectConnection) {
15696
+ firstPinIds = connection.pinIds.filter(
15697
+ (pinId) => firstComponent.pinIds.includes(pinId)
15698
+ );
15699
+ secondPinIds = connection.pinIds.filter(
15700
+ (pinId) => secondComponent.pinIds.includes(pinId)
15701
+ );
15702
+ }
15703
+ for (const firstPinId of firstPinIds) {
15704
+ for (const secondPinId of secondPinIds) {
15673
15705
  const firstPin = this.pinMap.get(firstPinId);
15674
15706
  const secondPin = this.pinMap.get(secondPinId);
15675
15707
  if (!firstPin || !secondPin) continue;
15708
+ if (isDirectConnection && firstPin.chipId === secondPin.chipId)
15709
+ continue;
15676
15710
  const perpendicularOffset = getPerpendicularOffset(
15677
15711
  firstPin,
15678
15712
  secondPin
@@ -15691,6 +15725,10 @@ var NetLabelToTraceSolver = class extends BaseSolver {
15691
15725
  continue;
15692
15726
  }
15693
15727
  const routeDistance = Math.abs(firstPin.x - secondPin.x) + Math.abs(firstPin.y - secondPin.y);
15728
+ let recoveryMode = "routed_components";
15729
+ if (isDirectConnection) {
15730
+ recoveryMode = "routed_direct_connection";
15731
+ }
15694
15732
  const candidate = {
15695
15733
  firstLabel,
15696
15734
  secondLabel,
@@ -15698,8 +15736,8 @@ var NetLabelToTraceSolver = class extends BaseSolver {
15698
15736
  perpendicularOffset,
15699
15737
  routeDistance,
15700
15738
  key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
15701
- recoveryMode: "routed_components",
15702
- netConnectionPinIds: connection.pinIds
15739
+ recoveryMode,
15740
+ netConnectionPinIds: connectionPinIds
15703
15741
  };
15704
15742
  if (!bestCandidate || candidate.routeDistance < bestCandidate.routeDistance || candidate.routeDistance === bestCandidate.routeDistance && (candidate.perpendicularOffset < bestCandidate.perpendicularOffset || candidate.perpendicularOffset === bestCandidate.perpendicularOffset && candidate.key.localeCompare(bestCandidate.key) < 0)) {
15705
15743
  bestCandidate = candidate;
@@ -15738,7 +15776,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
15738
15776
  (trace) => !this.isSupersededConnectorTrace(trace, candidate)
15739
15777
  );
15740
15778
  let collisionTraces = retainedTraces;
15741
- if (candidate.recoveryMode === "routed_components") {
15779
+ if (candidate.recoveryMode !== "fallback_labels") {
15742
15780
  collisionTraces = retainedTraces.filter(
15743
15781
  (trace) => trace.globalConnNetId !== candidate.firstLabel.globalConnNetId
15744
15782
  );
@@ -15764,7 +15802,7 @@ var NetLabelToTraceSolver = class extends BaseSolver {
15764
15802
  pinIds: [firstPin.pinId, secondPin.pinId]
15765
15803
  };
15766
15804
  this.outputTraces = [...retainedTraces, recoveredTrace];
15767
- if (candidate.recoveryMode === "fallback_labels") {
15805
+ if (candidate.recoveryMode !== "routed_components") {
15768
15806
  this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
15769
15807
  (label) => label !== candidate.firstLabel && label !== candidate.secondLabel
15770
15808
  );
@@ -1695,7 +1695,12 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1695
1695
  changed = true
1696
1696
  }
1697
1697
  }
1698
- if (connectedPinIds.size <= 3) return null
1698
+ if (
1699
+ connectedPinIds.size < 3 ||
1700
+ (connectedPinIds.size === 3 && !this.hasPortOnlyLabelOnSameNet(label))
1701
+ ) {
1702
+ return null
1703
+ }
1699
1704
 
1700
1705
  const pins = [...connectedPinIds].map((pinId) => this.pinMap[pinId]!)
1701
1706
  const direction = dir(orientation)
@@ -40,7 +40,10 @@ interface CandidatePair {
40
40
  perpendicularOffset: number
41
41
  routeDistance: number
42
42
  key: string
43
- recoveryMode: "fallback_labels" | "routed_components"
43
+ recoveryMode:
44
+ | "fallback_labels"
45
+ | "routed_components"
46
+ | "routed_direct_connection"
44
47
  netConnectionPinIds?: PinId[]
45
48
  }
46
49
 
@@ -260,7 +263,9 @@ export class NetLabelToTraceSolver extends BaseSolver {
260
263
  }
261
264
  }
262
265
 
263
- candidates.push(...this.buildRoutedComponentCandidates())
266
+ candidates.push(
267
+ ...this.buildRoutedComponentCandidates(groundGlobalConnNetIds),
268
+ )
264
269
 
265
270
  candidates.sort(
266
271
  (first, second) =>
@@ -271,20 +276,42 @@ export class NetLabelToTraceSolver extends BaseSolver {
271
276
  return candidates
272
277
  }
273
278
 
274
- private buildRoutedComponentCandidates() {
279
+ private buildRoutedComponentCandidates(
280
+ groundGlobalConnNetIds: Set<GlobalConnNetId>,
281
+ ) {
275
282
  const { netConnMap } = getConnectivityMapsFromInputProblem(
276
283
  this.inputProblem,
277
284
  )
278
285
  const candidates: CandidatePair[] = []
286
+ const directConnections = new Set<object>(
287
+ this.inputProblem.directConnections,
288
+ )
279
289
 
280
- for (const connection of this.inputProblem.netConnections) {
281
- if (connection.pinIds.length <= 2 || connection.isGround !== false)
290
+ for (const connection of [
291
+ ...this.inputProblem.netConnections.filter(
292
+ (connection) =>
293
+ connection.pinIds.length > 2 && connection.isGround === false,
294
+ ),
295
+ ...this.inputProblem.directConnections,
296
+ ]) {
297
+ const isDirectConnection = directConnections.has(connection)
298
+ const globalConnNetId = netConnMap.getNetConnectedToId(
299
+ connection.pinIds[0],
300
+ )
301
+ if (
302
+ !globalConnNetId ||
303
+ (isDirectConnection && groundGlobalConnNetIds.has(globalConnNetId))
304
+ )
282
305
  continue
283
- const globalConnNetId = netConnMap.getNetConnectedToId(connection.netId)
284
- if (!globalConnNetId) continue
306
+ let connectionPinIds: PinId[] = connection.pinIds
307
+ if (isDirectConnection) {
308
+ connectionPinIds = [...this.pinMap.keys()].filter(
309
+ (pinId) => netConnMap.getNetConnectedToId(pinId) === globalConnNetId,
310
+ )
311
+ }
285
312
 
286
313
  const traceConnectedPinComponents = getTraceConnectedPinComponents({
287
- pinIds: connection.pinIds,
314
+ pinIds: connectionPinIds,
288
315
  traces: this.outputTraces.filter(
289
316
  (trace) => trace.globalConnNetId === globalConnNetId,
290
317
  ),
@@ -302,6 +329,16 @@ export class NetLabelToTraceSolver extends BaseSolver {
302
329
  ) {
303
330
  const firstComponent = traceConnectedPinComponents[firstIndex]!
304
331
  const secondComponent = traceConnectedPinComponents[secondIndex]!
332
+ if (
333
+ isDirectConnection &&
334
+ !(
335
+ (firstComponent.pinIds.includes(connection.pinIds[0]) &&
336
+ secondComponent.pinIds.includes(connection.pinIds[1])) ||
337
+ (firstComponent.pinIds.includes(connection.pinIds[1]) &&
338
+ secondComponent.pinIds.includes(connection.pinIds[0]))
339
+ )
340
+ )
341
+ continue
305
342
  const firstLabel = this.input.netLabelPlacements.find(
306
343
  (label) =>
307
344
  label.globalConnNetId === globalConnNetId &&
@@ -330,11 +367,23 @@ export class NetLabelToTraceSolver extends BaseSolver {
330
367
  }
331
368
 
332
369
  let bestCandidate: CandidatePair | undefined
333
- for (const firstPinId of firstComponent.pinIds) {
334
- for (const secondPinId of secondComponent.pinIds) {
370
+ let firstPinIds = firstComponent.pinIds
371
+ let secondPinIds = secondComponent.pinIds
372
+ if (isDirectConnection) {
373
+ firstPinIds = connection.pinIds.filter((pinId) =>
374
+ firstComponent.pinIds.includes(pinId),
375
+ )
376
+ secondPinIds = connection.pinIds.filter((pinId) =>
377
+ secondComponent.pinIds.includes(pinId),
378
+ )
379
+ }
380
+ for (const firstPinId of firstPinIds) {
381
+ for (const secondPinId of secondPinIds) {
335
382
  const firstPin = this.pinMap.get(firstPinId)
336
383
  const secondPin = this.pinMap.get(secondPinId)
337
384
  if (!firstPin || !secondPin) continue
385
+ if (isDirectConnection && firstPin.chipId === secondPin.chipId)
386
+ continue
338
387
  const perpendicularOffset = getPerpendicularOffset(
339
388
  firstPin,
340
389
  secondPin,
@@ -362,6 +411,11 @@ export class NetLabelToTraceSolver extends BaseSolver {
362
411
  const routeDistance =
363
412
  Math.abs(firstPin.x - secondPin.x) +
364
413
  Math.abs(firstPin.y - secondPin.y)
414
+ let recoveryMode: CandidatePair["recoveryMode"] =
415
+ "routed_components"
416
+ if (isDirectConnection) {
417
+ recoveryMode = "routed_direct_connection"
418
+ }
365
419
  const candidate: CandidatePair = {
366
420
  firstLabel,
367
421
  secondLabel,
@@ -369,8 +423,8 @@ export class NetLabelToTraceSolver extends BaseSolver {
369
423
  perpendicularOffset,
370
424
  routeDistance,
371
425
  key: getCanonicalPairKey(firstPin.pinId, secondPin.pinId),
372
- recoveryMode: "routed_components",
373
- netConnectionPinIds: connection.pinIds,
426
+ recoveryMode,
427
+ netConnectionPinIds: connectionPinIds,
374
428
  }
375
429
  if (
376
430
  !bestCandidate ||
@@ -429,7 +483,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
429
483
  (trace) => !this.isSupersededConnectorTrace(trace, candidate),
430
484
  )
431
485
  let collisionTraces = retainedTraces
432
- if (candidate.recoveryMode === "routed_components") {
486
+ if (candidate.recoveryMode !== "fallback_labels") {
433
487
  collisionTraces = retainedTraces.filter(
434
488
  (trace) =>
435
489
  trace.globalConnNetId !== candidate.firstLabel.globalConnNetId,
@@ -466,7 +520,7 @@ export class NetLabelToTraceSolver extends BaseSolver {
466
520
  }
467
521
 
468
522
  this.outputTraces = [...retainedTraces, recoveredTrace]
469
- if (candidate.recoveryMode === "fallback_labels") {
523
+ if (candidate.recoveryMode !== "routed_components") {
470
524
  this.outputNetLabelPlacements = this.outputNetLabelPlacements.filter(
471
525
  (label) =>
472
526
  label !== candidate.firstLabel && label !== candidate.secondLabel,
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.170",
8
+ "version": "0.0.172",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",