@tscircuit/schematic-trace-solver 0.0.52 → 0.0.53

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 +0 -3
  2. package/dist/index.js +135 -156
  3. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +3 -39
  4. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +2 -0
  5. package/lib/solvers/SchematicTracePipelineSolver/colorAvailableNetOrientationLabels.ts +54 -0
  6. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver.ts +6 -6
  7. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/candidates.ts +63 -105
  8. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/geometry.ts +4 -0
  9. package/lib/solvers/TraceAnchoredNetLabelOverlapSolver/visualize.ts +38 -4
  10. package/package.json +1 -1
  11. package/tests/assets/example30.json +4 -1
  12. package/tests/examples/__snapshots__/example01.snap.svg +6 -3
  13. package/tests/examples/__snapshots__/example02.snap.svg +8 -4
  14. package/tests/examples/__snapshots__/example04.snap.svg +4 -2
  15. package/tests/examples/__snapshots__/example07.snap.svg +10 -5
  16. package/tests/examples/__snapshots__/example12.snap.svg +8 -4
  17. package/tests/examples/__snapshots__/example13.snap.svg +16 -8
  18. package/tests/examples/__snapshots__/example14.snap.svg +16 -8
  19. package/tests/examples/__snapshots__/example15.snap.svg +18 -9
  20. package/tests/examples/__snapshots__/example16.snap.svg +8 -4
  21. package/tests/examples/__snapshots__/example17.snap.svg +9 -5
  22. package/tests/examples/__snapshots__/example18.snap.svg +10 -5
  23. package/tests/examples/__snapshots__/example20.snap.svg +6 -3
  24. package/tests/examples/__snapshots__/example21.snap.svg +17 -12
  25. package/tests/examples/__snapshots__/example22.snap.svg +8 -4
  26. package/tests/examples/__snapshots__/example25.snap.svg +18 -9
  27. package/tests/examples/__snapshots__/example30.snap.svg +74 -62
  28. package/tests/examples/__snapshots__/example31.snap.svg +2 -1
  29. package/tests/fixtures/matcher.ts +21 -0
package/dist/index.d.ts CHANGED
@@ -691,11 +691,8 @@ declare class AvailableNetOrientationSolver extends BaseSolver {
691
691
  private getSearchDistanceLimit;
692
692
  private createCandidate;
693
693
  private getNetLabelWidth;
694
- private isLabelPlacementValid;
695
- private isCandidateValidWithOffset;
696
694
  private getCandidateStatus;
697
695
  private getBoundsStatus;
698
- private getOffsetCollisionBounds;
699
696
  private intersectsAnyOtherNetLabel;
700
697
  private sharesChipBoundary;
701
698
  private getChipSideForPoint;
package/dist/index.js CHANGED
@@ -2273,6 +2273,44 @@ orientation: ${p.orientation}`
2273
2273
  }
2274
2274
  };
2275
2275
 
2276
+ // lib/solvers/SchematicTracePipelineSolver/colorAvailableNetOrientationLabels.ts
2277
+ var AVAILABLE_Y_PLUS_COLOR = "#93c5fd";
2278
+ var AVAILABLE_Y_MINUS_COLOR = "#fca5a5";
2279
+ var AVAILABLE_ORIENTATION_FILL_ALPHA = "66";
2280
+ var colorAvailableNetOrientationLabels = (graphicsObject, inputProblem) => {
2281
+ const availableOrientations = inputProblem.availableNetLabelOrientations;
2282
+ if (!availableOrientations) return;
2283
+ for (const rect of graphicsObject.rects ?? []) {
2284
+ const orientations = getAvailableOrientationsForRect(
2285
+ rect.label,
2286
+ availableOrientations
2287
+ );
2288
+ if (!orientations) continue;
2289
+ const hasYPlus = orientations.includes("y+");
2290
+ const hasYMinus = orientations.includes("y-");
2291
+ if (hasYPlus === hasYMinus) continue;
2292
+ const color = hasYPlus ? AVAILABLE_Y_PLUS_COLOR : AVAILABLE_Y_MINUS_COLOR;
2293
+ rect.fill = `${color}${AVAILABLE_ORIENTATION_FILL_ALPHA}`;
2294
+ rect.stroke = color;
2295
+ rect.color = color;
2296
+ }
2297
+ };
2298
+ var getAvailableOrientationsForRect = (label, availableOrientations) => {
2299
+ for (const netId of getNetIdsFromRectLabel(label)) {
2300
+ if (Object.hasOwn(availableOrientations, netId)) {
2301
+ return availableOrientations[netId];
2302
+ }
2303
+ }
2304
+ return void 0;
2305
+ };
2306
+ var getNetIdsFromRectLabel = (label) => {
2307
+ if (!label) return [];
2308
+ return [
2309
+ label.match(/^netId: (.+)$/m)?.[1],
2310
+ label.match(/^globalConnNetId: (.+)$/m)?.[1]
2311
+ ].filter((netId) => Boolean(netId && netId !== "undefined"));
2312
+ };
2313
+
2276
2314
  // lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/groupLabelsByChipAndOrientation.ts
2277
2315
  var groupLabelsByChipAndOrientation = ({
2278
2316
  labels,
@@ -5641,16 +5679,15 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
5641
5679
  getProcessableLabelIndices() {
5642
5680
  const indices = [];
5643
5681
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
5644
- if (this.shouldProcessLabel(this.outputNetLabelPlacements[i], i)) {
5682
+ if (this.shouldProcessLabel(this.outputNetLabelPlacements[i])) {
5645
5683
  indices.push(i);
5646
5684
  }
5647
5685
  }
5648
5686
  return indices;
5649
5687
  }
5650
- shouldProcessLabel(label, labelIndex) {
5688
+ shouldProcessLabel(label) {
5651
5689
  const orientations = this.getAvailableOrientations(label);
5652
- if (orientations.length === 0) return false;
5653
- return !(orientations.includes(label.orientation) && this.isLabelPlacementValid(label, labelIndex));
5690
+ return orientations.length > 0 && !orientations.includes(label.orientation);
5654
5691
  }
5655
5692
  processLabel(labelIndex) {
5656
5693
  const label = this.outputNetLabelPlacements[labelIndex];
@@ -5879,22 +5916,6 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
5879
5916
  (connection) => connection.netId === label.netId
5880
5917
  )?.netLabelWidth;
5881
5918
  }
5882
- isLabelPlacementValid(label, labelIndex) {
5883
- return this.isCandidateValidWithOffset(
5884
- {
5885
- orientation: label.orientation,
5886
- anchorPoint: label.anchorPoint,
5887
- center: label.center,
5888
- width: label.width,
5889
- height: label.height
5890
- },
5891
- labelIndex
5892
- );
5893
- }
5894
- isCandidateValidWithOffset(candidate, labelIndex) {
5895
- const bounds = this.getOffsetCollisionBounds(candidate);
5896
- return this.getBoundsStatus(bounds, labelIndex) === "valid";
5897
- }
5898
5919
  getCandidateStatus(candidate, label, labelIndex) {
5899
5920
  const boundsStatus = this.getBoundsStatus(
5900
5921
  getRectBounds(candidate.center, candidate.width, candidate.height),
@@ -5928,15 +5949,6 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
5928
5949
  }
5929
5950
  return "valid";
5930
5951
  }
5931
- getOffsetCollisionBounds(candidate) {
5932
- const direction = dir(candidate.orientation);
5933
- const collisionTestOffset = 1e-4;
5934
- const testCenter = {
5935
- x: candidate.center.x + direction.x * collisionTestOffset,
5936
- y: candidate.center.y + direction.y * collisionTestOffset
5937
- };
5938
- return getRectBounds(testCenter, candidate.width, candidate.height);
5939
- }
5940
5952
  intersectsAnyOtherNetLabel(bounds, labelIndex) {
5941
5953
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
5942
5954
  if (i === labelIndex) continue;
@@ -6367,6 +6379,7 @@ var EPS8 = 1e-6;
6367
6379
  var TRACE_BOUNDARY_TOLERANCE2 = 1e-6;
6368
6380
  var getLabelBounds = (label) => getRectBounds(label.center, label.width, label.height);
6369
6381
  var rectsOverlap3 = (a, b) => Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS8 && Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS8;
6382
+ var rectsTouchOrOverlap = (a, b) => Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) >= -EPS8 && Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) >= -EPS8;
6370
6383
  var traceCrossesBoundsInterior2 = (bounds, traces) => {
6371
6384
  for (const trace of traces) {
6372
6385
  const points = trace.tracePath;
@@ -6439,41 +6452,7 @@ var getTraceVertexDistances = (trace) => {
6439
6452
  }
6440
6453
  return distances;
6441
6454
  };
6442
- var getTraceDistanceCompatibleOrientations = (trace, distance3) => {
6443
- const orientations = /* @__PURE__ */ new Set();
6444
- let pathDistance = 0;
6445
- for (let i = 0; i < trace.tracePath.length - 1; i++) {
6446
- const start = trace.tracePath[i];
6447
- const end = trace.tracePath[i + 1];
6448
- const segmentLength = getManhattanDistance(start, end);
6449
- const nextDistance = pathDistance + segmentLength;
6450
- if (distance3 >= pathDistance - EPS8 && distance3 <= nextDistance + EPS8) {
6451
- addSegmentCompatibleOrientations(orientations, trace, i);
6452
- if (Math.abs(distance3 - pathDistance) <= EPS8) {
6453
- addSegmentCompatibleOrientations(orientations, trace, i - 1);
6454
- }
6455
- if (Math.abs(distance3 - nextDistance) <= EPS8) {
6456
- addSegmentCompatibleOrientations(orientations, trace, i + 1);
6457
- }
6458
- return orientations;
6459
- }
6460
- pathDistance = nextDistance;
6461
- }
6462
- return orientations;
6463
- };
6464
6455
  var getManhattanDistance = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
6465
- var addSegmentCompatibleOrientations = (orientations, trace, segmentIndex) => {
6466
- const start = trace.tracePath[segmentIndex];
6467
- const end = trace.tracePath[segmentIndex + 1];
6468
- if (!start || !end) return;
6469
- if (isHorizontal2(start, end)) {
6470
- orientations.add("y+");
6471
- orientations.add("y-");
6472
- } else if (isVertical3(start, end)) {
6473
- orientations.add("x+");
6474
- orientations.add("x-");
6475
- }
6476
- };
6477
6456
  var segmentCrossesBoundsInterior2 = (start, end, bounds) => {
6478
6457
  const interior = {
6479
6458
  minX: bounds.minX + TRACE_BOUNDARY_TOLERANCE2,
@@ -6533,25 +6512,18 @@ var generateCandidatesAlongTrace = (params) => {
6533
6512
  const traceLength = getTraceLength(traceLocation.trace);
6534
6513
  const orientationConstraint = getOrientationConstraint(inputProblem, label);
6535
6514
  const candidateDistances = getCandidateDistances(
6536
- traceLocation,
6537
6515
  traceLength,
6538
- getTraceVertexDistances(traceLocation.trace),
6539
- orientationConstraint
6516
+ getTraceVertexDistances(traceLocation.trace)
6540
6517
  );
6541
6518
  const netLabelWidth = getNetLabelWidth(inputProblem, label);
6542
6519
  const candidates = [];
6543
6520
  const seenCandidateKeys = /* @__PURE__ */ new Set();
6544
6521
  for (const pathDistance of candidateDistances) {
6545
6522
  const point = getPointAtTraceDistance(traceLocation.trace, pathDistance);
6546
- const compatibleOrientations = getTraceDistanceCompatibleOrientations(
6547
- traceLocation.trace,
6548
- pathDistance
6549
- );
6550
6523
  const orientations = getOrientationsForPoint({
6551
6524
  inputProblem,
6552
6525
  label,
6553
6526
  point,
6554
- compatibleOrientations,
6555
6527
  orientationConstraint
6556
6528
  });
6557
6529
  for (const orientation of orientations) {
@@ -6573,11 +6545,9 @@ var generateCandidatesAlongTrace = (params) => {
6573
6545
  );
6574
6546
  }
6575
6547
  }
6576
- return candidates.sort(
6577
- (a, b) => a.distanceFromOriginal - b.distanceFromOriginal || a.pathDistance - b.pathDistance
6578
- );
6548
+ return candidates;
6579
6549
  };
6580
- var getCandidateDistances = (traceLocation, traceLength, vertexDistances, orientationConstraint) => {
6550
+ var getCandidateDistances = (traceLength, vertexDistances) => {
6581
6551
  const distances = /* @__PURE__ */ new Set();
6582
6552
  const maxSteps = Math.ceil(traceLength / CANDIDATE_STEP);
6583
6553
  for (let i = 0; i <= maxSteps; i++) {
@@ -6587,35 +6557,17 @@ var getCandidateDistances = (traceLocation, traceLength, vertexDistances, orient
6587
6557
  for (const distance3 of vertexDistances) {
6588
6558
  distances.add(roundDistance(distance3));
6589
6559
  }
6590
- distances.add(roundDistance(traceLocation.distance));
6591
- return [...distances].filter((distance3) => distance3 >= -EPS8 && distance3 <= traceLength + EPS8).filter(
6592
- (distance3) => isDistanceAllowedByOrientationConstraint(
6593
- traceLocation,
6594
- distance3,
6595
- orientationConstraint
6596
- )
6597
- ).sort(
6598
- (a, b) => Math.abs(a - traceLocation.distance) - Math.abs(b - traceLocation.distance) || a - b
6599
- );
6560
+ return [...distances].filter((distance3) => distance3 >= -EPS8 && distance3 <= traceLength + EPS8).sort((a, b) => a - b);
6600
6561
  };
6601
6562
  var getOrientationsForPoint = (params) => {
6602
- const {
6603
- inputProblem,
6604
- label,
6605
- point,
6606
- compatibleOrientations,
6607
- orientationConstraint
6608
- } = params;
6563
+ const { inputProblem, label, point, orientationConstraint } = params;
6609
6564
  if (orientationConstraint) {
6610
- return orientationConstraint.filter(
6611
- (orientation) => compatibleOrientations.has(orientation)
6612
- );
6565
+ return orientationConstraint;
6613
6566
  }
6614
6567
  return getUnconstrainedOrientations({
6615
6568
  label,
6616
6569
  inputProblem,
6617
- point,
6618
- compatibleOrientations
6570
+ point
6619
6571
  });
6620
6572
  };
6621
6573
  var createCandidate = (params) => {
@@ -6645,23 +6597,18 @@ var createCandidate = (params) => {
6645
6597
  };
6646
6598
  };
6647
6599
  var getOrientationConstraint = (inputProblem, label) => {
6600
+ const availableOrientations = inputProblem.availableNetLabelOrientations ?? {};
6648
6601
  for (const netId of getOrientationConstraintKeys(inputProblem, label)) {
6649
- if (Object.hasOwn(inputProblem.availableNetLabelOrientations, netId)) {
6650
- return inputProblem.availableNetLabelOrientations[netId] ?? [];
6602
+ if (Object.hasOwn(availableOrientations, netId)) {
6603
+ return dedupeOrientations(
6604
+ (availableOrientations[netId] ?? []).map(normalizeFacingDirection).filter(
6605
+ (orientation) => orientation !== void 0
6606
+ )
6607
+ );
6651
6608
  }
6652
6609
  }
6653
6610
  return null;
6654
6611
  };
6655
- var isDistanceAllowedByOrientationConstraint = (traceLocation, distance3, constrainedOrientations) => {
6656
- if (!constrainedOrientations) return true;
6657
- const compatibleOrientations = getTraceDistanceCompatibleOrientations(
6658
- traceLocation.trace,
6659
- distance3
6660
- );
6661
- return constrainedOrientations.some(
6662
- (orientation) => compatibleOrientations.has(orientation)
6663
- );
6664
- };
6665
6612
  var getOrientationConstraintKeys = (inputProblem, label) => dedupeStrings([
6666
6613
  label.netId,
6667
6614
  label.globalConnNetId,
@@ -6670,55 +6617,49 @@ var getOrientationConstraintKeys = (inputProblem, label) => dedupeStrings([
6670
6617
  ).map((connection) => connection.netId)
6671
6618
  ]);
6672
6619
  var getUnconstrainedOrientations = (params) => {
6673
- const { label, inputProblem, point, compatibleOrientations } = params;
6620
+ const { label, inputProblem, point } = params;
6674
6621
  return dedupeOrientations([
6675
- ...getInitialOrientations(label, compatibleOrientations),
6622
+ ...getInitialOrientations(label),
6676
6623
  ...getOutwardOrientations({
6677
6624
  inputProblem,
6678
- point,
6679
- compatibleOrientations
6625
+ point
6680
6626
  }),
6681
- ...ALL_ORIENTATIONS.filter(
6682
- (orientation) => compatibleOrientations.has(orientation)
6683
- )
6627
+ ...ALL_ORIENTATIONS
6684
6628
  ]);
6685
6629
  };
6686
- var getInitialOrientations = (label, compatibleOrientations) => [label.orientation, getFlippedOrientation(label.orientation)].filter(
6687
- (orientation) => compatibleOrientations.has(orientation)
6688
- );
6630
+ var getInitialOrientations = (label) => [
6631
+ label.orientation,
6632
+ getFlippedOrientation(label.orientation)
6633
+ ];
6689
6634
  var getOutwardOrientations = (params) => {
6690
- const { inputProblem, point, compatibleOrientations } = params;
6635
+ const { inputProblem, point } = params;
6691
6636
  const chipBounds = getChipBounds(inputProblem);
6692
6637
  return dedupeOrientations(
6693
- getOutwardOrientationOptions(point, chipBounds, compatibleOrientations).filter(({ orientation }) => compatibleOrientations.has(orientation)).sort((a, b) => b.outwardScore - a.outwardScore).map(({ orientation }) => orientation)
6638
+ getOutwardOrientationOptions(point, chipBounds).sort((a, b) => b.outwardScore - a.outwardScore).map(({ orientation }) => orientation)
6694
6639
  );
6695
6640
  };
6696
- var getOutwardOrientationOptions = (point, chipBounds, compatibleOrientations) => {
6641
+ var getOutwardOrientationOptions = (point, chipBounds) => {
6697
6642
  const options = [];
6698
- if (compatibleOrientations.has("y+") || compatibleOrientations.has("y-")) {
6699
- options.push(
6700
- getOutwardAxisOption({
6701
- value: point.y,
6702
- min: chipBounds.minY,
6703
- max: chipBounds.maxY,
6704
- center: chipBounds.centerY,
6705
- positiveOrientation: "y+",
6706
- negativeOrientation: "y-"
6707
- })
6708
- );
6709
- }
6710
- if (compatibleOrientations.has("x+") || compatibleOrientations.has("x-")) {
6711
- options.push(
6712
- getOutwardAxisOption({
6713
- value: point.x,
6714
- min: chipBounds.minX,
6715
- max: chipBounds.maxX,
6716
- center: chipBounds.centerX,
6717
- positiveOrientation: "x+",
6718
- negativeOrientation: "x-"
6719
- })
6720
- );
6721
- }
6643
+ options.push(
6644
+ getOutwardAxisOption({
6645
+ value: point.y,
6646
+ min: chipBounds.minY,
6647
+ max: chipBounds.maxY,
6648
+ center: chipBounds.centerY,
6649
+ positiveOrientation: "y+",
6650
+ negativeOrientation: "y-"
6651
+ })
6652
+ );
6653
+ options.push(
6654
+ getOutwardAxisOption({
6655
+ value: point.x,
6656
+ min: chipBounds.minX,
6657
+ max: chipBounds.maxX,
6658
+ center: chipBounds.centerX,
6659
+ positiveOrientation: "x+",
6660
+ negativeOrientation: "x-"
6661
+ })
6662
+ );
6722
6663
  return options;
6723
6664
  };
6724
6665
  var getOutwardAxisOption = (params) => {
@@ -6796,6 +6737,22 @@ var roundDistance = (distance3) => Number(distance3.toFixed(6));
6796
6737
  var dedupeOrientations = (orientations) => [
6797
6738
  ...new Set(orientations)
6798
6739
  ];
6740
+ var normalizeFacingDirection = (value) => {
6741
+ switch (value) {
6742
+ case "x+":
6743
+ case "+x":
6744
+ return "x+";
6745
+ case "x-":
6746
+ case "-x":
6747
+ return "x-";
6748
+ case "y+":
6749
+ case "+y":
6750
+ return "y+";
6751
+ case "y-":
6752
+ case "-y":
6753
+ return "y-";
6754
+ }
6755
+ };
6799
6756
  var dedupeStrings = (values) => [
6800
6757
  ...new Set(values.filter((value) => value !== void 0))
6801
6758
  ];
@@ -6808,7 +6765,7 @@ var visualizeTraceAnchoredNetLabelOverlapSolver = (state) => {
6808
6765
  const graphics = visualizeInputProblem(state.inputProblem);
6809
6766
  ensureGraphicsArrays3(graphics);
6810
6767
  drawTraces4(graphics, state.traces);
6811
- drawNetLabels4(graphics, state.outputNetLabelPlacements);
6768
+ drawNetLabels4(graphics, state.inputProblem, state.outputNetLabelPlacements);
6812
6769
  if (!state.solved) {
6813
6770
  drawCurrentOverlap2(graphics, state);
6814
6771
  drawCurrentCandidates4(graphics, state.currentCandidateResults);
@@ -6823,7 +6780,7 @@ var drawTraces4 = (graphics, traces) => {
6823
6780
  });
6824
6781
  }
6825
6782
  };
6826
- var drawNetLabels4 = (graphics, labels) => {
6783
+ var drawNetLabels4 = (graphics, inputProblem, labels) => {
6827
6784
  for (const label of labels) {
6828
6785
  graphics.rects.push({
6829
6786
  center: label.center,
@@ -6831,8 +6788,7 @@ var drawNetLabels4 = (graphics, labels) => {
6831
6788
  height: label.height,
6832
6789
  fill: getColorFromString(label.globalConnNetId, 0.35),
6833
6790
  strokeColor: getColorFromString(label.globalConnNetId, 0.9),
6834
- label: `netId: ${label.netId}
6835
- globalConnNetId: ${label.globalConnNetId}`
6791
+ label: getNetLabelVisualizationLabel(inputProblem, label)
6836
6792
  });
6837
6793
  graphics.points.push({
6838
6794
  x: label.anchorPoint.x,
@@ -6858,7 +6814,8 @@ var drawCurrentOverlap2 = (graphics, state) => {
6858
6814
  fill: "rgba(255, 0, 0, 0.2)",
6859
6815
  strokeColor: CANDIDATE_REJECTED_COLOR4,
6860
6816
  label: `netlabel overlap target
6861
- ${label.netId ?? label.globalConnNetId}`
6817
+ ${label.netId ?? label.globalConnNetId}
6818
+ ${getAvailableOrientationText(state.inputProblem, label)}`
6862
6819
  });
6863
6820
  }
6864
6821
  };
@@ -6874,7 +6831,9 @@ var drawCurrentCandidates4 = (graphics, candidates) => {
6874
6831
  strokeDash: candidate.selected ? void 0 : "4 2",
6875
6832
  label: `${candidate.selected ? "selected" : candidate.status} netlabel overlap candidate
6876
6833
  trace: ${candidate.traceId}
6877
- distance: ${candidate.distanceFromOriginal.toFixed(3)}`
6834
+ path distance: ${candidate.pathDistance.toFixed(3)}
6835
+ orientation: ${candidate.orientation}
6836
+ distance from original: ${candidate.distanceFromOriginal.toFixed(3)}`
6878
6837
  });
6879
6838
  graphics.points.push({
6880
6839
  ...candidate.anchorPoint,
@@ -6891,6 +6850,24 @@ var ensureGraphicsArrays3 = (graphics) => {
6891
6850
  if (!graphics.circles) graphics.circles = [];
6892
6851
  if (!graphics.texts) graphics.texts = [];
6893
6852
  };
6853
+ var getAvailableOrientationText = (inputProblem, label) => {
6854
+ const orientations = getAvailableOrientationsForLabel(inputProblem, label);
6855
+ return `available orientations: ${orientations?.join(", ") ?? "any"}`;
6856
+ };
6857
+ var getNetLabelVisualizationLabel = (inputProblem, label) => [
6858
+ `netId: ${label.netId}`,
6859
+ `globalConnNetId: ${label.globalConnNetId}`,
6860
+ getAvailableOrientationText(inputProblem, label)
6861
+ ].join("\n");
6862
+ var getAvailableOrientationsForLabel = (inputProblem, label) => {
6863
+ const availableOrientations = inputProblem.availableNetLabelOrientations ?? {};
6864
+ for (const netId of [label.netId, label.globalConnNetId]) {
6865
+ if (netId && Object.hasOwn(availableOrientations, netId)) {
6866
+ return availableOrientations[netId];
6867
+ }
6868
+ }
6869
+ return void 0;
6870
+ };
6894
6871
 
6895
6872
  // lib/solvers/TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver.ts
6896
6873
  var TraceAnchoredNetLabelOverlapSolver = class extends BaseSolver {
@@ -7039,9 +7016,7 @@ var TraceAnchoredNetLabelOverlapSolver = class extends BaseSolver {
7039
7016
  })
7040
7017
  );
7041
7018
  }
7042
- return candidates.sort(
7043
- (a, b) => a.distanceFromOriginal - b.distanceFromOriginal || a.pathDistance - b.pathDistance
7044
- );
7019
+ return candidates;
7045
7020
  }
7046
7021
  getCandidateStatus(candidate, labelIndex) {
7047
7022
  const bounds = getLabelBounds(candidate);
@@ -7065,7 +7040,10 @@ var TraceAnchoredNetLabelOverlapSolver = class extends BaseSolver {
7065
7040
  }
7066
7041
  intersectsAnyChip(bounds) {
7067
7042
  return this.inputProblem.chips.some(
7068
- (chip) => rectsOverlap3(bounds, getRectBounds(chip.center, chip.width, chip.height))
7043
+ (chip) => rectsTouchOrOverlap(
7044
+ bounds,
7045
+ getRectBounds(chip.center, chip.width, chip.height)
7046
+ )
7069
7047
  );
7070
7048
  }
7071
7049
  intersectsAnyOtherLabel(bounds, labelIndex) {
@@ -7424,6 +7402,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
7424
7402
  circles: visualizations.flatMap((v) => v.circles || []),
7425
7403
  texts: visualizations.flatMap((v) => v.texts || [])
7426
7404
  };
7405
+ colorAvailableNetOrientationLabels(finalGraphics, this.inputProblem);
7427
7406
  return finalGraphics;
7428
7407
  }
7429
7408
  /**
@@ -100,7 +100,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
100
100
  const indices: number[] = []
101
101
 
102
102
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
103
- if (this.shouldProcessLabel(this.outputNetLabelPlacements[i]!, i)) {
103
+ if (this.shouldProcessLabel(this.outputNetLabelPlacements[i]!)) {
104
104
  indices.push(i)
105
105
  }
106
106
  }
@@ -108,14 +108,9 @@ export class AvailableNetOrientationSolver extends BaseSolver {
108
108
  return indices
109
109
  }
110
110
 
111
- private shouldProcessLabel(label: NetLabelPlacement, labelIndex: number) {
111
+ private shouldProcessLabel(label: NetLabelPlacement) {
112
112
  const orientations = this.getAvailableOrientations(label)
113
- if (orientations.length === 0) return false
114
-
115
- return !(
116
- orientations.includes(label.orientation) &&
117
- this.isLabelPlacementValid(label, labelIndex)
118
- )
113
+ return orientations.length > 0 && !orientations.includes(label.orientation)
119
114
  }
120
115
 
121
116
  private processLabel(labelIndex: number) {
@@ -441,27 +436,6 @@ export class AvailableNetOrientationSolver extends BaseSolver {
441
436
  )?.netLabelWidth
442
437
  }
443
438
 
444
- private isLabelPlacementValid(label: NetLabelPlacement, labelIndex: number) {
445
- return this.isCandidateValidWithOffset(
446
- {
447
- orientation: label.orientation,
448
- anchorPoint: label.anchorPoint,
449
- center: label.center,
450
- width: label.width,
451
- height: label.height,
452
- },
453
- labelIndex,
454
- )
455
- }
456
-
457
- private isCandidateValidWithOffset(
458
- candidate: CandidateLabel,
459
- labelIndex: number,
460
- ) {
461
- const bounds = this.getOffsetCollisionBounds(candidate)
462
- return this.getBoundsStatus(bounds, labelIndex) === "valid"
463
- }
464
-
465
439
  private getCandidateStatus(
466
440
  candidate: CandidateLabel,
467
441
  label: NetLabelPlacement,
@@ -505,16 +479,6 @@ export class AvailableNetOrientationSolver extends BaseSolver {
505
479
  return "valid"
506
480
  }
507
481
 
508
- private getOffsetCollisionBounds(candidate: CandidateLabel) {
509
- const direction = dir(candidate.orientation)
510
- const collisionTestOffset = 1e-4
511
- const testCenter = {
512
- x: candidate.center.x + direction.x * collisionTestOffset,
513
- y: candidate.center.y + direction.y * collisionTestOffset,
514
- }
515
- return getRectBounds(testCenter, candidate.width, candidate.height)
516
- }
517
-
518
482
  private intersectsAnyOtherNetLabel(bounds: Bounds, labelIndex: number) {
519
483
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
520
484
  if (i === labelIndex) continue
@@ -13,6 +13,7 @@ import {
13
13
  } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
14
14
  import { TraceOverlapShiftSolver } from "../TraceOverlapShiftSolver/TraceOverlapShiftSolver"
15
15
  import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
16
+ import { colorAvailableNetOrientationLabels } from "./colorAvailableNetOrientationLabels"
16
17
  import { visualizeInputProblem } from "./visualizeInputProblem"
17
18
  import { TraceLabelOverlapAvoidanceSolver } from "../TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver"
18
19
  import { correctPinsInsideChips } from "./correctPinsInsideChip"
@@ -403,6 +404,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
403
404
  circles: visualizations.flatMap((v) => v.circles || []),
404
405
  texts: visualizations.flatMap((v) => v.texts || []),
405
406
  }
407
+ colorAvailableNetOrientationLabels(finalGraphics, this.inputProblem)
406
408
  return finalGraphics
407
409
  }
408
410
 
@@ -0,0 +1,54 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+
4
+ const AVAILABLE_Y_PLUS_COLOR = "#93c5fd"
5
+ const AVAILABLE_Y_MINUS_COLOR = "#fca5a5"
6
+ const AVAILABLE_ORIENTATION_FILL_ALPHA = "66"
7
+
8
+ export const colorAvailableNetOrientationLabels = (
9
+ graphicsObject: GraphicsObject,
10
+ inputProblem: InputProblem,
11
+ ) => {
12
+ const availableOrientations = inputProblem.availableNetLabelOrientations
13
+ if (!availableOrientations) return
14
+
15
+ for (const rect of graphicsObject.rects ?? []) {
16
+ const orientations = getAvailableOrientationsForRect(
17
+ rect.label,
18
+ availableOrientations,
19
+ )
20
+ if (!orientations) continue
21
+
22
+ const hasYPlus = orientations.includes("y+")
23
+ const hasYMinus = orientations.includes("y-")
24
+ if (hasYPlus === hasYMinus) continue
25
+
26
+ const color = hasYPlus ? AVAILABLE_Y_PLUS_COLOR : AVAILABLE_Y_MINUS_COLOR
27
+
28
+ rect.fill = `${color}${AVAILABLE_ORIENTATION_FILL_ALPHA}`
29
+ rect.stroke = color
30
+ rect.color = color
31
+ }
32
+ }
33
+
34
+ const getAvailableOrientationsForRect = (
35
+ label: string | undefined,
36
+ availableOrientations: InputProblem["availableNetLabelOrientations"],
37
+ ) => {
38
+ for (const netId of getNetIdsFromRectLabel(label)) {
39
+ if (Object.hasOwn(availableOrientations, netId)) {
40
+ return availableOrientations[netId]
41
+ }
42
+ }
43
+
44
+ return undefined
45
+ }
46
+
47
+ const getNetIdsFromRectLabel = (label: string | undefined) => {
48
+ if (!label) return []
49
+
50
+ return [
51
+ label.match(/^netId: (.+)$/m)?.[1],
52
+ label.match(/^globalConnNetId: (.+)$/m)?.[1],
53
+ ].filter((netId): netId is string => Boolean(netId && netId !== "undefined"))
54
+ }
@@ -9,6 +9,7 @@ import {
9
9
  getLabelBounds,
10
10
  getTraceLocationsForPoint,
11
11
  rectsOverlap,
12
+ rectsTouchOrOverlap,
12
13
  traceCrossesBoundsInterior,
13
14
  } from "./geometry"
14
15
  import type {
@@ -217,11 +218,7 @@ export class TraceAnchoredNetLabelOverlapSolver extends BaseSolver {
217
218
  )
218
219
  }
219
220
 
220
- return candidates.sort(
221
- (a, b) =>
222
- a.distanceFromOriginal - b.distanceFromOriginal ||
223
- a.pathDistance - b.pathDistance,
224
- )
221
+ return candidates
225
222
  }
226
223
 
227
224
  private getCandidateStatus(
@@ -256,7 +253,10 @@ export class TraceAnchoredNetLabelOverlapSolver extends BaseSolver {
256
253
 
257
254
  private intersectsAnyChip(bounds: Bounds) {
258
255
  return this.inputProblem.chips.some((chip) =>
259
- rectsOverlap(bounds, getRectBounds(chip.center, chip.width, chip.height)),
256
+ rectsTouchOrOverlap(
257
+ bounds,
258
+ getRectBounds(chip.center, chip.width, chip.height),
259
+ ),
260
260
  )
261
261
  }
262
262