Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,14 @@ public AssembleResult Assemble()
foreach (var (root, group) in groups)
{
var pins = new List<NetPin>();
var pinKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var e in group)
if (e.Kind == ElementKind.Pin && e.NetPin is not null)
pins.Add(e.NetPin);
{
var logicalKey = $"{e.NetPin.SheetInstanceId}\0{e.NetPin.Key}";
if (pinKeys.Add(logicalKey))
pins.Add(e.NetPin);
}

var hasLabel = _labelsByRoot.TryGetValue(root, out var labels) && labels.Count > 0;
var (name, scope, explicitName) = ChooseName(root, group, pins, hasLabel ? labels! : null);
Expand Down
39 changes: 36 additions & 3 deletions src/OriginalCircuit.Altium/Connectivity/Internal/SheetGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,15 @@ private void Extract(List<Element> global)
if (ip is not SchPin pin)
continue;

// A multi-part component record carries pins from every part, but only the displayed
// part (CurrentPartId) is actually placed on this sheet — the other parts' pins keep
// stale positions. Include only the current part's pins (plus part-shared pins).
// A component record carries pins from every part and every alternative display mode,
// but only the selected part/mode is actually placed on this sheet. The other pins
// retain coordinates for their inactive representation and can otherwise connect to
// adjacent wires (notably on dual-row headers).
if (sc.PartCount > 1 && sc.CurrentPartId > 0
&& pin.OwnerPartId > 0 && pin.OwnerPartId != sc.CurrentPartId)
continue;
if (pin.OwnerPartDisplayMode != sc.DisplayMode)
continue;

var tip = SchDesignators.PinTip(pin);
var e = NewElement(ElementKind.Pin, pin, global);
Expand Down Expand Up @@ -261,6 +264,11 @@ public void ApplyRules(UnionFind uf)
// Rule 1 & 3a: coincident connection points connect (shared endpoints, pin-tip on wire vertex).
Points.UnionCoincident(uf);

// A symbol may expose the same physical package pad at multiple graphical locations. Altium
// identifies that pad by component plus pin designator, so all of those endpoints are one
// electrical node even when the drawing locations differ.
UnifyDuplicatePhysicalPins(uf);

// Rule 2 & 3b: a connection point on the INTERIOR of a conductor connects (T-junction).
// Applies to any element's points landing on a different conductor's interior.
foreach (var e in Elements)
Expand Down Expand Up @@ -292,6 +300,31 @@ public void ApplyRules(UnionFind uf)
ApplyCollinearOverlap(uf);
}

private void UnifyDuplicatePhysicalPins(UnionFind uf)
{
var pinsByComponent = new Dictionary<SchComponent, Dictionary<string, int>>(
ReferenceEqualityComparer.Instance);

foreach (var element in Elements)
{
if (element.Kind != ElementKind.Pin
|| element.NetPin is null
|| string.IsNullOrEmpty(element.PinDesignator))
continue;

if (!pinsByComponent.TryGetValue(element.NetPin.Component, out var pinsByDesignator))
{
pinsByDesignator = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
pinsByComponent.Add(element.NetPin.Component, pinsByDesignator);
}

if (pinsByDesignator.TryGetValue(element.PinDesignator, out var firstElementId))
uf.Union(firstElementId, element.Id);
else
pinsByDesignator.Add(element.PinDesignator, element.Id);
}
}

private void ApplyCollinearOverlap(UnionFind uf)
{
// Conductors only; pairwise within shared coarse buckets would be ideal, but conductor counts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,87 @@ public void SameName_NetLabels_Unify_Across_Disjoint_Wires()
Assert.True(net.IsNamedExplicitly);
}

[Theory]
[InlineData(0, "MODE0")]
[InlineData(1, "MODE1")]
public void Only_Selected_Display_Mode_Pins_Participate_In_Connectivity(
int displayMode, string expectedNet)
{
var component = new SchComponent
{
Name = "U1",
PartCount = 1,
CurrentPartId = 1,
DisplayModeCount = 2,
DisplayMode = displayMode,
};
component.AddParameter(new SchParameter { Name = "Designator", Value = "U1" });

var mode0Pin = SchPin.Create("1")
.At(Coord.FromMils(0), Coord.FromMils(0))
.Length(Coord.Zero)
.Orient(PinOrientation.Right)
.Build();
mode0Pin.OwnerPartDisplayMode = 0;
component.AddPin(mode0Pin);

var mode1Pin = SchPin.Create("1")
.At(Coord.FromMils(0), Coord.FromMils(200))
.Length(Coord.Zero)
.Orient(PinOrientation.Right)
.Build();
mode1Pin.OwnerPartDisplayMode = 1;
component.AddPin(mode1Pin);

var netlist = Solve(
component,
Wire((0, 0), (100, 0)),
Wire((0, 200), (100, 200)),
Label("MODE0", 50, 0),
Label("MODE1", 50, 200));

Assert.Equal(expectedNet, netlist.NetForPin("U1", "1")?.Name);
Assert.Single(
netlist.Nets.SelectMany(net => net.Pins),
pin => pin.Key == "U1.1");
}

[Fact]
public void Duplicate_Pin_Designators_On_Component_Unify_By_Physical_Pad()
{
var component = new SchComponent
{
Name = "U1",
PartCount = 1,
CurrentPartId = 1,
};
component.AddParameter(new SchParameter { Name = "Designator", Value = "U1" });

component.AddPin(SchPin.Create("6")
.At(Coord.FromMils(100), Coord.FromMils(0))
.Length(Coord.Zero)
.Orient(PinOrientation.Right)
.Build());
component.AddPin(SchPin.Create("6")
.At(Coord.FromMils(100), Coord.FromMils(200))
.Length(Coord.Zero)
.Orient(PinOrientation.Right)
.Build());

var netlist = Solve(
component,
Wire((0, 0), (100, 0)),
Wire((0, 200), (100, 200)),
Power("GND", 0, 0));

var padNets = netlist.Nets
.Where(net => net.Pins.Any(pin => pin.Key == "U1.6"))
.ToList();
var padNet = Assert.Single(padNets);
Assert.Equal("GND", padNet.Name);
Assert.Single(padNet.Pins, pin => pin.Key == "U1.6");
}

[Fact]
public void Power_Objects_Unify_Globally_By_Name()
{
Expand Down