@tscircuit/schematic-trace-solver 0.0.145 → 0.0.146

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1250,6 +1250,7 @@ declare class InlineNetLabelSolver extends BaseSolver {
1250
1250
  queuedConnections: InlineEligibleConnection[];
1251
1251
  private tracesByPinPairKey;
1252
1252
  private hasAlignedPortOnlyStubs;
1253
+ private postProcessedOutput?;
1253
1254
  constructor(input: InlineNetLabelSolverInput);
1254
1255
  getConstructorParams(): [InlineNetLabelSolverInput];
1255
1256
  _step(): void;
@@ -1279,6 +1280,7 @@ declare class InlineNetLabelSolver extends BaseSolver {
1279
1280
  */
1280
1281
  private getSupersededNetLabelKeys;
1281
1282
  private getOutputTraces;
1283
+ private buildPostProcessedOutput;
1282
1284
  getOutput(): {
1283
1285
  traces: SolvedTracePath[];
1284
1286
  netLabelPlacements: NetLabelPlacement[];
package/dist/index.js CHANGED
@@ -10958,10 +10958,10 @@ function isPointOnPath(point, path) {
10958
10958
  }
10959
10959
  function buildMergedObstacleLabel(seedLabel, allLabels) {
10960
10960
  const TOUCH_MARGIN = 0.01;
10961
- const getBounds3 = (l) => getRectBounds(l.center, l.width, l.height);
10961
+ const getBounds4 = (l) => getRectBounds(l.center, l.width, l.height);
10962
10962
  const touches = (a, b) => {
10963
- const ba = getBounds3(a);
10964
- const bb = getBounds3(b);
10963
+ const ba = getBounds4(a);
10964
+ const bb = getBounds4(b);
10965
10965
  return ba.minX <= bb.maxX + TOUCH_MARGIN && ba.maxX >= bb.minX - TOUCH_MARGIN && ba.minY <= bb.maxY + TOUCH_MARGIN && ba.maxY >= bb.minY - TOUCH_MARGIN;
10966
10966
  };
10967
10967
  const group = /* @__PURE__ */ new Set([seedLabel]);
@@ -10978,7 +10978,7 @@ function buildMergedObstacleLabel(seedLabel, allLabels) {
10978
10978
  }
10979
10979
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10980
10980
  for (const l of group) {
10981
- const b = getBounds3(l);
10981
+ const b = getBounds4(l);
10982
10982
  if (b.minX < minX) minX = b.minX;
10983
10983
  if (b.minY < minY) minY = b.minY;
10984
10984
  if (b.maxX > maxX) maxX = b.maxX;
@@ -12789,6 +12789,365 @@ var alignPortOnlyInlineNetLabelStubs = ({
12789
12789
  return alignedPlacements;
12790
12790
  };
12791
12791
 
12792
+ // lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts
12793
+ var LABEL_CLEARANCE2 = 0.05;
12794
+ var POINT_EPSILON = 1e-6;
12795
+ var CONTIGUOUS_LABEL_GAP = 0.01;
12796
+ var MAX_OUTWARD_DISTANCE = 5;
12797
+ var getBounds3 = (placement) => {
12798
+ const renderedWidth = placement.axis === "y" ? placement.height : placement.width;
12799
+ const renderedHeight = placement.axis === "y" ? placement.width : placement.height;
12800
+ return {
12801
+ minX: placement.center.x - renderedWidth / 2,
12802
+ maxX: placement.center.x + renderedWidth / 2,
12803
+ minY: placement.center.y - renderedHeight / 2,
12804
+ maxY: placement.center.y + renderedHeight / 2
12805
+ };
12806
+ };
12807
+ var pointsEqual3 = (a, b) => Math.abs(a.x - b.x) <= POINT_EPSILON && Math.abs(a.y - b.y) <= POINT_EPSILON;
12808
+ var pathIntersectsBounds = (path, bounds) => {
12809
+ for (let index = 0; index < path.length - 1; index++) {
12810
+ const start = path[index];
12811
+ const end = path[index + 1];
12812
+ const segmentBounds = {
12813
+ minX: Math.min(start.x, end.x),
12814
+ maxX: Math.max(start.x, end.x),
12815
+ minY: Math.min(start.y, end.y),
12816
+ maxY: Math.max(start.y, end.y)
12817
+ };
12818
+ if (boundsOverlap(segmentBounds, bounds)) return true;
12819
+ }
12820
+ return false;
12821
+ };
12822
+ var isPointOnPath2 = (point, path) => {
12823
+ for (let index = 0; index < path.length - 1; index++) {
12824
+ const start = path[index];
12825
+ const end = path[index + 1];
12826
+ const minX = Math.min(start.x, end.x) - POINT_EPSILON;
12827
+ const maxX = Math.max(start.x, end.x) + POINT_EPSILON;
12828
+ const minY = Math.min(start.y, end.y) - POINT_EPSILON;
12829
+ const maxY = Math.max(start.y, end.y) + POINT_EPSILON;
12830
+ const isHorizontal4 = Math.abs(start.y - end.y) <= POINT_EPSILON;
12831
+ const isVertical5 = Math.abs(start.x - end.x) <= POINT_EPSILON;
12832
+ if ((isHorizontal4 && Math.abs(point.y - start.y) <= POINT_EPSILON || isVertical5 && Math.abs(point.x - start.x) <= POINT_EPSILON) && point.x >= minX && point.x <= maxX && point.y >= minY && point.y <= maxY) {
12833
+ return true;
12834
+ }
12835
+ }
12836
+ return false;
12837
+ };
12838
+ var getRequiredOutwardDistance = (label, inlineBounds) => {
12839
+ const labelBounds = getBounds3(label);
12840
+ if (label.orientation === "x-" || label.orientation === "x+") {
12841
+ const nearby2 = inlineBounds.filter(
12842
+ (bounds) => labelBounds.minY < bounds.maxY && labelBounds.maxY > bounds.minY
12843
+ );
12844
+ if (nearby2.length === 0) return 0;
12845
+ if (label.orientation === "x-") {
12846
+ const targetMaxX = Math.min(...nearby2.map((bounds) => bounds.minX));
12847
+ return Math.max(0, labelBounds.maxX - targetMaxX + LABEL_CLEARANCE2);
12848
+ }
12849
+ const targetMinX = Math.max(...nearby2.map((bounds) => bounds.maxX));
12850
+ return Math.max(0, targetMinX - labelBounds.minX + LABEL_CLEARANCE2);
12851
+ }
12852
+ const nearby = inlineBounds.filter(
12853
+ (bounds) => labelBounds.minX < bounds.maxX && labelBounds.maxX > bounds.minX
12854
+ );
12855
+ if (nearby.length === 0) return 0;
12856
+ if (label.orientation === "y-") {
12857
+ const targetMaxY = Math.min(...nearby.map((bounds) => bounds.minY));
12858
+ return Math.max(0, labelBounds.maxY - targetMaxY + LABEL_CLEARANCE2);
12859
+ }
12860
+ const targetMinY = Math.max(...nearby.map((bounds) => bounds.maxY));
12861
+ return Math.max(0, targetMinY - labelBounds.minY + LABEL_CLEARANCE2);
12862
+ };
12863
+ var moveLabel = (label, orientation, distance5) => {
12864
+ const direction = dir(orientation);
12865
+ return {
12866
+ ...label,
12867
+ anchorPoint: {
12868
+ x: label.anchorPoint.x + direction.x * distance5,
12869
+ y: label.anchorPoint.y + direction.y * distance5
12870
+ },
12871
+ center: {
12872
+ x: label.center.x + direction.x * distance5,
12873
+ y: label.center.y + direction.y * distance5
12874
+ }
12875
+ };
12876
+ };
12877
+ var getDistanceToShoveBoundsPast = (obstacleBounds, movingBounds, orientation) => {
12878
+ switch (orientation) {
12879
+ case "x-":
12880
+ return obstacleBounds.maxX - movingBounds.minX + LABEL_CLEARANCE2;
12881
+ case "x+":
12882
+ return movingBounds.maxX - obstacleBounds.minX + LABEL_CLEARANCE2;
12883
+ case "y-":
12884
+ return obstacleBounds.maxY - movingBounds.minY + LABEL_CLEARANCE2;
12885
+ case "y+":
12886
+ return movingBounds.maxY - obstacleBounds.minY + LABEL_CLEARANCE2;
12887
+ }
12888
+ };
12889
+ var boundsGapOnPerpendicularAxis = (a, b, orientation) => {
12890
+ if (orientation === "x-" || orientation === "x+") {
12891
+ return Math.max(0, a.minY - b.maxY, b.minY - a.maxY);
12892
+ }
12893
+ return Math.max(0, a.minX - b.maxX, b.minX - a.maxX);
12894
+ };
12895
+ var sharesOwnerChip = (label, ownerChipIds, chipIdByPinId) => label.pinIds.some((pinId) => ownerChipIds.has(chipIdByPinId.get(pinId) ?? ""));
12896
+ var isGeneratedLabelConnector = (trace) => trace.mspPairId.startsWith("available-net-orientation-") || trace.mspPairId.startsWith("inline-net-label-clearance-");
12897
+ var findConnectorTraceIndex = (label, traces) => traces.findIndex((trace) => {
12898
+ if (trace.globalConnNetId !== label.globalConnNetId) return false;
12899
+ if (!isGeneratedLabelConnector(trace)) return false;
12900
+ const first = trace.tracePath[0];
12901
+ const last = trace.tracePath.at(-1);
12902
+ return Boolean(
12903
+ first && pointsEqual3(first, label.anchorPoint) || last && pointsEqual3(last, label.anchorPoint)
12904
+ );
12905
+ });
12906
+ var canAddConnectorAtAnchor = (label, traces, pinMap) => {
12907
+ if (label.pinIds.some((pinId) => {
12908
+ const pin = pinMap[pinId];
12909
+ return pin && pointsEqual3(pin, label.anchorPoint);
12910
+ })) {
12911
+ return true;
12912
+ }
12913
+ return traces.some(
12914
+ (trace) => trace.globalConnNetId === label.globalConnNetId && isPointOnPath2(label.anchorPoint, trace.tracePath)
12915
+ );
12916
+ };
12917
+ var moveConnectorEndpoint = (trace, oldAnchor, newAnchor) => {
12918
+ const tracePath = trace.tracePath.map((point) => ({ ...point }));
12919
+ if (pointsEqual3(tracePath[0], oldAnchor)) tracePath[0] = newAnchor;
12920
+ if (pointsEqual3(tracePath.at(-1), oldAnchor)) {
12921
+ tracePath[tracePath.length - 1] = newAnchor;
12922
+ }
12923
+ return { ...trace, tracePath };
12924
+ };
12925
+ var createConnectorTrace = ({
12926
+ label,
12927
+ labelIndex,
12928
+ newAnchor,
12929
+ pinMap
12930
+ }) => {
12931
+ const mspPairId = `inline-net-label-clearance-${labelIndex}-${label.netId ?? label.globalConnNetId}`;
12932
+ return {
12933
+ mspPairId,
12934
+ dcConnNetId: label.dcConnNetId ?? label.globalConnNetId,
12935
+ globalConnNetId: label.globalConnNetId,
12936
+ userNetId: label.netId,
12937
+ pins: getTracePins(label, pinMap),
12938
+ tracePath: [label.anchorPoint, newAnchor],
12939
+ mspConnectionPairIds: [mspPairId],
12940
+ pinIds: label.pinIds
12941
+ };
12942
+ };
12943
+ var getContiguousLabelGroup = ({
12944
+ triggerIndex,
12945
+ labels,
12946
+ chipIdByPinId
12947
+ }) => {
12948
+ const trigger = labels[triggerIndex];
12949
+ const ownerChipIds = new Set(
12950
+ trigger.pinIds.flatMap((pinId) => {
12951
+ const chipId = chipIdByPinId.get(pinId);
12952
+ return chipId ? [chipId] : [];
12953
+ })
12954
+ );
12955
+ const candidates = labels.map((label, labelIndex) => ({ label, labelIndex })).filter(
12956
+ ({ label }) => label.orientation === trigger.orientation && label.mspConnectionPairIds.length === 0 && sharesOwnerChip(label, ownerChipIds, chipIdByPinId)
12957
+ );
12958
+ const group = /* @__PURE__ */ new Set([triggerIndex]);
12959
+ let changed = true;
12960
+ while (changed) {
12961
+ changed = false;
12962
+ for (const { label, labelIndex } of candidates) {
12963
+ if (group.has(labelIndex)) continue;
12964
+ if ([...group].some(
12965
+ (memberIndex) => boundsGapOnPerpendicularAxis(
12966
+ getBounds3(labels[memberIndex]),
12967
+ getBounds3(label),
12968
+ trigger.orientation
12969
+ ) <= CONTIGUOUS_LABEL_GAP
12970
+ )) {
12971
+ group.add(labelIndex);
12972
+ changed = true;
12973
+ }
12974
+ }
12975
+ }
12976
+ return { group, ownerChipIds };
12977
+ };
12978
+ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
12979
+ inputProblem,
12980
+ traces,
12981
+ netLabelPlacements,
12982
+ inlineNetLabelPlacements
12983
+ }) => {
12984
+ const outputTraces = traces.map((trace) => ({
12985
+ ...trace,
12986
+ tracePath: trace.tracePath.map((point) => ({ ...point }))
12987
+ }));
12988
+ const outputLabels = netLabelPlacements.map((label) => ({ ...label }));
12989
+ const inlineBounds = inlineNetLabelPlacements.map(getBounds3);
12990
+ const pinMap = getPinMap(inputProblem);
12991
+ const chipIdByPinId = /* @__PURE__ */ new Map();
12992
+ for (const chip of inputProblem.chips) {
12993
+ for (const pin of chip.pins) chipIdByPinId.set(pin.pinId, chip.chipId);
12994
+ }
12995
+ const movedLabelIndices = /* @__PURE__ */ new Set();
12996
+ for (let triggerIndex = 0; triggerIndex < outputLabels.length; triggerIndex++) {
12997
+ const trigger = outputLabels[triggerIndex];
12998
+ const distance5 = getRequiredOutwardDistance(trigger, inlineBounds);
12999
+ if (distance5 <= POINT_EPSILON || distance5 > MAX_OUTWARD_DISTANCE) continue;
13000
+ const { group, ownerChipIds } = getContiguousLabelGroup({
13001
+ triggerIndex,
13002
+ labels: outputLabels,
13003
+ chipIdByPinId
13004
+ });
13005
+ const distances = new Map(
13006
+ [...group].map((labelIndex) => [labelIndex, distance5])
13007
+ );
13008
+ let failed = false;
13009
+ for (let iteration = 0; iteration < outputLabels.length; iteration++) {
13010
+ let adjustedObstacle = false;
13011
+ for (const [movingIndex, movingDistance] of distances) {
13012
+ const movingBounds = getBounds3(
13013
+ moveLabel(
13014
+ outputLabels[movingIndex],
13015
+ trigger.orientation,
13016
+ movingDistance
13017
+ )
13018
+ );
13019
+ for (let obstacleIndex = 0; obstacleIndex < outputLabels.length; obstacleIndex++) {
13020
+ if (group.has(obstacleIndex)) continue;
13021
+ const obstacle = outputLabels[obstacleIndex];
13022
+ const existingObstacleDistance = distances.get(obstacleIndex) ?? 0;
13023
+ const obstacleBounds = getBounds3(
13024
+ moveLabel(obstacle, trigger.orientation, existingObstacleDistance)
13025
+ );
13026
+ if (!boundsOverlap(movingBounds, obstacleBounds)) continue;
13027
+ if (!sharesOwnerChip(obstacle, ownerChipIds, chipIdByPinId) || findConnectorTraceIndex(obstacle, outputTraces) === -1 && !canAddConnectorAtAnchor(obstacle, outputTraces, pinMap)) {
13028
+ failed = true;
13029
+ break;
13030
+ }
13031
+ const shoveDistance = getDistanceToShoveBoundsPast(
13032
+ getBounds3(obstacle),
13033
+ movingBounds,
13034
+ trigger.orientation
13035
+ );
13036
+ if (shoveDistance > MAX_OUTWARD_DISTANCE || shoveDistance <= existingObstacleDistance + POINT_EPSILON) {
13037
+ failed = true;
13038
+ break;
13039
+ }
13040
+ distances.set(obstacleIndex, shoveDistance);
13041
+ adjustedObstacle = true;
13042
+ }
13043
+ if (failed) break;
13044
+ }
13045
+ if (failed || !adjustedObstacle) break;
13046
+ }
13047
+ if (failed) continue;
13048
+ const proposals = /* @__PURE__ */ new Map();
13049
+ for (const [labelIndex, labelDistance] of distances) {
13050
+ proposals.set(
13051
+ labelIndex,
13052
+ moveLabel(
13053
+ outputLabels[labelIndex],
13054
+ trigger.orientation,
13055
+ labelDistance
13056
+ )
13057
+ );
13058
+ }
13059
+ const finalLabelAt = (labelIndex) => proposals.get(labelIndex) ?? outputLabels[labelIndex];
13060
+ for (const [labelIndex, movedLabel] of proposals) {
13061
+ const movedBounds = getBounds3(movedLabel);
13062
+ if (inlineBounds.some((bounds) => boundsOverlap(movedBounds, bounds))) {
13063
+ failed = true;
13064
+ break;
13065
+ }
13066
+ if (inputProblem.chips.some(
13067
+ (chip) => boundsOverlap(movedBounds, {
13068
+ minX: chip.center.x - chip.width / 2,
13069
+ maxX: chip.center.x + chip.width / 2,
13070
+ minY: chip.center.y - chip.height / 2,
13071
+ maxY: chip.center.y + chip.height / 2
13072
+ })
13073
+ ) || (inputProblem.textBoxes ?? []).some(
13074
+ (textBox) => boundsOverlap(movedBounds, getTextBoxBounds(textBox))
13075
+ )) {
13076
+ failed = true;
13077
+ break;
13078
+ }
13079
+ if (outputLabels.some(
13080
+ (_, otherIndex) => otherIndex !== labelIndex && boundsOverlap(movedBounds, getBounds3(finalLabelAt(otherIndex)))
13081
+ )) {
13082
+ failed = true;
13083
+ break;
13084
+ }
13085
+ if (outputTraces.some(
13086
+ (trace) => trace.globalConnNetId !== movedLabel.globalConnNetId && pathIntersectsBounds(trace.tracePath, movedBounds)
13087
+ )) {
13088
+ failed = true;
13089
+ break;
13090
+ }
13091
+ }
13092
+ if (failed) continue;
13093
+ const connectorUpdates = [];
13094
+ for (const [labelIndex, movedLabel] of proposals) {
13095
+ const label = outputLabels[labelIndex];
13096
+ const connectorIndex = findConnectorTraceIndex(label, outputTraces);
13097
+ if (connectorIndex === -1 && !canAddConnectorAtAnchor(label, outputTraces, pinMap)) {
13098
+ failed = true;
13099
+ break;
13100
+ }
13101
+ const connector = connectorIndex === -1 ? createConnectorTrace({
13102
+ label,
13103
+ labelIndex,
13104
+ newAnchor: movedLabel.anchorPoint,
13105
+ pinMap
13106
+ }) : moveConnectorEndpoint(
13107
+ outputTraces[connectorIndex],
13108
+ label.anchorPoint,
13109
+ movedLabel.anchorPoint
13110
+ );
13111
+ const connectorObstructed = inlineBounds.some(
13112
+ (bounds) => pathIntersectsBounds(connector.tracePath, bounds)
13113
+ ) || inputProblem.chips.some(
13114
+ (chip) => pathIntersectsBounds(connector.tracePath, {
13115
+ minX: chip.center.x - chip.width / 2,
13116
+ maxX: chip.center.x + chip.width / 2,
13117
+ minY: chip.center.y - chip.height / 2,
13118
+ maxY: chip.center.y + chip.height / 2
13119
+ })
13120
+ ) || (inputProblem.textBoxes ?? []).some(
13121
+ (textBox) => pathIntersectsBounds(connector.tracePath, getTextBoxBounds(textBox))
13122
+ ) || outputLabels.some(
13123
+ (_, otherIndex) => otherIndex !== labelIndex && pathIntersectsBounds(
13124
+ connector.tracePath,
13125
+ getBounds3(finalLabelAt(otherIndex))
13126
+ )
13127
+ );
13128
+ if (connectorObstructed) {
13129
+ failed = true;
13130
+ break;
13131
+ }
13132
+ connectorUpdates.push({ labelIndex, connectorIndex, trace: connector });
13133
+ }
13134
+ if (failed) continue;
13135
+ for (const [labelIndex, movedLabel] of proposals) {
13136
+ outputLabels[labelIndex] = movedLabel;
13137
+ movedLabelIndices.add(labelIndex);
13138
+ }
13139
+ for (const update of connectorUpdates) {
13140
+ if (update.connectorIndex === -1) outputTraces.push(update.trace);
13141
+ else outputTraces[update.connectorIndex] = update.trace;
13142
+ }
13143
+ }
13144
+ return {
13145
+ traces: outputTraces,
13146
+ netLabelPlacements: outputLabels,
13147
+ movedLabelCount: movedLabelIndices.size
13148
+ };
13149
+ };
13150
+
12792
13151
  // lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts
12793
13152
  var DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
12794
13153
  var INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
@@ -12803,6 +13162,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
12803
13162
  queuedConnections;
12804
13163
  tracesByPinPairKey;
12805
13164
  hasAlignedPortOnlyStubs = false;
13165
+ postProcessedOutput;
12806
13166
  constructor(input) {
12807
13167
  super();
12808
13168
  this.inputProblem = input.inputProblem;
@@ -12863,6 +13223,10 @@ var InlineNetLabelSolver = class extends BaseSolver {
12863
13223
  this.hasAlignedPortOnlyStubs = true;
12864
13224
  return;
12865
13225
  }
13226
+ if (!this.postProcessedOutput) {
13227
+ this.postProcessedOutput = this.buildPostProcessedOutput();
13228
+ return;
13229
+ }
12866
13230
  this.solved = true;
12867
13231
  this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length;
12868
13232
  }
@@ -13109,19 +13473,29 @@ var InlineNetLabelSolver = class extends BaseSolver {
13109
13473
  (trace) => !(supersededNetLabelKeys.has(trace.globalConnNetId) && trace.mspPairId.startsWith("available-net-orientation-"))
13110
13474
  );
13111
13475
  }
13112
- getOutput() {
13476
+ buildPostProcessedOutput() {
13113
13477
  const superseded = this.getSupersededNetLabelKeys();
13478
+ const retainedNetLabelPlacements = this.inputNetLabelPlacements.filter(
13479
+ (placement) => !superseded.has(placement.globalConnNetId)
13480
+ );
13481
+ const outputTraces = this.getOutputTraces(superseded);
13482
+ const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
13483
+ inputProblem: this.inputProblem,
13484
+ traces: outputTraces,
13485
+ netLabelPlacements: retainedNetLabelPlacements,
13486
+ inlineNetLabelPlacements: this.inlineNetLabelPlacements
13487
+ });
13488
+ this.stats.pushedAnchoredNetLabelCount = pushed.movedLabelCount;
13114
13489
  return {
13115
- // AvailableNetOrientationSolver may have routed an elbow from a port to
13116
- // the anchored label that this inline placement supersedes. Keep the
13117
- // actual net trace, but discard that now-orphaned label connector.
13118
- traces: this.getOutputTraces(superseded),
13119
- netLabelPlacements: this.inputNetLabelPlacements.filter(
13120
- (placement) => !superseded.has(placement.globalConnNetId)
13121
- ),
13490
+ traces: pushed.traces,
13491
+ netLabelPlacements: pushed.netLabelPlacements,
13122
13492
  inlineNetLabelPlacements: this.inlineNetLabelPlacements
13123
13493
  };
13124
13494
  }
13495
+ getOutput() {
13496
+ if (this.postProcessedOutput) return this.postProcessedOutput;
13497
+ return this.buildPostProcessedOutput();
13498
+ }
13125
13499
  visualize() {
13126
13500
  const graphics = visualizeInputProblem(this.inputProblem);
13127
13501
  graphics.lines ??= [];
@@ -18,6 +18,7 @@ import {
18
18
  getAxisAlignedSegments,
19
19
  } from "./getAxisAlignedSegments"
20
20
  import { alignPortOnlyInlineNetLabelStubs } from "./alignPortOnlyInlineNetLabelStubs"
21
+ import { pushAnchoredNetLabelsAwayFromInlineLabels } from "./pushAnchoredNetLabelsAwayFromInlineLabels"
21
22
 
22
23
  export const DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18
23
24
 
@@ -111,6 +112,11 @@ export class InlineNetLabelSolver extends BaseSolver {
111
112
 
112
113
  private tracesByPinPairKey: Map<string, SolvedTracePath[]>
113
114
  private hasAlignedPortOnlyStubs = false
115
+ private postProcessedOutput?: {
116
+ traces: SolvedTracePath[]
117
+ netLabelPlacements: NetLabelPlacement[]
118
+ inlineNetLabelPlacements: InlineNetLabelPlacement[]
119
+ }
114
120
 
115
121
  constructor(input: InlineNetLabelSolverInput) {
116
122
  super()
@@ -191,6 +197,11 @@ export class InlineNetLabelSolver extends BaseSolver {
191
197
  return
192
198
  }
193
199
 
200
+ if (!this.postProcessedOutput) {
201
+ this.postProcessedOutput = this.buildPostProcessedOutput()
202
+ return
203
+ }
204
+
194
205
  this.solved = true
195
206
  this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length
196
207
  }
@@ -584,20 +595,31 @@ export class InlineNetLabelSolver extends BaseSolver {
584
595
  )
585
596
  }
586
597
 
587
- getOutput() {
598
+ private buildPostProcessedOutput() {
588
599
  const superseded = this.getSupersededNetLabelKeys()
600
+ const retainedNetLabelPlacements = this.inputNetLabelPlacements.filter(
601
+ (placement) => !superseded.has(placement.globalConnNetId),
602
+ )
603
+ const outputTraces = this.getOutputTraces(superseded)
604
+ const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
605
+ inputProblem: this.inputProblem,
606
+ traces: outputTraces,
607
+ netLabelPlacements: retainedNetLabelPlacements,
608
+ inlineNetLabelPlacements: this.inlineNetLabelPlacements,
609
+ })
610
+ this.stats.pushedAnchoredNetLabelCount = pushed.movedLabelCount
589
611
  return {
590
- // AvailableNetOrientationSolver may have routed an elbow from a port to
591
- // the anchored label that this inline placement supersedes. Keep the
592
- // actual net trace, but discard that now-orphaned label connector.
593
- traces: this.getOutputTraces(superseded),
594
- netLabelPlacements: this.inputNetLabelPlacements.filter(
595
- (placement) => !superseded.has(placement.globalConnNetId),
596
- ),
612
+ traces: pushed.traces,
613
+ netLabelPlacements: pushed.netLabelPlacements,
597
614
  inlineNetLabelPlacements: this.inlineNetLabelPlacements,
598
615
  }
599
616
  }
600
617
 
618
+ getOutput() {
619
+ if (this.postProcessedOutput) return this.postProcessedOutput
620
+ return this.buildPostProcessedOutput()
621
+ }
622
+
601
623
  override visualize(): GraphicsObject {
602
624
  // Mirrors the previous pipeline stage's visualization so that a problem
603
625
  // with no inline labels renders identically, then layers the inline labels