@tscircuit/schematic-trace-solver 0.0.159 → 0.0.160

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.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) => {
@@ -13695,7 +13716,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
13695
13716
  const connectorObstructed = inlineBounds.some(
13696
13717
  (bounds) => pathIntersectsBounds(connector.tracePath, bounds)
13697
13718
  ) || inputProblem.chips.some(
13698
- (chip) => pathIntersectsBounds(connector.tracePath, {
13719
+ (chip) => !ownerChipIds.has(chip.chipId) && pathIntersectsBounds(connector.tracePath, {
13699
13720
  minX: chip.center.x - chip.width / 2,
13700
13721
  maxX: chip.center.x + chip.width / 2,
13701
13722
  minY: chip.center.y - chip.height / 2,
@@ -13732,20 +13753,247 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
13732
13753
  };
13733
13754
  };
13734
13755
 
13756
+ // lib/solvers/InlineNetLabelSolver/pushInlineTerminalLabelsAwayFromAnchoredLabels.ts
13757
+ var LABEL_CLEARANCE3 = 0.05;
13758
+ var MAX_OUTWARD_DISTANCE2 = 5;
13759
+ var getStubDirection2 = (path) => {
13760
+ const [start, end] = path;
13761
+ if (Math.abs(end.x - start.x) >= Math.abs(end.y - start.y)) {
13762
+ return end.x >= start.x ? "x+" : "x-";
13763
+ }
13764
+ return end.y >= start.y ? "y+" : "y-";
13765
+ };
13766
+ var getLabelBounds3 = (placement) => {
13767
+ const renderedWidth = placement.axis === "y" ? placement.height : placement.width;
13768
+ const renderedHeight = placement.axis === "y" ? placement.width : placement.height;
13769
+ return {
13770
+ minX: placement.center.x - renderedWidth / 2,
13771
+ maxX: placement.center.x + renderedWidth / 2,
13772
+ minY: placement.center.y - renderedHeight / 2,
13773
+ maxY: placement.center.y + renderedHeight / 2
13774
+ };
13775
+ };
13776
+ var getPathBounds2 = (path) => ({
13777
+ minX: Math.min(...path.map((point) => point.x)),
13778
+ maxX: Math.max(...path.map((point) => point.x)),
13779
+ minY: Math.min(...path.map((point) => point.y)),
13780
+ maxY: Math.max(...path.map((point) => point.y))
13781
+ });
13782
+ var doesPathIntersectBounds2 = (path, bounds) => {
13783
+ for (let index = 0; index < path.length - 1; index++) {
13784
+ if (boundsOverlap(getPathBounds2([path[index], path[index + 1]]), bounds)) {
13785
+ return true;
13786
+ }
13787
+ }
13788
+ return false;
13789
+ };
13790
+ var shiftPoint = (point, direction, distance7) => {
13791
+ switch (direction) {
13792
+ case "x+":
13793
+ return { x: point.x + distance7, y: point.y };
13794
+ case "x-":
13795
+ return { x: point.x - distance7, y: point.y };
13796
+ case "y+":
13797
+ return { x: point.x, y: point.y + distance7 };
13798
+ case "y-":
13799
+ return { x: point.x, y: point.y - distance7 };
13800
+ }
13801
+ };
13802
+ var shiftTerminalPlacement = (placement, direction, distance7) => {
13803
+ const [start, end] = placement.stubTracePath;
13804
+ const shiftedEnd = shiftPoint(end, direction, distance7);
13805
+ return {
13806
+ ...placement,
13807
+ stubTracePath: [start, shiftedEnd],
13808
+ anchorPoint: shiftPoint(placement.anchorPoint, direction, distance7),
13809
+ center: shiftPoint(placement.center, direction, distance7)
13810
+ };
13811
+ };
13812
+ var getRequiredOutwardDistance2 = (movingBounds, obstacleBounds, direction) => {
13813
+ switch (direction) {
13814
+ case "x+":
13815
+ return obstacleBounds.maxX - movingBounds.minX + LABEL_CLEARANCE3;
13816
+ case "x-":
13817
+ return movingBounds.maxX - obstacleBounds.minX + LABEL_CLEARANCE3;
13818
+ case "y+":
13819
+ return obstacleBounds.maxY - movingBounds.minY + LABEL_CLEARANCE3;
13820
+ case "y-":
13821
+ return movingBounds.maxY - obstacleBounds.minY + LABEL_CLEARANCE3;
13822
+ }
13823
+ };
13824
+ var pushInlineTerminalLabelsAwayFromAnchoredLabels = ({
13825
+ inputProblem,
13826
+ traces,
13827
+ anchoredNetLabelPlacements,
13828
+ inlineNetLabelPlacements
13829
+ }) => {
13830
+ const chipIdByPinId = /* @__PURE__ */ new Map();
13831
+ for (const chip of inputProblem.chips) {
13832
+ for (const pin of chip.pins) chipIdByPinId.set(pin.pinId, chip.chipId);
13833
+ }
13834
+ const groups = /* @__PURE__ */ new Map();
13835
+ for (const [
13836
+ placementIndex,
13837
+ placement
13838
+ ] of inlineNetLabelPlacements.entries()) {
13839
+ if (!placement.stubTracePath || placement.pinIds.length !== 1) continue;
13840
+ const direction = getStubDirection2(placement.stubTracePath);
13841
+ const ownerChipId = chipIdByPinId.get(placement.pinIds[0]);
13842
+ const groupKey = `${ownerChipId ?? placement.pinIds[0]}::${direction}`;
13843
+ const group = groups.get(groupKey) ?? [];
13844
+ group.push({ placementIndex, placement, direction, ownerChipId });
13845
+ groups.set(groupKey, group);
13846
+ }
13847
+ const outputPlacements = [...inlineNetLabelPlacements];
13848
+ let movedGroupCount = 0;
13849
+ for (const group of groups.values()) {
13850
+ const groupPlacementIndices = new Set(
13851
+ group.map(({ placementIndex }) => placementIndex)
13852
+ );
13853
+ const fixedInlinePlacements = outputPlacements.filter(
13854
+ (_, placementIndex) => !groupPlacementIndices.has(placementIndex)
13855
+ );
13856
+ const direction = group[0].direction;
13857
+ let distance7 = 0;
13858
+ for (let iteration = 0; iteration <= anchoredNetLabelPlacements.length; iteration++) {
13859
+ let requiredAdditionalDistance = 0;
13860
+ for (const { placement } of group) {
13861
+ const shiftedPlacement = shiftTerminalPlacement(
13862
+ placement,
13863
+ direction,
13864
+ distance7
13865
+ );
13866
+ const shiftedBounds = getLabelBounds3(shiftedPlacement);
13867
+ for (const anchoredPlacement of anchoredNetLabelPlacements) {
13868
+ if (anchoredPlacement.globalConnNetId === placement.globalConnNetId) {
13869
+ continue;
13870
+ }
13871
+ const anchoredBounds = getLabelBounds3(anchoredPlacement);
13872
+ if (!boundsOverlap(shiftedBounds, anchoredBounds)) continue;
13873
+ requiredAdditionalDistance = Math.max(
13874
+ requiredAdditionalDistance,
13875
+ getRequiredOutwardDistance2(
13876
+ shiftedBounds,
13877
+ anchoredBounds,
13878
+ direction
13879
+ )
13880
+ );
13881
+ }
13882
+ }
13883
+ if (requiredAdditionalDistance <= 0) break;
13884
+ distance7 += requiredAdditionalDistance;
13885
+ }
13886
+ if (distance7 <= 0 || distance7 > MAX_OUTWARD_DISTANCE2) continue;
13887
+ const proposals = group.map(
13888
+ ({ placement }) => shiftTerminalPlacement(placement, direction, distance7)
13889
+ );
13890
+ const hasConflict = proposals.some((proposal, proposalIndex) => {
13891
+ const originalPlacement = group[proposalIndex].placement;
13892
+ const originalEnd = originalPlacement.stubTracePath[1];
13893
+ const shiftedEnd = proposal.stubTracePath[1];
13894
+ const addedStubSegment = [originalEnd, shiftedEnd];
13895
+ const labelBounds = getLabelBounds3(proposal);
13896
+ const ownerChipId = group[proposalIndex].ownerChipId;
13897
+ if (anchoredNetLabelPlacements.some((anchoredPlacement) => {
13898
+ if (anchoredPlacement.globalConnNetId === proposal.globalConnNetId) {
13899
+ return false;
13900
+ }
13901
+ const anchoredBounds = getLabelBounds3(anchoredPlacement);
13902
+ return boundsOverlap(labelBounds, anchoredBounds) || doesPathIntersectBounds2(addedStubSegment, anchoredBounds);
13903
+ })) {
13904
+ return true;
13905
+ }
13906
+ for (const chip of inputProblem.chips) {
13907
+ if (chip.chipId === ownerChipId) continue;
13908
+ const chipBounds = {
13909
+ minX: chip.center.x - chip.width / 2,
13910
+ maxX: chip.center.x + chip.width / 2,
13911
+ minY: chip.center.y - chip.height / 2,
13912
+ maxY: chip.center.y + chip.height / 2
13913
+ };
13914
+ if (boundsOverlap(labelBounds, chipBounds) || doesPathIntersectBounds2(addedStubSegment, chipBounds)) {
13915
+ return true;
13916
+ }
13917
+ }
13918
+ for (const textBox of inputProblem.textBoxes ?? []) {
13919
+ const textBounds = getTextBoxBounds(textBox);
13920
+ if (boundsOverlap(labelBounds, textBounds) || doesPathIntersectBounds2(addedStubSegment, textBounds)) {
13921
+ return true;
13922
+ }
13923
+ }
13924
+ for (const trace of traces) {
13925
+ if (trace.globalConnNetId === proposal.globalConnNetId) continue;
13926
+ if (doesPathIntersectBounds2(trace.tracePath, labelBounds) || doesPathIntersectBounds2(
13927
+ trace.tracePath,
13928
+ getPathBounds2(addedStubSegment)
13929
+ )) {
13930
+ return true;
13931
+ }
13932
+ }
13933
+ for (const fixedPlacement of fixedInlinePlacements) {
13934
+ const fixedBounds = getLabelBounds3(fixedPlacement);
13935
+ if (boundsOverlap(labelBounds, fixedBounds) || doesPathIntersectBounds2(addedStubSegment, fixedBounds) || fixedPlacement.stubTracePath && doesPathIntersectBounds2(fixedPlacement.stubTracePath, labelBounds)) {
13936
+ return true;
13937
+ }
13938
+ }
13939
+ return false;
13940
+ });
13941
+ if (hasConflict) continue;
13942
+ for (const [groupIndex, { placementIndex }] of group.entries()) {
13943
+ outputPlacements[placementIndex] = proposals[groupIndex];
13944
+ }
13945
+ movedGroupCount++;
13946
+ }
13947
+ return {
13948
+ inlineNetLabelPlacements: outputPlacements,
13949
+ movedGroupCount
13950
+ };
13951
+ };
13952
+
13735
13953
  // lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts
13736
13954
  var DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
13737
13955
  var INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
13738
13956
  var INLINE_NET_LABEL_STUB_TRACE_CLEARANCE = 0.05;
13739
13957
  var INLINE_NET_LABEL_MAX_SPAN_JOG = 0.4;
13958
+ var AVAILABLE_NET_ORIENTATION_TRACE_PREFIX = "available-net-orientation-";
13959
+ var isGeneratedAnchoredLabelConnector = (trace) => trace.mspPairId.startsWith(AVAILABLE_NET_ORIENTATION_TRACE_PREFIX);
13740
13960
  var getPinPairKey2 = (pinIds) => [...pinIds].sort().join("::");
13961
+ var getTraceLength2 = (trace) => {
13962
+ let length = 0;
13963
+ for (let index = 0; index < trace.tracePath.length - 1; index++) {
13964
+ const start = trace.tracePath[index];
13965
+ const end = trace.tracePath[index + 1];
13966
+ length += Math.abs(end.x - start.x) + Math.abs(end.y - start.y);
13967
+ }
13968
+ return length;
13969
+ };
13970
+ var setsEqual = (first, second) => first.size === second.size && [...first].every((value) => second.has(value));
13971
+ var getInlinePlacementBounds = (placement) => {
13972
+ const renderedWidth = placement.axis === "y" ? placement.height : placement.width;
13973
+ const renderedHeight = placement.axis === "y" ? placement.width : placement.height;
13974
+ return {
13975
+ minX: placement.center.x - renderedWidth / 2,
13976
+ maxX: placement.center.x + renderedWidth / 2,
13977
+ minY: placement.center.y - renderedHeight / 2,
13978
+ maxY: placement.center.y + renderedHeight / 2
13979
+ };
13980
+ };
13981
+ var getAnchoredPlacementBounds = (placement) => ({
13982
+ minX: placement.center.x - placement.width / 2,
13983
+ maxX: placement.center.x + placement.width / 2,
13984
+ minY: placement.center.y - placement.height / 2,
13985
+ maxY: placement.center.y + placement.height / 2
13986
+ });
13741
13987
  var InlineNetLabelSolver = class extends BaseSolver {
13742
13988
  inputProblem;
13743
13989
  traces;
13744
13990
  inputNetLabelPlacements;
13745
13991
  inlineNetLabelPlacements = [];
13746
- /** Eligible one- or two-pin connections still waiting to be processed. */
13992
+ /** Eligible connections still waiting to be processed. */
13747
13993
  queuedConnections;
13748
13994
  tracesByPinPairKey;
13995
+ globalConnMap;
13996
+ supersededTraceIdsByGlobalConnNetId = /* @__PURE__ */ new Map();
13749
13997
  hasAlignedPortOnlyStubs = false;
13750
13998
  postProcessedOutput;
13751
13999
  constructor(input) {
@@ -13753,16 +14001,26 @@ var InlineNetLabelSolver = class extends BaseSolver {
13753
14001
  this.inputProblem = input.inputProblem;
13754
14002
  this.traces = input.traces;
13755
14003
  this.inputNetLabelPlacements = input.netLabelPlacements;
14004
+ this.globalConnMap = getConnectivityMapsFromInputProblem(
14005
+ this.inputProblem
14006
+ ).netConnMap;
13756
14007
  this.queuedConnections = [
13757
14008
  ...this.inputProblem.directConnections.filter(
13758
14009
  (connection) => connection.allowInlineNetLabel && connection.netId
13759
- ),
14010
+ ).map((connection) => ({
14011
+ kind: "direct_connection",
14012
+ connection
14013
+ })),
13760
14014
  ...this.inputProblem.netConnections.filter(
13761
- (connection) => connection.allowInlineNetLabel && connection.pinIds.length >= 1 && connection.pinIds.length <= 2 && connection.netId
13762
- )
14015
+ (connection) => connection.allowInlineNetLabel && connection.pinIds.length >= 1 && connection.netId
14016
+ ).map((connection) => ({
14017
+ kind: "net_connection",
14018
+ connection
14019
+ }))
13763
14020
  ];
13764
14021
  this.tracesByPinPairKey = /* @__PURE__ */ new Map();
13765
14022
  for (const trace of this.traces) {
14023
+ if (isGeneratedAnchoredLabelConnector(trace)) continue;
13766
14024
  const key = getPinPairKey2(trace.pins.map((p) => p.pinId));
13767
14025
  const existing = this.tracesByPinPairKey.get(key);
13768
14026
  if (existing) {
@@ -13782,11 +14040,43 @@ var InlineNetLabelSolver = class extends BaseSolver {
13782
14040
  ];
13783
14041
  }
13784
14042
  _step() {
13785
- const connection = this.queuedConnections.shift();
13786
- if (connection) {
14043
+ const queuedConnection = this.queuedConnections.shift();
14044
+ if (queuedConnection) {
14045
+ if (queuedConnection.kind === "net_connection" && queuedConnection.connection.pinIds.length > 2) {
14046
+ const netConnections = [
14047
+ queuedConnection.connection,
14048
+ ...this.queuedConnections.filter(
14049
+ (queued) => queued.kind === "net_connection" && queued.connection.pinIds.length > 2
14050
+ ).map((queued) => queued.connection)
14051
+ ];
14052
+ this.queuedConnections = this.queuedConnections.filter(
14053
+ (queued) => queued.kind !== "net_connection" || queued.connection.pinIds.length <= 2
14054
+ );
14055
+ for (const conversion of this.computeNetConnectionInlineConversions(
14056
+ netConnections
14057
+ )) {
14058
+ this.inlineNetLabelPlacements.push(...conversion.placements);
14059
+ const supersededTraceIds = this.supersededTraceIdsByGlobalConnNetId.get(
14060
+ conversion.globalConnNetId
14061
+ ) ?? /* @__PURE__ */ new Set();
14062
+ for (const traceId of conversion.supersededTraceIds) {
14063
+ supersededTraceIds.add(traceId);
14064
+ }
14065
+ this.supersededTraceIdsByGlobalConnNetId.set(
14066
+ conversion.globalConnNetId,
14067
+ supersededTraceIds
14068
+ );
14069
+ }
14070
+ return;
14071
+ }
14072
+ const { connection } = queuedConnection;
13787
14073
  const routedTraces = connection.pinIds.length === 2 ? this.tracesByPinPairKey.get(getPinPairKey2(connection.pinIds)) ?? [] : [];
13788
14074
  if (routedTraces.length > 0) {
13789
- const placement = this.computeInlinePlacement(connection);
14075
+ const placement = this.computeInlinePlacement({
14076
+ connection,
14077
+ trace: routedTraces[0],
14078
+ pinIds: connection.pinIds
14079
+ });
13790
14080
  if (placement) this.inlineNetLabelPlacements.push(placement);
13791
14081
  } else {
13792
14082
  const terminalPlacements = connection.pinIds.map(
@@ -13815,18 +14105,181 @@ var InlineNetLabelSolver = class extends BaseSolver {
13815
14105
  this.solved = true;
13816
14106
  this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length;
13817
14107
  }
14108
+ /**
14109
+ * Plans all opted-in multi-pin nets together. Short routed components can
14110
+ * mutually block the outward endpoint stubs that replace them, so tentative
14111
+ * replacements are ignored as obstacles only while their owning net still
14112
+ * converts successfully. Failed conversions retain both their traces and
14113
+ * their conventional labels, and the remaining conversions are retried.
14114
+ */
14115
+ computeNetConnectionInlineConversions(connections) {
14116
+ const prospectiveTraceIdsByConnection = /* @__PURE__ */ new Map();
14117
+ for (const connection of connections) {
14118
+ const prospectiveTraceIds = /* @__PURE__ */ new Set();
14119
+ for (const component of this.getNetConnectionComponents(connection)) {
14120
+ if (component.traces.length === 0) continue;
14121
+ const hasInlinePlacement = component.traces.some(
14122
+ (trace) => Boolean(
14123
+ this.computeInlinePlacement({
14124
+ connection,
14125
+ trace,
14126
+ pinIds: component.pinIds
14127
+ })
14128
+ )
14129
+ );
14130
+ if (!hasInlinePlacement) {
14131
+ for (const trace of component.traces) {
14132
+ prospectiveTraceIds.add(trace.mspPairId);
14133
+ }
14134
+ }
14135
+ }
14136
+ prospectiveTraceIdsByConnection.set(connection, prospectiveTraceIds);
14137
+ }
14138
+ let ignoredTraceIds = new Set(
14139
+ [...prospectiveTraceIdsByConnection.values()].flatMap((ids) => [...ids])
14140
+ );
14141
+ while (true) {
14142
+ const conversions = connections.map((connection) => ({
14143
+ connection,
14144
+ conversion: this.computeNetConnectionInlinePlacements(
14145
+ connection,
14146
+ ignoredTraceIds,
14147
+ prospectiveTraceIdsByConnection.get(connection) ?? /* @__PURE__ */ new Set()
14148
+ )
14149
+ }));
14150
+ const nextIgnoredTraceIds = new Set(
14151
+ conversions.flatMap(
14152
+ ({ conversion }) => conversion ? [...conversion.supersededTraceIds] : []
14153
+ )
14154
+ );
14155
+ if (setsEqual(ignoredTraceIds, nextIgnoredTraceIds)) {
14156
+ return conversions.flatMap(
14157
+ ({ conversion }) => conversion ? [conversion] : []
14158
+ );
14159
+ }
14160
+ ignoredTraceIds = nextIgnoredTraceIds;
14161
+ }
14162
+ }
14163
+ /**
14164
+ * Splits an input net into the connected components created by its solved
14165
+ * traces. This intentionally does not depend on anchored-label placement:
14166
+ * a group whose anchored label failed still has the same routing topology.
14167
+ */
14168
+ getNetConnectionComponents(connection) {
14169
+ const firstPinId = connection.pinIds[0];
14170
+ if (!firstPinId) return [];
14171
+ const canonicalGlobalConnNetId = this.globalConnMap.getNetConnectedToId(firstPinId);
14172
+ if (!canonicalGlobalConnNetId) return [];
14173
+ const inputPinIds = new Set(
14174
+ this.inputProblem.chips.flatMap(
14175
+ (chip) => chip.pins.map((pin) => pin.pinId)
14176
+ )
14177
+ );
14178
+ const pinIdsInGlobalNet = this.globalConnMap.getIdsConnectedToNet(
14179
+ canonicalGlobalConnNetId
14180
+ ).filter((id) => inputPinIds.has(id));
14181
+ const labeledPinIds = new Set(connection.pinIds);
14182
+ return getTraceConnectedPinComponents({
14183
+ pinIds: pinIdsInGlobalNet,
14184
+ // Available-net-orientation traces only connect a pin to its generated
14185
+ // anchored label. They are removed when that label becomes inline, so
14186
+ // treating them as routed circuitry would leave text on a deleted wire.
14187
+ traces: this.traces.filter(
14188
+ (trace) => !isGeneratedAnchoredLabelConnector(trace)
14189
+ )
14190
+ }).map((component) => ({
14191
+ pinIds: component.pinIds,
14192
+ labeledPinIds: component.pinIds.filter(
14193
+ (pinId) => labeledPinIds.has(pinId)
14194
+ ),
14195
+ traces: component.traces.sort(
14196
+ (a, b) => getTraceLength2(b) - getTraceLength2(a)
14197
+ )
14198
+ })).filter((component) => component.labeledPinIds.length > 0);
14199
+ }
14200
+ getGlobalConnNetId(connection) {
14201
+ const firstPinId = connection.pinIds[0];
14202
+ if (!firstPinId) return void 0;
14203
+ const connectionPinIds = new Set(connection.pinIds);
14204
+ return this.traces.find(
14205
+ (trace) => trace.pins.some((pin) => connectionPinIds.has(pin.pinId))
14206
+ )?.globalConnNetId ?? this.inputNetLabelPlacements.find(
14207
+ (placement) => placement.netId === connection.netId
14208
+ )?.globalConnNetId ?? this.globalConnMap.getNetConnectedToId(firstPinId) ?? void 0;
14209
+ }
14210
+ /**
14211
+ * Converts every conventional label representation for an opted-in named
14212
+ * net. Routed connected components become labels on their representative
14213
+ * trace; disconnected pins become outward terminal stubs. The conversion is
14214
+ * atomic so one blocked candidate cannot leave mixed label styles on a net.
14215
+ */
14216
+ computeNetConnectionInlinePlacements(connection, ignoredTraceIds, forcedTerminalTraceIds) {
14217
+ const components = this.getNetConnectionComponents(connection);
14218
+ const globalConnNetId = this.getGlobalConnNetId(connection);
14219
+ if (!globalConnNetId) return null;
14220
+ const inlinePlacements = [];
14221
+ const supersededTraceIds = /* @__PURE__ */ new Set();
14222
+ for (const component of components) {
14223
+ const shouldUseTerminalStubs = component.traces.some(
14224
+ (trace) => forcedTerminalTraceIds.has(trace.mspPairId)
14225
+ );
14226
+ let inlinePlacement = null;
14227
+ if (!shouldUseTerminalStubs) {
14228
+ for (const trace of component.traces) {
14229
+ inlinePlacement = this.computeInlinePlacement({
14230
+ connection,
14231
+ trace,
14232
+ pinIds: component.pinIds,
14233
+ ignoredTraceIds
14234
+ });
14235
+ if (inlinePlacement) break;
14236
+ }
14237
+ }
14238
+ if (inlinePlacement) {
14239
+ inlinePlacements.push(inlinePlacement);
14240
+ continue;
14241
+ }
14242
+ if (component.traces.length > 0 && !shouldUseTerminalStubs) {
14243
+ return null;
14244
+ }
14245
+ if (component.labeledPinIds.length !== component.pinIds.length) {
14246
+ return null;
14247
+ }
14248
+ const terminalPlacements = component.pinIds.map(
14249
+ (pinId) => this.computeTerminalInlinePlacement(
14250
+ connection,
14251
+ pinId,
14252
+ globalConnNetId,
14253
+ ignoredTraceIds
14254
+ )
14255
+ );
14256
+ if (terminalPlacements.some((placement) => placement === null)) {
14257
+ return null;
14258
+ }
14259
+ inlinePlacements.push(
14260
+ ...terminalPlacements.filter(
14261
+ (placement) => placement !== null
14262
+ )
14263
+ );
14264
+ for (const trace of component.traces) {
14265
+ supersededTraceIds.add(trace.mspPairId);
14266
+ }
14267
+ }
14268
+ return { globalConnNetId, placements: inlinePlacements, supersededTraceIds };
14269
+ }
13818
14270
  /**
13819
14271
  * Converts one conventional endpoint placement into an inline label on a
13820
14272
  * generated outward stub. The stub follows the pin's true facing direction;
13821
14273
  * an anchored label may finish in another direction after an elbow, which is
13822
14274
  * not the direction a terminal stub should leave the pin.
13823
14275
  */
13824
- computeTerminalInlinePlacement(connection, pinId) {
14276
+ computeTerminalInlinePlacement(connection, pinId, knownGlobalConnNetId, ignoredTraceIds = /* @__PURE__ */ new Set()) {
13825
14277
  if (!connection.netId) return null;
13826
14278
  const anchoredPlacement = this.inputNetLabelPlacements.find(
13827
14279
  (placement) => placement.netId === connection.netId && placement.pinIds.length === 1 && placement.pinIds[0] === pinId
13828
14280
  );
13829
- if (!anchoredPlacement) return null;
14281
+ const globalConnNetId = knownGlobalConnNetId ?? anchoredPlacement?.globalConnNetId;
14282
+ if (!globalConnNetId) return null;
13830
14283
  const inputChip = this.inputProblem.chips.find(
13831
14284
  (chip) => chip.pins.some((pin) => pin.pinId === pinId)
13832
14285
  );
@@ -13835,6 +14288,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
13835
14288
  const netLabelText = connection.netLabelText?.trim() || void 0;
13836
14289
  const width = connection.inlineNetLabelWidth ?? connection.netLabelWidth ?? estimateInlineNetLabelWidth(netLabelText ?? connection.netId, height);
13837
14290
  const stubLength = Math.max(width + 0.2, 0.6);
14291
+ if (!inputPin && !anchoredPlacement) return null;
13838
14292
  const start = inputPin ? { x: inputPin.x, y: inputPin.y } : anchoredPlacement.anchorPoint;
13839
14293
  const direction = inputPin && inputChip ? inputPin._facingDirection ?? getPinDirection(inputPin, inputChip) : anchoredPlacement.orientation;
13840
14294
  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 +14297,60 @@ var InlineNetLabelSolver = class extends BaseSolver {
13843
14297
  intendedEnd,
13844
14298
  minimumLength: width + 2 * INLINE_NET_LABEL_TRACE_MARGIN,
13845
14299
  traces: this.traces,
13846
- ownGlobalConnNetId: anchoredPlacement.globalConnNetId
14300
+ ownGlobalConnNetId: globalConnNetId,
14301
+ ignoredTraceIds
13847
14302
  });
13848
14303
  if (!end) return null;
13849
14304
  const axis = direction === "x+" || direction === "x-" ? "x" : "y";
13850
- const side = axis === "x" ? "y+" : "x-";
13851
14305
  const anchorPoint = {
13852
14306
  x: (start.x + end.x) / 2,
13853
14307
  y: (start.y + end.y) / 2
13854
14308
  };
13855
14309
  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;
14310
+ const sides = axis === "x" ? ["y+", "y-"] : ["x-", "x+"];
14311
+ for (const side of sides) {
14312
+ 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 };
14313
+ const halfAlong = width / 2;
14314
+ const halfAcross = height / 2;
14315
+ const bounds = axis === "x" ? {
14316
+ minX: center.x - halfAlong,
14317
+ maxX: center.x + halfAlong,
14318
+ minY: center.y - halfAcross,
14319
+ maxY: center.y + halfAcross
14320
+ } : {
14321
+ minX: center.x - halfAcross,
14322
+ maxX: center.x + halfAcross,
14323
+ minY: center.y - halfAlong,
14324
+ maxY: center.y + halfAlong
14325
+ };
14326
+ if (this.isObstructed(bounds, {
14327
+ ownGlobalConnNetId: globalConnNetId,
14328
+ ignoredTraceIds
14329
+ })) {
14330
+ continue;
14331
+ }
14332
+ return {
14333
+ globalConnNetId,
14334
+ netId: connection.netId,
14335
+ netLabelText,
14336
+ pinIds: [pinId],
14337
+ stubTracePath: [start, end],
14338
+ axis,
14339
+ anchorPoint,
14340
+ center,
14341
+ width,
14342
+ height,
14343
+ side
14344
+ };
13874
14345
  }
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
- };
14346
+ return null;
13888
14347
  }
13889
- computeInlinePlacement(connection) {
13890
- const traces = this.tracesByPinPairKey.get(getPinPairKey2(connection.pinIds)) ?? [];
13891
- if (traces.length === 0) return null;
13892
- const trace = traces[0];
14348
+ computeInlinePlacement({
14349
+ connection,
14350
+ trace,
14351
+ pinIds,
14352
+ ignoredTraceIds = /* @__PURE__ */ new Set()
14353
+ }) {
13893
14354
  const segments = getAxisAlignedSegments(trace.tracePath);
13894
14355
  if (segments.length === 0) return null;
13895
14356
  const height = connection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT;
@@ -13917,7 +14378,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
13917
14378
  };
13918
14379
  if (this.isObstructed(bounds, {
13919
14380
  ownTrace: trace,
13920
- ownGlobalConnNetId: trace.globalConnNetId
14381
+ ownGlobalConnNetId: trace.globalConnNetId,
14382
+ ignoredTraceIds
13921
14383
  }))
13922
14384
  continue;
13923
14385
  return {
@@ -13925,7 +14387,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
13925
14387
  netId: connection.netId,
13926
14388
  netLabelText,
13927
14389
  mspPairId: trace.mspPairId,
13928
- pinIds: [...connection.pinIds],
14390
+ pinIds: [...pinIds],
13929
14391
  axis: segment.axis,
13930
14392
  anchorPoint,
13931
14393
  center,
@@ -13942,7 +14404,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
13942
14404
  netLabelText,
13943
14405
  width,
13944
14406
  height,
13945
- offset
14407
+ offset,
14408
+ pinIds,
14409
+ ignoredTraceIds
13946
14410
  });
13947
14411
  if (spanPlacement) return spanPlacement;
13948
14412
  return null;
@@ -13959,7 +14423,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
13959
14423
  netLabelText,
13960
14424
  width,
13961
14425
  height,
13962
- offset
14426
+ offset,
14427
+ pinIds,
14428
+ ignoredTraceIds
13963
14429
  }) {
13964
14430
  const path = trace.tracePath;
13965
14431
  if (path.length < 2) return null;
@@ -14009,7 +14475,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
14009
14475
  };
14010
14476
  if (this.isObstructed(bounds, {
14011
14477
  ownTrace: trace,
14012
- ownGlobalConnNetId: trace.globalConnNetId
14478
+ ownGlobalConnNetId: trace.globalConnNetId,
14479
+ ignoredTraceIds
14013
14480
  }))
14014
14481
  continue;
14015
14482
  const anchorPoint = axis === "x" ? { x: along, y: side === "y+" ? maxY : minY } : { x: side === "x-" ? minX : maxX, y: along };
@@ -14018,7 +14485,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
14018
14485
  netId: connection.netId,
14019
14486
  netLabelText,
14020
14487
  mspPairId: trace.mspPairId,
14021
- pinIds: [...connection.pinIds],
14488
+ pinIds: [...pinIds],
14022
14489
  axis,
14023
14490
  anchorPoint,
14024
14491
  center,
@@ -14036,7 +14503,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
14036
14503
  */
14037
14504
  isObstructed(bounds, {
14038
14505
  ownTrace,
14039
- ownGlobalConnNetId
14506
+ ownGlobalConnNetId,
14507
+ ignoredTraceIds = /* @__PURE__ */ new Set()
14040
14508
  }) {
14041
14509
  for (const chip of this.inputProblem.chips) {
14042
14510
  const chipBounds = {
@@ -14051,9 +14519,10 @@ var InlineNetLabelSolver = class extends BaseSolver {
14051
14519
  if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
14052
14520
  }
14053
14521
  for (const trace of this.traces) {
14522
+ if (ignoredTraceIds.has(trace.mspPairId)) continue;
14054
14523
  if (ownTrace && trace.mspPairId === ownTrace.mspPairId) continue;
14055
14524
  if (trace.globalConnNetId === ownGlobalConnNetId) continue;
14056
- if (doesPathIntersectBounds2(trace.tracePath, bounds)) return true;
14525
+ if (doesPathIntersectBounds3(trace.tracePath, bounds)) return true;
14057
14526
  }
14058
14527
  return false;
14059
14528
  }
@@ -14061,36 +14530,94 @@ var InlineNetLabelSolver = class extends BaseSolver {
14061
14530
  * Net label placements superseded by an inline label. A net gets one label or
14062
14531
  * the other, never both.
14063
14532
  */
14064
- getSupersededNetLabelKeys() {
14533
+ getSupersededNetLabelKeys(placements = this.inlineNetLabelPlacements) {
14065
14534
  const keys = /* @__PURE__ */ new Set();
14066
- for (const placement of this.inlineNetLabelPlacements) {
14535
+ for (const placement of placements) {
14067
14536
  keys.add(placement.globalConnNetId);
14068
14537
  }
14069
14538
  return keys;
14070
14539
  }
14071
- getOutputTraces(supersededNetLabelKeys) {
14540
+ getOutputTraces(supersededNetLabelKeys, supersededTraceIds) {
14072
14541
  return this.traces.filter(
14073
- (trace) => !(supersededNetLabelKeys.has(trace.globalConnNetId) && trace.mspPairId.startsWith("available-net-orientation-"))
14542
+ (trace) => !supersededTraceIds.has(trace.mspPairId) && !(supersededNetLabelKeys.has(trace.globalConnNetId) && isGeneratedAnchoredLabelConnector(trace))
14074
14543
  );
14075
14544
  }
14545
+ getInlineGlobalsOverlappingAnchoredLabels({
14546
+ inlineNetLabelPlacements,
14547
+ anchoredNetLabelPlacements
14548
+ }) {
14549
+ const blockedGlobalConnNetIds = /* @__PURE__ */ new Set();
14550
+ for (const inlinePlacement of inlineNetLabelPlacements) {
14551
+ const inlineBounds = getInlinePlacementBounds(inlinePlacement);
14552
+ for (const anchoredPlacement of anchoredNetLabelPlacements) {
14553
+ if (anchoredPlacement.globalConnNetId === inlinePlacement.globalConnNetId) {
14554
+ continue;
14555
+ }
14556
+ const anchoredBounds = getAnchoredPlacementBounds(anchoredPlacement);
14557
+ if (boundsOverlap(inlineBounds, anchoredBounds) || inlinePlacement.stubTracePath && doesPathIntersectBounds3(
14558
+ inlinePlacement.stubTracePath,
14559
+ anchoredBounds
14560
+ )) {
14561
+ blockedGlobalConnNetIds.add(inlinePlacement.globalConnNetId);
14562
+ break;
14563
+ }
14564
+ }
14565
+ }
14566
+ return blockedGlobalConnNetIds;
14567
+ }
14076
14568
  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
- };
14569
+ let activeInlinePlacements = this.inlineNetLabelPlacements;
14570
+ while (true) {
14571
+ const supersededNetLabelKeys = this.getSupersededNetLabelKeys(
14572
+ activeInlinePlacements
14573
+ );
14574
+ const supersededTraceIds = new Set(
14575
+ [...supersededNetLabelKeys].flatMap((globalConnNetId) => [
14576
+ ...this.supersededTraceIdsByGlobalConnNetId.get(globalConnNetId) ?? []
14577
+ ])
14578
+ );
14579
+ const retainedNetLabelPlacements = this.inputNetLabelPlacements.filter(
14580
+ (placement) => !supersededNetLabelKeys.has(placement.globalConnNetId)
14581
+ );
14582
+ const outputTraces = this.getOutputTraces(
14583
+ supersededNetLabelKeys,
14584
+ supersededTraceIds
14585
+ );
14586
+ const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
14587
+ inputProblem: this.inputProblem,
14588
+ traces: outputTraces,
14589
+ netLabelPlacements: retainedNetLabelPlacements,
14590
+ inlineNetLabelPlacements: activeInlinePlacements
14591
+ });
14592
+ const blockedGlobalConnNetIds = this.getInlineGlobalsOverlappingAnchoredLabels({
14593
+ inlineNetLabelPlacements: activeInlinePlacements,
14594
+ anchoredNetLabelPlacements: pushed.netLabelPlacements
14595
+ });
14596
+ if (blockedGlobalConnNetIds.size > 0) {
14597
+ const shiftedInlineTerminalLabels = pushInlineTerminalLabelsAwayFromAnchoredLabels({
14598
+ inputProblem: this.inputProblem,
14599
+ traces: pushed.traces,
14600
+ anchoredNetLabelPlacements: pushed.netLabelPlacements,
14601
+ inlineNetLabelPlacements: activeInlinePlacements
14602
+ });
14603
+ if (shiftedInlineTerminalLabels.movedGroupCount > 0) {
14604
+ activeInlinePlacements = shiftedInlineTerminalLabels.inlineNetLabelPlacements;
14605
+ continue;
14606
+ }
14607
+ }
14608
+ if (blockedGlobalConnNetIds.size === 0) {
14609
+ this.inlineNetLabelPlacements = activeInlinePlacements;
14610
+ this.stats.pushedAnchoredNetLabelCount = pushed.movedLabelCount;
14611
+ return {
14612
+ traces: pushed.traces,
14613
+ netLabelPlacements: pushed.netLabelPlacements,
14614
+ inlineNetLabelPlacements: activeInlinePlacements
14615
+ };
14616
+ }
14617
+ activeInlinePlacements = activeInlinePlacements.filter(
14618
+ (placement) => !blockedGlobalConnNetIds.has(placement.globalConnNetId)
14619
+ );
14620
+ }
14094
14621
  }
14095
14622
  getOutput() {
14096
14623
  if (this.postProcessedOutput) return this.postProcessedOutput;
@@ -14225,7 +14752,7 @@ var getAnchorCandidates = (segment, labelWidth, step = 0.1) => {
14225
14752
  offsets.push(slack / 2, -slack / 2);
14226
14753
  return offsets.map((offset) => pointAt(mid + offset * direction));
14227
14754
  };
14228
- var doesPathIntersectBounds2 = (path, bounds) => {
14755
+ var doesPathIntersectBounds3 = (path, bounds) => {
14229
14756
  for (let i = 0; i < path.length - 1; i++) {
14230
14757
  const a = path[i];
14231
14758
  const b = path[i + 1];
@@ -14245,7 +14772,8 @@ var getCollisionLimitedTerminalStubEnd = ({
14245
14772
  intendedEnd,
14246
14773
  minimumLength,
14247
14774
  traces,
14248
- ownGlobalConnNetId
14775
+ ownGlobalConnNetId,
14776
+ ignoredTraceIds
14249
14777
  }) => {
14250
14778
  const isHorizontal4 = Math.abs(intendedEnd.x - start.x) >= Math.abs(intendedEnd.y - start.y);
14251
14779
  const alongAxis = isHorizontal4 ? "x" : "y";
@@ -14254,6 +14782,7 @@ var getCollisionLimitedTerminalStubEnd = ({
14254
14782
  const intendedLength = Math.abs(intendedEnd[alongAxis] - start[alongAxis]);
14255
14783
  let availableLength = intendedLength;
14256
14784
  for (const trace of traces) {
14785
+ if (ignoredTraceIds.has(trace.mspPairId)) continue;
14257
14786
  if (trace.globalConnNetId === ownGlobalConnNetId) continue;
14258
14787
  for (let segmentIndex = 0; segmentIndex < trace.tracePath.length - 1; segmentIndex++) {
14259
14788
  const segmentStart = trace.tracePath[segmentIndex];