@tscircuit/schematic-trace-solver 0.0.185 → 0.0.186

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
@@ -936,6 +936,16 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
936
936
  private hasOppositeVerticalRailOnSameChipSide;
937
937
  private getSingleChipSideForNetConnection;
938
938
  private isPortOnlyLabel;
939
+ private getNetConnectionForLabel;
940
+ private isGroundLabel;
941
+ /**
942
+ * A vertical-only input stores its text extent in netLabelHeight. If its
943
+ * initial port-only placement is horizontal, use that extent for collision
944
+ * checks without changing the placement dimensions returned by the solver.
945
+ */
946
+ private getHorizontalConstraintMismatchBounds;
947
+ private includeHorizontalConstraintMismatches;
948
+ private getLabelBoundsForCollisionWithGround;
939
949
  private shouldIgnorePendingCrowdedLabel;
940
950
  private intersectsAnyOtherNetLabel;
941
951
  private sharesChipBoundary;
package/dist/index.js CHANGED
@@ -9413,6 +9413,17 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9413
9413
  if (this.getTextBlockedMultiPinRailTraces(label, orientations[0])) {
9414
9414
  return true;
9415
9415
  }
9416
+ const hasSingleVerticalOrientation = orientations.length === 1 && isYOrientation(orientations[0]);
9417
+ if (hasSingleVerticalOrientation && this.isGroundLabel(label)) {
9418
+ const bounds2 = getRectBounds(label.center, label.width, label.height);
9419
+ const overlapsHorizontalConstraintMismatch = this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
9420
+ if (otherIndex === labelIndex) return false;
9421
+ if (otherLabel.globalConnNetId === label.globalConnNetId) return false;
9422
+ const otherBounds = this.getHorizontalConstraintMismatchBounds(otherLabel);
9423
+ return otherBounds ? rectsOverlap2(bounds2, otherBounds) : false;
9424
+ });
9425
+ if (overlapsHorizontalConstraintMismatch) return true;
9426
+ }
9416
9427
  if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false;
9417
9428
  const bounds = getRectBounds(label.center, label.width, label.height);
9418
9429
  return this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
@@ -10386,15 +10397,17 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
10386
10397
  return "text-collision";
10387
10398
  }
10388
10399
  }
10389
- for (const i of this.obstacleIndex.getLabelIndicesNearTracePath(
10390
- connectorTrace
10391
- )) {
10400
+ const nearbyLabelIndices = this.includeHorizontalConstraintMismatches(
10401
+ this.obstacleIndex.getLabelIndicesNearTracePath(connectorTrace),
10402
+ this.isGroundLabel(label)
10403
+ );
10404
+ for (const i of nearbyLabelIndices) {
10392
10405
  if (i === labelIndex) continue;
10393
10406
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
10394
10407
  const otherLabel = this.outputNetLabelPlacements[i];
10395
10408
  if (tracePathIntersectsBounds(
10396
10409
  connectorTrace,
10397
- getRectBounds(otherLabel.center, otherLabel.width, otherLabel.height)
10410
+ this.getLabelBoundsForCollisionWithGround(otherLabel, label)
10398
10411
  )) {
10399
10412
  return "netlabel-collision";
10400
10413
  }
@@ -10445,7 +10458,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
10445
10458
  if (this.isTraceTooCloseToLabel(bounds, label)) {
10446
10459
  return "trace-clearance-violation";
10447
10460
  }
10448
- if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
10461
+ if (this.intersectsAnyOtherNetLabel(bounds, labelIndex, label)) {
10449
10462
  return "netlabel-collision";
10450
10463
  }
10451
10464
  return "valid";
@@ -10645,22 +10658,95 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
10645
10658
  isPortOnlyLabel(label) {
10646
10659
  return label.mspConnectionPairIds.length === 0;
10647
10660
  }
10661
+ getNetConnectionForLabel(label) {
10662
+ return label.netId ? this.inputProblem.netConnections.find(
10663
+ (connection) => connection.netId === label.netId
10664
+ ) : this.inputProblem.netConnections.find(
10665
+ (connection) => connection.pinIds.some((pinId) => label.pinIds.includes(pinId))
10666
+ );
10667
+ }
10668
+ isGroundLabel(label) {
10669
+ return this.getNetConnectionForLabel(label)?.isGround === true;
10670
+ }
10671
+ /**
10672
+ * A vertical-only input stores its text extent in netLabelHeight. If its
10673
+ * initial port-only placement is horizontal, use that extent for collision
10674
+ * checks without changing the placement dimensions returned by the solver.
10675
+ */
10676
+ getHorizontalConstraintMismatchBounds(label) {
10677
+ if (!this.isPortOnlyLabel(label)) return null;
10678
+ if (label.orientation !== "x+" && label.orientation !== "x-") return null;
10679
+ const effectiveNetId = label.netId ?? label.globalConnNetId;
10680
+ const orientations = this.inputProblem.availableNetLabelOrientations[effectiveNetId] ?? [];
10681
+ if (orientations.length === 0 || orientations.some((orientation) => !isYOrientation(orientation))) {
10682
+ return null;
10683
+ }
10684
+ const renderedWidth = this.getNetConnectionForLabel(label)?.netLabelHeight;
10685
+ if (renderedWidth === void 0 || renderedWidth <= label.width + label.height + EPS13) {
10686
+ return null;
10687
+ }
10688
+ const placementBaseCenter = getCenterFromAnchor(
10689
+ label.anchorPoint,
10690
+ label.orientation,
10691
+ label.width,
10692
+ label.height
10693
+ );
10694
+ const renderedBaseCenter = getCenterFromAnchor(
10695
+ label.anchorPoint,
10696
+ label.orientation,
10697
+ renderedWidth,
10698
+ label.height
10699
+ );
10700
+ return getRectBounds(
10701
+ {
10702
+ x: renderedBaseCenter.x + label.center.x - placementBaseCenter.x,
10703
+ y: renderedBaseCenter.y + label.center.y - placementBaseCenter.y
10704
+ },
10705
+ renderedWidth,
10706
+ label.height
10707
+ );
10708
+ }
10709
+ includeHorizontalConstraintMismatches(labelIndices, include) {
10710
+ if (!include) return [...labelIndices].sort((a, b) => a - b);
10711
+ const result = new Set(labelIndices);
10712
+ for (let index = 0; index < this.outputNetLabelPlacements.length; index++) {
10713
+ if (this.getHorizontalConstraintMismatchBounds(
10714
+ this.outputNetLabelPlacements[index]
10715
+ )) {
10716
+ result.add(index);
10717
+ }
10718
+ }
10719
+ return [...result].sort((a, b) => a - b);
10720
+ }
10721
+ getLabelBoundsForCollisionWithGround(label, currentLabel) {
10722
+ if (this.isGroundLabel(currentLabel)) {
10723
+ const adjustedBounds = this.getHorizontalConstraintMismatchBounds(label);
10724
+ if (adjustedBounds) return adjustedBounds;
10725
+ }
10726
+ return getRectBounds(label.center, label.width, label.height);
10727
+ }
10648
10728
  shouldIgnorePendingCrowdedLabel(labelIndex, otherLabelIndex) {
10649
10729
  return this.crowdedPortOnlyLabelIndices.has(labelIndex) && this.crowdedPortOnlyLabelIndices.has(otherLabelIndex) && this.queuedLabelIndices.includes(otherLabelIndex);
10650
10730
  }
10651
- intersectsAnyOtherNetLabel(bounds, labelIndex) {
10731
+ intersectsAnyOtherNetLabel(bounds, labelIndex, currentLabel) {
10652
10732
  const searchBounds = {
10653
10733
  minX: bounds.minX - LABEL_SEARCH_STEP,
10654
10734
  minY: bounds.minY - LABEL_SEARCH_STEP,
10655
10735
  maxX: bounds.maxX + LABEL_SEARCH_STEP,
10656
10736
  maxY: bounds.maxY + LABEL_SEARCH_STEP
10657
10737
  };
10658
- const nearbyLabelIndices = this.obstacleIndex.getLabelIndicesInBounds(searchBounds).sort((a, b) => a - b);
10738
+ const nearbyLabelIndices = this.includeHorizontalConstraintMismatches(
10739
+ this.obstacleIndex.getLabelIndicesInBounds(searchBounds),
10740
+ this.isGroundLabel(currentLabel)
10741
+ );
10659
10742
  for (const i of nearbyLabelIndices) {
10660
10743
  if (i === labelIndex) continue;
10661
10744
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
10662
10745
  const label = this.outputNetLabelPlacements[i];
10663
- const otherBounds = getRectBounds(label.center, label.width, label.height);
10746
+ const otherBounds = this.getLabelBoundsForCollisionWithGround(
10747
+ label,
10748
+ currentLabel
10749
+ );
10664
10750
  if (i === this.blockedStandaloneLabelIndex && isYOrientation(label.orientation)) {
10665
10751
  otherBounds.minX -= LABEL_SEARCH_STEP;
10666
10752
  otherBounds.maxX += LABEL_SEARCH_STEP;
@@ -268,6 +268,22 @@ export class AvailableNetOrientationSolver extends BaseSolver {
268
268
  if (this.getTextBlockedMultiPinRailTraces(label, orientations[0]!)) {
269
269
  return true
270
270
  }
271
+
272
+ const hasSingleVerticalOrientation =
273
+ orientations.length === 1 && isYOrientation(orientations[0]!)
274
+ if (hasSingleVerticalOrientation && this.isGroundLabel(label)) {
275
+ const bounds = getRectBounds(label.center, label.width, label.height)
276
+ const overlapsHorizontalConstraintMismatch =
277
+ this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
278
+ if (otherIndex === labelIndex) return false
279
+ if (otherLabel.globalConnNetId === label.globalConnNetId) return false
280
+ const otherBounds =
281
+ this.getHorizontalConstraintMismatchBounds(otherLabel)
282
+ return otherBounds ? rectsOverlap(bounds, otherBounds) : false
283
+ })
284
+ if (overlapsHorizontalConstraintMismatch) return true
285
+ }
286
+
271
287
  if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false
272
288
 
273
289
  const bounds = getRectBounds(label.center, label.width, label.height)
@@ -1627,16 +1643,18 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1627
1643
  }
1628
1644
  }
1629
1645
 
1630
- for (const i of this.obstacleIndex.getLabelIndicesNearTracePath(
1631
- connectorTrace,
1632
- )) {
1646
+ const nearbyLabelIndices = this.includeHorizontalConstraintMismatches(
1647
+ this.obstacleIndex.getLabelIndicesNearTracePath(connectorTrace),
1648
+ this.isGroundLabel(label),
1649
+ )
1650
+ for (const i of nearbyLabelIndices) {
1633
1651
  if (i === labelIndex) continue
1634
1652
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
1635
1653
  const otherLabel = this.outputNetLabelPlacements[i]!
1636
1654
  if (
1637
1655
  tracePathIntersectsBounds(
1638
1656
  connectorTrace,
1639
- getRectBounds(otherLabel.center, otherLabel.width, otherLabel.height),
1657
+ this.getLabelBoundsForCollisionWithGround(otherLabel, label),
1640
1658
  )
1641
1659
  ) {
1642
1660
  return "netlabel-collision"
@@ -1719,7 +1737,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1719
1737
  if (this.isTraceTooCloseToLabel(bounds, label)) {
1720
1738
  return "trace-clearance-violation"
1721
1739
  }
1722
- if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
1740
+ if (this.intersectsAnyOtherNetLabel(bounds, labelIndex, label)) {
1723
1741
  return "netlabel-collision"
1724
1742
  }
1725
1743
  return "valid"
@@ -2020,6 +2038,99 @@ export class AvailableNetOrientationSolver extends BaseSolver {
2020
2038
  return label.mspConnectionPairIds.length === 0
2021
2039
  }
2022
2040
 
2041
+ private getNetConnectionForLabel(label: NetLabelPlacement) {
2042
+ return label.netId
2043
+ ? this.inputProblem.netConnections.find(
2044
+ (connection) => connection.netId === label.netId,
2045
+ )
2046
+ : this.inputProblem.netConnections.find((connection) =>
2047
+ connection.pinIds.some((pinId) => label.pinIds.includes(pinId)),
2048
+ )
2049
+ }
2050
+
2051
+ private isGroundLabel(label: NetLabelPlacement) {
2052
+ return this.getNetConnectionForLabel(label)?.isGround === true
2053
+ }
2054
+
2055
+ /**
2056
+ * A vertical-only input stores its text extent in netLabelHeight. If its
2057
+ * initial port-only placement is horizontal, use that extent for collision
2058
+ * checks without changing the placement dimensions returned by the solver.
2059
+ */
2060
+ private getHorizontalConstraintMismatchBounds(label: NetLabelPlacement) {
2061
+ if (!this.isPortOnlyLabel(label)) return null
2062
+ if (label.orientation !== "x+" && label.orientation !== "x-") return null
2063
+
2064
+ const effectiveNetId = label.netId ?? label.globalConnNetId
2065
+ const orientations =
2066
+ this.inputProblem.availableNetLabelOrientations[effectiveNetId] ?? []
2067
+ if (
2068
+ orientations.length === 0 ||
2069
+ orientations.some((orientation) => !isYOrientation(orientation))
2070
+ ) {
2071
+ return null
2072
+ }
2073
+
2074
+ const renderedWidth = this.getNetConnectionForLabel(label)?.netLabelHeight
2075
+ if (
2076
+ renderedWidth === undefined ||
2077
+ renderedWidth <= label.width + label.height + EPS
2078
+ ) {
2079
+ return null
2080
+ }
2081
+
2082
+ const placementBaseCenter = getCenterFromAnchor(
2083
+ label.anchorPoint,
2084
+ label.orientation,
2085
+ label.width,
2086
+ label.height,
2087
+ )
2088
+ const renderedBaseCenter = getCenterFromAnchor(
2089
+ label.anchorPoint,
2090
+ label.orientation,
2091
+ renderedWidth,
2092
+ label.height,
2093
+ )
2094
+ return getRectBounds(
2095
+ {
2096
+ x: renderedBaseCenter.x + label.center.x - placementBaseCenter.x,
2097
+ y: renderedBaseCenter.y + label.center.y - placementBaseCenter.y,
2098
+ },
2099
+ renderedWidth,
2100
+ label.height,
2101
+ )
2102
+ }
2103
+
2104
+ private includeHorizontalConstraintMismatches(
2105
+ labelIndices: number[],
2106
+ include: boolean,
2107
+ ) {
2108
+ if (!include) return [...labelIndices].sort((a, b) => a - b)
2109
+
2110
+ const result = new Set(labelIndices)
2111
+ for (let index = 0; index < this.outputNetLabelPlacements.length; index++) {
2112
+ if (
2113
+ this.getHorizontalConstraintMismatchBounds(
2114
+ this.outputNetLabelPlacements[index]!,
2115
+ )
2116
+ ) {
2117
+ result.add(index)
2118
+ }
2119
+ }
2120
+ return [...result].sort((a, b) => a - b)
2121
+ }
2122
+
2123
+ private getLabelBoundsForCollisionWithGround(
2124
+ label: NetLabelPlacement,
2125
+ currentLabel: NetLabelPlacement,
2126
+ ) {
2127
+ if (this.isGroundLabel(currentLabel)) {
2128
+ const adjustedBounds = this.getHorizontalConstraintMismatchBounds(label)
2129
+ if (adjustedBounds) return adjustedBounds
2130
+ }
2131
+ return getRectBounds(label.center, label.width, label.height)
2132
+ }
2133
+
2023
2134
  private shouldIgnorePendingCrowdedLabel(
2024
2135
  labelIndex: number,
2025
2136
  otherLabelIndex: number,
@@ -2031,22 +2142,30 @@ export class AvailableNetOrientationSolver extends BaseSolver {
2031
2142
  )
2032
2143
  }
2033
2144
 
2034
- private intersectsAnyOtherNetLabel(bounds: Bounds, labelIndex: number) {
2145
+ private intersectsAnyOtherNetLabel(
2146
+ bounds: Bounds,
2147
+ labelIndex: number,
2148
+ currentLabel: NetLabelPlacement,
2149
+ ) {
2035
2150
  const searchBounds = {
2036
2151
  minX: bounds.minX - LABEL_SEARCH_STEP,
2037
2152
  minY: bounds.minY - LABEL_SEARCH_STEP,
2038
2153
  maxX: bounds.maxX + LABEL_SEARCH_STEP,
2039
2154
  maxY: bounds.maxY + LABEL_SEARCH_STEP,
2040
2155
  }
2041
- const nearbyLabelIndices = this.obstacleIndex
2042
- .getLabelIndicesInBounds(searchBounds)
2043
- .sort((a, b) => a - b)
2156
+ const nearbyLabelIndices = this.includeHorizontalConstraintMismatches(
2157
+ this.obstacleIndex.getLabelIndicesInBounds(searchBounds),
2158
+ this.isGroundLabel(currentLabel),
2159
+ )
2044
2160
 
2045
2161
  for (const i of nearbyLabelIndices) {
2046
2162
  if (i === labelIndex) continue
2047
2163
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
2048
2164
  const label = this.outputNetLabelPlacements[i]!
2049
- const otherBounds = getRectBounds(label.center, label.width, label.height)
2165
+ const otherBounds = this.getLabelBoundsForCollisionWithGround(
2166
+ label,
2167
+ currentLabel,
2168
+ )
2050
2169
  if (
2051
2170
  i === this.blockedStandaloneLabelIndex &&
2052
2171
  isYOrientation(label.orientation)
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.185",
8
+ "version": "0.0.186",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",