@tscircuit/schematic-trace-solver 0.0.158 → 0.0.159

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -202,6 +202,7 @@ declare class MspConnectionPairSolver extends BaseSolver {
202
202
  }>;
203
203
  userNetIdByPinId: Record<string, string | undefined>;
204
204
  directConnectionPinPairKeys: Set<string>;
205
+ private canRouteGroundPair;
205
206
  constructor({ inputProblem }: {
206
207
  inputProblem: InputProblem;
207
208
  });
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
@@ -4827,6 +4872,7 @@ var LongDistancePairSolver = class extends BaseSolver {
4827
4872
  super();
4828
4873
  this.params = params;
4829
4874
  const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
4875
+ const canRouteGroundPair = getGroundConnectionPolicy(inputProblem);
4830
4876
  this.inputProblem = inputProblem;
4831
4877
  this.allSolvedTraces = [...alreadySolvedTraces];
4832
4878
  this.maxMspPairDistance = inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE;
@@ -4845,7 +4891,10 @@ var LongDistancePairSolver = class extends BaseSolver {
4845
4891
  }
4846
4892
  }
4847
4893
  this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
4848
- (connectionPair) => isLabeledPeripheralConnection({
4894
+ (connectionPair) => canRouteGroundPair(
4895
+ connectionPair.pins[0].pinId,
4896
+ connectionPair.pins[1].pinId
4897
+ ) && isLabeledPeripheralConnection({
4849
4898
  inputProblem: this.inputProblem,
4850
4899
  chipMap: this.chipMap,
4851
4900
  pins: connectionPair.pins
@@ -4865,6 +4914,7 @@ var LongDistancePairSolver = class extends BaseSolver {
4865
4914
  const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
4866
4915
  const targetPin = pinMap.get(otherPinId);
4867
4916
  if (!targetPin) return [];
4917
+ if (!canRouteGroundPair(sourcePin.pinId, targetPin.pinId)) return [];
4868
4918
  const isNamedTwoPinConnection = inputProblem.netConnections.some(
4869
4919
  (connection) => connection.pinIds.length === 2 && connection.pinIds.includes(sourcePin.pinId) && connection.pinIds.includes(targetPin.pinId)
4870
4920
  );
@@ -12731,6 +12781,75 @@ var alignSameNetJunctions = ({
12731
12781
  };
12732
12782
  };
12733
12783
 
12784
+ // lib/solvers/SameNetJunctionAlignmentSolver/placeGroundRailLabelsAtOuterEnd.ts
12785
+ var placeGroundRailLabelsAtOuterEnd = ({
12786
+ inputProblem,
12787
+ traces,
12788
+ netLabelPlacements
12789
+ }) => {
12790
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
12791
+ const groundNetId = netConnMap.getNetConnectedToId("GND");
12792
+ if (!groundNetId) return netLabelPlacements;
12793
+ const chipMap = new Map(inputProblem.chips.map((chip) => [chip.chipId, chip]));
12794
+ const traceMap = Object.fromEntries(
12795
+ traces.map((trace) => [trace.mspPairId, trace])
12796
+ );
12797
+ const obstacles = getObstacleRects(inputProblem);
12798
+ const output = [...netLabelPlacements];
12799
+ for (const rail of traces) {
12800
+ if (rail.globalConnNetId !== groundNetId) continue;
12801
+ const [a, b] = rail.pins;
12802
+ if (a.chipId === b.chipId || !nearlyEqual(a.y, b.y) || ![a, b].every(
12803
+ (pin) => pin._facingDirection === "y-" && chipMap.get(pin.chipId)?.pins.length === 2
12804
+ ))
12805
+ continue;
12806
+ const path = simplifyPath(rail.tracePath);
12807
+ if (path.length !== 4 || !isVertical2(path[0], path[1]) || !isHorizontal2(path[1], path[2]) || !isVertical2(path[2], path[3]) || path[1].y >= a.y)
12808
+ continue;
12809
+ for (const feed of traces) {
12810
+ if (feed.globalConnNetId !== groundNetId) continue;
12811
+ const shared = feed.pins.find((pin) => rail.pinIds.includes(pin.pinId));
12812
+ const icPin = feed.pins.find(
12813
+ (pin) => !rail.pinIds.includes(pin.pinId) && (chipMap.get(pin.chipId)?.pins.length ?? 0) > 2
12814
+ );
12815
+ if (!shared || !icPin || !nearlyEqual(icPin.y, path[1].y)) continue;
12816
+ const outer = shared.pinId === a.pinId ? b : a;
12817
+ if (Math.abs(outer.x - icPin.x) <= Math.abs(shared.x - icPin.x)) continue;
12818
+ const anchorPoint = { x: outer.x, y: path[1].y };
12819
+ if (!tracePathContainsPoint(rail.tracePath, anchorPoint)) continue;
12820
+ for (let index = 0; index < output.length; index++) {
12821
+ const label = output[index];
12822
+ if (label.globalConnNetId !== groundNetId || label.orientation !== "y-" || !label.mspConnectionPairIds.some(
12823
+ (id) => id === feed.mspPairId || id === rail.mspPairId
12824
+ ) || !tracePathContainsPoint(feed.tracePath, label.anchorPoint) && !tracePathContainsPoint(rail.tracePath, label.anchorPoint))
12825
+ continue;
12826
+ const center = getCenterFromAnchor(
12827
+ anchorPoint,
12828
+ label.orientation,
12829
+ label.width,
12830
+ label.height
12831
+ );
12832
+ const bounds = getRectBounds(center, label.width, label.height);
12833
+ if (obstacles.some((obstacle) => rectsOverlap(bounds, obstacle)) || traceCrossesBoundsInterior(bounds, traceMap) || output.some(
12834
+ (other, otherIndex) => otherIndex !== index && rectsOverlap(
12835
+ bounds,
12836
+ getRectBounds(other.center, other.width, other.height)
12837
+ )
12838
+ ))
12839
+ continue;
12840
+ output[index] = {
12841
+ ...label,
12842
+ anchorPoint,
12843
+ center,
12844
+ mspConnectionPairIds: [rail.mspPairId],
12845
+ pinIds: [...rail.pinIds]
12846
+ };
12847
+ }
12848
+ }
12849
+ }
12850
+ return output;
12851
+ };
12852
+
12734
12853
  // lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts
12735
12854
  var SameNetJunctionAlignmentSolver = class extends BaseSolver {
12736
12855
  input;
@@ -12745,7 +12864,11 @@ var SameNetJunctionAlignmentSolver = class extends BaseSolver {
12745
12864
  _step() {
12746
12865
  const result = alignSameNetJunctions(this.input);
12747
12866
  this.outputTraces = result.traces;
12748
- this.outputNetLabelPlacements = result.netLabelPlacements;
12867
+ this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
12868
+ inputProblem: this.input.inputProblem,
12869
+ traces: result.traces,
12870
+ netLabelPlacements: result.netLabelPlacements
12871
+ });
12749
12872
  this.stats.alignedJunctionCount = result.alignedJunctionCount;
12750
12873
  this.solved = true;
12751
12874
  }
@@ -1,23 +1,24 @@
1
- import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
2
1
  import type { Point } from "@tscircuit/math-utils"
2
+ import type { ConnectivityMap } from "connectivity-map"
3
+ import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
3
4
  import {
4
5
  DEFAULT_MAX_MSP_PAIR_DISTANCE,
5
6
  type MspConnectionPair,
6
7
  } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
7
8
  import type {
8
- InputProblem,
9
+ InputChip,
9
10
  InputPin,
11
+ InputProblem,
10
12
  PinId,
11
- InputChip,
12
13
  } from "lib/types/InputProblem"
13
- import { BaseSolver } from "../BaseSolver/BaseSolver"
14
- import { SchematicTraceSingleLineSolver2 } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
15
- import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
16
- import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
17
- import type { ConnectivityMap } from "connectivity-map"
18
14
  import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces"
19
15
  import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
16
+ import { BaseSolver } from "../BaseSolver/BaseSolver"
17
+ import { getGroundConnectionPolicy } from "../MspConnectionPairSolver/getGroundConnectionPolicy"
20
18
  import { isLabeledPeripheralConnection } from "../MspConnectionPairSolver/isLabeledPeripheralConnection"
19
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
20
+ import { SchematicTraceSingleLineSolver2 } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
21
+ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
21
22
 
22
23
  const NEAREST_NEIGHBOR_COUNT = 3
23
24
 
@@ -163,6 +164,7 @@ export class LongDistancePairSolver extends BaseSolver {
163
164
 
164
165
  const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } =
165
166
  this.params
167
+ const canRouteGroundPair = getGroundConnectionPolicy(inputProblem)
166
168
 
167
169
  this.inputProblem = inputProblem
168
170
  this.allSolvedTraces = [...alreadySolvedTraces]
@@ -189,6 +191,10 @@ export class LongDistancePairSolver extends BaseSolver {
189
191
  // new nearest-neighbor candidates.
190
192
  this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
191
193
  (connectionPair) =>
194
+ canRouteGroundPair(
195
+ connectionPair.pins[0].pinId,
196
+ connectionPair.pins[1].pinId,
197
+ ) &&
192
198
  isLabeledPeripheralConnection({
193
199
  inputProblem: this.inputProblem,
194
200
  chipMap: this.chipMap,
@@ -219,6 +225,7 @@ export class LongDistancePairSolver extends BaseSolver {
219
225
  .flatMap((otherPinId) => {
220
226
  const targetPin = pinMap.get(otherPinId)
221
227
  if (!targetPin) return [] // Gracefully handle missing pins
228
+ if (!canRouteGroundPair(sourcePin.pinId, targetPin.pinId)) return []
222
229
  const isNamedTwoPinConnection = inputProblem.netConnections.some(
223
230
  (connection) =>
224
231
  connection.pinIds.length === 2 &&
@@ -13,6 +13,7 @@ import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDiffer
13
13
  import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
14
14
  import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
15
15
  import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
16
+ import { getGroundConnectionPolicy } from "./getGroundConnectionPolicy"
16
17
  import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
17
18
  import { getLabeledConnectionRouteReason } from "./isLabeledPeripheralConnection"
18
19
 
@@ -45,11 +46,13 @@ export class MspConnectionPairSolver extends BaseSolver {
45
46
  pinMap: Record<string, InputPin & { chipId: string }>
46
47
  userNetIdByPinId: Record<string, string | undefined>
47
48
  directConnectionPinPairKeys: Set<string>
49
+ private canRouteGroundPair: (firstPinId: PinId, secondPinId: PinId) => boolean
48
50
 
49
51
  constructor({ inputProblem }: { inputProblem: InputProblem }) {
50
52
  super()
51
53
 
52
54
  this.inputProblem = inputProblem
55
+ this.canRouteGroundPair = getGroundConnectionPolicy(inputProblem)
53
56
  this.maxMspPairDistance =
54
57
  inputProblem.maxMspPairDistance ?? DEFAULT_MAX_MSP_PAIR_DISTANCE
55
58
 
@@ -124,6 +127,7 @@ export class MspConnectionPairSolver extends BaseSolver {
124
127
  const [pin1, pin2] = directlyConnectedPins
125
128
  const p1 = this.pinMap[pin1!]!
126
129
  const p2 = this.pinMap[pin2!]!
130
+ if (!this.canRouteGroundPair(pin1!, pin2!)) return
127
131
  const pinPairKey = getPinPairKey([pin1!, pin2!])
128
132
  // Explicit source traces are classified by straight-line distance when
129
133
  // their input is created; named nets retain the orthogonal route metric.
@@ -195,6 +199,7 @@ export class MspConnectionPairSolver extends BaseSolver {
195
199
  {
196
200
  maxDistance: this.maxMspPairDistance,
197
201
  forbidEdge: (a, b) =>
202
+ !this.canRouteGroundPair(a.pinId, b.pinId) ||
198
203
  arePinsInDifferentSchematicSections(
199
204
  this.inputProblem,
200
205
  a as InputPin & { chipId: string },
@@ -0,0 +1,83 @@
1
+ import { ConnectivityMap } from "connectivity-map"
2
+ import type { InputProblem, PinId } from "lib/types/InputProblem"
3
+ import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
4
+ import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
5
+
6
+ // Keep nearby ground connections, but do not extend the usual 1 mm local
7
+ // routing range vertically just because a sheet allows long signal traces.
8
+ const MAX_LOCAL_GROUND_BRANCH_OFFSET = 1
9
+
10
+ /** Avoid return wires between staggered, net-only ground-facing branches. */
11
+ export const getGroundConnectionPolicy = (inputProblem: InputProblem) => {
12
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
13
+ const groundNetId = netConnMap.getNetConnectedToId("GND")
14
+ if (!groundNetId) return () => true
15
+ // Net identifiers do not constitute physical edges: two separate direct
16
+ // connections may have the same netId without requesting a wire between them.
17
+ const physicalConnMap = new ConnectivityMap({})
18
+ for (const connection of inputProblem.directConnections) {
19
+ physicalConnMap.addConnections([connection.pinIds])
20
+ }
21
+ const pins = new Map(
22
+ inputProblem.chips.flatMap((chip) =>
23
+ chip.pins.map((pin) => [pin.pinId, { pin, chip }] as const),
24
+ ),
25
+ )
26
+ const groundFacingPins = [...pins.values()].filter(
27
+ ({ pin, chip }) =>
28
+ chip.pins.length === 2 &&
29
+ netConnMap.getNetConnectedToId(pin.pinId) === groundNetId &&
30
+ (pin._facingDirection ?? getPinDirection(pin, chip)) === "y-",
31
+ )
32
+ // A deliberately wired, level bank of two-pin loads already has its own
33
+ // shared return rail. Do not extend that rail to independent lower branches
34
+ // just to reduce the number of GND symbols. Other ground topologies retain
35
+ // their existing routing behavior.
36
+ const hasExplicitParallelGroundRail = groundFacingPins.some(({ pin: a }) => {
37
+ const group = physicalConnMap.getNetConnectedToId(a.pinId)
38
+ return (
39
+ group !== undefined &&
40
+ groundFacingPins.some(
41
+ ({ pin: b }) =>
42
+ a.pinId !== b.pinId &&
43
+ Math.abs(a.y - b.y) < 1e-6 &&
44
+ Math.abs(a.x - b.x) > 1e-6 &&
45
+ physicalConnMap.getNetConnectedToId(b.pinId) === group,
46
+ )
47
+ )
48
+ })
49
+ if (!hasExplicitParallelGroundRail) return () => true
50
+ const isGroundFacingTerminal = (pinId: PinId) => {
51
+ const entry = pins.get(pinId)
52
+ return (
53
+ entry?.chip.pins.length === 2 &&
54
+ !physicalConnMap.getNetConnectedToId(pinId) &&
55
+ (entry.pin._facingDirection ?? getPinDirection(entry.pin, entry.chip)) ===
56
+ "y-"
57
+ )
58
+ }
59
+
60
+ return (firstPinId: PinId, secondPinId: PinId): boolean => {
61
+ if (
62
+ !groundNetId ||
63
+ netConnMap.getNetConnectedToId(firstPinId) !== groundNetId ||
64
+ netConnMap.getNetConnectedToId(secondPinId) !== groundNetId
65
+ ) {
66
+ return true
67
+ }
68
+ if (
69
+ !isGroundFacingTerminal(firstPinId) &&
70
+ !isGroundFacingTerminal(secondPinId)
71
+ )
72
+ return true
73
+ const first = pins.get(firstPinId)?.pin
74
+ const second = pins.get(secondPinId)?.pin
75
+ // Level ground pins can still form a shared decoupling rail. A staggered
76
+ // terminal would need a return detour; use its local GND label instead.
77
+ return (
78
+ !first ||
79
+ !second ||
80
+ Math.abs(first.y - second.y) <= MAX_LOCAL_GROUND_BRANCH_OFFSET + 1e-6
81
+ )
82
+ }
83
+ }
@@ -6,6 +6,7 @@ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/
6
6
  import type { InputProblem } from "lib/types/InputProblem"
7
7
  import { getColorFromString } from "lib/utils/getColorFromString"
8
8
  import { alignSameNetJunctions } from "./alignSameNetJunctions"
9
+ import { placeGroundRailLabelsAtOuterEnd } from "./placeGroundRailLabelsAtOuterEnd"
9
10
 
10
11
  interface SameNetJunctionAlignmentSolverInput {
11
12
  inputProblem: InputProblem
@@ -29,7 +30,11 @@ export class SameNetJunctionAlignmentSolver extends BaseSolver {
29
30
  override _step() {
30
31
  const result = alignSameNetJunctions(this.input)
31
32
  this.outputTraces = result.traces
32
- this.outputNetLabelPlacements = result.netLabelPlacements
33
+ this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
34
+ inputProblem: this.input.inputProblem,
35
+ traces: result.traces,
36
+ netLabelPlacements: result.netLabelPlacements,
37
+ })
33
38
  this.stats.alignedJunctionCount = result.alignedJunctionCount
34
39
  this.solved = true
35
40
  }
@@ -0,0 +1,122 @@
1
+ import { traceCrossesBoundsInterior } from "lib/solvers/AvailableNetOrientationSolver/geometry"
2
+ import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
3
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import {
5
+ getCenterFromAnchor,
6
+ getRectBounds,
7
+ } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
8
+ import {
9
+ rectsOverlap,
10
+ tracePathContainsPoint,
11
+ } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
12
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
13
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
14
+ import {
15
+ isHorizontal,
16
+ isVertical,
17
+ nearlyEqual,
18
+ } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
19
+ import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
20
+ import type { InputProblem } from "lib/types/InputProblem"
21
+
22
+ /** Put a shared decoupling GND at the load end of its rail, away from the IC. */
23
+ export const placeGroundRailLabelsAtOuterEnd = ({
24
+ inputProblem,
25
+ traces,
26
+ netLabelPlacements,
27
+ }: {
28
+ inputProblem: InputProblem
29
+ traces: SolvedTracePath[]
30
+ netLabelPlacements: NetLabelPlacement[]
31
+ }): NetLabelPlacement[] => {
32
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
33
+ const groundNetId = netConnMap.getNetConnectedToId("GND")
34
+ if (!groundNetId) return netLabelPlacements
35
+ const chipMap = new Map(inputProblem.chips.map((chip) => [chip.chipId, chip]))
36
+ const traceMap = Object.fromEntries(
37
+ traces.map((trace) => [trace.mspPairId, trace]),
38
+ )
39
+ const obstacles = getObstacleRects(inputProblem)
40
+ const output = [...netLabelPlacements]
41
+
42
+ for (const rail of traces) {
43
+ if (rail.globalConnNetId !== groundNetId) continue
44
+ const [a, b] = rail.pins
45
+ if (
46
+ a.chipId === b.chipId ||
47
+ !nearlyEqual(a.y, b.y) ||
48
+ ![a, b].every(
49
+ (pin) =>
50
+ pin._facingDirection === "y-" &&
51
+ chipMap.get(pin.chipId)?.pins.length === 2,
52
+ )
53
+ )
54
+ continue
55
+ const path = simplifyPath(rail.tracePath)
56
+ if (
57
+ path.length !== 4 ||
58
+ !isVertical(path[0]!, path[1]!) ||
59
+ !isHorizontal(path[1]!, path[2]!) ||
60
+ !isVertical(path[2]!, path[3]!) ||
61
+ path[1]!.y >= a.y
62
+ )
63
+ continue
64
+
65
+ for (const feed of traces) {
66
+ if (feed.globalConnNetId !== groundNetId) continue
67
+ const shared = feed.pins.find((pin) => rail.pinIds.includes(pin.pinId))
68
+ const icPin = feed.pins.find(
69
+ (pin) =>
70
+ !rail.pinIds.includes(pin.pinId) &&
71
+ (chipMap.get(pin.chipId)?.pins.length ?? 0) > 2,
72
+ )
73
+ if (!shared || !icPin || !nearlyEqual(icPin.y, path[1]!.y)) continue
74
+ const outer = shared.pinId === a.pinId ? b : a
75
+ if (Math.abs(outer.x - icPin.x) <= Math.abs(shared.x - icPin.x)) continue
76
+ const anchorPoint = { x: outer.x, y: path[1]!.y }
77
+ if (!tracePathContainsPoint(rail.tracePath, anchorPoint)) continue
78
+
79
+ for (let index = 0; index < output.length; index++) {
80
+ const label = output[index]!
81
+ if (
82
+ label.globalConnNetId !== groundNetId ||
83
+ label.orientation !== "y-" ||
84
+ !label.mspConnectionPairIds.some(
85
+ (id) => id === feed.mspPairId || id === rail.mspPairId,
86
+ ) ||
87
+ (!tracePathContainsPoint(feed.tracePath, label.anchorPoint) &&
88
+ !tracePathContainsPoint(rail.tracePath, label.anchorPoint))
89
+ )
90
+ continue
91
+ const center = getCenterFromAnchor(
92
+ anchorPoint,
93
+ label.orientation,
94
+ label.width,
95
+ label.height,
96
+ )
97
+ const bounds = getRectBounds(center, label.width, label.height)
98
+ if (
99
+ obstacles.some((obstacle) => rectsOverlap(bounds, obstacle)) ||
100
+ traceCrossesBoundsInterior(bounds, traceMap) ||
101
+ output.some(
102
+ (other, otherIndex) =>
103
+ otherIndex !== index &&
104
+ rectsOverlap(
105
+ bounds,
106
+ getRectBounds(other.center, other.width, other.height),
107
+ ),
108
+ )
109
+ )
110
+ continue
111
+ output[index] = {
112
+ ...label,
113
+ anchorPoint,
114
+ center,
115
+ mspConnectionPairIds: [rail.mspPairId],
116
+ pinIds: [...rail.pinIds],
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return output
122
+ }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
6
  },
7
7
  "main": "dist/index.js",
8
- "version": "0.0.158",
8
+ "version": "0.0.159",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "start": "cosmos",