@tscircuit/schematic-trace-solver 0.0.118 → 0.0.120

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
@@ -721,12 +721,13 @@ interface AvailableNetOrientationSolverParams {
721
721
  type CandidateLabel = {
722
722
  orientation: FacingDirection;
723
723
  anchorPoint: Point;
724
+ connectorSource?: Point;
724
725
  center: Point;
725
726
  width: number;
726
727
  height: number;
727
728
  };
728
729
  type CandidateStatus$2 = "valid" | "chip-collision" | "text-collision" | "trace-collision" | "trace-clearance-violation" | "netlabel-collision";
729
- type CandidatePhase = "rotate" | "trace-anchor" | "shift" | "lateral-shift";
730
+ type CandidatePhase = "rotate" | "trace-anchor" | "outward-trace-anchor" | "shift" | "lateral-shift";
730
731
  type EvaluatedCandidate = CandidateLabel & {
731
732
  status: CandidateStatus$2;
732
733
  selected: boolean;
@@ -768,6 +769,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
768
769
  private hasTraceContinuingInOrientation;
769
770
  private findValidRotatedCandidate;
770
771
  private findValidTraceAnchorCandidate;
772
+ private findValidOutwardTraceAnchorCandidate;
771
773
  private getTraceAnchorCandidatePoints;
772
774
  private getAvailableOrientations;
773
775
  private findValidShiftedCandidate;
@@ -801,6 +803,10 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
801
803
  private getLabelTraceClearanceBounds;
802
804
  private shouldCheckTraceClearanceForLabel;
803
805
  private hasRoutedLabelOnSameNet;
806
+ private isOutwardHorizontalFallback;
807
+ private hasPortOnlyLabelOnSameNet;
808
+ private hasOppositeVerticalRailOnSameChipSide;
809
+ private getSingleChipSideForNetConnection;
804
810
  private isPortOnlyLabel;
805
811
  private intersectsAnyOtherNetLabel;
806
812
  private sharesChipBoundary;
package/dist/index.js CHANGED
@@ -7995,9 +7995,20 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
7995
7995
  findCorrectedCandidate(label, labelIndex) {
7996
7996
  const orientations = this.getAvailableOrientations(label);
7997
7997
  const requiredOrientation = orientations[0];
7998
- const isTwoPinNet = this.inputProblem.netConnections.some(
7999
- (connection) => connection.netId === label.netId && connection.pinIds.length === 2
7998
+ const netConnection = this.inputProblem.netConnections.find(
7999
+ (connection) => connection.netId === label.netId
8000
8000
  );
8001
+ const isTwoPinNet = netConnection?.pinIds.length === 2;
8002
+ const isDistanceSplitVerticalRail = (netConnection?.pinIds.length ?? 0) > 2 && isYOrientation(requiredOrientation) && this.hasPortOnlyLabelOnSameNet(label);
8003
+ const isPairedSameSidePowerRail = isYOrientation(requiredOrientation) && this.hasOppositeVerticalRailOnSameChipSide(label, requiredOrientation);
8004
+ if (orientations.length === 1 && isYOrientation(requiredOrientation) && this.isOutwardHorizontalFallback(label) && this.hasTraceContinuingInOrientation(label, requiredOrientation) && (isDistanceSplitVerticalRail || isPairedSameSidePowerRail)) {
8005
+ const traceAnchorCandidate2 = this.findValidOutwardTraceAnchorCandidate(
8006
+ label,
8007
+ requiredOrientation,
8008
+ labelIndex
8009
+ );
8010
+ if (traceAnchorCandidate2) return traceAnchorCandidate2;
8011
+ }
8001
8012
  if (isTwoPinNet && orientations.length === 1 && isYOrientation(requiredOrientation) && this.hasTraceContinuingInOrientation(label, requiredOrientation)) {
8002
8013
  const alignedCandidate = this.findValidCandidateInShiftColumn({
8003
8014
  label,
@@ -8099,6 +8110,72 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8099
8110
  }
8100
8111
  return null;
8101
8112
  }
8113
+ findValidOutwardTraceAnchorCandidate(label, orientation, labelIndex) {
8114
+ const direction = dir(orientation);
8115
+ const candidatePoints = this.getTraceAnchorCandidatePoints(label).sort(
8116
+ (a, b) => {
8117
+ const aAlongDirection = a.x * direction.x + a.y * direction.y;
8118
+ const bAlongDirection = b.x * direction.x + b.y * direction.y;
8119
+ return bAlongDirection - aAlongDirection;
8120
+ }
8121
+ );
8122
+ for (const connectorSource of candidatePoints) {
8123
+ const preservedColumnAnchor = this.getSearchStartAnchor(
8124
+ label,
8125
+ orientation
8126
+ );
8127
+ const preservedColumnCandidate = this.createCandidate(
8128
+ label,
8129
+ {
8130
+ x: preservedColumnAnchor.x,
8131
+ y: connectorSource.y
8132
+ },
8133
+ orientation,
8134
+ connectorSource
8135
+ );
8136
+ const preservedColumnResult = this.evaluateCandidate(
8137
+ preservedColumnCandidate,
8138
+ label,
8139
+ labelIndex,
8140
+ "outward-trace-anchor"
8141
+ );
8142
+ this.currentCandidateResults.push(preservedColumnResult);
8143
+ if (preservedColumnResult.status === "valid") {
8144
+ preservedColumnResult.selected = true;
8145
+ return preservedColumnResult;
8146
+ }
8147
+ const outwardDirection = this.getPerpendicularOutwardDirection(
8148
+ connectorSource,
8149
+ orientation
8150
+ );
8151
+ if (outwardDirection.x === 0 && outwardDirection.y === 0) continue;
8152
+ for (let distance5 = LABEL_SEARCH_STEP; distance5 <= this.maxSearchDistance + EPS11; distance5 += LABEL_SEARCH_STEP) {
8153
+ const anchorPoint = {
8154
+ x: connectorSource.x + outwardDirection.x * distance5,
8155
+ y: connectorSource.y + outwardDirection.y * distance5
8156
+ };
8157
+ const candidate = this.createCandidate(
8158
+ label,
8159
+ anchorPoint,
8160
+ orientation,
8161
+ connectorSource
8162
+ );
8163
+ const result = this.evaluateCandidate(
8164
+ candidate,
8165
+ label,
8166
+ labelIndex,
8167
+ "outward-trace-anchor",
8168
+ distance5
8169
+ );
8170
+ this.currentCandidateResults.push(result);
8171
+ if (result.status === "valid") {
8172
+ result.selected = true;
8173
+ return result;
8174
+ }
8175
+ }
8176
+ }
8177
+ return null;
8178
+ }
8102
8179
  getTraceAnchorCandidatePoints(label) {
8103
8180
  const seen = /* @__PURE__ */ new Set();
8104
8181
  const points = [];
@@ -8256,7 +8333,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8256
8333
  ]);
8257
8334
  }
8258
8335
  return getConnectorTracePath(
8259
- label.anchorPoint,
8336
+ candidate.connectorSource ?? label.anchorPoint,
8260
8337
  candidate.anchorPoint,
8261
8338
  candidate.orientation
8262
8339
  );
@@ -8390,7 +8467,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8390
8467
  }
8391
8468
  return this.getSearchDistanceLimit(label, orientation);
8392
8469
  }
8393
- createCandidate(label, anchorPoint, orientation) {
8470
+ createCandidate(label, anchorPoint, orientation, connectorSource) {
8394
8471
  const { width, height } = getDimsForOrientation({
8395
8472
  orientation,
8396
8473
  netLabelWidth: this.getNetLabelWidth(label),
@@ -8399,6 +8476,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8399
8476
  return {
8400
8477
  orientation,
8401
8478
  anchorPoint,
8479
+ connectorSource,
8402
8480
  width,
8403
8481
  height,
8404
8482
  center: getCenterFromAnchor(anchorPoint, orientation, width, height)
@@ -8552,6 +8630,59 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8552
8630
  }
8553
8631
  return false;
8554
8632
  }
8633
+ isOutwardHorizontalFallback(label) {
8634
+ const chipSide = this.getChipSideForPoint(label.anchorPoint);
8635
+ return chipSide === "left" && label.orientation === "x-" || chipSide === "right" && label.orientation === "x+";
8636
+ }
8637
+ hasPortOnlyLabelOnSameNet(label) {
8638
+ return this.outputNetLabelPlacements.some(
8639
+ (otherLabel) => otherLabel !== label && otherLabel.globalConnNetId === label.globalConnNetId && this.isPortOnlyLabel(otherLabel)
8640
+ );
8641
+ }
8642
+ hasOppositeVerticalRailOnSameChipSide(label, orientation) {
8643
+ const currentConnection = this.inputProblem.netConnections.find(
8644
+ (connection) => connection.netId === label.netId
8645
+ );
8646
+ if (!currentConnection) return false;
8647
+ const currentSide = this.getSingleChipSideForNetConnection(currentConnection);
8648
+ if (!currentSide) return false;
8649
+ const oppositeOrientation = orientation === "y+" ? "y-" : "y+";
8650
+ return this.inputProblem.netConnections.some((connection) => {
8651
+ if (connection === currentConnection || connection.pinIds.length < 2) {
8652
+ return false;
8653
+ }
8654
+ const availableOrientations = this.inputProblem.availableNetLabelOrientations[connection.netId] ?? [];
8655
+ if (availableOrientations.length !== 1 || availableOrientations[0] !== oppositeOrientation) {
8656
+ return false;
8657
+ }
8658
+ const connectionSide = this.getSingleChipSideForNetConnection(connection);
8659
+ return connectionSide?.chipId === currentSide.chipId && connectionSide.side === currentSide.side;
8660
+ });
8661
+ }
8662
+ getSingleChipSideForNetConnection(connection) {
8663
+ if (connection.pinIds.length < 2) return null;
8664
+ const pins = connection.pinIds.map((pinId) => this.pinMap[pinId]);
8665
+ if (pins.some((pin) => !pin)) return null;
8666
+ const chipIds = new Set(pins.map((pin) => pin.chipId));
8667
+ if (chipIds.size !== 1) return null;
8668
+ const chipId = pins[0].chipId;
8669
+ const chip = this.chipObstacleSpatialIndex.chips.find(
8670
+ (candidate) => candidate.chipId === chipId
8671
+ );
8672
+ if (!chip) return null;
8673
+ const sides = new Set(
8674
+ pins.map((pin) => {
8675
+ if (Math.abs(pin.x - chip.bounds.minX) <= EPS11) return "left";
8676
+ if (Math.abs(pin.x - chip.bounds.maxX) <= EPS11) return "right";
8677
+ return null;
8678
+ })
8679
+ );
8680
+ if (sides.size !== 1 || sides.has(null)) return null;
8681
+ return {
8682
+ chipId,
8683
+ side: [...sides][0]
8684
+ };
8685
+ }
8555
8686
  isPortOnlyLabel(label) {
8556
8687
  return label.mspConnectionPairIds.length === 0;
8557
8688
  }
@@ -9,7 +9,11 @@ import {
9
9
  getRectBounds,
10
10
  } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
11
11
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
12
- import type { InputPin, InputProblem } from "lib/types/InputProblem"
12
+ import type {
13
+ InputNetConnection,
14
+ InputPin,
15
+ InputProblem,
16
+ } from "lib/types/InputProblem"
13
17
  import { dir, type FacingDirection } from "lib/utils/dir"
14
18
  import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
15
19
  import { EPS, LABEL_SEARCH_STEP, WICK_CLEARANCE } from "./constants"
@@ -250,10 +254,35 @@ export class AvailableNetOrientationSolver extends BaseSolver {
250
254
  private findCorrectedCandidate(label: NetLabelPlacement, labelIndex: number) {
251
255
  const orientations = this.getAvailableOrientations(label)
252
256
  const requiredOrientation = orientations[0]!
253
- const isTwoPinNet = this.inputProblem.netConnections.some(
254
- (connection) =>
255
- connection.netId === label.netId && connection.pinIds.length === 2,
257
+ const netConnection = this.inputProblem.netConnections.find(
258
+ (connection) => connection.netId === label.netId,
256
259
  )
260
+ const isTwoPinNet = netConnection?.pinIds.length === 2
261
+ const isDistanceSplitVerticalRail =
262
+ (netConnection?.pinIds.length ?? 0) > 2 &&
263
+ isYOrientation(requiredOrientation) &&
264
+ this.hasPortOnlyLabelOnSameNet(label)
265
+ const isPairedSameSidePowerRail =
266
+ isYOrientation(requiredOrientation) &&
267
+ this.hasOppositeVerticalRailOnSameChipSide(label, requiredOrientation)
268
+ if (
269
+ orientations.length === 1 &&
270
+ isYOrientation(requiredOrientation) &&
271
+ this.isOutwardHorizontalFallback(label) &&
272
+ this.hasTraceContinuingInOrientation(label, requiredOrientation) &&
273
+ (isDistanceSplitVerticalRail || isPairedSameSidePowerRail)
274
+ ) {
275
+ // Keep the established outward column, but attach at the furthest trace
276
+ // point in the required vertical direction. This places y+ labels above
277
+ // a rail and y- labels below it while using a short horizontal connector.
278
+ const traceAnchorCandidate = this.findValidOutwardTraceAnchorCandidate(
279
+ label,
280
+ requiredOrientation,
281
+ labelIndex,
282
+ )
283
+ if (traceAnchorCandidate) return traceAnchorCandidate
284
+ }
285
+
257
286
  if (
258
287
  isTwoPinNet &&
259
288
  orientations.length === 1 &&
@@ -390,6 +419,86 @@ export class AvailableNetOrientationSolver extends BaseSolver {
390
419
  return null
391
420
  }
392
421
 
422
+ private findValidOutwardTraceAnchorCandidate(
423
+ label: NetLabelPlacement,
424
+ orientation: FacingDirection,
425
+ labelIndex: number,
426
+ ) {
427
+ const direction = dir(orientation)
428
+ const candidatePoints = this.getTraceAnchorCandidatePoints(label).sort(
429
+ (a, b) => {
430
+ const aAlongDirection = a.x * direction.x + a.y * direction.y
431
+ const bAlongDirection = b.x * direction.x + b.y * direction.y
432
+ return bAlongDirection - aAlongDirection
433
+ },
434
+ )
435
+
436
+ for (const connectorSource of candidatePoints) {
437
+ const preservedColumnAnchor = this.getSearchStartAnchor(
438
+ label,
439
+ orientation,
440
+ )
441
+ const preservedColumnCandidate = this.createCandidate(
442
+ label,
443
+ {
444
+ x: preservedColumnAnchor.x,
445
+ y: connectorSource.y,
446
+ },
447
+ orientation,
448
+ connectorSource,
449
+ )
450
+ const preservedColumnResult = this.evaluateCandidate(
451
+ preservedColumnCandidate,
452
+ label,
453
+ labelIndex,
454
+ "outward-trace-anchor",
455
+ )
456
+ this.currentCandidateResults.push(preservedColumnResult)
457
+ if (preservedColumnResult.status === "valid") {
458
+ preservedColumnResult.selected = true
459
+ return preservedColumnResult
460
+ }
461
+
462
+ const outwardDirection = this.getPerpendicularOutwardDirection(
463
+ connectorSource,
464
+ orientation,
465
+ )
466
+ if (outwardDirection.x === 0 && outwardDirection.y === 0) continue
467
+
468
+ for (
469
+ let distance = LABEL_SEARCH_STEP;
470
+ distance <= this.maxSearchDistance + EPS;
471
+ distance += LABEL_SEARCH_STEP
472
+ ) {
473
+ const anchorPoint = {
474
+ x: connectorSource.x + outwardDirection.x * distance,
475
+ y: connectorSource.y + outwardDirection.y * distance,
476
+ }
477
+ const candidate = this.createCandidate(
478
+ label,
479
+ anchorPoint,
480
+ orientation,
481
+ connectorSource,
482
+ )
483
+ const result = this.evaluateCandidate(
484
+ candidate,
485
+ label,
486
+ labelIndex,
487
+ "outward-trace-anchor",
488
+ distance,
489
+ )
490
+ this.currentCandidateResults.push(result)
491
+
492
+ if (result.status === "valid") {
493
+ result.selected = true
494
+ return result
495
+ }
496
+ }
497
+ }
498
+
499
+ return null
500
+ }
501
+
393
502
  private getTraceAnchorCandidatePoints(label: NetLabelPlacement) {
394
503
  const seen = new Set<string>()
395
504
  const points: Point[] = []
@@ -592,7 +701,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
592
701
  label: NetLabelPlacement,
593
702
  candidate: Pick<
594
703
  EvaluatedCandidate,
595
- "anchorPoint" | "orientation" | "phase"
704
+ "anchorPoint" | "connectorSource" | "orientation" | "phase"
596
705
  >,
597
706
  ) {
598
707
  if (candidate.phase === "lateral-shift") {
@@ -612,7 +721,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
612
721
  }
613
722
 
614
723
  return getConnectorTracePath(
615
- label.anchorPoint,
724
+ candidate.connectorSource ?? label.anchorPoint,
616
725
  candidate.anchorPoint,
617
726
  candidate.orientation,
618
727
  )
@@ -810,6 +919,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
810
919
  label: NetLabelPlacement,
811
920
  anchorPoint: Point,
812
921
  orientation: FacingDirection,
922
+ connectorSource?: Point,
813
923
  ): CandidateLabel {
814
924
  const { width, height } = getDimsForOrientation({
815
925
  orientation,
@@ -819,6 +929,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
819
929
  return {
820
930
  orientation,
821
931
  anchorPoint,
932
+ connectorSource,
822
933
  width,
823
934
  height,
824
935
  center: getCenterFromAnchor(anchorPoint, orientation, width, height),
@@ -1029,6 +1140,89 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1029
1140
  return false
1030
1141
  }
1031
1142
 
1143
+ private isOutwardHorizontalFallback(label: NetLabelPlacement) {
1144
+ const chipSide = this.getChipSideForPoint(label.anchorPoint)
1145
+ return (
1146
+ (chipSide === "left" && label.orientation === "x-") ||
1147
+ (chipSide === "right" && label.orientation === "x+")
1148
+ )
1149
+ }
1150
+
1151
+ private hasPortOnlyLabelOnSameNet(label: NetLabelPlacement) {
1152
+ return this.outputNetLabelPlacements.some(
1153
+ (otherLabel) =>
1154
+ otherLabel !== label &&
1155
+ otherLabel.globalConnNetId === label.globalConnNetId &&
1156
+ this.isPortOnlyLabel(otherLabel),
1157
+ )
1158
+ }
1159
+
1160
+ private hasOppositeVerticalRailOnSameChipSide(
1161
+ label: NetLabelPlacement,
1162
+ orientation: "y+" | "y-",
1163
+ ) {
1164
+ const currentConnection = this.inputProblem.netConnections.find(
1165
+ (connection) => connection.netId === label.netId,
1166
+ )
1167
+ if (!currentConnection) return false
1168
+
1169
+ const currentSide =
1170
+ this.getSingleChipSideForNetConnection(currentConnection)
1171
+ if (!currentSide) return false
1172
+
1173
+ const oppositeOrientation = orientation === "y+" ? "y-" : "y+"
1174
+ return this.inputProblem.netConnections.some((connection) => {
1175
+ if (connection === currentConnection || connection.pinIds.length < 2) {
1176
+ return false
1177
+ }
1178
+ const availableOrientations =
1179
+ this.inputProblem.availableNetLabelOrientations[connection.netId] ?? []
1180
+ if (
1181
+ availableOrientations.length !== 1 ||
1182
+ availableOrientations[0] !== oppositeOrientation
1183
+ ) {
1184
+ return false
1185
+ }
1186
+
1187
+ const connectionSide = this.getSingleChipSideForNetConnection(connection)
1188
+ return (
1189
+ connectionSide?.chipId === currentSide.chipId &&
1190
+ connectionSide.side === currentSide.side
1191
+ )
1192
+ })
1193
+ }
1194
+
1195
+ private getSingleChipSideForNetConnection(
1196
+ connection: InputNetConnection,
1197
+ ): { chipId: string; side: "left" | "right" } | null {
1198
+ if (connection.pinIds.length < 2) return null
1199
+
1200
+ const pins = connection.pinIds.map((pinId) => this.pinMap[pinId])
1201
+ if (pins.some((pin) => !pin)) return null
1202
+ const chipIds = new Set(pins.map((pin) => pin!.chipId))
1203
+ if (chipIds.size !== 1) return null
1204
+
1205
+ const chipId = pins[0]!.chipId
1206
+ const chip = this.chipObstacleSpatialIndex.chips.find(
1207
+ (candidate) => candidate.chipId === chipId,
1208
+ )
1209
+ if (!chip) return null
1210
+
1211
+ const sides = new Set(
1212
+ pins.map((pin) => {
1213
+ if (Math.abs(pin!.x - chip.bounds.minX) <= EPS) return "left"
1214
+ if (Math.abs(pin!.x - chip.bounds.maxX) <= EPS) return "right"
1215
+ return null
1216
+ }),
1217
+ )
1218
+ if (sides.size !== 1 || sides.has(null)) return null
1219
+
1220
+ return {
1221
+ chipId,
1222
+ side: [...sides][0] as "left" | "right",
1223
+ }
1224
+ }
1225
+
1032
1226
  private isPortOnlyLabel(label: NetLabelPlacement) {
1033
1227
  return label.mspConnectionPairIds.length === 0
1034
1228
  }
@@ -22,6 +22,7 @@ export type ChipSide = "left" | "right" | "top" | "bottom"
22
22
  export type CandidateLabel = {
23
23
  orientation: FacingDirection
24
24
  anchorPoint: Point
25
+ connectorSource?: Point
25
26
  center: Point
26
27
  width: number
27
28
  height: number
@@ -38,6 +39,7 @@ export type CandidateStatus =
38
39
  export type CandidatePhase =
39
40
  | "rotate"
40
41
  | "trace-anchor"
42
+ | "outward-trace-anchor"
41
43
  | "shift"
42
44
  | "lateral-shift"
43
45
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.118",
4
+ "version": "0.0.120",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",