@tscircuit/schematic-trace-solver 0.0.60 → 0.0.62

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 (29) hide show
  1. package/dist/index.d.ts +77 -6
  2. package/dist/index.js +445 -8
  3. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +131 -11
  4. package/lib/solvers/AvailableNetOrientationSolver/geometry.ts +1 -1
  5. package/lib/solvers/AvailableNetOrientationSolver/types.ts +1 -1
  6. package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +441 -0
  7. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +15 -0
  8. package/package.json +1 -1
  9. package/site/examples/example37.page.tsx +4 -0
  10. package/site/examples/example38.page.tsx +4 -0
  11. package/site/examples/example39.page.tsx +4 -0
  12. package/site/examples/example40.page.tsx +4 -0
  13. package/site/examples/example41.page.tsx +4 -0
  14. package/tests/assets/example37.json +173 -0
  15. package/tests/assets/example38.json +32 -0
  16. package/tests/assets/example39.json +71 -0
  17. package/tests/assets/example40.json +157 -0
  18. package/tests/assets/example41.json +81 -0
  19. package/tests/examples/__snapshots__/example04.snap.svg +16 -17
  20. package/tests/examples/__snapshots__/example37.snap.svg +320 -0
  21. package/tests/examples/__snapshots__/example38.snap.svg +77 -0
  22. package/tests/examples/__snapshots__/example39.snap.svg +107 -0
  23. package/tests/examples/__snapshots__/example40.snap.svg +231 -0
  24. package/tests/examples/__snapshots__/example41.snap.svg +119 -0
  25. package/tests/examples/example37.test.ts +12 -0
  26. package/tests/examples/example38.test.ts +12 -0
  27. package/tests/examples/example39.test.ts +12 -0
  28. package/tests/examples/example40.test.ts +12 -0
  29. package/tests/examples/example41.test.ts +12 -0
package/dist/index.d.ts CHANGED
@@ -647,10 +647,10 @@ type CandidateLabel = {
647
647
  width: number;
648
648
  height: number;
649
649
  };
650
- type CandidateStatus$1 = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
651
- type CandidatePhase = "rotate" | "shift";
650
+ type CandidateStatus$2 = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
651
+ type CandidatePhase = "rotate" | "shift" | "lateral-shift";
652
652
  type EvaluatedCandidate = CandidateLabel & {
653
- status: CandidateStatus$1;
653
+ status: CandidateStatus$2;
654
654
  selected: boolean;
655
655
  phase: CandidatePhase;
656
656
  distance?: number;
@@ -688,12 +688,23 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
688
688
  private findValidRotatedCandidate;
689
689
  private getAvailableOrientations;
690
690
  private findValidShiftedCandidate;
691
+ /**
692
+ * When all candidates fail for the current (unshifted) position, try
693
+ * shifting the label anchor laterally — x for y-orientations, y for
694
+ * x-orientations — and re-attempting the required orientation.
695
+ *
696
+ * Offsets are tried in alternating sign order:
697
+ * -1·step, +1·step, -2·step, +2·step, …
698
+ * so the nearest escape routes are tested first.
699
+ */
700
+ private findValidLateralShiftedCandidate;
691
701
  private findValidCandidateInShiftColumn;
692
702
  private evaluateCandidate;
693
703
  private getSearchStartAnchor;
694
704
  private getWickOffsetAnchor;
695
705
  private getSideOffsetAnchor;
696
706
  private getSearchDistanceLimit;
707
+ private getLateralColumnMaxDistance;
697
708
  private createCandidate;
698
709
  private getNetLabelWidth;
699
710
  private getCandidateStatus;
@@ -771,7 +782,7 @@ type LabelOverlap = {
771
782
  firstLabelIndex: number;
772
783
  secondLabelIndex: number;
773
784
  };
774
- type CandidateStatus = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
785
+ type CandidateStatus$1 = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
775
786
  type LabelCandidate = {
776
787
  anchorPoint: Point;
777
788
  center: Point;
@@ -781,7 +792,7 @@ type LabelCandidate = {
781
792
  traceId: string;
782
793
  pathDistance: number;
783
794
  distanceFromOriginal: number;
784
- status: CandidateStatus;
795
+ status: CandidateStatus$1;
785
796
  selected: boolean;
786
797
  };
787
798
 
@@ -849,6 +860,65 @@ declare class NetLabelTraceCollisionSolver extends BaseSolver {
849
860
  visualize(): GraphicsObject;
850
861
  }
851
862
 
863
+ type CandidateStatus = "ok" | "chip-collision" | "trace-collision" | "label-collision";
864
+ type Candidate = {
865
+ orientation: FacingDirection;
866
+ anchor: {
867
+ x: number;
868
+ y: number;
869
+ };
870
+ center: {
871
+ x: number;
872
+ y: number;
873
+ };
874
+ width: number;
875
+ height: number;
876
+ bounds: {
877
+ minX: number;
878
+ minY: number;
879
+ maxX: number;
880
+ maxY: number;
881
+ };
882
+ hostPairId?: MspConnectionPairId;
883
+ hostSegIndex?: number;
884
+ status: CandidateStatus | null;
885
+ };
886
+ interface NetLabelNetLabelCollisionSolverParams {
887
+ inputProblem: InputProblem;
888
+ traces: SolvedTracePath[];
889
+ netLabelPlacements: NetLabelPlacement[];
890
+ }
891
+ declare class NetLabelNetLabelCollisionSolver extends BaseSolver {
892
+ inputProblem: InputProblem;
893
+ traces: SolvedTracePath[];
894
+ netLabelPlacements: NetLabelPlacement[];
895
+ outputNetLabelPlacements: NetLabelPlacement[];
896
+ currentCollision: [NetLabelPlacement, NetLabelPlacement] | null;
897
+ currentLabelToMove: NetLabelPlacement | null;
898
+ candidateResults: Candidate[];
899
+ private chipIndex;
900
+ private traceMap;
901
+ private skippedCollisionKeys;
902
+ private labelsToTry;
903
+ private candidateQueue;
904
+ private candidateIndex;
905
+ constructor(params: NetLabelNetLabelCollisionSolverParams);
906
+ getConstructorParams(): ConstructorParameters<typeof NetLabelNetLabelCollisionSolver>[0];
907
+ getOutput(): {
908
+ netLabelPlacements: NetLabelPlacement[];
909
+ };
910
+ private labelBounds;
911
+ private collisionKey;
912
+ private findNextCollidingPair;
913
+ private netLabelWidthOf;
914
+ private buildCandidatesForLabel;
915
+ private checkCandidate;
916
+ private beginSearchForLabel;
917
+ private clearActiveSearch;
918
+ _step(): void;
919
+ visualize(): GraphicsObject;
920
+ }
921
+
852
922
  /**
853
923
  * Pipeline solver that runs a series of solvers to find the best schematic layout.
854
924
  * Coordinates the entire layout process from chip partitioning through final packing.
@@ -879,12 +949,13 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
879
949
  vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver;
880
950
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver;
881
951
  netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver;
952
+ netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver;
882
953
  startTimeOfPhase: Record<string, number>;
883
954
  endTimeOfPhase: Record<string, number>;
884
955
  timeSpentOnPhase: Record<string, number>;
885
956
  firstIterationOfPhase: Record<string, number>;
886
957
  inputProblem: InputProblem;
887
- pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof VccNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver>)[];
958
+ pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver> | PipelineStep<typeof TraceCleanupSolver> | PipelineStep<typeof Example28Solver> | PipelineStep<typeof AvailableNetOrientationSolver> | PipelineStep<typeof VccNetLabelCornerPlacementSolver> | PipelineStep<typeof TraceAnchoredNetLabelOverlapSolver> | PipelineStep<typeof NetLabelTraceCollisionSolver> | PipelineStep<typeof NetLabelNetLabelCollisionSolver>)[];
888
959
  constructor(inputProblem: InputProblem);
889
960
  getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
890
961
  currentPipelineStepIndex: number;
package/dist/index.js CHANGED
@@ -5873,11 +5873,28 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
5873
5873
  this.addConnectorTrace(label, candidate, labelIndex);
5874
5874
  }
5875
5875
  addConnectorTrace(label, candidate, labelIndex) {
5876
- const tracePath = getConnectorTracePath(
5877
- label.anchorPoint,
5878
- candidate.anchorPoint,
5879
- candidate.orientation
5880
- );
5876
+ let tracePath;
5877
+ if (candidate.phase === "lateral-shift") {
5878
+ const orientDir = dir(candidate.orientation);
5879
+ const kickedSource = {
5880
+ x: label.anchorPoint.x - orientDir.x * LABEL_SEARCH_STEP,
5881
+ y: label.anchorPoint.y - orientDir.y * LABEL_SEARCH_STEP
5882
+ };
5883
+ tracePath = simplifyOrthogonalPath([
5884
+ label.anchorPoint,
5885
+ ...getConnectorTracePath(
5886
+ kickedSource,
5887
+ candidate.anchorPoint,
5888
+ candidate.orientation
5889
+ )
5890
+ ]);
5891
+ } else {
5892
+ tracePath = getConnectorTracePath(
5893
+ label.anchorPoint,
5894
+ candidate.anchorPoint,
5895
+ candidate.orientation
5896
+ );
5897
+ }
5881
5898
  if (tracePath.length < 2) return;
5882
5899
  const mspPairId = `available-net-orientation-${labelIndex}-${label.netId ?? label.globalConnNetId}`;
5883
5900
  const connectorTrace = {
@@ -5910,7 +5927,18 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
5910
5927
  labelIndex,
5911
5928
  orientations
5912
5929
  );
5913
- return rotatedCandidate ?? this.findValidShiftedCandidate(label, orientations[0], labelIndex);
5930
+ if (rotatedCandidate) return rotatedCandidate;
5931
+ const shiftedCandidate = this.findValidShiftedCandidate(
5932
+ label,
5933
+ orientations[0],
5934
+ labelIndex
5935
+ );
5936
+ if (shiftedCandidate) return shiftedCandidate;
5937
+ return this.findValidLateralShiftedCandidate(
5938
+ label,
5939
+ orientations[0],
5940
+ labelIndex
5941
+ );
5914
5942
  }
5915
5943
  findValidRotatedCandidate(label, labelIndex, orientations) {
5916
5944
  for (const orientation of orientations) {
@@ -5964,6 +5992,50 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
5964
5992
  }
5965
5993
  return null;
5966
5994
  }
5995
+ /**
5996
+ * When all candidates fail for the current (unshifted) position, try
5997
+ * shifting the label anchor laterally — x for y-orientations, y for
5998
+ * x-orientations — and re-attempting the required orientation.
5999
+ *
6000
+ * Offsets are tried in alternating sign order:
6001
+ * -1·step, +1·step, -2·step, +2·step, …
6002
+ * so the nearest escape routes are tested first.
6003
+ */
6004
+ findValidLateralShiftedCandidate(label, orientation, labelIndex) {
6005
+ const direction = dir(orientation);
6006
+ const initialBaseAnchor = this.getSearchStartAnchor(label, orientation);
6007
+ const lateralDir = {
6008
+ x: isYOrientation(orientation) ? 1 : 0,
6009
+ y: isXOrientation(orientation) ? 1 : 0
6010
+ };
6011
+ const maxSteps = Math.ceil(this.maxSearchDistance / LABEL_SEARCH_STEP);
6012
+ for (let step = 1; step <= maxSteps; step++) {
6013
+ for (const sign of [-1, 1]) {
6014
+ const lateralOffset = sign * step * LABEL_SEARCH_STEP;
6015
+ const baseAnchor = {
6016
+ x: initialBaseAnchor.x + lateralDir.x * lateralOffset,
6017
+ y: initialBaseAnchor.y + lateralDir.y * lateralOffset
6018
+ };
6019
+ const maxSearchDistance = this.getLateralColumnMaxDistance(
6020
+ label,
6021
+ orientation,
6022
+ baseAnchor
6023
+ );
6024
+ const candidate = this.findValidCandidateInShiftColumn({
6025
+ label,
6026
+ labelIndex,
6027
+ orientation,
6028
+ direction,
6029
+ baseAnchor,
6030
+ maxSearchDistance,
6031
+ outwardDistance: lateralOffset,
6032
+ phase: "lateral-shift"
6033
+ });
6034
+ if (candidate) return candidate;
6035
+ }
6036
+ }
6037
+ return null;
6038
+ }
5967
6039
  findValidCandidateInShiftColumn(params) {
5968
6040
  const {
5969
6041
  label,
@@ -5972,7 +6044,8 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
5972
6044
  direction,
5973
6045
  baseAnchor,
5974
6046
  maxSearchDistance,
5975
- outwardDistance
6047
+ outwardDistance,
6048
+ phase = "shift"
5976
6049
  } = params;
5977
6050
  for (let distance3 = LABEL_SEARCH_STEP; distance3 <= maxSearchDistance + EPS7; distance3 += LABEL_SEARCH_STEP) {
5978
6051
  const anchorPoint = {
@@ -5984,7 +6057,7 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
5984
6057
  candidate,
5985
6058
  label,
5986
6059
  labelIndex,
5987
- "shift",
6060
+ phase,
5988
6061
  distance3,
5989
6062
  outwardDistance
5990
6063
  );
@@ -6065,6 +6138,21 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6065
6138
  const labelLength = orientation === "y+" || orientation === "y-" ? height : width;
6066
6139
  return Math.min(this.maxSearchDistance, labelLength * 2);
6067
6140
  }
6141
+ getLateralColumnMaxDistance(label, orientation, baseAnchor) {
6142
+ const chipId = label.pinIds.map((pid) => this.pinMap[pid]?.chipId).find(Boolean);
6143
+ const chip = chipId ? this.chipObstacleSpatialIndex.chips.find((c) => c.chipId === chipId) : null;
6144
+ if (chip) {
6145
+ if (orientation === "y-")
6146
+ return Math.max(0, baseAnchor.y - chip.bounds.minY);
6147
+ if (orientation === "y+")
6148
+ return Math.max(0, chip.bounds.maxY - baseAnchor.y);
6149
+ if (orientation === "x-")
6150
+ return Math.max(0, baseAnchor.x - chip.bounds.minX);
6151
+ if (orientation === "x+")
6152
+ return Math.max(0, chip.bounds.maxX - baseAnchor.x);
6153
+ }
6154
+ return this.getSearchDistanceLimit(label, orientation);
6155
+ }
6068
6156
  createCandidate(label, anchorPoint, orientation) {
6069
6157
  const { width, height } = getDimsForOrientation({
6070
6158
  orientation,
@@ -6111,6 +6199,11 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
6111
6199
  if (tracePathCrossesAnyTrace(connectorTrace, this.traceMap)) {
6112
6200
  return "trace-collision";
6113
6201
  }
6202
+ for (const chip of this.chipObstacleSpatialIndex.chips) {
6203
+ if (tracePathCrossesAnyBounds(connectorTrace, chip.bounds)) {
6204
+ return "chip-collision";
6205
+ }
6206
+ }
6114
6207
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
6115
6208
  if (i === labelIndex) continue;
6116
6209
  const otherLabel = this.outputNetLabelPlacements[i];
@@ -7512,6 +7605,338 @@ globalConnNetId: ${label.globalConnNetId}`
7512
7605
  }
7513
7606
  };
7514
7607
 
7608
+ // lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts
7609
+ var ANCHOR_TRACE_CLEARANCE = 1e-4;
7610
+ var SEGMENT_PARALLEL_EPS = 1e-6;
7611
+ var CANDIDATE_STEP2 = 0.1;
7612
+ var OUTWARD_DIR = {
7613
+ "x+": { x: 1, y: 0 },
7614
+ "x-": { x: -1, y: 0 },
7615
+ "y+": { x: 0, y: 1 },
7616
+ "y-": { x: 0, y: -1 }
7617
+ };
7618
+ var CANDIDATE_STATUS_COLOR = {
7619
+ ok: "green",
7620
+ "label-collision": "orange",
7621
+ "trace-collision": "darkorange",
7622
+ "chip-collision": "red"
7623
+ };
7624
+ var CANDIDATE_STATUS_FILL = {
7625
+ ok: "rgba(0, 200, 0, 0.25)",
7626
+ "label-collision": "rgba(255, 160, 0, 0.2)",
7627
+ "trace-collision": "rgba(200, 80, 0, 0.2)",
7628
+ "chip-collision": "rgba(220, 0, 0, 0.15)"
7629
+ };
7630
+ function boundsOverlap(a, b) {
7631
+ return a.minX < b.maxX - 1e-9 && a.maxX > b.minX + 1e-9 && a.minY < b.maxY - 1e-9 && a.maxY > b.minY + 1e-9;
7632
+ }
7633
+ function sampleAnchorsAlongSegment(a, b) {
7634
+ const dx = b.x - a.x;
7635
+ const dy = b.y - a.y;
7636
+ const len = Math.sqrt(dx * dx + dy * dy);
7637
+ const steps = Math.max(1, Math.round(len / CANDIDATE_STEP2));
7638
+ const anchors = [];
7639
+ for (let k = 0; k <= steps; k++) {
7640
+ const t = k / steps;
7641
+ anchors.push({ x: a.x + t * dx, y: a.y + t * dy });
7642
+ }
7643
+ return anchors;
7644
+ }
7645
+ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
7646
+ inputProblem;
7647
+ traces;
7648
+ netLabelPlacements;
7649
+ outputNetLabelPlacements;
7650
+ currentCollision = null;
7651
+ currentLabelToMove = null;
7652
+ candidateResults = [];
7653
+ chipIndex;
7654
+ traceMap;
7655
+ skippedCollisionKeys = /* @__PURE__ */ new Set();
7656
+ labelsToTry = [];
7657
+ candidateQueue = [];
7658
+ candidateIndex = 0;
7659
+ constructor(params) {
7660
+ super();
7661
+ this.inputProblem = params.inputProblem;
7662
+ this.traces = params.traces;
7663
+ this.netLabelPlacements = params.netLabelPlacements;
7664
+ this.outputNetLabelPlacements = [...params.netLabelPlacements];
7665
+ this.chipIndex = params.inputProblem._chipObstacleSpatialIndex ?? new ChipObstacleSpatialIndex(params.inputProblem.chips);
7666
+ this.traceMap = Object.fromEntries(
7667
+ params.traces.map((t) => [t.mspPairId, t])
7668
+ );
7669
+ }
7670
+ getConstructorParams() {
7671
+ return {
7672
+ inputProblem: this.inputProblem,
7673
+ traces: this.traces,
7674
+ netLabelPlacements: this.netLabelPlacements
7675
+ };
7676
+ }
7677
+ getOutput() {
7678
+ return { netLabelPlacements: this.outputNetLabelPlacements };
7679
+ }
7680
+ labelBounds(label) {
7681
+ return getRectBounds(label.center, label.width, label.height);
7682
+ }
7683
+ collisionKey(a, b) {
7684
+ return [a.globalConnNetId, b.globalConnNetId].sort().join("::");
7685
+ }
7686
+ findNextCollidingPair() {
7687
+ const labels = this.outputNetLabelPlacements;
7688
+ for (let i = 0; i < labels.length; i++) {
7689
+ for (let j = i + 1; j < labels.length; j++) {
7690
+ const a = labels[i];
7691
+ const b = labels[j];
7692
+ if (a.globalConnNetId === b.globalConnNetId) continue;
7693
+ if (this.skippedCollisionKeys.has(this.collisionKey(a, b))) continue;
7694
+ if (boundsOverlap(this.labelBounds(a), this.labelBounds(b)))
7695
+ return [a, b];
7696
+ }
7697
+ }
7698
+ return null;
7699
+ }
7700
+ netLabelWidthOf(label) {
7701
+ if (label.orientation === "x+" || label.orientation === "x-")
7702
+ return label.width;
7703
+ return label.height;
7704
+ }
7705
+ buildCandidatesForLabel(label) {
7706
+ const netLabelWidth = this.netLabelWidthOf(label);
7707
+ const candidates = [];
7708
+ const buildCandidate = (orientation, anchor, hostPairId, hostSegIndex) => {
7709
+ const { width, height } = getDimsForOrientation({
7710
+ orientation,
7711
+ netLabelWidth
7712
+ });
7713
+ const baseCenter = getCenterFromAnchor(anchor, orientation, width, height);
7714
+ const outwardDir = OUTWARD_DIR[orientation];
7715
+ const center = {
7716
+ x: baseCenter.x + outwardDir.x * ANCHOR_TRACE_CLEARANCE,
7717
+ y: baseCenter.y + outwardDir.y * ANCHOR_TRACE_CLEARANCE
7718
+ };
7719
+ return {
7720
+ orientation,
7721
+ anchor,
7722
+ center,
7723
+ width,
7724
+ height,
7725
+ bounds: getRectBounds(center, width, height),
7726
+ hostPairId,
7727
+ hostSegIndex,
7728
+ status: null
7729
+ };
7730
+ };
7731
+ const isPortOnly = label.mspConnectionPairIds.length === 0;
7732
+ if (isPortOnly) {
7733
+ const allOrientations = ["x+", "x-", "y+", "y-"];
7734
+ const orderedOrientations = [
7735
+ label.orientation,
7736
+ ...allOrientations.filter((o) => o !== label.orientation)
7737
+ ];
7738
+ for (const orientation of orderedOrientations) {
7739
+ candidates.push(buildCandidate(orientation, label.anchorPoint));
7740
+ }
7741
+ } else {
7742
+ for (const mspPairId of label.mspConnectionPairIds) {
7743
+ const trace = this.traceMap[mspPairId];
7744
+ if (!trace) continue;
7745
+ const pts = trace.tracePath;
7746
+ for (let si = 0; si < pts.length - 1; si++) {
7747
+ const segStart = pts[si];
7748
+ const segEnd = pts[si + 1];
7749
+ const isHorizontal3 = Math.abs(segStart.y - segEnd.y) < SEGMENT_PARALLEL_EPS;
7750
+ const isVertical4 = Math.abs(segStart.x - segEnd.x) < SEGMENT_PARALLEL_EPS;
7751
+ if (!isHorizontal3 && !isVertical4) continue;
7752
+ let perpendicularOrientations;
7753
+ if (isHorizontal3) {
7754
+ perpendicularOrientations = ["y+", "y-"];
7755
+ } else {
7756
+ perpendicularOrientations = ["x+", "x-"];
7757
+ }
7758
+ for (const anchor of sampleAnchorsAlongSegment(segStart, segEnd)) {
7759
+ for (const orientation of perpendicularOrientations) {
7760
+ candidates.push(
7761
+ buildCandidate(
7762
+ orientation,
7763
+ anchor,
7764
+ mspPairId,
7765
+ si
7766
+ )
7767
+ );
7768
+ }
7769
+ }
7770
+ }
7771
+ }
7772
+ }
7773
+ return candidates;
7774
+ }
7775
+ checkCandidate(candidate, movingLabelNetId, obstacleLabels) {
7776
+ const { bounds, hostPairId, hostSegIndex } = candidate;
7777
+ if (this.chipIndex.getChipsInBounds(bounds).length > 0)
7778
+ return "chip-collision";
7779
+ if (rectIntersectsAnyTrace(bounds, this.traceMap, hostPairId, hostSegIndex).hasIntersection) {
7780
+ return "trace-collision";
7781
+ }
7782
+ for (const obstacle of obstacleLabels) {
7783
+ if (obstacle.globalConnNetId === movingLabelNetId) continue;
7784
+ if (boundsOverlap(bounds, this.labelBounds(obstacle)))
7785
+ return "label-collision";
7786
+ }
7787
+ return "ok";
7788
+ }
7789
+ beginSearchForLabel(label) {
7790
+ this.currentLabelToMove = label;
7791
+ this.candidateQueue = this.buildCandidatesForLabel(label);
7792
+ this.candidateIndex = 0;
7793
+ this.candidateResults = [];
7794
+ }
7795
+ clearActiveSearch() {
7796
+ this.currentCollision = null;
7797
+ this.currentLabelToMove = null;
7798
+ this.labelsToTry = [];
7799
+ this.candidateQueue = [];
7800
+ this.candidateIndex = 0;
7801
+ this.candidateResults = [];
7802
+ }
7803
+ _step() {
7804
+ if (!this.currentCollision) {
7805
+ const pair = this.findNextCollidingPair();
7806
+ if (!pair) {
7807
+ this.solved = true;
7808
+ return;
7809
+ }
7810
+ this.currentCollision = pair;
7811
+ this.labelsToTry = [pair[1], pair[0]];
7812
+ this.beginSearchForLabel(this.labelsToTry.shift());
7813
+ return;
7814
+ }
7815
+ if (this.candidateIndex >= this.candidateQueue.length) {
7816
+ if (this.labelsToTry.length > 0) {
7817
+ this.beginSearchForLabel(this.labelsToTry.shift());
7818
+ } else {
7819
+ this.skippedCollisionKeys.add(
7820
+ this.collisionKey(this.currentCollision[0], this.currentCollision[1])
7821
+ );
7822
+ this.clearActiveSearch();
7823
+ }
7824
+ return;
7825
+ }
7826
+ const candidate = this.candidateQueue[this.candidateIndex++];
7827
+ const [labelA, labelB] = this.currentCollision;
7828
+ let fixedLabel;
7829
+ if (this.currentLabelToMove === labelB) {
7830
+ fixedLabel = labelA;
7831
+ } else {
7832
+ fixedLabel = labelB;
7833
+ }
7834
+ const obstacleLabels = [
7835
+ ...this.outputNetLabelPlacements.filter(
7836
+ (l) => l !== labelA && l !== labelB
7837
+ ),
7838
+ fixedLabel
7839
+ ];
7840
+ const status = this.checkCandidate(
7841
+ candidate,
7842
+ this.currentLabelToMove.globalConnNetId,
7843
+ obstacleLabels
7844
+ );
7845
+ candidate.status = status;
7846
+ this.candidateResults.push({ ...candidate });
7847
+ if (status === "ok") {
7848
+ const idx = this.outputNetLabelPlacements.indexOf(
7849
+ this.currentLabelToMove
7850
+ );
7851
+ if (idx !== -1) {
7852
+ this.outputNetLabelPlacements[idx] = {
7853
+ ...this.currentLabelToMove,
7854
+ orientation: candidate.orientation,
7855
+ anchorPoint: candidate.anchor,
7856
+ width: candidate.width,
7857
+ height: candidate.height,
7858
+ center: candidate.center
7859
+ };
7860
+ }
7861
+ this.clearActiveSearch();
7862
+ }
7863
+ }
7864
+ visualize() {
7865
+ const graphics = visualizeInputProblem(this.inputProblem);
7866
+ if (!graphics.lines) graphics.lines = [];
7867
+ if (!graphics.rects) graphics.rects = [];
7868
+ if (!graphics.points) graphics.points = [];
7869
+ for (const trace of this.traces) {
7870
+ graphics.lines.push({
7871
+ points: trace.tracePath,
7872
+ strokeColor: "purple"
7873
+ });
7874
+ }
7875
+ for (const label of this.outputNetLabelPlacements) {
7876
+ const isInActiveCollision = this.currentCollision != null && (label === this.currentCollision[0] || label === this.currentCollision[1]);
7877
+ let labelFill;
7878
+ let labelStroke;
7879
+ let labelText;
7880
+ let pointColor;
7881
+ if (isInActiveCollision) {
7882
+ labelFill = "rgba(255, 0, 0, 0.2)";
7883
+ labelStroke = "red";
7884
+ labelText = `netId: ${label.netId}
7885
+ globalConnNetId: ${label.globalConnNetId}
7886
+ \u26A0 COLLIDING`;
7887
+ pointColor = "red";
7888
+ } else {
7889
+ labelFill = getColorFromString(label.globalConnNetId, 0.35);
7890
+ labelStroke = getColorFromString(label.globalConnNetId, 0.9);
7891
+ labelText = `netId: ${label.netId}
7892
+ globalConnNetId: ${label.globalConnNetId}`;
7893
+ pointColor = getColorFromString(label.globalConnNetId, 0.9);
7894
+ }
7895
+ graphics.rects.push({
7896
+ center: label.center,
7897
+ width: label.width,
7898
+ height: label.height,
7899
+ fill: labelFill,
7900
+ strokeColor: labelStroke,
7901
+ label: labelText
7902
+ });
7903
+ graphics.points.push({
7904
+ x: label.anchorPoint.x,
7905
+ y: label.anchorPoint.y,
7906
+ color: pointColor,
7907
+ label: `anchorPoint
7908
+ orientation: ${label.orientation}`
7909
+ });
7910
+ }
7911
+ const movingNetId = this.currentLabelToMove ? this.currentLabelToMove.netId : "?";
7912
+ for (const c of this.candidateResults) {
7913
+ const statusColor = CANDIDATE_STATUS_COLOR[c.status];
7914
+ const statusFill = CANDIDATE_STATUS_FILL[c.status];
7915
+ let strokeDash;
7916
+ if (c.status !== "ok") strokeDash = "4 2";
7917
+ graphics.rects.push({
7918
+ center: c.center,
7919
+ width: c.width,
7920
+ height: c.height,
7921
+ fill: statusFill,
7922
+ strokeColor: statusColor,
7923
+ strokeDash,
7924
+ label: `candidate: ${c.status}
7925
+ orientation: ${c.orientation}
7926
+ moving: ${movingNetId}`
7927
+ });
7928
+ graphics.points.push({
7929
+ x: c.anchor.x,
7930
+ y: c.anchor.y,
7931
+ color: statusColor,
7932
+ label: `candidate anchor
7933
+ ${c.status}`
7934
+ });
7935
+ }
7936
+ return graphics;
7937
+ }
7938
+ };
7939
+
7515
7940
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
7516
7941
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
7517
7942
  return {
@@ -7537,6 +7962,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
7537
7962
  vccNetLabelCornerPlacementSolver;
7538
7963
  traceAnchoredNetLabelOverlapSolver;
7539
7964
  netLabelTraceCollisionSolver;
7965
+ netLabelNetLabelCollisionSolver;
7540
7966
  startTimeOfPhase;
7541
7967
  endTimeOfPhase;
7542
7968
  timeSpentOnPhase;
@@ -7728,6 +8154,17 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
7728
8154
  netLabelPlacements: instance.traceAnchoredNetLabelOverlapSolver.outputNetLabelPlacements
7729
8155
  }
7730
8156
  ]
8157
+ ),
8158
+ definePipelineStep(
8159
+ "netLabelNetLabelCollisionSolver",
8160
+ NetLabelNetLabelCollisionSolver,
8161
+ (instance) => [
8162
+ {
8163
+ inputProblem: instance.inputProblem,
8164
+ traces: instance.netLabelTraceCollisionSolver.getOutput().traces,
8165
+ netLabelPlacements: instance.netLabelTraceCollisionSolver.getOutput().netLabelPlacements
8166
+ }
8167
+ ]
7731
8168
  )
7732
8169
  ];
7733
8170
  constructor(inputProblem) {