@tscircuit/schematic-trace-solver 0.0.188 → 0.0.190

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 (26) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +275 -228
  3. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +1 -0
  4. package/lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts +13 -0
  5. package/lib/solvers/NetLabelNetLabelCollisionSolver/NetLabelNetLabelCollisionSolver.ts +11 -2
  6. package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +4 -9
  7. package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +17 -9
  8. package/lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts +75 -38
  9. package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +5 -1
  10. package/package.json +1 -1
  11. package/site/SchematicTracePipelineSolver/repro-robot-controller-imu-tof.page.tsx +7 -0
  12. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +2 -2
  13. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +18 -18
  14. package/tests/repros/__snapshots__/repro-repeated-power-rail-junctions.snap.svg +255 -0
  15. package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.snap.svg +199 -0
  16. package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.source.svg +33 -0
  17. package/tests/repros/__snapshots__/rp2040-robot-controller-disconnected-gnd.snap.svg +103 -0
  18. package/tests/repros/assets/repro-repeated-power-rail-junctions.input.json +927 -0
  19. package/tests/repros/assets/repro-robot-controller-imu-tof.input.json +540 -0
  20. package/tests/repros/assets/rp2040-robot-controller.input.json +349 -0
  21. package/tests/repros/repro-repeated-power-rail-junctions.test.ts +22 -0
  22. package/tests/repros/repro-robot-controller-imu-tof.test.ts +83 -0
  23. package/tests/repros/rp2040-robot-controller-disconnected-gnd.test.ts +59 -0
  24. package/tests/solvers/InlineNetLabelSolver/get-anchored-net-label-rendered-bounds.test.ts +24 -0
  25. package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-same-net-cycles.test.ts +30 -0
  26. package/tests/solvers/TraceCleanupSolver/perpendicular-detours-preserve-endpoints.test.ts +60 -0
package/dist/index.d.ts CHANGED
@@ -1148,6 +1148,8 @@ interface NetLabelNetLabelCollisionSolverParams {
1148
1148
  useRenderedLabelBounds?: boolean;
1149
1149
  /** Already placed labels that are obstacles, but must not be moved. */
1150
1150
  fixedNetLabelPlacements?: NetLabelPlacement[];
1151
+ /** Existing connector identities from AvailableNetOrientationSolver. */
1152
+ netLabelConnectorTraceIds?: ReadonlySet<MspConnectionPairId>;
1151
1153
  inputProblem: InputProblem;
1152
1154
  traces: SolvedTracePath[];
1153
1155
  netLabelPlacements: NetLabelPlacement[];
package/dist/index.js CHANGED
@@ -7005,13 +7005,15 @@ var generatePerpendicularTraceDetours = ({
7005
7005
  bounds2[highBound] + clearance
7006
7006
  ])
7007
7007
  ].filter((coordinate) => coordinate !== void 0);
7008
+ const remainingPath = path.slice(index + 2);
7009
+ if (remainingPath.length === 0) remainingPath.push(end);
7008
7010
  return [...new Set(detourCoordinates)].map(
7009
7011
  (detour) => simplifyPath([
7010
7012
  ...path.slice(0, index + 1),
7011
7013
  { ...start, [movingAxis]: gate },
7012
7014
  { ...start, [movingAxis]: gate, [detourAxis]: detour },
7013
7015
  { ...end, [detourAxis]: detour },
7014
- ...path.slice(index + 2)
7016
+ ...remainingPath
7015
7017
  ])
7016
7018
  );
7017
7019
  };
@@ -12403,6 +12405,17 @@ globalConnNetId: ${label.globalConnNetId}`
12403
12405
  // lib/solvers/InlineNetLabelSolver/getAnchoredNetLabelRenderedBounds.ts
12404
12406
  var getAnchoredNetLabelRenderedBounds = (placement) => {
12405
12407
  if (placement.orientation !== "x-" && placement.orientation !== "x+") {
12408
+ if (placement.netLabelText && placement.width > placement.height) {
12409
+ const width2 = placement.height;
12410
+ const height2 = placement.width;
12411
+ const center2 = getCenterFromAnchor(
12412
+ placement.anchorPoint,
12413
+ placement.orientation,
12414
+ width2,
12415
+ height2
12416
+ );
12417
+ return getRectBounds(center2, width2, height2);
12418
+ }
12406
12419
  return getRectBounds(placement.center, placement.width, placement.height);
12407
12420
  }
12408
12421
  const width = Math.max(placement.width, placement.height);
@@ -12565,8 +12578,10 @@ var NetLabelNetLabelCollisionSolver = class extends BaseSolver {
12565
12578
  status: null
12566
12579
  };
12567
12580
  };
12568
- const isPortOnly = label.mspConnectionPairIds.length === 0;
12569
- if (isPortOnly) {
12581
+ const isAnchoredOnConnector = this.traces.some(
12582
+ (trace) => this.params.netLabelConnectorTraceIds?.has(trace.mspPairId) && trace.globalConnNetId === label.globalConnNetId && tracePathContainsPoint(trace.tracePath, label.anchorPoint)
12583
+ );
12584
+ if (label.mspConnectionPairIds.length === 0 || isAnchoredOnConnector) {
12570
12585
  const allOrientations = ["x+", "x-", "y+", "y-"];
12571
12586
  const orderedOrientations = [
12572
12587
  label.orientation,
@@ -13260,7 +13275,7 @@ var UnroutedTraceRecoverySolver = class extends BaseSolver {
13260
13275
  };
13261
13276
 
13262
13277
  // lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts
13263
- import { getSegmentIntersection as getSegmentIntersection3 } from "@tscircuit/math-utils/line-intersections";
13278
+ import { getSegmentIntersection as getSegmentIntersection4 } from "@tscircuit/math-utils/line-intersections";
13264
13279
 
13265
13280
  // lib/solvers/SameNetJunctionAlignmentSolver/pathIntersectsAnyNetLabel.ts
13266
13281
  var NET_LABEL_INTERIOR_INSET = 1e-6;
@@ -13579,6 +13594,242 @@ var collapseRedundantSharedEndpointStubs = ({
13579
13594
  return outputTraces;
13580
13595
  };
13581
13596
 
13597
+ // lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts
13598
+ import { getSegmentIntersection as getSegmentIntersection3 } from "@tscircuit/math-utils/line-intersections";
13599
+ var TRACE_LENGTH_EPSILON = 1e-6;
13600
+ var getTracePathStartingAtPin = (trace, pin) => {
13601
+ const pathStart = trace.tracePath[0];
13602
+ const pathEnd = trace.tracePath.at(-1);
13603
+ if (!pathStart || !pathEnd) return null;
13604
+ if (pointsEqual2(pathStart, pin)) return trace.tracePath;
13605
+ if (pointsEqual2(pathEnd, pin)) return [...trace.tracePath].reverse();
13606
+ return null;
13607
+ };
13608
+ var getPerpendicularPathCrossings = (targetPath, donorPath) => {
13609
+ const crossings = [];
13610
+ const sharedEndpoint = targetPath[0];
13611
+ for (let targetSegmentIndex = 0; targetSegmentIndex < targetPath.length - 1; targetSegmentIndex++) {
13612
+ const targetStart = targetPath[targetSegmentIndex];
13613
+ const targetEnd = targetPath[targetSegmentIndex + 1];
13614
+ const targetOrientation = getRailOrientation(targetStart, targetEnd);
13615
+ if (!targetOrientation) continue;
13616
+ for (let donorSegmentIndex = 0; donorSegmentIndex < donorPath.length - 1; donorSegmentIndex++) {
13617
+ const donorStart = donorPath[donorSegmentIndex];
13618
+ const donorEnd = donorPath[donorSegmentIndex + 1];
13619
+ const donorOrientation = getRailOrientation(donorStart, donorEnd);
13620
+ if (!donorOrientation || donorOrientation === targetOrientation) continue;
13621
+ const intersectionPoint = getSegmentIntersection3(
13622
+ targetStart,
13623
+ targetEnd,
13624
+ donorStart,
13625
+ donorEnd
13626
+ );
13627
+ if (!intersectionPoint || pointsEqual2(intersectionPoint, sharedEndpoint)) {
13628
+ continue;
13629
+ }
13630
+ crossings.push({
13631
+ intersectionPoint,
13632
+ targetSegmentIndex,
13633
+ donorSegmentIndex
13634
+ });
13635
+ }
13636
+ }
13637
+ return crossings;
13638
+ };
13639
+ var buildCollapsedSelfCyclePath = (path, firstSegmentIndex, secondSegmentIndex) => {
13640
+ const intersectionPoint = getSegmentIntersection3(
13641
+ path[firstSegmentIndex],
13642
+ path[firstSegmentIndex + 1],
13643
+ path[secondSegmentIndex],
13644
+ path[secondSegmentIndex + 1]
13645
+ );
13646
+ if (!intersectionPoint) return path;
13647
+ return simplifyPath([
13648
+ ...path.slice(0, firstSegmentIndex + 1),
13649
+ intersectionPoint,
13650
+ ...path.slice(secondSegmentIndex + 1)
13651
+ ]);
13652
+ };
13653
+ var buildCollapsedCyclePath = ({
13654
+ targetTrace,
13655
+ targetPath,
13656
+ donorPath,
13657
+ crossing
13658
+ }) => {
13659
+ const pathFromSharedPin = simplifyPath([
13660
+ ...donorPath.slice(0, crossing.donorSegmentIndex + 1),
13661
+ crossing.intersectionPoint,
13662
+ ...targetPath.slice(crossing.targetSegmentIndex + 1)
13663
+ ]);
13664
+ if (pointsEqual2(targetTrace.tracePath[0], targetPath[0])) {
13665
+ return pathFromSharedPin;
13666
+ }
13667
+ return pathFromSharedPin.reverse();
13668
+ };
13669
+ var getNetLabelPlacementsForCycleCollapse = ({
13670
+ targetTrace,
13671
+ tracePath,
13672
+ netLabelPlacements
13673
+ }) => {
13674
+ const candidateNetLabelPlacements = moveAttachedLabelsToReroutedTrace({
13675
+ trace: targetTrace,
13676
+ originalTracePath: targetTrace.tracePath,
13677
+ reroutedTracePath: tracePath,
13678
+ netLabelPlacements
13679
+ });
13680
+ for (let labelIndex = 0; labelIndex < netLabelPlacements.length; labelIndex++) {
13681
+ const label = netLabelPlacements[labelIndex];
13682
+ if (label.globalConnNetId !== targetTrace.globalConnNetId) continue;
13683
+ if (!tracePathContainsPoint(targetTrace.tracePath, label.anchorPoint)) {
13684
+ continue;
13685
+ }
13686
+ const candidateLabel = candidateNetLabelPlacements[labelIndex];
13687
+ if (!tracePathContainsPoint(tracePath, candidateLabel.anchorPoint)) {
13688
+ return null;
13689
+ }
13690
+ }
13691
+ const otherNetLabelPlacements = candidateNetLabelPlacements.filter(
13692
+ (label) => label.globalConnNetId !== targetTrace.globalConnNetId
13693
+ );
13694
+ if (pathIntersectsAnyNetLabel({
13695
+ path: tracePath,
13696
+ netLabelPlacements: otherNetLabelPlacements
13697
+ })) {
13698
+ return null;
13699
+ }
13700
+ const sameNetLabelPlacements = candidateNetLabelPlacements.filter(
13701
+ (label) => label.globalConnNetId === targetTrace.globalConnNetId
13702
+ );
13703
+ if (pathEntersAnyNetLabel({
13704
+ path: tracePath,
13705
+ netLabelPlacements: sameNetLabelPlacements
13706
+ })) {
13707
+ return null;
13708
+ }
13709
+ return candidateNetLabelPlacements;
13710
+ };
13711
+ var candidateIsBetter = (candidate, bestCandidate) => {
13712
+ if (!bestCandidate) return true;
13713
+ const visibleLengthDelta = candidate.netVisibleLength - bestCandidate.netVisibleLength;
13714
+ if (Math.abs(visibleLengthDelta) > TRACE_LENGTH_EPSILON) {
13715
+ return visibleLengthDelta < 0;
13716
+ }
13717
+ return candidate.netVisibleSegmentCount < bestCandidate.netVisibleSegmentCount;
13718
+ };
13719
+ var getBestCycleCollapseCandidate = ({
13720
+ targetTrace,
13721
+ traces,
13722
+ netLabelPlacements
13723
+ }) => {
13724
+ const sameNetTraces = traces.filter(
13725
+ (trace) => trace.globalConnNetId === targetTrace.globalConnNetId
13726
+ );
13727
+ const baselineNetVisibleLength = getVisibleTraceLength(sameNetTraces);
13728
+ const baselineTargetVisibleLength = getVisibleTraceLength([targetTrace]);
13729
+ let bestCandidate = null;
13730
+ const considerTracePath = (tracePath) => {
13731
+ const candidateTrace = { ...targetTrace, tracePath };
13732
+ const candidateTargetVisibleLength = getVisibleTraceLength([candidateTrace]);
13733
+ if (candidateTargetVisibleLength > baselineTargetVisibleLength + TRACE_LENGTH_EPSILON) {
13734
+ return;
13735
+ }
13736
+ const candidateNetLabelPlacements = getNetLabelPlacementsForCycleCollapse({
13737
+ targetTrace,
13738
+ tracePath,
13739
+ netLabelPlacements
13740
+ });
13741
+ if (!candidateNetLabelPlacements) return;
13742
+ const candidateNetTraces = sameNetTraces.map((trace) => {
13743
+ if (trace.mspPairId === targetTrace.mspPairId) return candidateTrace;
13744
+ return trace;
13745
+ });
13746
+ const netVisibleLength = getVisibleTraceLength(candidateNetTraces);
13747
+ if (netVisibleLength >= baselineNetVisibleLength - TRACE_LENGTH_EPSILON) {
13748
+ return;
13749
+ }
13750
+ const candidate = {
13751
+ tracePath,
13752
+ netLabelPlacements: candidateNetLabelPlacements,
13753
+ netVisibleLength,
13754
+ netVisibleSegmentCount: getVisibleTraceSegmentCount(candidateNetTraces)
13755
+ };
13756
+ if (candidateIsBetter(candidate, bestCandidate)) {
13757
+ bestCandidate = candidate;
13758
+ }
13759
+ };
13760
+ const selfCrossings = findPerpendicularPathCrossings(
13761
+ targetTrace.tracePath,
13762
+ targetTrace.tracePath,
13763
+ { includeTerminalSegments: true }
13764
+ ).filter(
13765
+ ({ pathSegmentIndex, otherPathSegmentIndex }) => pathSegmentIndex < otherPathSegmentIndex
13766
+ );
13767
+ for (const crossing of selfCrossings) {
13768
+ considerTracePath(
13769
+ buildCollapsedSelfCyclePath(
13770
+ targetTrace.tracePath,
13771
+ crossing.pathSegmentIndex,
13772
+ crossing.otherPathSegmentIndex
13773
+ )
13774
+ );
13775
+ }
13776
+ for (const donorTrace of sameNetTraces) {
13777
+ if (donorTrace.mspPairId === targetTrace.mspPairId) continue;
13778
+ const sharedPin = getSharedPin({
13779
+ donorTrace,
13780
+ branchTrace: targetTrace
13781
+ });
13782
+ if (!sharedPin) continue;
13783
+ const targetPath = getTracePathStartingAtPin(targetTrace, sharedPin);
13784
+ const donorPath = getTracePathStartingAtPin(donorTrace, sharedPin);
13785
+ if (!targetPath || !donorPath) continue;
13786
+ const crossings = getPerpendicularPathCrossings(targetPath, donorPath);
13787
+ for (const crossing of crossings) {
13788
+ const tracePath = buildCollapsedCyclePath({
13789
+ targetTrace,
13790
+ targetPath,
13791
+ donorPath,
13792
+ crossing
13793
+ });
13794
+ considerTracePath(tracePath);
13795
+ }
13796
+ }
13797
+ return bestCandidate;
13798
+ };
13799
+ var collapseSameNetCycles = ({
13800
+ traces,
13801
+ netLabelPlacements
13802
+ }) => {
13803
+ const outputTraces = [...traces];
13804
+ let outputNetLabelPlacements = [...netLabelPlacements];
13805
+ let collapsedCycleCount = 0;
13806
+ let traceIndex = 0;
13807
+ while (traceIndex < outputTraces.length) {
13808
+ const targetTrace = outputTraces[traceIndex];
13809
+ const candidate = getBestCycleCollapseCandidate({
13810
+ targetTrace,
13811
+ traces: outputTraces,
13812
+ netLabelPlacements: outputNetLabelPlacements
13813
+ });
13814
+ if (!candidate) {
13815
+ traceIndex++;
13816
+ continue;
13817
+ }
13818
+ outputTraces[traceIndex] = {
13819
+ ...targetTrace,
13820
+ tracePath: candidate.tracePath
13821
+ };
13822
+ outputNetLabelPlacements = candidate.netLabelPlacements;
13823
+ collapsedCycleCount++;
13824
+ traceIndex = 0;
13825
+ }
13826
+ return {
13827
+ traces: outputTraces,
13828
+ netLabelPlacements: outputNetLabelPlacements,
13829
+ collapsedCycleCount
13830
+ };
13831
+ };
13832
+
13582
13833
  // lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts
13583
13834
  var MAX_ALIGNED_LOAD_PIN_OFFSET = 0.2;
13584
13835
  var MAX_SAME_NET_LABEL_BOUNDARY_RAIL_OFFSET = 0.2;
@@ -13659,8 +13910,10 @@ var getHorizontalPinApproachPoint = ({
13659
13910
  return approachesFromFacingSide ? approachPoint : null;
13660
13911
  };
13661
13912
  var getAttachedLabelIndexes = (trace, netLabelPlacements) => netLabelPlacements.flatMap((label, index) => {
13662
- const labelOwnsTrace = label.mspConnectionPairIds.includes(trace.mspPairId) || label.mspConnectionPairIds.length === 0 && label.globalConnNetId === trace.globalConnNetId;
13663
- return labelOwnsTrace && tracePathContainsPoint(trace.tracePath, label.anchorPoint) ? [index] : [];
13913
+ if (isLabelAttachedToTrace(label, trace) && tracePathContainsPoint(trace.tracePath, label.anchorPoint)) {
13914
+ return [index];
13915
+ }
13916
+ return [];
13664
13917
  });
13665
13918
  var moveAttachedLabels = ({
13666
13919
  trace,
@@ -13961,7 +14214,7 @@ var pathsIntersect = (firstPath, secondPath) => {
13961
14214
  }
13962
14215
  for (let firstIndex = 1; firstIndex < firstPath.length; firstIndex++) {
13963
14216
  for (let secondIndex = 1; secondIndex < secondPath.length; secondIndex++) {
13964
- if (getSegmentIntersection3(
14217
+ if (getSegmentIntersection4(
13965
14218
  firstPath[firstIndex - 1],
13966
14219
  firstPath[firstIndex],
13967
14220
  secondPath[secondIndex - 1],
@@ -14145,7 +14398,7 @@ var candidateIsClear = ({
14145
14398
  const intersections2 = /* @__PURE__ */ new Set();
14146
14399
  for (let i = 1; i < path.length; i++) {
14147
14400
  for (let j = 1; j < otherTrace.tracePath.length; j++) {
14148
- const point = getSegmentIntersection3(
14401
+ const point = getSegmentIntersection4(
14149
14402
  path[i - 1],
14150
14403
  path[i],
14151
14404
  otherTrace.tracePath[j - 1],
@@ -14428,225 +14681,22 @@ var alignSameNetJunctions = ({
14428
14681
  }
14429
14682
  }
14430
14683
  }
14431
- const collapsedSharedEndpointStubs = collapseRedundantSharedEndpointStubs({
14684
+ const cycleCollapse = collapseSameNetCycles({
14432
14685
  traces: outputTraces,
14433
- netLabelPlacements: outputNetLabelPlacements,
14686
+ netLabelPlacements: outputNetLabelPlacements
14687
+ });
14688
+ outputTraces = collapseRedundantSharedEndpointStubs({
14689
+ traces: cycleCollapse.traces,
14690
+ netLabelPlacements: cycleCollapse.netLabelPlacements,
14434
14691
  netLabelConnectorTraceIds,
14435
14692
  multiPinNetPinIds: getMultiPinNetPinIds(inputProblem)
14436
14693
  });
14437
- outputTraces = collapsedSharedEndpointStubs;
14438
- return {
14439
- traces: outputTraces,
14440
- netLabelPlacements: outputNetLabelPlacements,
14441
- alignedJunctionCount
14442
- };
14443
- };
14444
-
14445
- // lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts
14446
- import { getSegmentIntersection as getSegmentIntersection4 } from "@tscircuit/math-utils/line-intersections";
14447
- var TRACE_LENGTH_EPSILON = 1e-6;
14448
- var getTracePathStartingAtPin = (trace, pin) => {
14449
- const pathStart = trace.tracePath[0];
14450
- const pathEnd = trace.tracePath.at(-1);
14451
- if (!pathStart || !pathEnd) return null;
14452
- if (pointsEqual2(pathStart, pin)) return trace.tracePath;
14453
- if (pointsEqual2(pathEnd, pin)) return [...trace.tracePath].reverse();
14454
- return null;
14455
- };
14456
- var getPerpendicularPathCrossings = (targetPath, donorPath) => {
14457
- const crossings = [];
14458
- const sharedEndpoint = targetPath[0];
14459
- for (let targetSegmentIndex = 0; targetSegmentIndex < targetPath.length - 1; targetSegmentIndex++) {
14460
- const targetStart = targetPath[targetSegmentIndex];
14461
- const targetEnd = targetPath[targetSegmentIndex + 1];
14462
- const targetOrientation = getRailOrientation(targetStart, targetEnd);
14463
- if (!targetOrientation) continue;
14464
- for (let donorSegmentIndex = 0; donorSegmentIndex < donorPath.length - 1; donorSegmentIndex++) {
14465
- const donorStart = donorPath[donorSegmentIndex];
14466
- const donorEnd = donorPath[donorSegmentIndex + 1];
14467
- const donorOrientation = getRailOrientation(donorStart, donorEnd);
14468
- if (!donorOrientation || donorOrientation === targetOrientation) continue;
14469
- const intersectionPoint = getSegmentIntersection4(
14470
- targetStart,
14471
- targetEnd,
14472
- donorStart,
14473
- donorEnd
14474
- );
14475
- if (!intersectionPoint || pointsEqual2(intersectionPoint, sharedEndpoint)) {
14476
- continue;
14477
- }
14478
- crossings.push({
14479
- intersectionPoint,
14480
- targetSegmentIndex,
14481
- donorSegmentIndex
14482
- });
14483
- }
14484
- }
14485
- return crossings;
14486
- };
14487
- var buildCollapsedCyclePath = ({
14488
- targetTrace,
14489
- targetPath,
14490
- donorPath,
14491
- crossing
14492
- }) => {
14493
- const pathFromSharedPin = simplifyPath([
14494
- ...donorPath.slice(0, crossing.donorSegmentIndex + 1),
14495
- crossing.intersectionPoint,
14496
- ...targetPath.slice(crossing.targetSegmentIndex + 1)
14497
- ]);
14498
- if (pointsEqual2(targetTrace.tracePath[0], targetPath[0])) {
14499
- return pathFromSharedPin;
14500
- }
14501
- return pathFromSharedPin.reverse();
14502
- };
14503
- var getNetLabelPlacementsForCycleCollapse = ({
14504
- targetTrace,
14505
- tracePath,
14506
- netLabelPlacements
14507
- }) => {
14508
- const candidateNetLabelPlacements = moveAttachedLabelsToReroutedTrace({
14509
- trace: targetTrace,
14510
- originalTracePath: targetTrace.tracePath,
14511
- reroutedTracePath: tracePath,
14512
- netLabelPlacements
14513
- });
14514
- for (let labelIndex = 0; labelIndex < netLabelPlacements.length; labelIndex++) {
14515
- const label = netLabelPlacements[labelIndex];
14516
- if (label.globalConnNetId !== targetTrace.globalConnNetId) continue;
14517
- if (!tracePathContainsPoint(targetTrace.tracePath, label.anchorPoint)) {
14518
- continue;
14519
- }
14520
- const candidateLabel = candidateNetLabelPlacements[labelIndex];
14521
- if (!tracePathContainsPoint(tracePath, candidateLabel.anchorPoint)) {
14522
- return null;
14523
- }
14524
- }
14525
- const otherNetLabelPlacements = candidateNetLabelPlacements.filter(
14526
- (label) => label.globalConnNetId !== targetTrace.globalConnNetId
14527
- );
14528
- if (pathIntersectsAnyNetLabel({
14529
- path: tracePath,
14530
- netLabelPlacements: otherNetLabelPlacements
14531
- })) {
14532
- return null;
14533
- }
14534
- const sameNetLabelPlacements = candidateNetLabelPlacements.filter(
14535
- (label) => label.globalConnNetId === targetTrace.globalConnNetId
14536
- );
14537
- if (pathEntersAnyNetLabel({
14538
- path: tracePath,
14539
- netLabelPlacements: sameNetLabelPlacements
14540
- })) {
14541
- return null;
14542
- }
14543
- return candidateNetLabelPlacements;
14544
- };
14545
- var candidateIsBetter = (candidate, bestCandidate) => {
14546
- if (!bestCandidate) return true;
14547
- const visibleLengthDelta = candidate.netVisibleLength - bestCandidate.netVisibleLength;
14548
- if (Math.abs(visibleLengthDelta) > TRACE_LENGTH_EPSILON) {
14549
- return visibleLengthDelta < 0;
14550
- }
14551
- return candidate.netVisibleSegmentCount < bestCandidate.netVisibleSegmentCount;
14552
- };
14553
- var getBestCycleCollapseCandidate = ({
14554
- targetTrace,
14555
- traces,
14556
- netLabelPlacements
14557
- }) => {
14558
- const sameNetTraces = traces.filter(
14559
- (trace) => trace.globalConnNetId === targetTrace.globalConnNetId
14560
- );
14561
- const baselineNetVisibleLength = getVisibleTraceLength(sameNetTraces);
14562
- const baselineTargetVisibleLength = getVisibleTraceLength([targetTrace]);
14563
- let bestCandidate = null;
14564
- for (const donorTrace of sameNetTraces) {
14565
- if (donorTrace.mspPairId === targetTrace.mspPairId) continue;
14566
- const sharedPin = getSharedPin({
14567
- donorTrace,
14568
- branchTrace: targetTrace
14569
- });
14570
- if (!sharedPin) continue;
14571
- const targetPath = getTracePathStartingAtPin(targetTrace, sharedPin);
14572
- const donorPath = getTracePathStartingAtPin(donorTrace, sharedPin);
14573
- if (!targetPath || !donorPath) continue;
14574
- const crossings = getPerpendicularPathCrossings(targetPath, donorPath);
14575
- for (const crossing of crossings) {
14576
- const tracePath = buildCollapsedCyclePath({
14577
- targetTrace,
14578
- targetPath,
14579
- donorPath,
14580
- crossing
14581
- });
14582
- const candidateTrace = { ...targetTrace, tracePath };
14583
- const candidateTargetVisibleLength = getVisibleTraceLength([
14584
- candidateTrace
14585
- ]);
14586
- if (candidateTargetVisibleLength > baselineTargetVisibleLength + TRACE_LENGTH_EPSILON) {
14587
- continue;
14588
- }
14589
- const candidateNetLabelPlacements = getNetLabelPlacementsForCycleCollapse(
14590
- {
14591
- targetTrace,
14592
- tracePath,
14593
- netLabelPlacements
14594
- }
14595
- );
14596
- if (!candidateNetLabelPlacements) continue;
14597
- const candidateNetTraces = sameNetTraces.map((trace) => {
14598
- if (trace.mspPairId === targetTrace.mspPairId) return candidateTrace;
14599
- return trace;
14600
- });
14601
- const netVisibleLength = getVisibleTraceLength(candidateNetTraces);
14602
- if (netVisibleLength >= baselineNetVisibleLength - TRACE_LENGTH_EPSILON) {
14603
- continue;
14604
- }
14605
- const netVisibleSegmentCount = getVisibleTraceSegmentCount(candidateNetTraces);
14606
- const candidate = {
14607
- tracePath,
14608
- netLabelPlacements: candidateNetLabelPlacements,
14609
- netVisibleLength,
14610
- netVisibleSegmentCount
14611
- };
14612
- if (candidateIsBetter(candidate, bestCandidate)) {
14613
- bestCandidate = candidate;
14614
- }
14615
- }
14616
- }
14617
- return bestCandidate;
14618
- };
14619
- var collapseSameNetCycles = ({
14620
- traces,
14621
- netLabelPlacements
14622
- }) => {
14623
- const outputTraces = [...traces];
14624
- let outputNetLabelPlacements = [...netLabelPlacements];
14625
- let collapsedCycleCount = 0;
14626
- let traceIndex = 0;
14627
- while (traceIndex < outputTraces.length) {
14628
- const targetTrace = outputTraces[traceIndex];
14629
- const candidate = getBestCycleCollapseCandidate({
14630
- targetTrace,
14631
- traces: outputTraces,
14632
- netLabelPlacements: outputNetLabelPlacements
14633
- });
14634
- if (!candidate) {
14635
- traceIndex++;
14636
- continue;
14637
- }
14638
- outputTraces[traceIndex] = {
14639
- ...targetTrace,
14640
- tracePath: candidate.tracePath
14641
- };
14642
- outputNetLabelPlacements = candidate.netLabelPlacements;
14643
- collapsedCycleCount++;
14644
- traceIndex = 0;
14645
- }
14694
+ outputNetLabelPlacements = cycleCollapse.netLabelPlacements;
14646
14695
  return {
14647
14696
  traces: outputTraces,
14648
14697
  netLabelPlacements: outputNetLabelPlacements,
14649
- collapsedCycleCount
14698
+ alignedJunctionCount,
14699
+ collapsedCycleCount: cycleCollapse.collapsedCycleCount
14650
14700
  };
14651
14701
  };
14652
14702
 
@@ -14759,18 +14809,14 @@ var SameNetJunctionAlignmentSolver = class extends BaseSolver {
14759
14809
  }
14760
14810
  _step() {
14761
14811
  const alignment = alignSameNetJunctions(this.input);
14762
- const cycleCollapse = collapseSameNetCycles({
14763
- traces: alignment.traces,
14764
- netLabelPlacements: alignment.netLabelPlacements
14765
- });
14766
- this.outputTraces = cycleCollapse.traces;
14812
+ this.outputTraces = alignment.traces;
14767
14813
  this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
14768
14814
  inputProblem: this.input.inputProblem,
14769
- traces: cycleCollapse.traces,
14770
- netLabelPlacements: cycleCollapse.netLabelPlacements
14815
+ traces: alignment.traces,
14816
+ netLabelPlacements: alignment.netLabelPlacements
14771
14817
  });
14772
14818
  this.stats.alignedJunctionCount = alignment.alignedJunctionCount;
14773
- this.stats.collapsedCycleCount = cycleCollapse.collapsedCycleCount;
14819
+ this.stats.collapsedCycleCount = alignment.collapsedCycleCount;
14774
14820
  this.solved = true;
14775
14821
  }
14776
14822
  getOutput() {
@@ -17098,6 +17144,7 @@ var InlineNetLabelSolver = class _InlineNetLabelSolver extends BaseSolver {
17098
17144
  traces: [...outputTraces, ...terminalTraces],
17099
17145
  netLabelPlacements: retainedNetLabelPlacements,
17100
17146
  fixedNetLabelPlacements: fixedLabels,
17147
+ netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
17101
17148
  useRenderedLabelBounds: activeInlinePlacements.length > 0
17102
17149
  });
17103
17150
  if (activeInlinePlacements.length > 0) labelCollisionSolver.solve();
@@ -1193,6 +1193,7 @@ export class InlineNetLabelSolver extends BaseSolver {
1193
1193
  traces: [...outputTraces, ...terminalTraces],
1194
1194
  netLabelPlacements: retainedNetLabelPlacements,
1195
1195
  fixedNetLabelPlacements: fixedLabels,
1196
+ netLabelConnectorTraceIds: this.netLabelConnectorTraceIds,
1196
1197
  useRenderedLabelBounds: activeInlinePlacements.length > 0,
1197
1198
  })
1198
1199
  if (activeInlinePlacements.length > 0) labelCollisionSolver.solve()
@@ -19,6 +19,19 @@ export const getAnchoredNetLabelRenderedBounds = (
19
19
  placement: NetLabelPlacement,
20
20
  ): Bounds => {
21
21
  if (placement.orientation !== "x-" && placement.orientation !== "x+") {
22
+ // Named vertical tags render their long text extent along y. Width-only
23
+ // placeholders retain the supplied envelope instead.
24
+ if (placement.netLabelText && placement.width > placement.height) {
25
+ const width = placement.height
26
+ const height = placement.width
27
+ const center = getCenterFromAnchor(
28
+ placement.anchorPoint,
29
+ placement.orientation,
30
+ width,
31
+ height,
32
+ )
33
+ return getRectBounds(center, width, height)
34
+ }
22
35
  return getRectBounds(placement.center, placement.width, placement.height)
23
36
  }
24
37
 
@@ -2,6 +2,7 @@ import { getAnchoredNetLabelRenderedBounds } from "../InlineNetLabelSolver/getAn
2
2
  import type { GraphicsObject } from "graphics-debug"
3
3
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
4
4
  import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
5
+ import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
5
6
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
7
  import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
7
8
  import type { InputProblem } from "lib/types/InputProblem"
@@ -96,6 +97,8 @@ export interface NetLabelNetLabelCollisionSolverParams {
96
97
  useRenderedLabelBounds?: boolean
97
98
  /** Already placed labels that are obstacles, but must not be moved. */
98
99
  fixedNetLabelPlacements?: NetLabelPlacement[]
100
+ /** Existing connector identities from AvailableNetOrientationSolver. */
101
+ netLabelConnectorTraceIds?: ReadonlySet<MspConnectionPairId>
99
102
  inputProblem: InputProblem
100
103
  traces: SolvedTracePath[]
101
104
  netLabelPlacements: NetLabelPlacement[]
@@ -245,9 +248,15 @@ export class NetLabelNetLabelCollisionSolver extends BaseSolver {
245
248
  }
246
249
  }
247
250
 
248
- const isPortOnly = label.mspConnectionPairIds.length === 0
251
+ // A label on an extended connector must keep that attachment point.
252
+ const isAnchoredOnConnector = this.traces.some(
253
+ (trace) =>
254
+ this.params.netLabelConnectorTraceIds?.has(trace.mspPairId) &&
255
+ trace.globalConnNetId === label.globalConnNetId &&
256
+ tracePathContainsPoint(trace.tracePath, label.anchorPoint),
257
+ )
249
258
 
250
- if (isPortOnly) {
259
+ if (label.mspConnectionPairIds.length === 0 || isAnchoredOnConnector) {
251
260
  const allOrientations: FacingDirection[] = ["x+", "x-", "y+", "y-"]
252
261
  const orderedOrientations = [
253
262
  label.orientation,