@tscircuit/schematic-trace-solver 0.0.61 → 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.
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";
650
+ type CandidateStatus$2 = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
651
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;
@@ -782,7 +782,7 @@ type LabelOverlap = {
782
782
  firstLabelIndex: number;
783
783
  secondLabelIndex: number;
784
784
  };
785
- type CandidateStatus = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
785
+ type CandidateStatus$1 = "valid" | "chip-collision" | "trace-collision" | "netlabel-collision";
786
786
  type LabelCandidate = {
787
787
  anchorPoint: Point;
788
788
  center: Point;
@@ -792,7 +792,7 @@ type LabelCandidate = {
792
792
  traceId: string;
793
793
  pathDistance: number;
794
794
  distanceFromOriginal: number;
795
- status: CandidateStatus;
795
+ status: CandidateStatus$1;
796
796
  selected: boolean;
797
797
  };
798
798
 
@@ -860,6 +860,65 @@ declare class NetLabelTraceCollisionSolver extends BaseSolver {
860
860
  visualize(): GraphicsObject;
861
861
  }
862
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
+
863
922
  /**
864
923
  * Pipeline solver that runs a series of solvers to find the best schematic layout.
865
924
  * Coordinates the entire layout process from chip partitioning through final packing.
@@ -890,12 +949,13 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
890
949
  vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver;
891
950
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver;
892
951
  netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver;
952
+ netLabelNetLabelCollisionSolver?: NetLabelNetLabelCollisionSolver;
893
953
  startTimeOfPhase: Record<string, number>;
894
954
  endTimeOfPhase: Record<string, number>;
895
955
  timeSpentOnPhase: Record<string, number>;
896
956
  firstIterationOfPhase: Record<string, number>;
897
957
  inputProblem: InputProblem;
898
- 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>)[];
899
959
  constructor(inputProblem: InputProblem);
900
960
  getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
901
961
  currentPipelineStepIndex: number;
package/dist/index.js CHANGED
@@ -7605,6 +7605,338 @@ globalConnNetId: ${label.globalConnNetId}`
7605
7605
  }
7606
7606
  };
7607
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
+
7608
7940
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
7609
7941
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
7610
7942
  return {
@@ -7630,6 +7962,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
7630
7962
  vccNetLabelCornerPlacementSolver;
7631
7963
  traceAnchoredNetLabelOverlapSolver;
7632
7964
  netLabelTraceCollisionSolver;
7965
+ netLabelNetLabelCollisionSolver;
7633
7966
  startTimeOfPhase;
7634
7967
  endTimeOfPhase;
7635
7968
  timeSpentOnPhase;
@@ -7821,6 +8154,17 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
7821
8154
  netLabelPlacements: instance.traceAnchoredNetLabelOverlapSolver.outputNetLabelPlacements
7822
8155
  }
7823
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
+ ]
7824
8168
  )
7825
8169
  ];
7826
8170
  constructor(inputProblem) {