@tscircuit/schematic-trace-solver 0.0.158 → 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.
Files changed (27) hide show
  1. package/dist/index.d.ts +41 -9
  2. package/dist/index.js +762 -110
  3. package/lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts +536 -94
  4. package/lib/solvers/InlineNetLabelSolver/pushAnchoredNetLabelsAwayFromInlineLabels.ts +9 -7
  5. package/lib/solvers/InlineNetLabelSolver/pushInlineTerminalLabelsAwayFromAnchoredLabels.ts +295 -0
  6. package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +15 -8
  7. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +5 -0
  8. package/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts +83 -0
  9. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +7 -35
  10. package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +6 -1
  11. package/lib/solvers/SameNetJunctionAlignmentSolver/placeGroundRailLabelsAtOuterEnd.ts +122 -0
  12. package/lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents.ts +60 -0
  13. package/lib/types/InputProblem.ts +5 -4
  14. package/package.json +1 -1
  15. package/tests/bug-reports/bug-report-20260826T072956Z/__snapshots__/bug-report-20260826T072956Z.snap.svg +600 -783
  16. package/tests/bug-reports/bug-report-20260826T072956Z/bug-report-20260826T072956Z.test.ts +66 -1
  17. package/tests/fixtures/parallel-ground-rail.ts +53 -0
  18. package/tests/repros/__snapshots__/repro-mspm0l1306-capacitor-symmetry.snap.svg +34 -16
  19. package/tests/repros/repro-mspm0l1306-capacitor-symmetry.test.ts +40 -7
  20. package/tests/repros/repro-usb-power-vbus-label-detour.test.ts +14 -0
  21. package/tests/solvers/InlineNetLabelSolver/multi-pin-connected-components.test.ts +252 -0
  22. package/tests/solvers/InlineNetLabelSolver/opposite-side-placement.test.ts +71 -0
  23. package/tests/solvers/InlineNetLabelSolver/push-anchored-net-labels-away.test.ts +8 -2
  24. package/tests/solvers/InlineNetLabelSolver/push-inline-terminal-labels-away.test.ts +91 -0
  25. package/tests/solvers/InlineNetLabelSolver/two-pin-terminal-stubs-atomic.test.ts +7 -1
  26. package/tests/solvers/MspConnectionPairSolver/local-ground-branches.test.ts +105 -0
  27. package/tests/solvers/SameNetJunctionAlignmentSolver/ground-rail-label.test.ts +188 -0
package/dist/index.js CHANGED
@@ -363,6 +363,48 @@ var getConnectivityMapsFromInputProblem = (inputProblem) => {
363
363
  return { directConnMap, netConnMap };
364
364
  };
365
365
 
366
+ // lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts
367
+ import { ConnectivityMap as ConnectivityMap2 } from "connectivity-map";
368
+ var MAX_LOCAL_GROUND_BRANCH_OFFSET = 1;
369
+ var getGroundConnectionPolicy = (inputProblem) => {
370
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
371
+ const groundNetId = netConnMap.getNetConnectedToId("GND");
372
+ if (!groundNetId) return () => true;
373
+ const physicalConnMap = new ConnectivityMap2({});
374
+ for (const connection of inputProblem.directConnections) {
375
+ physicalConnMap.addConnections([connection.pinIds]);
376
+ }
377
+ const pins = new Map(
378
+ inputProblem.chips.flatMap(
379
+ (chip) => chip.pins.map((pin) => [pin.pinId, { pin, chip }])
380
+ )
381
+ );
382
+ const groundFacingPins = [...pins.values()].filter(
383
+ ({ pin, chip }) => chip.pins.length === 2 && netConnMap.getNetConnectedToId(pin.pinId) === groundNetId && (pin._facingDirection ?? getPinDirection(pin, chip)) === "y-"
384
+ );
385
+ const hasExplicitParallelGroundRail = groundFacingPins.some(({ pin: a }) => {
386
+ const group = physicalConnMap.getNetConnectedToId(a.pinId);
387
+ return group !== void 0 && groundFacingPins.some(
388
+ ({ pin: b }) => a.pinId !== b.pinId && Math.abs(a.y - b.y) < 1e-6 && Math.abs(a.x - b.x) > 1e-6 && physicalConnMap.getNetConnectedToId(b.pinId) === group
389
+ );
390
+ });
391
+ if (!hasExplicitParallelGroundRail) return () => true;
392
+ const isGroundFacingTerminal = (pinId) => {
393
+ const entry = pins.get(pinId);
394
+ return entry?.chip.pins.length === 2 && !physicalConnMap.getNetConnectedToId(pinId) && (entry.pin._facingDirection ?? getPinDirection(entry.pin, entry.chip)) === "y-";
395
+ };
396
+ return (firstPinId, secondPinId) => {
397
+ if (!groundNetId || netConnMap.getNetConnectedToId(firstPinId) !== groundNetId || netConnMap.getNetConnectedToId(secondPinId) !== groundNetId) {
398
+ return true;
399
+ }
400
+ if (!isGroundFacingTerminal(firstPinId) && !isGroundFacingTerminal(secondPinId))
401
+ return true;
402
+ const first = pins.get(firstPinId)?.pin;
403
+ const second = pins.get(secondPinId)?.pin;
404
+ return !first || !second || Math.abs(first.y - second.y) <= MAX_LOCAL_GROUND_BRANCH_OFFSET + 1e-6;
405
+ };
406
+ };
407
+
366
408
  // lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts
367
409
  function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
368
410
  const n = pins.length;
@@ -512,9 +554,11 @@ var MspConnectionPairSolver = class extends BaseSolver {
512
554
  pinMap;
513
555
  userNetIdByPinId;
514
556
  directConnectionPinPairKeys;
557
+ canRouteGroundPair;
515
558
  constructor({ inputProblem }) {
516
559
  super();
517
560
  this.inputProblem = inputProblem;
561
+ this.canRouteGroundPair = getGroundConnectionPolicy(inputProblem);
518
562
  this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE;
519
563
  const { directConnMap, netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
520
564
  this.dcConnMap = directConnMap;
@@ -571,6 +615,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
571
615
  const [pin1, pin2] = directlyConnectedPins;
572
616
  const p1 = this.pinMap[pin1];
573
617
  const p2 = this.pinMap[pin2];
618
+ if (!this.canRouteGroundPair(pin1, pin2)) return;
574
619
  const pinPairKey = getPinPairKey([pin1, pin2]);
575
620
  let pairDistance = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
576
621
  if (this.directConnectionPinPairKeys.has(pinPairKey)) {
@@ -615,7 +660,7 @@ var MspConnectionPairSolver = class extends BaseSolver {
615
660
  directlyConnectedPins.map((p) => this.pinMap[p]).filter(Boolean),
616
661
  {
617
662
  maxDistance: this.maxMspPairDistance,
618
- forbidEdge: (a, b) => arePinsInDifferentSchematicSections(
663
+ forbidEdge: (a, b) => !this.canRouteGroundPair(a.pinId, b.pinId) || arePinsInDifferentSchematicSections(
619
664
  this.inputProblem,
620
665
  a,
621
666
  b
@@ -3136,6 +3181,50 @@ var SingleNetLabelPlacementSolver = class extends BaseSolver {
3136
3181
  }
3137
3182
  };
3138
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
+
3139
3228
  // lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts
3140
3229
  var NetLabelPlacementSolver = class extends BaseSolver {
3141
3230
  inputProblem;
@@ -3209,35 +3298,12 @@ var NetLabelPlacementSolver = class extends BaseSolver {
3209
3298
  globalConnNetId
3210
3299
  );
3211
3300
  const pinsInNet = allIdsInNet.filter((id) => pinIdToPinMap.has(id));
3212
- const adj = {};
3213
- for (const pid of pinsInNet) adj[pid] = /* @__PURE__ */ new Set();
3214
- for (const t of byGlobal[globalConnNetId] ?? []) {
3215
- const a = t.pins[0].pinId;
3216
- const b = t.pins[1].pinId;
3217
- if (adj[a] && adj[b]) {
3218
- adj[a].add(b);
3219
- adj[b].add(a);
3220
- }
3221
- }
3222
- const visited = /* @__PURE__ */ new Set();
3223
- for (const pid of pinsInNet) {
3224
- if (visited.has(pid)) continue;
3225
- const stack = [pid];
3226
- const component = /* @__PURE__ */ new Set();
3227
- visited.add(pid);
3228
- while (stack.length > 0) {
3229
- const u = stack.pop();
3230
- component.add(u);
3231
- for (const v of adj[u] ?? []) {
3232
- if (!visited.has(v)) {
3233
- visited.add(v);
3234
- stack.push(v);
3235
- }
3236
- }
3237
- }
3238
- const compTraces = (byGlobal[globalConnNetId] ?? []).filter(
3239
- (t) => component.has(t.pins[0].pinId) && component.has(t.pins[1].pinId)
3240
- );
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;
3241
3307
  if (compTraces.length > 0) {
3242
3308
  if (compTraces.some((trace) => trace.suppressNetLabel)) continue;
3243
3309
  const lengthOf = (path) => {
@@ -4827,6 +4893,7 @@ var LongDistancePairSolver = class extends BaseSolver {
4827
4893
  super();
4828
4894
  this.params = params;
4829
4895
  const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
4896
+ const canRouteGroundPair = getGroundConnectionPolicy(inputProblem);
4830
4897
  this.inputProblem = inputProblem;
4831
4898
  this.allSolvedTraces = [...alreadySolvedTraces];
4832
4899
  this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE;
@@ -4845,7 +4912,10 @@ var LongDistancePairSolver = class extends BaseSolver {
4845
4912
  }
4846
4913
  }
4847
4914
  this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
4848
- (connectionPair) => isLabeledPeripheralConnection({
4915
+ (connectionPair) => canRouteGroundPair(
4916
+ connectionPair.pins[0].pinId,
4917
+ connectionPair.pins[1].pinId
4918
+ ) && isLabeledPeripheralConnection({
4849
4919
  inputProblem: this.inputProblem,
4850
4920
  chipMap: this.chipMap,
4851
4921
  pins: connectionPair.pins
@@ -4865,6 +4935,7 @@ var LongDistancePairSolver = class extends BaseSolver {
4865
4935
  const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
4866
4936
  const targetPin = pinMap.get(otherPinId);
4867
4937
  if (!targetPin) return [];
4938
+ if (!canRouteGroundPair(sourcePin.pinId, targetPin.pinId)) return [];
4868
4939
  const isNamedTwoPinConnection = inputProblem.netConnections.some(
4869
4940
  (connection) => connection.pinIds.length === 2 && connection.pinIds.includes(sourcePin.pinId) && connection.pinIds.includes(targetPin.pinId)
4870
4941
  );
@@ -12731,6 +12802,75 @@ var alignSameNetJunctions = ({
12731
12802
  };
12732
12803
  };
12733
12804
 
12805
+ // lib/solvers/SameNetJunctionAlignmentSolver/placeGroundRailLabelsAtOuterEnd.ts
12806
+ var placeGroundRailLabelsAtOuterEnd = ({
12807
+ inputProblem,
12808
+ traces,
12809
+ netLabelPlacements
12810
+ }) => {
12811
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
12812
+ const groundNetId = netConnMap.getNetConnectedToId("GND");
12813
+ if (!groundNetId) return netLabelPlacements;
12814
+ const chipMap = new Map(inputProblem.chips.map((chip) => [chip.chipId, chip]));
12815
+ const traceMap = Object.fromEntries(
12816
+ traces.map((trace) => [trace.mspPairId, trace])
12817
+ );
12818
+ const obstacles = getObstacleRects(inputProblem);
12819
+ const output = [...netLabelPlacements];
12820
+ for (const rail of traces) {
12821
+ if (rail.globalConnNetId !== groundNetId) continue;
12822
+ const [a, b] = rail.pins;
12823
+ if (a.chipId === b.chipId || !nearlyEqual(a.y, b.y) || ![a, b].every(
12824
+ (pin) => pin._facingDirection === "y-" && chipMap.get(pin.chipId)?.pins.length === 2
12825
+ ))
12826
+ continue;
12827
+ const path = simplifyPath(rail.tracePath);
12828
+ if (path.length !== 4 || !isVertical2(path[0], path[1]) || !isHorizontal2(path[1], path[2]) || !isVertical2(path[2], path[3]) || path[1].y >= a.y)
12829
+ continue;
12830
+ for (const feed of traces) {
12831
+ if (feed.globalConnNetId !== groundNetId) continue;
12832
+ const shared = feed.pins.find((pin) => rail.pinIds.includes(pin.pinId));
12833
+ const icPin = feed.pins.find(
12834
+ (pin) => !rail.pinIds.includes(pin.pinId) && (chipMap.get(pin.chipId)?.pins.length ?? 0) > 2
12835
+ );
12836
+ if (!shared || !icPin || !nearlyEqual(icPin.y, path[1].y)) continue;
12837
+ const outer = shared.pinId === a.pinId ? b : a;
12838
+ if (Math.abs(outer.x - icPin.x) <= Math.abs(shared.x - icPin.x)) continue;
12839
+ const anchorPoint = { x: outer.x, y: path[1].y };
12840
+ if (!tracePathContainsPoint(rail.tracePath, anchorPoint)) continue;
12841
+ for (let index = 0; index < output.length; index++) {
12842
+ const label = output[index];
12843
+ if (label.globalConnNetId !== groundNetId || label.orientation !== "y-" || !label.mspConnectionPairIds.some(
12844
+ (id) => id === feed.mspPairId || id === rail.mspPairId
12845
+ ) || !tracePathContainsPoint(feed.tracePath, label.anchorPoint) && !tracePathContainsPoint(rail.tracePath, label.anchorPoint))
12846
+ continue;
12847
+ const center = getCenterFromAnchor(
12848
+ anchorPoint,
12849
+ label.orientation,
12850
+ label.width,
12851
+ label.height
12852
+ );
12853
+ const bounds = getRectBounds(center, label.width, label.height);
12854
+ if (obstacles.some((obstacle) => rectsOverlap(bounds, obstacle)) || traceCrossesBoundsInterior(bounds, traceMap) || output.some(
12855
+ (other, otherIndex) => otherIndex !== index && rectsOverlap(
12856
+ bounds,
12857
+ getRectBounds(other.center, other.width, other.height)
12858
+ )
12859
+ ))
12860
+ continue;
12861
+ output[index] = {
12862
+ ...label,
12863
+ anchorPoint,
12864
+ center,
12865
+ mspConnectionPairIds: [rail.mspPairId],
12866
+ pinIds: [...rail.pinIds]
12867
+ };
12868
+ }
12869
+ }
12870
+ }
12871
+ return output;
12872
+ };
12873
+
12734
12874
  // lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts
12735
12875
  var SameNetJunctionAlignmentSolver = class extends BaseSolver {
12736
12876
  input;
@@ -12745,7 +12885,11 @@ var SameNetJunctionAlignmentSolver = class extends BaseSolver {
12745
12885
  _step() {
12746
12886
  const result = alignSameNetJunctions(this.input);
12747
12887
  this.outputTraces = result.traces;
12748
- this.outputNetLabelPlacements = result.netLabelPlacements;
12888
+ this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
12889
+ inputProblem: this.input.inputProblem,
12890
+ traces: result.traces,
12891
+ netLabelPlacements: result.netLabelPlacements
12892
+ });
12749
12893
  this.stats.alignedJunctionCount = result.alignedJunctionCount;
12750
12894
  this.solved = true;
12751
12895
  }
@@ -13572,7 +13716,7 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
13572
13716
  const connectorObstructed = inlineBounds.some(
13573
13717
  (bounds) => pathIntersectsBounds(connector.tracePath, bounds)
13574
13718
  ) || inputProblem.chips.some(
13575
- (chip) => pathIntersectsBounds(connector.tracePath, {
13719
+ (chip) => !ownerChipIds.has(chip.chipId) && pathIntersectsBounds(connector.tracePath, {
13576
13720
  minX: chip.center.x - chip.width / 2,
13577
13721
  maxX: chip.center.x + chip.width / 2,
13578
13722
  minY: chip.center.y - chip.height / 2,
@@ -13609,20 +13753,247 @@ var pushAnchoredNetLabelsAwayFromInlineLabels = ({
13609
13753
  };
13610
13754
  };
13611
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
+
13612
13953
  // lib/solvers/InlineNetLabelSolver/InlineNetLabelSolver.ts
13613
13954
  var DEFAULT_INLINE_NET_LABEL_HEIGHT = 0.18;
13614
13955
  var INLINE_NET_LABEL_TRACE_MARGIN = 0.05;
13615
13956
  var INLINE_NET_LABEL_STUB_TRACE_CLEARANCE = 0.05;
13616
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);
13617
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
+ });
13618
13987
  var InlineNetLabelSolver = class extends BaseSolver {
13619
13988
  inputProblem;
13620
13989
  traces;
13621
13990
  inputNetLabelPlacements;
13622
13991
  inlineNetLabelPlacements = [];
13623
- /** Eligible one- or two-pin connections still waiting to be processed. */
13992
+ /** Eligible connections still waiting to be processed. */
13624
13993
  queuedConnections;
13625
13994
  tracesByPinPairKey;
13995
+ globalConnMap;
13996
+ supersededTraceIdsByGlobalConnNetId = /* @__PURE__ */ new Map();
13626
13997
  hasAlignedPortOnlyStubs = false;
13627
13998
  postProcessedOutput;
13628
13999
  constructor(input) {
@@ -13630,16 +14001,26 @@ var InlineNetLabelSolver = class extends BaseSolver {
13630
14001
  this.inputProblem = input.inputProblem;
13631
14002
  this.traces = input.traces;
13632
14003
  this.inputNetLabelPlacements = input.netLabelPlacements;
14004
+ this.globalConnMap = getConnectivityMapsFromInputProblem(
14005
+ this.inputProblem
14006
+ ).netConnMap;
13633
14007
  this.queuedConnections = [
13634
14008
  ...this.inputProblem.directConnections.filter(
13635
14009
  (connection) => connection.allowInlineNetLabel && connection.netId
13636
- ),
14010
+ ).map((connection) => ({
14011
+ kind: "direct_connection",
14012
+ connection
14013
+ })),
13637
14014
  ...this.inputProblem.netConnections.filter(
13638
- (connection) => connection.allowInlineNetLabel && connection.pinIds.length >= 1 && connection.pinIds.length <= 2 && connection.netId
13639
- )
14015
+ (connection) => connection.allowInlineNetLabel && connection.pinIds.length >= 1 && connection.netId
14016
+ ).map((connection) => ({
14017
+ kind: "net_connection",
14018
+ connection
14019
+ }))
13640
14020
  ];
13641
14021
  this.tracesByPinPairKey = /* @__PURE__ */ new Map();
13642
14022
  for (const trace of this.traces) {
14023
+ if (isGeneratedAnchoredLabelConnector(trace)) continue;
13643
14024
  const key = getPinPairKey2(trace.pins.map((p) => p.pinId));
13644
14025
  const existing = this.tracesByPinPairKey.get(key);
13645
14026
  if (existing) {
@@ -13659,11 +14040,43 @@ var InlineNetLabelSolver = class extends BaseSolver {
13659
14040
  ];
13660
14041
  }
13661
14042
  _step() {
13662
- const connection = this.queuedConnections.shift();
13663
- 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;
13664
14073
  const routedTraces = connection.pinIds.length === 2 ? this.tracesByPinPairKey.get(getPinPairKey2(connection.pinIds)) ?? [] : [];
13665
14074
  if (routedTraces.length > 0) {
13666
- const placement = this.computeInlinePlacement(connection);
14075
+ const placement = this.computeInlinePlacement({
14076
+ connection,
14077
+ trace: routedTraces[0],
14078
+ pinIds: connection.pinIds
14079
+ });
13667
14080
  if (placement) this.inlineNetLabelPlacements.push(placement);
13668
14081
  } else {
13669
14082
  const terminalPlacements = connection.pinIds.map(
@@ -13692,18 +14105,181 @@ var InlineNetLabelSolver = class extends BaseSolver {
13692
14105
  this.solved = true;
13693
14106
  this.stats.inlineNetLabelCount = this.inlineNetLabelPlacements.length;
13694
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
+ }
13695
14270
  /**
13696
14271
  * Converts one conventional endpoint placement into an inline label on a
13697
14272
  * generated outward stub. The stub follows the pin's true facing direction;
13698
14273
  * an anchored label may finish in another direction after an elbow, which is
13699
14274
  * not the direction a terminal stub should leave the pin.
13700
14275
  */
13701
- computeTerminalInlinePlacement(connection, pinId) {
14276
+ computeTerminalInlinePlacement(connection, pinId, knownGlobalConnNetId, ignoredTraceIds = /* @__PURE__ */ new Set()) {
13702
14277
  if (!connection.netId) return null;
13703
14278
  const anchoredPlacement = this.inputNetLabelPlacements.find(
13704
14279
  (placement) => placement.netId === connection.netId && placement.pinIds.length === 1 && placement.pinIds[0] === pinId
13705
14280
  );
13706
- if (!anchoredPlacement) return null;
14281
+ const globalConnNetId = knownGlobalConnNetId ?? anchoredPlacement?.globalConnNetId;
14282
+ if (!globalConnNetId) return null;
13707
14283
  const inputChip = this.inputProblem.chips.find(
13708
14284
  (chip) => chip.pins.some((pin) => pin.pinId === pinId)
13709
14285
  );
@@ -13712,6 +14288,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
13712
14288
  const netLabelText = connection.netLabelText?.trim() || void 0;
13713
14289
  const width = connection.inlineNetLabelWidth ?? connection.netLabelWidth ?? estimateInlineNetLabelWidth(netLabelText ?? connection.netId, height);
13714
14290
  const stubLength = Math.max(width + 0.2, 0.6);
14291
+ if (!inputPin && !anchoredPlacement) return null;
13715
14292
  const start = inputPin ? { x: inputPin.x, y: inputPin.y } : anchoredPlacement.anchorPoint;
13716
14293
  const direction = inputPin && inputChip ? inputPin._facingDirection ?? getPinDirection(inputPin, inputChip) : anchoredPlacement.orientation;
13717
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 };
@@ -13720,53 +14297,60 @@ var InlineNetLabelSolver = class extends BaseSolver {
13720
14297
  intendedEnd,
13721
14298
  minimumLength: width + 2 * INLINE_NET_LABEL_TRACE_MARGIN,
13722
14299
  traces: this.traces,
13723
- ownGlobalConnNetId: anchoredPlacement.globalConnNetId
14300
+ ownGlobalConnNetId: globalConnNetId,
14301
+ ignoredTraceIds
13724
14302
  });
13725
14303
  if (!end) return null;
13726
14304
  const axis = direction === "x+" || direction === "x-" ? "x" : "y";
13727
- const side = axis === "x" ? "y+" : "x-";
13728
14305
  const anchorPoint = {
13729
14306
  x: (start.x + end.x) / 2,
13730
14307
  y: (start.y + end.y) / 2
13731
14308
  };
13732
14309
  const offset = height / 2 + INLINE_NET_LABEL_TRACE_MARGIN;
13733
- const center = side === "y+" ? { x: anchorPoint.x, y: anchorPoint.y + offset } : { x: anchorPoint.x - offset, y: anchorPoint.y };
13734
- const halfAlong = width / 2;
13735
- const halfAcross = height / 2;
13736
- const bounds = axis === "x" ? {
13737
- minX: center.x - halfAlong,
13738
- maxX: center.x + halfAlong,
13739
- minY: center.y - halfAcross,
13740
- maxY: center.y + halfAcross
13741
- } : {
13742
- minX: center.x - halfAcross,
13743
- maxX: center.x + halfAcross,
13744
- minY: center.y - halfAlong,
13745
- maxY: center.y + halfAlong
13746
- };
13747
- if (this.isObstructed(bounds, {
13748
- ownGlobalConnNetId: anchoredPlacement.globalConnNetId
13749
- })) {
13750
- 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
+ };
13751
14345
  }
13752
- return {
13753
- globalConnNetId: anchoredPlacement.globalConnNetId,
13754
- netId: connection.netId,
13755
- netLabelText,
13756
- pinIds: [pinId],
13757
- stubTracePath: [start, end],
13758
- axis,
13759
- anchorPoint,
13760
- center,
13761
- width,
13762
- height,
13763
- side
13764
- };
14346
+ return null;
13765
14347
  }
13766
- computeInlinePlacement(connection) {
13767
- const traces = this.tracesByPinPairKey.get(getPinPairKey2(connection.pinIds)) ?? [];
13768
- if (traces.length === 0) return null;
13769
- const trace = traces[0];
14348
+ computeInlinePlacement({
14349
+ connection,
14350
+ trace,
14351
+ pinIds,
14352
+ ignoredTraceIds = /* @__PURE__ */ new Set()
14353
+ }) {
13770
14354
  const segments = getAxisAlignedSegments(trace.tracePath);
13771
14355
  if (segments.length === 0) return null;
13772
14356
  const height = connection.inlineNetLabelHeight ?? DEFAULT_INLINE_NET_LABEL_HEIGHT;
@@ -13794,7 +14378,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
13794
14378
  };
13795
14379
  if (this.isObstructed(bounds, {
13796
14380
  ownTrace: trace,
13797
- ownGlobalConnNetId: trace.globalConnNetId
14381
+ ownGlobalConnNetId: trace.globalConnNetId,
14382
+ ignoredTraceIds
13798
14383
  }))
13799
14384
  continue;
13800
14385
  return {
@@ -13802,7 +14387,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
13802
14387
  netId: connection.netId,
13803
14388
  netLabelText,
13804
14389
  mspPairId: trace.mspPairId,
13805
- pinIds: [...connection.pinIds],
14390
+ pinIds: [...pinIds],
13806
14391
  axis: segment.axis,
13807
14392
  anchorPoint,
13808
14393
  center,
@@ -13819,7 +14404,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
13819
14404
  netLabelText,
13820
14405
  width,
13821
14406
  height,
13822
- offset
14407
+ offset,
14408
+ pinIds,
14409
+ ignoredTraceIds
13823
14410
  });
13824
14411
  if (spanPlacement) return spanPlacement;
13825
14412
  return null;
@@ -13836,7 +14423,9 @@ var InlineNetLabelSolver = class extends BaseSolver {
13836
14423
  netLabelText,
13837
14424
  width,
13838
14425
  height,
13839
- offset
14426
+ offset,
14427
+ pinIds,
14428
+ ignoredTraceIds
13840
14429
  }) {
13841
14430
  const path = trace.tracePath;
13842
14431
  if (path.length < 2) return null;
@@ -13886,7 +14475,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
13886
14475
  };
13887
14476
  if (this.isObstructed(bounds, {
13888
14477
  ownTrace: trace,
13889
- ownGlobalConnNetId: trace.globalConnNetId
14478
+ ownGlobalConnNetId: trace.globalConnNetId,
14479
+ ignoredTraceIds
13890
14480
  }))
13891
14481
  continue;
13892
14482
  const anchorPoint = axis === "x" ? { x: along, y: side === "y+" ? maxY : minY } : { x: side === "x-" ? minX : maxX, y: along };
@@ -13895,7 +14485,7 @@ var InlineNetLabelSolver = class extends BaseSolver {
13895
14485
  netId: connection.netId,
13896
14486
  netLabelText,
13897
14487
  mspPairId: trace.mspPairId,
13898
- pinIds: [...connection.pinIds],
14488
+ pinIds: [...pinIds],
13899
14489
  axis,
13900
14490
  anchorPoint,
13901
14491
  center,
@@ -13913,7 +14503,8 @@ var InlineNetLabelSolver = class extends BaseSolver {
13913
14503
  */
13914
14504
  isObstructed(bounds, {
13915
14505
  ownTrace,
13916
- ownGlobalConnNetId
14506
+ ownGlobalConnNetId,
14507
+ ignoredTraceIds = /* @__PURE__ */ new Set()
13917
14508
  }) {
13918
14509
  for (const chip of this.inputProblem.chips) {
13919
14510
  const chipBounds = {
@@ -13928,9 +14519,10 @@ var InlineNetLabelSolver = class extends BaseSolver {
13928
14519
  if (boundsOverlap(bounds, getTextBoxBounds(textBox))) return true;
13929
14520
  }
13930
14521
  for (const trace of this.traces) {
14522
+ if (ignoredTraceIds.has(trace.mspPairId)) continue;
13931
14523
  if (ownTrace && trace.mspPairId === ownTrace.mspPairId) continue;
13932
14524
  if (trace.globalConnNetId === ownGlobalConnNetId) continue;
13933
- if (doesPathIntersectBounds2(trace.tracePath, bounds)) return true;
14525
+ if (doesPathIntersectBounds3(trace.tracePath, bounds)) return true;
13934
14526
  }
13935
14527
  return false;
13936
14528
  }
@@ -13938,36 +14530,94 @@ var InlineNetLabelSolver = class extends BaseSolver {
13938
14530
  * Net label placements superseded by an inline label. A net gets one label or
13939
14531
  * the other, never both.
13940
14532
  */
13941
- getSupersededNetLabelKeys() {
14533
+ getSupersededNetLabelKeys(placements = this.inlineNetLabelPlacements) {
13942
14534
  const keys = /* @__PURE__ */ new Set();
13943
- for (const placement of this.inlineNetLabelPlacements) {
14535
+ for (const placement of placements) {
13944
14536
  keys.add(placement.globalConnNetId);
13945
14537
  }
13946
14538
  return keys;
13947
14539
  }
13948
- getOutputTraces(supersededNetLabelKeys) {
14540
+ getOutputTraces(supersededNetLabelKeys, supersededTraceIds) {
13949
14541
  return this.traces.filter(
13950
- (trace) => !(supersededNetLabelKeys.has(trace.globalConnNetId) && trace.mspPairId.startsWith("available-net-orientation-"))
14542
+ (trace) => !supersededTraceIds.has(trace.mspPairId) && !(supersededNetLabelKeys.has(trace.globalConnNetId) && isGeneratedAnchoredLabelConnector(trace))
13951
14543
  );
13952
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
+ }
13953
14568
  buildPostProcessedOutput() {
13954
- const superseded = this.getSupersededNetLabelKeys();
13955
- const retainedNetLabelPlacements = this.inputNetLabelPlacements.filter(
13956
- (placement) => !superseded.has(placement.globalConnNetId)
13957
- );
13958
- const outputTraces = this.getOutputTraces(superseded);
13959
- const pushed = pushAnchoredNetLabelsAwayFromInlineLabels({
13960
- inputProblem: this.inputProblem,
13961
- traces: outputTraces,
13962
- netLabelPlacements: retainedNetLabelPlacements,
13963
- inlineNetLabelPlacements: this.inlineNetLabelPlacements
13964
- });
13965
- this.stats.pushedAnchoredNetLabelCount = pushed.movedLabelCount;
13966
- return {
13967
- traces: pushed.traces,
13968
- netLabelPlacements: pushed.netLabelPlacements,
13969
- inlineNetLabelPlacements: this.inlineNetLabelPlacements
13970
- };
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
+ }
13971
14621
  }
13972
14622
  getOutput() {
13973
14623
  if (this.postProcessedOutput) return this.postProcessedOutput;
@@ -14102,7 +14752,7 @@ var getAnchorCandidates = (segment, labelWidth, step = 0.1) => {
14102
14752
  offsets.push(slack / 2, -slack / 2);
14103
14753
  return offsets.map((offset) => pointAt(mid + offset * direction));
14104
14754
  };
14105
- var doesPathIntersectBounds2 = (path, bounds) => {
14755
+ var doesPathIntersectBounds3 = (path, bounds) => {
14106
14756
  for (let i = 0; i < path.length - 1; i++) {
14107
14757
  const a = path[i];
14108
14758
  const b = path[i + 1];
@@ -14122,7 +14772,8 @@ var getCollisionLimitedTerminalStubEnd = ({
14122
14772
  intendedEnd,
14123
14773
  minimumLength,
14124
14774
  traces,
14125
- ownGlobalConnNetId
14775
+ ownGlobalConnNetId,
14776
+ ignoredTraceIds
14126
14777
  }) => {
14127
14778
  const isHorizontal4 = Math.abs(intendedEnd.x - start.x) >= Math.abs(intendedEnd.y - start.y);
14128
14779
  const alongAxis = isHorizontal4 ? "x" : "y";
@@ -14131,6 +14782,7 @@ var getCollisionLimitedTerminalStubEnd = ({
14131
14782
  const intendedLength = Math.abs(intendedEnd[alongAxis] - start[alongAxis]);
14132
14783
  let availableLength = intendedLength;
14133
14784
  for (const trace of traces) {
14785
+ if (ignoredTraceIds.has(trace.mspPairId)) continue;
14134
14786
  if (trace.globalConnNetId === ownGlobalConnNetId) continue;
14135
14787
  for (let segmentIndex = 0; segmentIndex < trace.tracePath.length - 1; segmentIndex++) {
14136
14788
  const segmentStart = trace.tracePath[segmentIndex];