@tscircuit/schematic-trace-solver 0.0.130 → 0.0.131

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
@@ -779,6 +779,7 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
779
779
  currentLabelIndex: number | null;
780
780
  currentLabel: NetLabelPlacement | null;
781
781
  currentCandidateResults: EvaluatedCandidate[];
782
+ private crowdedPortOnlyLabelIndices;
782
783
  blockedStandaloneLabelIndex: number | null;
783
784
  blockedStandaloneLabelTargetAnchorX: number | null;
784
785
  private traceMap;
@@ -793,13 +794,16 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
793
794
  netLabelPlacements: NetLabelPlacement[];
794
795
  };
795
796
  private getProcessableLabelIndices;
797
+ private sortCrowdedTopBankByPin;
796
798
  private shouldProcessLabel;
799
+ private getCrowdedPortOnlyLabelIndices;
797
800
  private processLabel;
798
801
  private applyCandidate;
799
802
  private addConnectorTrace;
800
803
  private finish;
801
804
  private setCurrentLabel;
802
805
  private findCorrectedCandidate;
806
+ private findValidCrowdedTopVerticalFanoutCandidate;
803
807
  private hasTraceContinuingInOrientation;
804
808
  private findValidRotatedCandidate;
805
809
  private findValidTraceAnchorCandidate;
@@ -842,10 +846,12 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
842
846
  private hasOppositeVerticalRailOnSameChipSide;
843
847
  private getSingleChipSideForNetConnection;
844
848
  private isPortOnlyLabel;
849
+ private shouldIgnorePendingCrowdedLabel;
845
850
  private intersectsAnyOtherNetLabel;
846
851
  private sharesChipBoundary;
847
852
  private getChipSideForPoint;
848
853
  private getPerpendicularOutwardDirection;
854
+ private getChipOutwardDirection;
849
855
  private getContainingChipSide;
850
856
  private getOutsideChipSide;
851
857
  private getNearestChipSide;
package/dist/index.js CHANGED
@@ -8062,6 +8062,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8062
8062
  currentLabelIndex = null;
8063
8063
  currentLabel = null;
8064
8064
  currentCandidateResults = [];
8065
+ crowdedPortOnlyLabelIndices = /* @__PURE__ */ new Set();
8065
8066
  blockedStandaloneLabelIndex = null;
8066
8067
  blockedStandaloneLabelTargetAnchorX = null;
8067
8068
  traceMap;
@@ -8080,6 +8081,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8080
8081
  this.pinMap = getPinMap(params.inputProblem);
8081
8082
  this.chipObstacleSpatialIndex = params.inputProblem._chipObstacleSpatialIndex ?? new ChipObstacleSpatialIndex(params.inputProblem.chips);
8082
8083
  this.maxSearchDistance = getMaxSearchDistance(params.inputProblem);
8084
+ this.crowdedPortOnlyLabelIndices = this.getCrowdedPortOnlyLabelIndices();
8083
8085
  this.queuedLabelIndices = this.getProcessableLabelIndices();
8084
8086
  this.setCurrentLabel(this.queuedLabelIndices[0] ?? null);
8085
8087
  }
@@ -8107,10 +8109,11 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8107
8109
  getProcessableLabelIndices() {
8108
8110
  const indices = [];
8109
8111
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
8110
- if (this.shouldProcessLabel(this.outputNetLabelPlacements[i])) {
8112
+ if (this.shouldProcessLabel(this.outputNetLabelPlacements[i], i)) {
8111
8113
  indices.push(i);
8112
8114
  }
8113
8115
  }
8116
+ this.sortCrowdedTopBankByPin(indices);
8114
8117
  const blockedStandaloneLabelIndex = indices.find((index) => {
8115
8118
  const label = this.outputNetLabelPlacements[index];
8116
8119
  const orientations = this.getAvailableOrientations(label);
@@ -8159,9 +8162,87 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8159
8162
  }
8160
8163
  return indices;
8161
8164
  }
8162
- shouldProcessLabel(label) {
8165
+ sortCrowdedTopBankByPin(indices) {
8166
+ const slotsByChip = /* @__PURE__ */ new Map();
8167
+ for (let slot = 0; slot < indices.length; slot++) {
8168
+ const labelIndex = indices[slot];
8169
+ if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) continue;
8170
+ const label = this.outputNetLabelPlacements[labelIndex];
8171
+ if (this.getChipSideForPoint(label.anchorPoint) !== "top") continue;
8172
+ const pin = this.pinMap[label.pinIds[0]];
8173
+ if (!pin) continue;
8174
+ const slots = slotsByChip.get(pin.chipId) ?? [];
8175
+ slots.push(slot);
8176
+ slotsByChip.set(pin.chipId, slots);
8177
+ }
8178
+ for (const slots of slotsByChip.values()) {
8179
+ const labelIndices = slots.map((slot) => indices[slot]).sort((a, b) => {
8180
+ const aPin = this.pinMap[this.outputNetLabelPlacements[a].pinIds[0]];
8181
+ const bPin = this.pinMap[this.outputNetLabelPlacements[b].pinIds[0]];
8182
+ return aPin.x - bPin.x;
8183
+ });
8184
+ for (let position = 0; position < slots.length; position++) {
8185
+ indices[slots[position]] = labelIndices[position];
8186
+ }
8187
+ }
8188
+ }
8189
+ shouldProcessLabel(label, labelIndex) {
8163
8190
  const orientations = this.getAvailableOrientations(label);
8164
- return orientations.length > 0 && !orientations.includes(label.orientation);
8191
+ if (orientations.length === 0) return false;
8192
+ if (!orientations.includes(label.orientation)) return true;
8193
+ if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false;
8194
+ const bounds = getRectBounds(label.center, label.width, label.height);
8195
+ return this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
8196
+ if (otherIndex === labelIndex) return false;
8197
+ if (otherLabel.globalConnNetId === label.globalConnNetId) return false;
8198
+ return rectsOverlap2(
8199
+ bounds,
8200
+ getRectBounds(otherLabel.center, otherLabel.width, otherLabel.height)
8201
+ );
8202
+ });
8203
+ }
8204
+ getCrowdedPortOnlyLabelIndices() {
8205
+ const labelIndicesByChipAndSide = /* @__PURE__ */ new Map();
8206
+ for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
8207
+ const label = this.outputNetLabelPlacements[i];
8208
+ if (!this.isPortOnlyLabel(label)) continue;
8209
+ if (!this.isStandaloneSinglePinNetLabel(label)) continue;
8210
+ if (this.getAvailableOrientations(label).length === 0) continue;
8211
+ const pin = this.pinMap[label.pinIds[0]];
8212
+ if (!pin) continue;
8213
+ const chipSide = this.getChipSideForPoint(label.anchorPoint);
8214
+ if (!chipSide) continue;
8215
+ const labelIndicesBySide = labelIndicesByChipAndSide.get(pin.chipId) ?? /* @__PURE__ */ new Map();
8216
+ const labelIndices = labelIndicesBySide.get(chipSide) ?? [];
8217
+ labelIndices.push(i);
8218
+ labelIndicesBySide.set(chipSide, labelIndices);
8219
+ labelIndicesByChipAndSide.set(pin.chipId, labelIndicesBySide);
8220
+ }
8221
+ const crowdedIndices = /* @__PURE__ */ new Set();
8222
+ for (const labelIndicesBySide of labelIndicesByChipAndSide.values()) {
8223
+ for (const labelIndices of labelIndicesBySide.values()) {
8224
+ const hasWrongOrientation = labelIndices.some((index) => {
8225
+ const label = this.outputNetLabelPlacements[index];
8226
+ return !this.getAvailableOrientations(label).includes(
8227
+ label.orientation
8228
+ );
8229
+ });
8230
+ const hasOverlap = labelIndices.some((index, position) => {
8231
+ const label = this.outputNetLabelPlacements[index];
8232
+ const bounds = getRectBounds(label.center, label.width, label.height);
8233
+ return labelIndices.slice(position + 1).some((otherIndex) => {
8234
+ const other = this.outputNetLabelPlacements[otherIndex];
8235
+ return rectsOverlap2(
8236
+ bounds,
8237
+ getRectBounds(other.center, other.width, other.height)
8238
+ );
8239
+ });
8240
+ });
8241
+ if (!hasWrongOrientation || !hasOverlap) continue;
8242
+ for (const index of labelIndices) crowdedIndices.add(index);
8243
+ }
8244
+ }
8245
+ return crowdedIndices;
8165
8246
  }
8166
8247
  processLabel(labelIndex) {
8167
8248
  const label = this.outputNetLabelPlacements[labelIndex];
@@ -8180,7 +8261,11 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8180
8261
  }
8181
8262
  addConnectorTrace(label, candidate, labelIndex) {
8182
8263
  if (candidate.phase === "trace-anchor") return;
8183
- const tracePath = this.getCandidateConnectorTrace(label, candidate);
8264
+ const tracePath = this.getCandidateConnectorTrace(
8265
+ label,
8266
+ candidate,
8267
+ labelIndex
8268
+ );
8184
8269
  if (tracePath.length < 2) return;
8185
8270
  const mspPairId = `available-net-orientation-${labelIndex}-${label.netId ?? label.globalConnNetId}`;
8186
8271
  const connectorTrace = {
@@ -8209,6 +8294,14 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8209
8294
  findCorrectedCandidate(label, labelIndex) {
8210
8295
  const orientations = this.getAvailableOrientations(label);
8211
8296
  const requiredOrientation = orientations[0];
8297
+ if (this.crowdedPortOnlyLabelIndices.has(labelIndex) && this.getChipSideForPoint(label.anchorPoint) === "top" && isYOrientation(requiredOrientation)) {
8298
+ const topFanoutCandidate = this.findValidCrowdedTopVerticalFanoutCandidate(
8299
+ label,
8300
+ labelIndex,
8301
+ requiredOrientation
8302
+ );
8303
+ if (topFanoutCandidate) return topFanoutCandidate;
8304
+ }
8212
8305
  const netConnection = this.inputProblem.netConnections.find(
8213
8306
  (connection) => connection.netId === label.netId
8214
8307
  );
@@ -8263,6 +8356,77 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8263
8356
  labelIndex
8264
8357
  );
8265
8358
  }
8359
+ findValidCrowdedTopVerticalFanoutCandidate(label, labelIndex, orientation) {
8360
+ const pin = this.pinMap[label.pinIds[0]];
8361
+ const chip = pin ? this.chipObstacleSpatialIndex.chips.find(
8362
+ (candidate) => candidate.chipId === pin.chipId
8363
+ ) : void 0;
8364
+ if (!chip) return null;
8365
+ const { width, height } = getDimsForOrientation({
8366
+ orientation,
8367
+ netLabelWidth: this.getNetLabelWidth(label),
8368
+ netLabelHeight: this.getNetLabelHeight(label)
8369
+ });
8370
+ const nextPinToRight = this.outputNetLabelPlacements.map((otherLabel, index) => ({
8371
+ index,
8372
+ pin: this.pinMap[otherLabel.pinIds[0]]
8373
+ })).filter(
8374
+ ({ index, pin: otherPin }) => this.crowdedPortOnlyLabelIndices.has(index) && otherPin?.chipId === pin.chipId && otherPin.x > pin.x + EPS11
8375
+ ).sort((a, b) => a.pin.x - b.pin.x)[0]?.pin;
8376
+ if (orientation === "y-" && nextPinToRight) return null;
8377
+ const anchorX = orientation === "y-" ? Math.max(
8378
+ label.anchorPoint.x + LABEL_SEARCH_STEP,
8379
+ chip.bounds.maxX + width / 2 + LABEL_TRACE_CLEARANCE2
8380
+ ) : Math.min(
8381
+ label.anchorPoint.x,
8382
+ (nextPinToRight?.x ?? chip.bounds.maxX) - LABEL_SEARCH_STEP - width / 2
8383
+ );
8384
+ let highestPlacedLabelY = chip.bounds.maxY;
8385
+ let lowestUpFacingLabelY = Number.POSITIVE_INFINITY;
8386
+ let highestUpFacingLabelY = Number.NEGATIVE_INFINITY;
8387
+ for (let index = 0; index < this.outputNetLabelPlacements.length; index++) {
8388
+ if (index === labelIndex) continue;
8389
+ if (!this.crowdedPortOnlyLabelIndices.has(index)) continue;
8390
+ if (this.queuedLabelIndices.includes(index)) continue;
8391
+ const placedLabel = this.outputNetLabelPlacements[index];
8392
+ const placedBounds = getRectBounds(
8393
+ placedLabel.center,
8394
+ placedLabel.width,
8395
+ placedLabel.height
8396
+ );
8397
+ highestPlacedLabelY = Math.max(highestPlacedLabelY, placedBounds.maxY);
8398
+ if (placedLabel.orientation !== "y+") continue;
8399
+ lowestUpFacingLabelY = Math.min(lowestUpFacingLabelY, placedBounds.minY);
8400
+ highestUpFacingLabelY = Math.max(highestUpFacingLabelY, placedBounds.maxY);
8401
+ }
8402
+ const stackedAnchorY = highestPlacedLabelY + LABEL_SEARCH_STEP + (orientation === "y-" ? height : 0);
8403
+ const hasUpFacingStack = Number.isFinite(lowestUpFacingLabelY) && Number.isFinite(highestUpFacingLabelY);
8404
+ const centeredAnchorY = hasUpFacingStack ? (lowestUpFacingLabelY + highestUpFacingLabelY) / 2 : stackedAnchorY;
8405
+ const anchorYs = orientation === "y-" ? [centeredAnchorY, stackedAnchorY] : [stackedAnchorY];
8406
+ for (const anchorY of anchorYs) {
8407
+ if (anchorY - label.anchorPoint.y > this.maxSearchDistance + EPS11) {
8408
+ continue;
8409
+ }
8410
+ const candidate = this.createCandidate(
8411
+ label,
8412
+ { x: anchorX, y: anchorY },
8413
+ orientation
8414
+ );
8415
+ const result = this.evaluateCandidate(
8416
+ candidate,
8417
+ label,
8418
+ labelIndex,
8419
+ "lateral-shift",
8420
+ anchorY - label.anchorPoint.y,
8421
+ anchorX - label.anchorPoint.x
8422
+ );
8423
+ this.currentCandidateResults.push(result);
8424
+ if (result.status !== "valid") continue;
8425
+ result.selected = true;
8426
+ return result;
8427
+ }
8428
+ return null;
8429
+ }
8266
8430
  hasTraceContinuingInOrientation(label, orientation) {
8267
8431
  const direction = dir(orientation);
8268
8432
  return Object.values(this.traceMap).some((trace) => {
@@ -8530,12 +8694,22 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8530
8694
  })
8531
8695
  };
8532
8696
  }
8533
- getCandidateConnectorTrace(label, candidate) {
8697
+ getCandidateConnectorTrace(label, candidate, labelIndex) {
8698
+ if (this.crowdedPortOnlyLabelIndices.has(labelIndex) && this.getChipSideForPoint(label.anchorPoint) === "top" && isYOrientation(candidate.orientation)) {
8699
+ return simplifyOrthogonalPath([
8700
+ label.anchorPoint,
8701
+ { x: label.anchorPoint.x, y: candidate.anchorPoint.y },
8702
+ candidate.anchorPoint
8703
+ ]);
8704
+ }
8534
8705
  if (candidate.phase === "lateral-shift") {
8535
- const orientDir = dir(candidate.orientation);
8706
+ const chipOutwardDir = this.crowdedPortOnlyLabelIndices.has(labelIndex) ? this.getChipOutwardDirection(label.anchorPoint) : (() => {
8707
+ const orientDir = dir(candidate.orientation);
8708
+ return { x: -orientDir.x, y: -orientDir.y };
8709
+ })();
8536
8710
  const kickedSource = {
8537
- x: label.anchorPoint.x - orientDir.x * LABEL_SEARCH_STEP,
8538
- y: label.anchorPoint.y - orientDir.y * LABEL_SEARCH_STEP
8711
+ x: label.anchorPoint.x + chipOutwardDir.x * LABEL_SEARCH_STEP,
8712
+ y: label.anchorPoint.y + chipOutwardDir.y * LABEL_SEARCH_STEP
8539
8713
  };
8540
8714
  return simplifyOrthogonalPath([
8541
8715
  label.anchorPoint,
@@ -8750,11 +8924,15 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8750
8924
  });
8751
8925
  if (nonChipBoundsStatus !== "valid") return nonChipBoundsStatus;
8752
8926
  }
8753
- const connectorTrace = this.getCandidateConnectorTrace(label, {
8754
- anchorPoint: candidate.anchorPoint,
8755
- orientation: candidate.orientation,
8756
- phase
8757
- });
8927
+ const connectorTrace = this.getCandidateConnectorTrace(
8928
+ label,
8929
+ {
8930
+ anchorPoint: candidate.anchorPoint,
8931
+ orientation: candidate.orientation,
8932
+ phase
8933
+ },
8934
+ labelIndex
8935
+ );
8758
8936
  for (const chip of this.chipObstacleSpatialIndex.chips) {
8759
8937
  if (tracePathCrossesAnyBounds(connectorTrace, chip.bounds)) {
8760
8938
  return "chip-collision";
@@ -8762,6 +8940,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8762
8940
  }
8763
8941
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
8764
8942
  if (i === labelIndex) continue;
8943
+ if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
8765
8944
  const otherLabel = this.outputNetLabelPlacements[i];
8766
8945
  if (tracePathIntersectsBounds(
8767
8946
  connectorTrace,
@@ -8900,9 +9079,13 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8900
9079
  isPortOnlyLabel(label) {
8901
9080
  return label.mspConnectionPairIds.length === 0;
8902
9081
  }
9082
+ shouldIgnorePendingCrowdedLabel(labelIndex, otherLabelIndex) {
9083
+ return this.crowdedPortOnlyLabelIndices.has(labelIndex) && this.crowdedPortOnlyLabelIndices.has(otherLabelIndex) && this.queuedLabelIndices.includes(otherLabelIndex);
9084
+ }
8903
9085
  intersectsAnyOtherNetLabel(bounds, labelIndex) {
8904
9086
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
8905
9087
  if (i === labelIndex) continue;
9088
+ if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue;
8906
9089
  const label = this.outputNetLabelPlacements[i];
8907
9090
  const otherBounds = getRectBounds(label.center, label.width, label.height);
8908
9091
  if (i === this.blockedStandaloneLabelIndex && isYOrientation(label.orientation)) {
@@ -8960,6 +9143,20 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8960
9143
  }
8961
9144
  return { x: 0, y: 0 };
8962
9145
  }
9146
+ getChipOutwardDirection(point) {
9147
+ switch (this.getChipSideForPoint(point)) {
9148
+ case "left":
9149
+ return { x: -1, y: 0 };
9150
+ case "right":
9151
+ return { x: 1, y: 0 };
9152
+ case "bottom":
9153
+ return { x: 0, y: -1 };
9154
+ case "top":
9155
+ return { x: 0, y: 1 };
9156
+ default:
9157
+ return { x: 0, y: 0 };
9158
+ }
9159
+ }
8963
9160
  getContainingChipSide(point) {
8964
9161
  let nearestSide = null;
8965
9162
  let nearestDistance = Number.POSITIVE_INFINITY;