@tscircuit/schematic-trace-solver 0.0.159 → 0.0.161

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 (71) hide show
  1. package/dist/index.d.ts +43 -9
  2. package/dist/index.js +807 -115
  3. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +210 -6
  4. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +536 -94
  5. package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +9 -7
  6. package/lib/solvers/InlineNetLabelSolver/pushInlineTerminalLabelsAwayFromAnchoredLabels.ts +295 -0
  7. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +7 -35
  8. package/lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents.ts +60 -0
  9. package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getFixedLabelCoordinate.ts +44 -13
  10. package/lib/types/InputProblem.ts +5 -4
  11. package/package.json +1 -1
  12. package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +1 -1
  13. package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +1 -1
  14. package/tests/bug-reports/bug-report-20260728T225606Z/__snapshots__/bug-report-20260728T225606Z.snap.svg +79 -79
  15. package/tests/bug-reports/bug-report-20260730T061837Z/__snapshots__/bug-report-20260730T061837Z.snap.svg +85 -85
  16. package/tests/bug-reports/bug-report-20260731T052229Z/__snapshots__/bug-report-20260731T052229Z.snap.svg +1 -1
  17. package/tests/bug-reports/bug-report-20260804T095800Z/__snapshots__/bug-report-20260804T095800Z.snap.svg +163 -163
  18. package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +547 -547
  19. package/tests/bug-reports/bug-report-20260804T225912Z/__snapshots__/bug-report-20260804T225912Z.snap.svg +13 -13
  20. package/tests/bug-reports/bug-report-20260805T061316Z/__snapshots__/bug-report-20260805T061316Z.snap.svg +267 -267
  21. package/tests/bug-reports/bug-report-20260806T061548Z/__snapshots__/bug-report-20260806T061548Z.snap.svg +267 -267
  22. package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +109 -109
  23. package/tests/bug-reports/bug-report-20260811T075142Z/__snapshots__/bug-report-20260811T075142Z.snap.svg +1 -1
  24. package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +49 -49
  25. package/tests/bug-reports/bug-report-20260819T091818Z/__snapshots__/bug-report-20260819T091818Z.snap.svg +1 -1
  26. package/tests/bug-reports/bug-report-20260825T045913Z/__snapshots__/bug-report-20260825T045913Z.snap.svg +103 -103
  27. package/tests/bug-reports/bug-report-20260825T103621Z/__snapshots__/bug-report-20260825T103621Z.snap.svg +103 -103
  28. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +600 -792
  29. package/tests/bug-reports/bug-report-20260826T072956Z/bug-report-20260826T072956Z.test.ts +66 -1
  30. package/tests/examples/__snapshots__/example51.snap.svg +568 -568
  31. package/tests/fixtures/convertSolverOutputToCircuitJson.test.ts +16 -1
  32. package/tests/fixtures/convertSolverOutputToCircuitJson.ts +23 -12
  33. package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +736 -736
  34. package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +79 -79
  35. package/tests/repros/__snapshots__/board1096-usb-label-overlap-iteration.snap.svg +127 -127
  36. package/tests/repros/__snapshots__/repro-bluetooth-controller-ground-decoupling-groups.snap.svg +106 -0
  37. package/tests/repros/__snapshots__/repro-board-589-regulator-section.snap.svg +31 -31
  38. package/tests/repros/__snapshots__/repro-board-648-esp12f-section.snap.svg +121 -121
  39. package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +43 -43
  40. package/tests/repros/__snapshots__/repro-core-555-power-cross-net-overlap.snap.svg +19 -19
  41. package/tests/repros/__snapshots__/repro-core-ground-inline-label-fallback.snap.svg +277 -0
  42. package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +19 -19
  43. package/tests/repros/__snapshots__/repro-core-tb67s579ftg-inline-label-routing.snap.svg +7 -7
  44. package/tests/repros/__snapshots__/repro-dc-power-isolated-routing.snap.svg +37 -37
  45. package/tests/repros/__snapshots__/repro-hdc2080-ground-label-alignment.snap.svg +137 -0
  46. package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +61 -61
  47. package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +115 -115
  48. package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +743 -743
  49. package/tests/repros/__snapshots__/repro-rp2040-usb-cc2-ground-overlap.snap.svg +43 -43
  50. package/tests/repros/__snapshots__/repro-ti-power-output-section.snap.svg +1 -1
  51. package/tests/repros/__snapshots__/repro-tida010076-j4-ground-bus.snap.svg +17 -17
  52. package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +1131 -1131
  53. package/tests/repros/__snapshots__/repro-type-c-16pin-self-connection.snap.svg +7 -7
  54. package/tests/repros/__snapshots__/repro-type-c-16pin-to-resistor.snap.svg +7 -7
  55. package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +666 -666
  56. package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +73 -73
  57. package/tests/repros/__snapshots__/repro48-555-timer-vcc-rail-label.snap.svg +73 -73
  58. package/tests/repros/assets/repro-bluetooth-controller-ground-decoupling-groups.input.json +214 -0
  59. package/tests/repros/assets/repro-core-ground-inline-label-fallback.input.json +863 -0
  60. package/tests/repros/repro-bluetooth-controller-ground-decoupling-groups.test.ts +18 -0
  61. package/tests/repros/repro-core-ground-inline-label-fallback.test.ts +34 -0
  62. package/tests/repros/repro-hdc2080-ground-label-alignment.test.ts +239 -0
  63. package/tests/repros/repro-tida010076-j4-ground-bus.test.ts +14 -10
  64. package/tests/repros/repro-tida010076-page02.test.ts +2 -2
  65. package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +14 -0
  66. package/tests/solvers/InlineNetLabelSolver/__snapshots__/inline-net-label02.snap.svg +1 -1
  67. package/tests/solvers/InlineNetLabelSolver/multi-pin-connected-components.test.ts +252 -0
  68. package/tests/solvers/InlineNetLabelSolver/opposite-side-placement.test.ts +71 -0
  69. package/tests/solvers/InlineNetLabelSolver/push-anchored-net-labels-away.test.ts +8 -2
  70. package/tests/solvers/InlineNetLabelSolver/push-inline-terminal-labels-away.test.ts +91 -0
  71. package/tests/solvers/InlineNetLabelSolver/two-pin-terminal-stubs-atomic.test.ts +7 -1
package/dist/index.js CHANGED
@@ -3181,6 +3181,50 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
3181
3181
  }
3182
3182
  };
3183
3183
 
3184
+ // lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents.ts
3185
+ var getTraceConnectedPinComponents = ({
3186
+ pinIds,
3187
+ traces
3188
+ }) => {
3189
+ const adjacency = /* @__PURE__ */ new Map();
3190
+ for (const pinId of pinIds) adjacency.set(pinId, /* @__PURE__ */ new Set());
3191
+ const relevantTraces = traces.filter((trace) => {
3192
+ if (trace.pins.length !== 2) return false;
3193
+ const [firstPin, secondPin] = trace.pins;
3194
+ return adjacency.has(firstPin.pinId) && adjacency.has(secondPin.pinId);
3195
+ });
3196
+ for (const trace of relevantTraces) {
3197
+ const [firstPin, secondPin] = trace.pins;
3198
+ adjacency.get(firstPin.pinId).add(secondPin.pinId);
3199
+ adjacency.get(secondPin.pinId).add(firstPin.pinId);
3200
+ }
3201
+ const components = [];
3202
+ const visited = /* @__PURE__ */ new Set();
3203
+ for (const pinId of pinIds) {
3204
+ if (visited.has(pinId)) continue;
3205
+ const componentPinIds = [];
3206
+ const pending = [pinId];
3207
+ visited.add(pinId);
3208
+ while (pending.length > 0) {
3209
+ const currentPinId = pending.pop();
3210
+ componentPinIds.push(currentPinId);
3211
+ for (const adjacentPinId of adjacency.get(currentPinId) ?? []) {
3212
+ if (visited.has(adjacentPinId)) continue;
3213
+ visited.add(adjacentPinId);
3214
+ pending.push(adjacentPinId);
3215
+ }
3216
+ }
3217
+ const componentPinIdSet = new Set(componentPinIds);
3218
+ components.push({
3219
+ pinIds: componentPinIds,
3220
+ traces: relevantTraces.filter(
3221
+ (trace) => trace.pins.every((pin) => componentPinIdSet.has(pin.pinId))
3222
+ )
3223
+ });
3224
+ }
3225
+ return components;
3226
+ };
3227
+
3184
3228
  // lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts
3185
3229
  var NetLabelPlacementSolver = class extends BaseSolver {
3186
3230
  inputProblem;
@@ -3254,35 +3298,12 @@ var NetLabelPlacementSolver = class extends BaseSolver {
3254
3298
  globalConnNetId
3255
3299
  );
3256
3300
  const pinsInNet = allIdsInNet.filter((id) => pinIdToPinMap.has(id));
3257
- const adj = {};
3258
- for (const pid of pinsInNet) adj[pid] = /* @__PURE__ */ new Set();
3259
- for (const t of byGlobal[globalConnNetId] ?? []) {
3260
- const a = t.pins[0].pinId;
3261
- const b = t.pins[1].pinId;
3262
- if (adj[a] && adj[b]) {
3263
- adj[a].add(b);
3264
- adj[b].add(a);
3265
- }
3266
- }
3267
- const visited = /* @__PURE__ */ new Set();
3268
- for (const pid of pinsInNet) {
3269
- if (visited.has(pid)) continue;
3270
- const stack = [pid];
3271
- const component = /* @__PURE__ */ new Set();
3272
- visited.add(pid);
3273
- while (stack.length > 0) {
3274
- const u = stack.pop();
3275
- component.add(u);
3276
- for (const v of adj[u] ?? []) {
3277
- if (!visited.has(v)) {
3278
- visited.add(v);
3279
- stack.push(v);
3280
- }
3281
- }
3282
- }
3283
- const compTraces = (byGlobal[globalConnNetId] ?? []).filter(
3284
- (t) => component.has(t.pins[0].pinId) && component.has(t.pins[1].pinId)
3285
- );
3301
+ for (const traceConnectedComponent of getTraceConnectedPinComponents({
3302
+ pinIds: pinsInNet,
3303
+ traces: byGlobal[globalConnNetId] ?? []
3304
+ })) {
3305
+ const component = new Set(traceConnectedComponent.pinIds);
3306
+ const compTraces = traceConnectedComponent.traces;
3286
3307
  if (compTraces.length > 0) {
3287
3308
  if (compTraces.some((trace) => trace.suppressNetLabel)) continue;
3288
3309
  const lengthOf = (path) => {
@@ -5843,6 +5864,9 @@ var getLabelsLinkedToRailGroup = (group, netLabelPlacements, traces) => {
5843
5864
  var getFixedLabelCoordinate = (group, netLabelPlacements, traces) => {
5844
5865
  const traceMap = new Map(traces.map((trace) => [trace.mspPairId, trace]));
5845
5866
  const groupTraceIds = new Set(group.map((segment) => segment.traceId));
5867
+ const groupIsWithinSingleComponent = [...groupTraceIds].every(
5868
+ (traceId) => traceMap.get(traceId)?.pins.every((pin) => pin.chipId === group[0].componentId) === true
5869
+ );
5846
5870
  const groupHasOnlySingleRailTraces = [...groupTraceIds].every(
5847
5871
  (traceId) => traceMap.get(traceId)?.tracePath.length === 4
5848
5872
  );
@@ -5853,12 +5877,20 @@ var getFixedLabelCoordinate = (group, netLabelPlacements, traces) => {
5853
5877
  netLabelPlacements,
5854
5878
  traces
5855
5879
  );
5856
- const anchoringLabels = linkedLabels.filter(
5857
- (label) => label.orientation === group[0].componentFacingDirection && label.mspConnectionPairIds.some((traceId) => {
5880
+ const labelsOrientedAlongRail = new Set(
5881
+ linkedLabels.filter(
5882
+ (label) => orientation === "vertical" && (label.orientation === "y+" || label.orientation === "y-") || orientation === "horizontal" && (label.orientation === "x+" || label.orientation === "x-")
5883
+ )
5884
+ );
5885
+ const anchoringLabels = linkedLabels.filter((label) => {
5886
+ if (label.orientation !== group[0].componentFacingDirection && (!groupIsWithinSingleComponent || !labelsOrientedAlongRail.has(label))) {
5887
+ return false;
5888
+ }
5889
+ return label.mspConnectionPairIds.some((traceId) => {
5858
5890
  const trace = traceMap.get(traceId);
5859
5891
  return trace && tracePathContainsPoint(trace.tracePath, label.anchorPoint);
5860
- })
5861
- );
5892
+ });
5893
+ });
5862
5894
  const coordinates = getDistinctCoordinates(
5863
5895
  anchoringLabels.map(
5864
5896
  (label) => orientation === "vertical" ? label.anchorPoint.x : label.anchorPoint.y
@@ -5875,12 +5907,21 @@ var getFixedLabelCoordinate = (group, netLabelPlacements, traces) => {
5875
5907
  )
5876
5908
  );
5877
5909
  if (labelIsAnchoredToRoutedBackbone) return coordinate;
5910
+ if (anchoringLabels.some((label) => labelsOrientedAlongRail.has(label))) {
5911
+ const railGroupSpan = Math.max(...group.map((segment) => segment.maxAlong)) - Math.min(...group.map((segment) => segment.minAlong));
5912
+ const coordinateIsLocalToRailGroup = group.every(
5913
+ (segment) => Math.abs(segment.coordinate - coordinate) <= railGroupSpan + RAIL_ALIGNMENT_EPSILON
5914
+ );
5915
+ if (coordinateIsLocalToRailGroup) return coordinate;
5916
+ return null;
5917
+ }
5878
5918
  const coordinateIsLocalToEveryTrace = group.every((segment) => {
5879
5919
  const trace = traceMap.get(segment.traceId);
5880
5920
  if (!trace) return false;
5881
5921
  return Math.abs(segment.coordinate - coordinate) <= distance4(trace.pins[0], trace.pins[1]) + RAIL_ALIGNMENT_EPSILON;
5882
5922
  });
5883
- return coordinateIsLocalToEveryTrace ? coordinate : null;
5923
+ if (coordinateIsLocalToEveryTrace) return coordinate;
5924
+ return null;
5884
5925
  };
5885
5926
 
5886
5927
  // lib/solvers/TraceCleanupSolver/sameNetRailAlignment/moveRailSegments.ts
@@ -8918,6 +8959,9 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
8918
8959
  const orientations = this.getAvailableOrientations(label);
8919
8960
  if (orientations.length === 0) return false;
8920
8961
  if (!orientations.includes(label.orientation)) return true;
8962
+ if (this.getTextBlockedMultiPinRailTraces(label, orientations[0])) {
8963
+ return true;
8964
+ }
8921
8965
  if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false;
8922
8966
  const bounds = getRectBounds(label.center, label.width, label.height);
8923
8967
  return this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
@@ -9039,8 +9083,34 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9039
9083
  );
9040
9084
  const isTwoPinNet = netConnection?.pinIds.length === 2;
9041
9085
  const isDistanceSplitVerticalRail = (netConnection?.pinIds.length ?? 0) > 2 && isYOrientation(requiredOrientation) && this.hasPortOnlyLabelOnSameNet(label);
9086
+ const textBlockedMultiPinRailTraces = this.getTextBlockedMultiPinRailTraces(
9087
+ label,
9088
+ requiredOrientation
9089
+ );
9042
9090
  const isPairedSameSidePowerRail = isYOrientation(requiredOrientation) && this.hasOppositeVerticalRailOnSameChipSide(label, requiredOrientation);
9091
+ if (textBlockedMultiPinRailTraces) {
9092
+ const nearbyCandidate = this.findValidNearbyRailCandidate(
9093
+ label,
9094
+ requiredOrientation,
9095
+ labelIndex,
9096
+ textBlockedMultiPinRailTraces
9097
+ );
9098
+ if (nearbyCandidate) return nearbyCandidate;
9099
+ }
9043
9100
  if (orientations.length === 1 && isYOrientation(requiredOrientation) && this.isOutwardHorizontalFallback(label) && this.hasTraceContinuingInOrientation(label, requiredOrientation) && (isDistanceSplitVerticalRail || isPairedSameSidePowerRail)) {
9101
+ const hostRailCandidate = this.findValidTraceAnchorCandidate(
9102
+ label,
9103
+ requiredOrientation,
9104
+ labelIndex
9105
+ );
9106
+ if (hostRailCandidate) {
9107
+ const canUseHostRail = this.canPlaceVerticalLabelOnHostRail(
9108
+ hostRailCandidate,
9109
+ label
9110
+ );
9111
+ if (canUseHostRail) return hostRailCandidate;
9112
+ hostRailCandidate.selected = false;
9113
+ }
9044
9114
  const traceAnchorCandidate2 = this.findValidOutwardTraceAnchorCandidate(
9045
9115
  label,
9046
9116
  requiredOrientation,
@@ -9474,14 +9544,21 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9474
9544
  maxSearchDistance,
9475
9545
  outwardDistance,
9476
9546
  phase = "shift",
9477
- stopOnTraceCollision = true
9547
+ stopOnTraceCollision = true,
9548
+ connectorSource,
9549
+ startDistance = LABEL_SEARCH_STEP
9478
9550
  } = params;
9479
- for (let distance7 = LABEL_SEARCH_STEP; distance7 <= maxSearchDistance + EPS12; distance7 += LABEL_SEARCH_STEP) {
9551
+ for (let distance7 = startDistance; distance7 <= maxSearchDistance + EPS12; distance7 += LABEL_SEARCH_STEP) {
9480
9552
  const anchorPoint = {
9481
9553
  x: baseAnchor.x + direction.x * distance7,
9482
9554
  y: baseAnchor.y + direction.y * distance7
9483
9555
  };
9484
- const candidate = this.createCandidate(label, anchorPoint, orientation);
9556
+ const candidate = this.createCandidate(
9557
+ label,
9558
+ anchorPoint,
9559
+ orientation,
9560
+ connectorSource
9561
+ );
9485
9562
  const result = this.evaluateCandidate(
9486
9563
  candidate,
9487
9564
  label,
@@ -9857,6 +9934,113 @@ var AvailableNetOrientationSolver = class extends BaseSolver {
9857
9934
  const chipSide = this.getChipSideForPoint(label.anchorPoint);
9858
9935
  return chipSide === "left" && label.orientation === "x-" || chipSide === "right" && label.orientation === "x+";
9859
9936
  }
9937
+ canPlaceVerticalLabelOnHostRail(candidate, label) {
9938
+ const isOnHostRail = label.mspConnectionPairIds.some((traceId) => {
9939
+ const trace = this.traceMap[traceId];
9940
+ return trace && tracePathContainsPoint(trace.tracePath, candidate.anchorPoint);
9941
+ });
9942
+ if (!isOnHostRail) return false;
9943
+ const hostPin = this.pinMap[label.pinIds[0]];
9944
+ if (!hostPin) return false;
9945
+ if (label.pinIds.some(
9946
+ (pinId) => this.pinMap[pinId]?.chipId !== hostPin.chipId
9947
+ )) {
9948
+ return false;
9949
+ }
9950
+ const chip = this.chipObstacleSpatialIndex.chips.find(
9951
+ (chip2) => chip2.chipId === hostPin.chipId
9952
+ );
9953
+ if (!chip) return false;
9954
+ const labelBounds = getRectBounds(
9955
+ candidate.center,
9956
+ candidate.width,
9957
+ candidate.height
9958
+ );
9959
+ if (labelBounds.minY < chip.bounds.minY - EPS12 || labelBounds.maxY > chip.bounds.maxY + EPS12) {
9960
+ return false;
9961
+ }
9962
+ if (candidate.anchorPoint.x >= chip.bounds.minX - EPS12 && candidate.anchorPoint.x <= chip.bounds.maxX + EPS12) {
9963
+ return false;
9964
+ }
9965
+ return chip.pins.every(
9966
+ (pin) => label.pinIds.includes(pin.pinId) || pin.y <= labelBounds.minY + EPS12 || pin.y >= labelBounds.maxY - EPS12
9967
+ );
9968
+ }
9969
+ getTextBlockedMultiPinRailTraces(label, orientation) {
9970
+ if (!isYOrientation(orientation)) return null;
9971
+ const componentId = this.pinMap[label.pinIds[0]]?.chipId;
9972
+ const localTraces = Object.values(this.traceMap).filter(
9973
+ (trace) => trace.globalConnNetId === label.globalConnNetId && trace.pins.every((pin) => pin.chipId === componentId)
9974
+ );
9975
+ const connectedPinIds = new Set(label.pinIds);
9976
+ const connectedTraces = /* @__PURE__ */ new Set();
9977
+ for (let changed = true; changed; ) {
9978
+ changed = false;
9979
+ for (const trace of localTraces) {
9980
+ if (connectedTraces.has(trace)) continue;
9981
+ if (!trace.pinIds.some((pinId) => connectedPinIds.has(pinId))) continue;
9982
+ connectedTraces.add(trace);
9983
+ for (const pinId of trace.pinIds) connectedPinIds.add(pinId);
9984
+ changed = true;
9985
+ }
9986
+ }
9987
+ if (connectedPinIds.size <= 3) return null;
9988
+ const pins = [...connectedPinIds].map((pinId) => this.pinMap[pinId]);
9989
+ const direction = dir(orientation);
9990
+ const pinX = pins[0].x;
9991
+ if (pins.some((pin) => Math.abs(pin.x - pinX) > EPS12)) return null;
9992
+ const sidePins = this.inputProblem.chips.find((chip) => chip.chipId === componentId).pins.filter((pin) => Math.abs(pin.x - pinX) <= EPS12);
9993
+ const minY = Math.min(...pins.map((pin) => pin.y));
9994
+ const maxY = Math.max(...pins.map((pin) => pin.y));
9995
+ if (sidePins.some(
9996
+ (pin) => pin.y >= minY - EPS12 && pin.y <= maxY + EPS12 && !connectedPinIds.has(pin.pinId)
9997
+ ) || orientation === "y-" && minY > Math.min(...sidePins.map((pin) => pin.y)) + EPS12 || orientation === "y+" && maxY < Math.max(...sidePins.map((pin) => pin.y)) - EPS12) {
9998
+ return null;
9999
+ }
10000
+ const outerPinPosition = Math.max(
10001
+ ...pins.map((pin) => pin.x * direction.x + pin.y * direction.y)
10002
+ );
10003
+ const blockedByText = (this.inputProblem.textBoxes ?? []).some(
10004
+ (textBox) => textBox.chipId === componentId && textBox.center.x * direction.x + textBox.center.y * direction.y > outerPinPosition + EPS12
10005
+ );
10006
+ return blockedByText ? [...connectedTraces] : null;
10007
+ }
10008
+ findValidNearbyRailCandidate(label, orientation, labelIndex, localTraces) {
10009
+ if (!isYOrientation(orientation)) return null;
10010
+ const direction = dir(orientation);
10011
+ const connectorY = [
10012
+ ...new Set(localTraces.flatMap((trace) => trace.pinIds))
10013
+ ].map((pinId) => this.pinMap[pinId]).sort((a, b) => b.y * direction.y - a.y * direction.y)[0]?.y;
10014
+ if (connectorY === void 0) return null;
10015
+ const pinX = this.pinMap[label.pinIds[0]].x;
10016
+ const railXs = [
10017
+ ...new Set(
10018
+ localTraces.flatMap((trace) => trace.tracePath.map((point) => point.x))
10019
+ )
10020
+ ].filter((x) => Math.abs(x - pinX) > EPS12).sort((a, b) => Math.abs(a - pinX) - Math.abs(b - pinX));
10021
+ for (const x of railXs) {
10022
+ const connectorSource = { x, y: connectorY };
10023
+ if (!Object.values(this.traceMap).some(
10024
+ (trace) => trace.globalConnNetId === label.globalConnNetId && tracePathContainsPoint(trace.tracePath, connectorSource)
10025
+ )) {
10026
+ continue;
10027
+ }
10028
+ const candidate = this.findValidCandidateInShiftColumn({
10029
+ label,
10030
+ labelIndex,
10031
+ orientation,
10032
+ direction,
10033
+ baseAnchor: connectorSource,
10034
+ maxSearchDistance: this.maxSearchDistance,
10035
+ outwardDistance: 0,
10036
+ stopOnTraceCollision: false,
10037
+ connectorSource,
10038
+ startDistance: 0
10039
+ });
10040
+ if (candidate) return candidate;
10041
+ }
10042
+ return null;
10043
+ }
9860
10044
  hasPortOnlyLabelOnSameNet(label) {
9861
10045
  return this.outputNetLabelPlacements.some(
9862
10046
  (otherLabel) => otherLabel !== label && otherLabel.globalConnNetId === label.globalConnNetId && this.isPortOnlyLabel(otherLabel)
@@ -13695,7 +13879,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
13695
13879
  const connectorObstructed = inlineBounds.some(
13696
13880
  (bounds) => pathIntersectsBounds(connector.tracePath, bounds)
13697
13881
  ) || inputProblem.chips.some(
13698
- (chip) => pathIntersectsBounds(connector.tracePath, {
13882
+ (chip) => !ownerChipIds.has(chip.chipId) && pathIntersectsBounds(connector.tracePath, {
13699
13883
  minX: chip.center.x - chip.width / 2,
13700
13884
  maxX: chip.center.x + chip.width / 2,
13701
13885
  minY: chip.center.y - chip.height / 2,
@@ -13732,20 +13916,247 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
13732
13916
  };
13733
13917
  };
13734
13918
 
13919
+ // lib/solvers/InlineNetLabelSolver/pushInlineTerminalLabelsAwayFromAnchoredLabels.ts
13920
+ var LABEL_CLEARANCE3 = 0.05;
13921
+ var MAX_OUTWARD_DISTANCE2 = 5;
13922
+ var getStubDirection2 = (path) => {
13923
+ const [start, end] = path;
13924
+ if (Math.abs(end.x - start.x) >= Math.abs(end.y - start.y)) {
13925
+ return end.x >= start.x ? "x+" : "x-";
13926
+ }
13927
+ return end.y >= start.y ? "y+" : "y-";
13928
+ };
13929
+ var getLabelBounds3 = (placement) => {
13930
+ const renderedWidth = placement.axis === "y" ? placement.height : placement.width;
13931
+ const renderedHeight = placement.axis === "y" ? placement.width : placement.height;
13932
+ return {
13933
+ minX: placement.center.x - renderedWidth / 2,
13934
+ maxX: placement.center.x + renderedWidth / 2,
13935
+ minY: placement.center.y - renderedHeight / 2,
13936
+ maxY: placement.center.y + renderedHeight / 2
13937
+ };
13938
+ };
13939
+ var getPathBounds2 = (path) => ({
13940
+ minX: Math.min(...path.map((point) => point.x)),
13941
+ maxX: Math.max(...path.map((point) => point.x)),
13942
+ minY: Math.min(...path.map((point) => point.y)),
13943
+ maxY: Math.max(...path.map((point) => point.y))
13944
+ });
13945
+ var doesPathIntersectBounds2 = (path, bounds) => {
13946
+ for (let index = 0; index < path.length - 1; index++) {
13947
+ if (boundsOverlap(getPathBounds2([path[index], path[index + 1]]), bounds)) {
13948
+ return true;
13949
+ }
13950
+ }
13951
+ return false;
13952
+ };
13953
+ var shiftPoint = (point, direction, distance7) => {
13954
+ switch (direction) {
13955
+ case "x+":
13956
+ return { x: point.x + distance7, y: point.y };
13957
+ case "x-":
13958
+ return { x: point.x - distance7, y: point.y };
13959
+ case "y+":
13960
+ return { x: point.x, y: point.y + distance7 };
13961
+ case "y-":
13962
+ return { x: point.x, y: point.y - distance7 };
13963
+ }
13964
+ };
13965
+ var shiftTerminalPlacement = (placement, direction, distance7) => {
13966
+ const [start, end] = placement.stubTracePath;
13967
+ const shiftedEnd = shiftPoint(end, direction, distance7);
13968
+ return {
13969
+ ...placement,
13970
+ stubTracePath: [start, shiftedEnd],
13971
+ anchorPoint: shiftPoint(placement.anchorPoint, direction, distance7),
13972
+ center: shiftPoint(placement.center, direction, distance7)
13973
+ };
13974
+ };
13975
+ var getRequiredOutwardDistance2 = (movingBounds, obstacleBounds, direction) => {
13976
+ switch (direction) {
13977
+ case "x+":
13978
+ return obstacleBounds.maxX - movingBounds.minX + LABEL_CLEARANCE3;
13979
+ case "x-":
13980
+ return movingBounds.maxX - obstacleBounds.minX + LABEL_CLEARANCE3;
13981
+ case "y+":
13982
+ return obstacleBounds.maxY - movingBounds.minY + LABEL_CLEARANCE3;
13983
+ case "y-":
13984
+ return movingBounds.maxY - obstacleBounds.minY + LABEL_CLEARANCE3;
13985
+ }
13986
+ };
13987
+ var pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
13988
+ inputProblem,
13989
+ traces,
13990
+ anchoredNetLabelPlacements,
13991
+ inlineNetLabelPlacements
13992
+ }) => {
13993
+ const chipIdByPinId = /* @__PURE__ */ new Map();
13994
+ for (const chip of inputProblem.chips) {
13995
+ for (const pin of chip.pins) chipIdByPinId.set(pin.pinId, chip.chipId);
13996
+ }
13997
+ const groups = /* @__PURE__ */ new Map();
13998
+ for (const [
13999
+ placementIndex,
14000
+ placement
14001
+ ] of inlineNetLabelPlacements.entries()) {
14002
+ if (!placement.stubTracePath || placement.pinIds.length !== 1) continue;
14003
+ const direction = getStubDirection2(placement.stubTracePath);
14004
+ const ownerChipId = chipIdByPinId.get(placement.pinIds[0]);
14005
+ const groupKey = `${ownerChipId ?? placement.pinIds[0]}::${direction}`;
14006
+ const group = groups.get(groupKey) ?? [];
14007
+ group.push({ placementIndex, placement, direction, ownerChipId });
14008
+ groups.set(groupKey, group);
14009
+ }
14010
+ const outputPlacements = [...inlineNetLabelPlacements];
14011
+ let movedGroupCount = 0;
14012
+ for (const group of groups.values()) {
14013
+ const groupPlacementIndices = new Set(
14014
+ group.map(({ placementIndex }) => placementIndex)
14015
+ );
14016
+ const fixedInlinePlacements = outputPlacements.filter(
14017
+ (_, placementIndex) => !groupPlacementIndices.has(placementIndex)
14018
+ );
14019
+ const direction = group[0].direction;
14020
+ let distance7 = 0;
14021
+ for (let iteration = 0; iteration <= anchoredNetLabelPlacements.length; iteration++) {
14022
+ let requiredAdditionalDistance = 0;
14023
+ for (const { placement } of group) {
14024
+ const shiftedPlacement = shiftTerminalPlacement(
14025
+ placement,
14026
+ direction,
14027
+ distance7
14028
+ );
14029
+ const shiftedBounds = getLabelBounds3(shiftedPlacement);
14030
+ for (const anchoredPlacement of anchoredNetLabelPlacements) {
14031
+ if (anchoredPlacement.globalConnNetId === placement.globalConnNetId) {
14032
+ continue;
14033
+ }
14034
+ const anchoredBounds = getLabelBounds3(anchoredPlacement);
14035
+ if (!boundsOverlap(shiftedBounds, anchoredBounds)) continue;
14036
+ requiredAdditionalDistance = Math.max(
14037
+ requiredAdditionalDistance,
14038
+ getRequiredOutwardDistance2(
14039
+ shiftedBounds,
14040
+ anchoredBounds,
14041
+ direction
14042
+ )
14043
+ );
14044
+ }
14045
+ }
14046
+ if (requiredAdditionalDistance <= 0) break;
14047
+ distance7 += requiredAdditionalDistance;
14048
+ }
14049
+ if (distance7 <= 0 || distance7 > MAX_OUTWARD_DISTANCE2) continue;
14050
+ const proposals = group.map(
14051
+ ({ placement }) => shiftTerminalPlacement(placement, direction, distance7)
14052
+ );
14053
+ const hasConflict = proposals.some((proposal, proposalIndex) => {
14054
+ const originalPlacement = group[proposalIndex].placement;
14055
+ const originalEnd = originalPlacement.stubTracePath[1];
14056
+ const shiftedEnd = proposal.stubTracePath[1];
14057
+ const addedStubSegment = [originalEnd, shiftedEnd];
14058
+ const labelBounds = getLabelBounds3(proposal);
14059
+ const ownerChipId = group[proposalIndex].ownerChipId;
14060
+ if (anchoredNetLabelPlacements.some((anchoredPlacement) => {
14061
+ if (anchoredPlacement.globalConnNetId === proposal.globalConnNetId) {
14062
+ return false;
14063
+ }
14064
+ const anchoredBounds = getLabelBounds3(anchoredPlacement);
14065
+ return boundsOverlap(labelBounds, anchoredBounds) || doesPathIntersectBounds2(addedStubSegment, anchoredBounds);
14066
+ })) {
14067
+ return true;
14068
+ }
14069
+ for (const chip of inputProblem.chips) {
14070
+ if (chip.chipId === ownerChipId) continue;
14071
+ const chipBounds = {
14072
+ minX: chip.center.x - chip.width / 2,
14073
+ maxX: chip.center.x + chip.width / 2,
14074
+ minY: chip.center.y - chip.height / 2,
14075
+ maxY: chip.center.y + chip.height / 2
14076
+ };
14077
+ if (boundsOverlap(labelBounds, chipBounds) || doesPathIntersectBounds2(addedStubSegment, chipBounds)) {
14078
+ return true;
14079
+ }
14080
+ }
14081
+ for (const textBox of inputProblem.textBoxes ?? []) {
14082
+ const textBounds = getTextBoxBounds(textBox);
14083
+ if (boundsOverlap(labelBounds, textBounds) || doesPathIntersectBounds2(addedStubSegment, textBounds)) {
14084
+ return true;
14085
+ }
14086
+ }
14087
+ for (const trace of traces) {
14088
+ if (trace.globalConnNetId === proposal.globalConnNetId) continue;
14089
+ if (doesPathIntersectBounds2(trace.tracePath, labelBounds) || doesPathIntersectBounds2(
14090
+ trace.tracePath,
14091
+ getPathBounds2(addedStubSegment)
14092
+ )) {
14093
+ return true;
14094
+ }
14095
+ }
14096
+ for (const fixedPlacement of fixedInlinePlacements) {
14097
+ const fixedBounds = getLabelBounds3(fixedPlacement);
14098
+ if (boundsOverlap(labelBounds, fixedBounds) || doesPathIntersectBounds2(addedStubSegment, fixedBounds) || fixedPlacement.stubTracePath && doesPathIntersectBounds2(fixedPlacement.stubTracePath, labelBounds)) {
14099
+ return true;
14100
+ }
14101
+ }
14102
+ return false;
14103
+ });
14104
+ if (hasConflict) continue;
14105
+ for (const [groupIndex, { placementIndex }] of group.entries()) {
14106
+ outputPlacements[placementIndex] = proposals[groupIndex];
14107
+ }
14108
+ movedGroupCount++;
14109
+ }
14110
+ return {
14111
+ inlineNetLabelPlacements: outputPlacements,
14112
+ movedGroupCount
14113
+ };
14114
+ };
14115
+
13735
14116
  // lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts
13736
14117
  var DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
13737
14118
  var INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
13738
14119
  var INLINE_NET_LABEL_STUB_TRACE_CLEARANCE = 0.05;
13739
14120
  var INLINE_NET_LABEL_MAX_SPAN_JOG = 0.4;
14121
+ var AVAILABLE_NET_ORIENTATION_TRACE_PREFIX = "available-net-orientation-";
14122
+ var isGeneratedAnchoredLabelConnector = (trace) => trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_TRACE_PREFIX);
13740
14123
  var getPinPairKey2 = (pinIds) => [...pinIds].sort().join("::");
14124
+ var getTraceLength2 = (trace) => {
14125
+ let length = 0;
14126
+ for (let index = 0; index < trace.tracePath.length - 1; index++) {
14127
+ const start = trace.tracePath[index];
14128
+ const end = trace.tracePath[index + 1];
14129
+ length += Math.abs(end.x - start.x) + Math.abs(end.y - start.y);
14130
+ }
14131
+ return length;
14132
+ };
14133
+ var setsEqual = (first, second) => first.size === second.size && [...first].every((value) => second.has(value));
14134
+ var getInlinePlacementBounds = (placement) => {
14135
+ const renderedWidth = placement.axis === "y" ? placement.height : placement.width;
14136
+ const renderedHeight = placement.axis === "y" ? placement.width : placement.height;
14137
+ return {
14138
+ minX: placement.center.x - renderedWidth / 2,
14139
+ maxX: placement.center.x + renderedWidth / 2,
14140
+ minY: placement.center.y - renderedHeight / 2,
14141
+ maxY: placement.center.y + renderedHeight / 2
14142
+ };
14143
+ };
14144
+ var getAnchoredPlacementBounds = (placement) => ({
14145
+ minX: placement.center.x - placement.width / 2,
14146
+ maxX: placement.center.x + placement.width / 2,
14147
+ minY: placement.center.y - placement.height / 2,
14148
+ maxY: placement.center.y + placement.height / 2
14149
+ });
13741
14150
  var InlineNetLabelSolver = class extends BaseSolver {
13742
14151
  inputProblem;
13743
14152
  traces;
13744
14153
  inputNetLabelPlacements;
13745
14154
  inlineNetLabelPlacements = [];
13746
- /** Eligible one- or two-pin connections still waiting to be processed. */
14155
+ /** Eligible connections still waiting to be processed. */
13747
14156
  queuedConnections;
13748
14157
  tracesByPinPairKey;
14158
+ globalConnMap;
14159
+ supersededTraceIdsByGlobalConnNetId = /* @__PURE__ */ new Map();
13749
14160
  hasAlignedPortOnlyStubs = false;
13750
14161
  postProcessedOutput;
13751
14162
  constructor(input) {
@@ -13753,16 +14164,26 @@ var InlineNetLabelSolver = class extends BaseSolver {
13753
14164
  this.inputProblem = input.inputProblem;
13754
14165
  this.traces = input.traces;
13755
14166
  this.inputNetLabelPlacements = input.netLabelPlacements;
14167
+ this.globalConnMap = getConnectivityMapsFromInputProblem(
14168
+ this.inputProblem
14169
+ ).netConnMap;
13756
14170
  this.queuedConnections = [
13757
14171
  ...this.inputProblem.directConnections.filter(
13758
14172
  (connection) => connection.allowInlineNetLabel && connection.netId
13759
- ),
14173
+ ).map((connection) => ({
14174
+ kind: "direct_connection",
14175
+ connection
14176
+ })),
13760
14177
  ...this.inputProblem.netConnections.filter(
13761
- (connection) => connection.allowInlineNetLabel && connection.pinIds.length >= 1 && connection.pinIds.length <= 2 && connection.netId
13762
- )
14178
+ (connection) => connection.allowInlineNetLabel && connection.pinIds.length >= 1 && connection.netId
14179
+ ).map((connection) => ({
14180
+ kind: "net_connection",
14181
+ connection
14182
+ }))
13763
14183
  ];
13764
14184
  this.tracesByPinPairKey = /* @__PURE__ */ new Map();
13765
14185
  for (const trace of this.traces) {
14186
+ if (isGeneratedAnchoredLabelConnector(trace)) continue;
13766
14187
  const key = getPinPairKey2(trace.pins.map((p) => p.pinId));
13767
14188
  const existing = this.tracesByPinPairKey.get(key);
13768
14189
  if (existing) {
@@ -13782,11 +14203,43 @@ var InlineNetLabelSolver = class extends BaseSolver {
13782
14203
  ];
13783
14204
  }
13784
14205
  _step() {
13785
- const connection = this.queuedConnections.shift();
13786
- if (connection) {
14206
+ const queuedConnection = this.queuedConnections.shift();
14207
+ if (queuedConnection) {
14208
+ if (queuedConnection.kind === "net_connection" && queuedConnection.connection.pinIds.length > 2) {
14209
+ const netConnections = [
14210
+ queuedConnection.connection,
14211
+ ...this.queuedConnections.filter(
14212
+ (queued) => queued.kind === "net_connection" && queued.connection.pinIds.length > 2
14213
+ ).map((queued) => queued.connection)
14214
+ ];
14215
+ this.queuedConnections = this.queuedConnections.filter(
14216
+ (queued) => queued.kind !== "net_connection" || queued.connection.pinIds.length <= 2
14217
+ );
14218
+ for (const conversion of this.computeNetConnectionInlineConversions(
14219
+ netConnections
14220
+ )) {
14221
+ this.inlineNetLabelPlacements.push(...conversion.placements);
14222
+ const supersededTraceIds = this.supersededTraceIdsByGlobalConnNetId.get(
14223
+ conversion.globalConnNetId
14224
+ ) ?? /* @__PURE__ */ new Set();
14225
+ for (const traceId of conversion.supersededTraceIds) {
14226
+ supersededTraceIds.add(traceId);
14227
+ }
14228
+ this.supersededTraceIdsByGlobalConnNetId.set(
14229
+ conversion.globalConnNetId,
14230
+ supersededTraceIds
14231
+ );
14232
+ }
14233
+ return;
14234
+ }
14235
+ const { connection } = queuedConnection;
13787
14236
  const routedTraces = connection.pinIds.length === 2 ? this.tracesByPinPairKey.get(getPinPairKey2(connection.pinIds)) ?? [] : [];
13788
14237
  if (routedTraces.length > 0) {
13789
- const placement = this.computeInlinePlacement(connection);
14238
+ const placement = this.computeInlinePlacement({
14239
+ connection,
14240
+ trace: routedTraces[0],
14241
+ pinIds: connection.pinIds
14242
+ });
13790
14243
  if (placement) this.inlineNetLabelPlacements.push(placement);
13791
14244
  } else {
13792
14245
  const terminalPlacements = connection.pinIds.map(
@@ -13815,18 +14268,181 @@ var InlineNetLabelSolver = class extends BaseSolver {
13815
14268
  this.solved = true;
13816
14269
  this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length;
13817
14270
  }
14271
+ /**
14272
+ * Plans all opted-in multi-pin nets together. Short routed components can
14273
+ * mutually block the outward endpoint stubs that replace them, so tentative
14274
+ * replacements are ignored as obstacles only while their owning net still
14275
+ * converts successfully. Failed conversions retain both their traces and
14276
+ * their conventional labels, and the remaining conversions are retried.
14277
+ */
14278
+ computeNetConnectionInlineConversions(connections) {
14279
+ const prospectiveTraceIdsByConnection = /* @__PURE__ */ new Map();
14280
+ for (const connection of connections) {
14281
+ const prospectiveTraceIds = /* @__PURE__ */ new Set();
14282
+ for (const component of this.getNetConnectionComponents(connection)) {
14283
+ if (component.traces.length === 0) continue;
14284
+ const hasInlinePlacement = component.traces.some(
14285
+ (trace) => Boolean(
14286
+ this.computeInlinePlacement({
14287
+ connection,
14288
+ trace,
14289
+ pinIds: component.pinIds
14290
+ })
14291
+ )
14292
+ );
14293
+ if (!hasInlinePlacement) {
14294
+ for (const trace of component.traces) {
14295
+ prospectiveTraceIds.add(trace.mspPairId);
14296
+ }
14297
+ }
14298
+ }
14299
+ prospectiveTraceIdsByConnection.set(connection, prospectiveTraceIds);
14300
+ }
14301
+ let ignoredTraceIds = new Set(
14302
+ [...prospectiveTraceIdsByConnection.values()].flatMap((ids) => [...ids])
14303
+ );
14304
+ while (true) {
14305
+ const conversions = connections.map((connection) => ({
14306
+ connection,
14307
+ conversion: this.computeNetConnectionInlinePlacements(
14308
+ connection,
14309
+ ignoredTraceIds,
14310
+ prospectiveTraceIdsByConnection.get(connection) ?? /* @__PURE__ */ new Set()
14311
+ )
14312
+ }));
14313
+ const nextIgnoredTraceIds = new Set(
14314
+ conversions.flatMap(
14315
+ ({ conversion }) => conversion ? [...conversion.supersededTraceIds] : []
14316
+ )
14317
+ );
14318
+ if (setsEqual(ignoredTraceIds, nextIgnoredTraceIds)) {
14319
+ return conversions.flatMap(
14320
+ ({ conversion }) => conversion ? [conversion] : []
14321
+ );
14322
+ }
14323
+ ignoredTraceIds = nextIgnoredTraceIds;
14324
+ }
14325
+ }
14326
+ /**
14327
+ * Splits an input net into the connected components created by its solved
14328
+ * traces. This intentionally does not depend on anchored-label placement:
14329
+ * a group whose anchored label failed still has the same routing topology.
14330
+ */
14331
+ getNetConnectionComponents(connection) {
14332
+ const firstPinId = connection.pinIds[0];
14333
+ if (!firstPinId) return [];
14334
+ const canonicalGlobalConnNetId = this.globalConnMap.getNetConnectedToId(firstPinId);
14335
+ if (!canonicalGlobalConnNetId) return [];
14336
+ const inputPinIds = new Set(
14337
+ this.inputProblem.chips.flatMap(
14338
+ (chip) => chip.pins.map((pin) => pin.pinId)
14339
+ )
14340
+ );
14341
+ const pinIdsInGlobalNet = this.globalConnMap.getIdsConnectedToNet(
14342
+ canonicalGlobalConnNetId
14343
+ ).filter((id) => inputPinIds.has(id));
14344
+ const labeledPinIds = new Set(connection.pinIds);
14345
+ return getTraceConnectedPinComponents({
14346
+ pinIds: pinIdsInGlobalNet,
14347
+ // Available-net-orientation traces only connect a pin to its generated
14348
+ // anchored label. They are removed when that label becomes inline, so
14349
+ // treating them as routed circuitry would leave text on a deleted wire.
14350
+ traces: this.traces.filter(
14351
+ (trace) => !isGeneratedAnchoredLabelConnector(trace)
14352
+ )
14353
+ }).map((component) => ({
14354
+ pinIds: component.pinIds,
14355
+ labeledPinIds: component.pinIds.filter(
14356
+ (pinId) => labeledPinIds.has(pinId)
14357
+ ),
14358
+ traces: component.traces.sort(
14359
+ (a, b) => getTraceLength2(b) - getTraceLength2(a)
14360
+ )
14361
+ })).filter((component) => component.labeledPinIds.length > 0);
14362
+ }
14363
+ getGlobalConnNetId(connection) {
14364
+ const firstPinId = connection.pinIds[0];
14365
+ if (!firstPinId) return void 0;
14366
+ const connectionPinIds = new Set(connection.pinIds);
14367
+ return this.traces.find(
14368
+ (trace) => trace.pins.some((pin) => connectionPinIds.has(pin.pinId))
14369
+ )?.globalConnNetId ?? this.inputNetLabelPlacements.find(
14370
+ (placement) => placement.netId === connection.netId
14371
+ )?.globalConnNetId ?? this.globalConnMap.getNetConnectedToId(firstPinId) ?? void 0;
14372
+ }
14373
+ /**
14374
+ * Converts every conventional label representation for an opted-in named
14375
+ * net. Routed connected components become labels on their representative
14376
+ * trace; disconnected pins become outward terminal stubs. The conversion is
14377
+ * atomic so one blocked candidate cannot leave mixed label styles on a net.
14378
+ */
14379
+ computeNetConnectionInlinePlacements(connection, ignoredTraceIds, forcedTerminalTraceIds) {
14380
+ const components = this.getNetConnectionComponents(connection);
14381
+ const globalConnNetId = this.getGlobalConnNetId(connection);
14382
+ if (!globalConnNetId) return null;
14383
+ const inlinePlacements = [];
14384
+ const supersededTraceIds = /* @__PURE__ */ new Set();
14385
+ for (const component of components) {
14386
+ const shouldUseTerminalStubs = component.traces.some(
14387
+ (trace) => forcedTerminalTraceIds.has(trace.mspPairId)
14388
+ );
14389
+ let inlinePlacement = null;
14390
+ if (!shouldUseTerminalStubs) {
14391
+ for (const trace of component.traces) {
14392
+ inlinePlacement = this.computeInlinePlacement({
14393
+ connection,
14394
+ trace,
14395
+ pinIds: component.pinIds,
14396
+ ignoredTraceIds
14397
+ });
14398
+ if (inlinePlacement) break;
14399
+ }
14400
+ }
14401
+ if (inlinePlacement) {
14402
+ inlinePlacements.push(inlinePlacement);
14403
+ continue;
14404
+ }
14405
+ if (component.traces.length > 0 && !shouldUseTerminalStubs) {
14406
+ return null;
14407
+ }
14408
+ if (component.labeledPinIds.length !== component.pinIds.length) {
14409
+ return null;
14410
+ }
14411
+ const terminalPlacements = component.pinIds.map(
14412
+ (pinId) => this.computeTerminalInlinePlacement(
14413
+ connection,
14414
+ pinId,
14415
+ globalConnNetId,
14416
+ ignoredTraceIds
14417
+ )
14418
+ );
14419
+ if (terminalPlacements.some((placement) => placement === null)) {
14420
+ return null;
14421
+ }
14422
+ inlinePlacements.push(
14423
+ ...terminalPlacements.filter(
14424
+ (placement) => placement !== null
14425
+ )
14426
+ );
14427
+ for (const trace of component.traces) {
14428
+ supersededTraceIds.add(trace.mspPairId);
14429
+ }
14430
+ }
14431
+ return { globalConnNetId, placements: inlinePlacements, supersededTraceIds };
14432
+ }
13818
14433
  /**
13819
14434
  * Converts one conventional endpoint placement into an inline label on a
13820
14435
  * generated outward stub. The stub follows the pin's true facing direction;
13821
14436
  * an anchored label may finish in another direction after an elbow, which is
13822
14437
  * not the direction a terminal stub should leave the pin.
13823
14438
  */
13824
- computeTerminalInlinePlacement(connection, pinId) {
14439
+ computeTerminalInlinePlacement(connection, pinId, knownGlobalConnNetId, ignoredTraceIds = /* @__PURE__ */ new Set()) {
13825
14440
  if (!connection.netId) return null;
13826
14441
  const anchoredPlacement = this.inputNetLabelPlacements.find(
13827
14442
  (placement) => placement.netId === connection.netId && placement.pinIds.length === 1 && placement.pinIds[0] === pinId
13828
14443
  );
13829
- if (!anchoredPlacement) return null;
14444
+ const globalConnNetId = knownGlobalConnNetId ?? anchoredPlacement?.globalConnNetId;
14445
+ if (!globalConnNetId) return null;
13830
14446
  const inputChip = this.inputProblem.chips.find(
13831
14447
  (chip) => chip.pins.some((pin) => pin.pinId === pinId)
13832
14448
  );
@@ -13835,6 +14451,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
13835
14451
  const netLabelText = connection.netLabelText?.trim() || void 0;
13836
14452
  const width = connection.inlineNetLabelWidth ?? connection.netLabelWidth ?? estimateInlineNetLabelWidth(netLabelText ?? connection.netId, height);
13837
14453
  const stubLength = Math.max(width + 0.2, 0.6);
14454
+ if (!inputPin && !anchoredPlacement) return null;
13838
14455
  const start = inputPin ? { x: inputPin.x, y: inputPin.y } : anchoredPlacement.anchorPoint;
13839
14456
  const direction = inputPin && inputChip ? inputPin._facingDirection ?? getPinDirection(inputPin, inputChip) : anchoredPlacement.orientation;
13840
14457
  const intendedEnd = direction === "x+" ? { x: start.x + stubLength, y: start.y } : direction === "x-" ? { x: start.x - stubLength, y: start.y } : direction === "y+" ? { x: start.x, y: start.y + stubLength } : { x: start.x, y: start.y - stubLength };
@@ -13843,53 +14460,60 @@ var InlineNetLabelSolver = class extends BaseSolver {
13843
14460
  intendedEnd,
13844
14461
  minimumLength: width + 2 * INLINE_NET_LABEL_TRACE_MARGIN,
13845
14462
  traces: this.traces,
13846
- ownGlobalConnNetId: anchoredPlacement.globalConnNetId
14463
+ ownGlobalConnNetId: globalConnNetId,
14464
+ ignoredTraceIds
13847
14465
  });
13848
14466
  if (!end) return null;
13849
14467
  const axis = direction === "x+" || direction === "x-" ? "x" : "y";
13850
- const side = axis === "x" ? "y+" : "x-";
13851
14468
  const anchorPoint = {
13852
14469
  x: (start.x + end.x) / 2,
13853
14470
  y: (start.y + end.y) / 2
13854
14471
  };
13855
14472
  const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN;
13856
- const center = side === "y+" ? { x: anchorPoint.x, y: anchorPoint.y + offset } : { x: anchorPoint.x - offset, y: anchorPoint.y };
13857
- const halfAlong = width / 2;
13858
- const halfAcross = height / 2;
13859
- const bounds = axis === "x" ? {
13860
- minX: center.x - halfAlong,
13861
- maxX: center.x + halfAlong,
13862
- minY: center.y - halfAcross,
13863
- maxY: center.y + halfAcross
13864
- } : {
13865
- minX: center.x - halfAcross,
13866
- maxX: center.x + halfAcross,
13867
- minY: center.y - halfAlong,
13868
- maxY: center.y + halfAlong
13869
- };
13870
- if (this.isObstructed(bounds, {
13871
- ownGlobalConnNetId: anchoredPlacement.globalConnNetId
13872
- })) {
13873
- return null;
14473
+ const sides = axis === "x" ? ["y+", "y-"] : ["x-", "x+"];
14474
+ for (const side of sides) {
14475
+ const center = side === "y+" ? { x: anchorPoint.x, y: anchorPoint.y + offset } : side === "y-" ? { x: anchorPoint.x, y: anchorPoint.y - offset } : side === "x-" ? { x: anchorPoint.x - offset, y: anchorPoint.y } : { x: anchorPoint.x + offset, y: anchorPoint.y };
14476
+ const halfAlong = width / 2;
14477
+ const halfAcross = height / 2;
14478
+ const bounds = axis === "x" ? {
14479
+ minX: center.x - halfAlong,
14480
+ maxX: center.x + halfAlong,
14481
+ minY: center.y - halfAcross,
14482
+ maxY: center.y + halfAcross
14483
+ } : {
14484
+ minX: center.x - halfAcross,
14485
+ maxX: center.x + halfAcross,
14486
+ minY: center.y - halfAlong,
14487
+ maxY: center.y + halfAlong
14488
+ };
14489
+ if (this.isObstructed(bounds, {
14490
+ ownGlobalConnNetId: globalConnNetId,
14491
+ ignoredTraceIds
14492
+ })) {
14493
+ continue;
14494
+ }
14495
+ return {
14496
+ globalConnNetId,
14497
+ netId: connection.netId,
14498
+ netLabelText,
14499
+ pinIds: [pinId],
14500
+ stubTracePath: [start, end],
14501
+ axis,
14502
+ anchorPoint,
14503
+ center,
14504
+ width,
14505
+ height,
14506
+ side
14507
+ };
13874
14508
  }
13875
- return {
13876
- globalConnNetId: anchoredPlacement.globalConnNetId,
13877
- netId: connection.netId,
13878
- netLabelText,
13879
- pinIds: [pinId],
13880
- stubTracePath: [start, end],
13881
- axis,
13882
- anchorPoint,
13883
- center,
13884
- width,
13885
- height,
13886
- side
13887
- };
14509
+ return null;
13888
14510
  }
13889
- computeInlinePlacement(connection) {
13890
- const traces = this.tracesByPinPairKey.get(getPinPairKey2(connection.pinIds)) ?? [];
13891
- if (traces.length === 0) return null;
13892
- const trace = traces[0];
14511
+ computeInlinePlacement({
14512
+ connection,
14513
+ trace,
14514
+ pinIds,
14515
+ ignoredTraceIds = /* @__PURE__ */ new Set()
14516
+ }) {
13893
14517
  const segments = getAxisAlignedSegments(trace.tracePath);
13894
14518
  if (segments.length === 0) return null;
13895
14519
  const height = connection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT;
@@ -13917,7 +14541,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
13917
14541
  };
13918
14542
  if (this.isObstructed(bounds, {
13919
14543
  ownTrace: trace,
13920
- ownGlobalConnNetId: trace.globalConnNetId
14544
+ ownGlobalConnNetId: trace.globalConnNetId,
14545
+ ignoredTraceIds
13921
14546
  }))
13922
14547
  continue;
13923
14548
  return {
@@ -13925,7 +14550,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
13925
14550
  netId: connection.netId,
13926
14551
  netLabelText,
13927
14552
  mspPairId: trace.mspPairId,
13928
- pinIds: [...connection.pinIds],
14553
+ pinIds: [...pinIds],
13929
14554
  axis: segment.axis,
13930
14555
  anchorPoint,
13931
14556
  center,
@@ -13942,7 +14567,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
13942
14567
  netLabelText,
13943
14568
  width,
13944
14569
  height,
13945
- offset
14570
+ offset,
14571
+ pinIds,
14572
+ ignoredTraceIds
13946
14573
  });
13947
14574
  if (spanPlacement) return spanPlacement;
13948
14575
  return null;
@@ -13959,7 +14586,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
13959
14586
  netLabelText,
13960
14587
  width,
13961
14588
  height,
13962
- offset
14589
+ offset,
14590
+ pinIds,
14591
+ ignoredTraceIds
13963
14592
  }) {
13964
14593
  const path = trace.tracePath;
13965
14594
  if (path.length < 2) return null;
@@ -14009,7 +14638,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
14009
14638
  };
14010
14639
  if (this.isObstructed(bounds, {
14011
14640
  ownTrace: trace,
14012
- ownGlobalConnNetId: trace.globalConnNetId
14641
+ ownGlobalConnNetId: trace.globalConnNetId,
14642
+ ignoredTraceIds
14013
14643
  }))
14014
14644
  continue;
14015
14645
  const anchorPoint = axis === "x" ? { x: along, y: side === "y+" ? maxY : minY } : { x: side === "x-" ? minX : maxX, y: along };
@@ -14018,7 +14648,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
14018
14648
  netId: connection.netId,
14019
14649
  netLabelText,
14020
14650
  mspPairId: trace.mspPairId,
14021
- pinIds: [...connection.pinIds],
14651
+ pinIds: [...pinIds],
14022
14652
  axis,
14023
14653
  anchorPoint,
14024
14654
  center,
@@ -14036,7 +14666,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
14036
14666
  */
14037
14667
  isObstructed(bounds, {
14038
14668
  ownTrace,
14039
- ownGlobalConnNetId
14669
+ ownGlobalConnNetId,
14670
+ ignoredTraceIds = /* @__PURE__ */ new Set()
14040
14671
  }) {
14041
14672
  for (const chip of this.inputProblem.chips) {
14042
14673
  const chipBounds = {
@@ -14051,9 +14682,10 @@ var InlineNetLabelSolver = class extends BaseSolver {
14051
14682
  if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
14052
14683
  }
14053
14684
  for (const trace of this.traces) {
14685
+ if (ignoredTraceIds.has(trace.mspPairId)) continue;
14054
14686
  if (ownTrace && trace.mspPairId === ownTrace.mspPairId) continue;
14055
14687
  if (trace.globalConnNetId === ownGlobalConnNetId) continue;
14056
- if (doesPathIntersectBounds2(trace.tracePath, bounds)) return true;
14688
+ if (doesPathIntersectBounds3(trace.tracePath, bounds)) return true;
14057
14689
  }
14058
14690
  return false;
14059
14691
  }
@@ -14061,36 +14693,94 @@ var InlineNetLabelSolver = class extends BaseSolver {
14061
14693
  * Net label placements superseded by an inline label. A net gets one label or
14062
14694
  * the other, never both.
14063
14695
  */
14064
- getSupersededNetLabelKeys() {
14696
+ getSupersededNetLabelKeys(placements = this.inlineNetLabelPlacements) {
14065
14697
  const keys = /* @__PURE__ */ new Set();
14066
- for (const placement of this.inlineNetLabelPlacements) {
14698
+ for (const placement of placements) {
14067
14699
  keys.add(placement.globalConnNetId);
14068
14700
  }
14069
14701
  return keys;
14070
14702
  }
14071
- getOutputTraces(supersededNetLabelKeys) {
14703
+ getOutputTraces(supersededNetLabelKeys, supersededTraceIds) {
14072
14704
  return this.traces.filter(
14073
- (trace) => !(supersededNetLabelKeys.has(trace.globalConnNetId) && trace.mspPairId.startsWith("available-net-orientation-"))
14705
+ (trace) => !supersededTraceIds.has(trace.mspPairId) && !(supersededNetLabelKeys.has(trace.globalConnNetId) && isGeneratedAnchoredLabelConnector(trace))
14074
14706
  );
14075
14707
  }
14708
+ getInlineGlobalsOverlappingAnchoredLabels({
14709
+ inlineNetLabelPlacements,
14710
+ anchoredNetLabelPlacements
14711
+ }) {
14712
+ const blockedGlobalConnNetIds = /* @__PURE__ */ new Set();
14713
+ for (const inlinePlacement of inlineNetLabelPlacements) {
14714
+ const inlineBounds = getInlinePlacementBounds(inlinePlacement);
14715
+ for (const anchoredPlacement of anchoredNetLabelPlacements) {
14716
+ if (anchoredPlacement.globalConnNetId === inlinePlacement.globalConnNetId) {
14717
+ continue;
14718
+ }
14719
+ const anchoredBounds = getAnchoredPlacementBounds(anchoredPlacement);
14720
+ if (boundsOverlap(inlineBounds, anchoredBounds) || inlinePlacement.stubTracePath && doesPathIntersectBounds3(
14721
+ inlinePlacement.stubTracePath,
14722
+ anchoredBounds
14723
+ )) {
14724
+ blockedGlobalConnNetIds.add(inlinePlacement.globalConnNetId);
14725
+ break;
14726
+ }
14727
+ }
14728
+ }
14729
+ return blockedGlobalConnNetIds;
14730
+ }
14076
14731
  buildPostProcessedOutput() {
14077
- const superseded = this.getSupersededNetLabelKeys();
14078
- const retainedNetLabelPlacements = this.inputNetLabelPlacements.filter(
14079
- (placement) => !superseded.has(placement.globalConnNetId)
14080
- );
14081
- const outputTraces = this.getOutputTraces(superseded);
14082
- const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
14083
- inputProblem: this.inputProblem,
14084
- traces: outputTraces,
14085
- netLabelPlacements: retainedNetLabelPlacements,
14086
- inlineNetLabelPlacements: this.inlineNetLabelPlacements
14087
- });
14088
- this.stats.pushedAnchoredNetLabelCount = pushed.movedLabelCount;
14089
- return {
14090
- traces: pushed.traces,
14091
- netLabelPlacements: pushed.netLabelPlacements,
14092
- inlineNetLabelPlacements: this.inlineNetLabelPlacements
14093
- };
14732
+ let activeInlinePlacements = this.inlineNetLabelPlacements;
14733
+ while (true) {
14734
+ const supersededNetLabelKeys = this.getSupersededNetLabelKeys(
14735
+ activeInlinePlacements
14736
+ );
14737
+ const supersededTraceIds = new Set(
14738
+ [...supersededNetLabelKeys].flatMap((globalConnNetId) => [
14739
+ ...this.supersededTraceIdsByGlobalConnNetId.get(globalConnNetId) ?? []
14740
+ ])
14741
+ );
14742
+ const retainedNetLabelPlacements = this.inputNetLabelPlacements.filter(
14743
+ (placement) => !supersededNetLabelKeys.has(placement.globalConnNetId)
14744
+ );
14745
+ const outputTraces = this.getOutputTraces(
14746
+ supersededNetLabelKeys,
14747
+ supersededTraceIds
14748
+ );
14749
+ const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
14750
+ inputProblem: this.inputProblem,
14751
+ traces: outputTraces,
14752
+ netLabelPlacements: retainedNetLabelPlacements,
14753
+ inlineNetLabelPlacements: activeInlinePlacements
14754
+ });
14755
+ const blockedGlobalConnNetIds = this.getInlineGlobalsOverlappingAnchoredLabels({
14756
+ inlineNetLabelPlacements: activeInlinePlacements,
14757
+ anchoredNetLabelPlacements: pushed.netLabelPlacements
14758
+ });
14759
+ if (blockedGlobalConnNetIds.size > 0) {
14760
+ const shiftedInlineTerminalLabels = pushInlineTerminalLabelsAwayFromAnchoredLabels({
14761
+ inputProblem: this.inputProblem,
14762
+ traces: pushed.traces,
14763
+ anchoredNetLabelPlacements: pushed.netLabelPlacements,
14764
+ inlineNetLabelPlacements: activeInlinePlacements
14765
+ });
14766
+ if (shiftedInlineTerminalLabels.movedGroupCount > 0) {
14767
+ activeInlinePlacements = shiftedInlineTerminalLabels.inlineNetLabelPlacements;
14768
+ continue;
14769
+ }
14770
+ }
14771
+ if (blockedGlobalConnNetIds.size === 0) {
14772
+ this.inlineNetLabelPlacements = activeInlinePlacements;
14773
+ this.stats.pushedAnchoredNetLabelCount = pushed.movedLabelCount;
14774
+ return {
14775
+ traces: pushed.traces,
14776
+ netLabelPlacements: pushed.netLabelPlacements,
14777
+ inlineNetLabelPlacements: activeInlinePlacements
14778
+ };
14779
+ }
14780
+ activeInlinePlacements = activeInlinePlacements.filter(
14781
+ (placement) => !blockedGlobalConnNetIds.has(placement.globalConnNetId)
14782
+ );
14783
+ }
14094
14784
  }
14095
14785
  getOutput() {
14096
14786
  if (this.postProcessedOutput) return this.postProcessedOutput;
@@ -14225,7 +14915,7 @@ var getAnchorCandidates = (segment, labelWidth, step = 0.1) => {
14225
14915
  offsets.push(slack / 2, -slack / 2);
14226
14916
  return offsets.map((offset) => pointAt(mid + offset * direction));
14227
14917
  };
14228
- var doesPathIntersectBounds2 = (path, bounds) => {
14918
+ var doesPathIntersectBounds3 = (path, bounds) => {
14229
14919
  for (let i = 0; i < path.length - 1; i++) {
14230
14920
  const a = path[i];
14231
14921
  const b = path[i + 1];
@@ -14245,7 +14935,8 @@ var getCollisionLimitedTerminalStubEnd = ({
14245
14935
  intendedEnd,
14246
14936
  minimumLength,
14247
14937
  traces,
14248
- ownGlobalConnNetId
14938
+ ownGlobalConnNetId,
14939
+ ignoredTraceIds
14249
14940
  }) => {
14250
14941
  const isHorizontal4 = Math.abs(intendedEnd.x - start.x) >= Math.abs(intendedEnd.y - start.y);
14251
14942
  const alongAxis = isHorizontal4 ? "x" : "y";
@@ -14254,6 +14945,7 @@ var getCollisionLimitedTerminalStubEnd = ({
14254
14945
  const intendedLength = Math.abs(intendedEnd[alongAxis] - start[alongAxis]);
14255
14946
  let availableLength = intendedLength;
14256
14947
  for (const trace of traces) {
14948
+ if (ignoredTraceIds.has(trace.mspPairId)) continue;
14257
14949
  if (trace.globalConnNetId === ownGlobalConnNetId) continue;
14258
14950
  for (let segmentIndex = 0; segmentIndex < trace.tracePath.length - 1; segmentIndex++) {
14259
14951
  const segmentStart = trace.tracePath[segmentIndex];