use std::collections::{HashMap, HashSet};
use crate::records::sch::{SchPin, SchRecord};
use crate::types::Coord;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ConnectionPoint {
pub location: (i32, i32),
pub kind: ConnectionKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ConnectionKind {
Pin { component: String, pin: String },
WireEnd { wire_index: usize },
Junction { index: usize },
NetLabel { name: String },
PowerPort { name: String },
Port { name: String },
}
#[derive(Debug, Clone)]
pub struct Net {
pub name: String,
pub named: bool,
pub connections: Vec<ConnectionPoint>,
}
impl Net {
pub fn pins(&self) -> Vec<(&str, &str)> {
self.connections
.iter()
.filter_map(|c| match &c.kind {
ConnectionKind::Pin { component, pin } => Some((component.as_str(), pin.as_str())),
_ => None,
})
.collect()
}
pub fn connects_to(&self, component: &str) -> bool {
self.connections.iter().any(|c| match &c.kind {
ConnectionKind::Pin { component: c, .. } => c == component,
_ => false,
})
}
}
#[derive(Debug, Clone, Default)]
pub struct Netlist {
pub nets: Vec<Net>,
location_to_net: HashMap<(i32, i32), usize>,
}
impl Netlist {
pub fn get_by_name(&self, name: &str) -> Option<&Net> {
self.nets.iter().find(|n| n.name == name)
}
pub fn nets_for_component(&self, designator: &str) -> Vec<&Net> {
self.nets
.iter()
.filter(|n| n.connects_to(designator))
.collect()
}
pub fn net_at(&self, x: i32, y: i32) -> Option<&Net> {
self.location_to_net.get(&(x, y)).map(|&i| &self.nets[i])
}
}
pub struct NetlistBuilder {
tolerance: i32,
}
impl Default for NetlistBuilder {
fn default() -> Self {
Self::new()
}
}
impl NetlistBuilder {
pub fn new() -> Self {
Self {
tolerance: Coord::from_mils(1.0).to_raw(), }
}
pub fn with_tolerance(mut self, mils: f64) -> Self {
self.tolerance = Coord::from_mils(mils).to_raw();
self
}
pub fn build(&self, primitives: &[SchRecord]) -> Netlist {
let mut points: Vec<ConnectionPoint> = Vec::new();
let mut designators: HashMap<usize, String> = HashMap::new();
for record in primitives.iter() {
if let SchRecord::Designator(d) = record {
let owner = d.param.label.graphical.base.owner_index as usize;
designators.insert(owner, d.text().to_string());
}
}
for (i, record) in primitives.iter().enumerate() {
match record {
SchRecord::Pin(pin) => {
let owner = pin.graphical.base.owner_index as usize;
let component = designators.get(&owner).cloned().unwrap_or_default();
let endpoint = self.get_pin_endpoint(pin);
points.push(ConnectionPoint {
location: endpoint,
kind: ConnectionKind::Pin {
component,
pin: pin.designator.clone(),
},
});
}
SchRecord::Wire(wire) => {
for (j, vertex) in wire.vertices.iter().enumerate() {
if j == 0 || j == wire.vertices.len() - 1 {
points.push(ConnectionPoint {
location: *vertex,
kind: ConnectionKind::WireEnd { wire_index: i },
});
}
}
}
SchRecord::Junction(junction) => {
points.push(ConnectionPoint {
location: (junction.graphical.location_x, junction.graphical.location_y),
kind: ConnectionKind::Junction { index: i },
});
}
SchRecord::NetLabel(label) => {
points.push(ConnectionPoint {
location: (
label.label.graphical.location_x,
label.label.graphical.location_y,
),
kind: ConnectionKind::NetLabel {
name: label.label.text.clone(),
},
});
}
SchRecord::PowerObject(power) => {
points.push(ConnectionPoint {
location: (power.graphical.location_x, power.graphical.location_y),
kind: ConnectionKind::PowerPort {
name: power.text.clone(),
},
});
}
SchRecord::Port(port) => {
points.push(ConnectionPoint {
location: (port.graphical.location_x, port.graphical.location_y),
kind: ConnectionKind::Port {
name: port.name.clone(),
},
});
}
_ => {}
}
}
let mut wire_segments: Vec<((i32, i32), (i32, i32))> = Vec::new();
for record in primitives {
if let SchRecord::Wire(wire) = record {
for i in 0..wire.vertices.len().saturating_sub(1) {
wire_segments.push((wire.vertices[i], wire.vertices[i + 1]));
}
}
}
let mut union_find = UnionFind::new(points.len());
let mut location_map: HashMap<(i32, i32), Vec<usize>> = HashMap::new();
for (i, point) in points.iter().enumerate() {
let snapped = self.snap_location(point.location);
location_map.entry(snapped).or_default().push(i);
}
for indices in location_map.values() {
for i in 1..indices.len() {
union_find.union(indices[0], indices[i]);
}
}
for (start, end) in &wire_segments {
let snapped_start = self.snap_location(*start);
let snapped_end = self.snap_location(*end);
if let (Some(start_indices), Some(end_indices)) = (
location_map.get(&snapped_start),
location_map.get(&snapped_end),
) {
if !start_indices.is_empty() && !end_indices.is_empty() {
union_find.union(start_indices[0], end_indices[0]);
}
}
for (loc, indices) in &location_map {
if self.point_on_segment(*loc, *start, *end) && !indices.is_empty() {
if let Some(start_idx) =
location_map.get(&snapped_start).and_then(|v| v.first())
{
union_find.union(*start_idx, indices[0]);
}
}
}
}
let mut net_groups: HashMap<usize, Vec<usize>> = HashMap::new();
for i in 0..points.len() {
let root = union_find.find(i);
net_groups.entry(root).or_default().push(i);
}
let mut nets = Vec::new();
let mut net_counter = 1;
for (_, group) in net_groups {
let connections: Vec<ConnectionPoint> =
group.iter().map(|&i| points[i].clone()).collect();
let (name, named) = self.find_net_name(&connections, &mut net_counter);
nets.push(Net {
name,
named,
connections,
});
}
nets.sort_by(|a, b| a.name.cmp(&b.name));
let mut location_to_net = HashMap::new();
for (i, net) in nets.iter().enumerate() {
for conn in &net.connections {
location_to_net.insert(conn.location, i);
}
}
Netlist {
nets,
location_to_net,
}
}
fn snap_location(&self, loc: (i32, i32)) -> (i32, i32) {
let tol = self.tolerance.max(1);
((loc.0 / tol) * tol, (loc.1 / tol) * tol)
}
fn get_pin_endpoint(&self, pin: &SchPin) -> (i32, i32) {
use crate::records::sch::PinConglomerateFlags;
let base_x = pin.graphical.location_x;
let base_y = pin.graphical.location_y;
let length = pin.pin_length;
let rotated = pin.pin_conglomerate.contains(PinConglomerateFlags::ROTATED);
let flipped = pin.pin_conglomerate.contains(PinConglomerateFlags::FLIPPED);
let (dx, dy) = match (rotated, flipped) {
(false, false) => (length, 0),
(true, false) => (0, length),
(false, true) => (-length, 0),
(true, true) => (0, -length),
};
(base_x + dx, base_y + dy)
}
fn point_on_segment(&self, point: (i32, i32), start: (i32, i32), end: (i32, i32)) -> bool {
let (px, py) = point;
let (sx, sy) = start;
let (ex, ey) = end;
let min_x = sx.min(ex) - self.tolerance;
let max_x = sx.max(ex) + self.tolerance;
let min_y = sy.min(ey) - self.tolerance;
let max_y = sy.max(ey) + self.tolerance;
if px < min_x || px > max_x || py < min_y || py > max_y {
return false;
}
if sy == ey && (py - sy).abs() <= self.tolerance {
return true;
}
if sx == ex && (px - sx).abs() <= self.tolerance {
return true;
}
false
}
fn find_net_name(&self, connections: &[ConnectionPoint], counter: &mut u32) -> (String, bool) {
for conn in connections {
if let ConnectionKind::NetLabel { name } = &conn.kind {
if !name.is_empty() {
return (name.clone(), true);
}
}
}
for conn in connections {
if let ConnectionKind::PowerPort { name } = &conn.kind {
if !name.is_empty() {
return (name.clone(), true);
}
}
}
for conn in connections {
if let ConnectionKind::Port { name } = &conn.kind {
if !name.is_empty() {
return (name.clone(), true);
}
}
}
let name = format!("Net{}", counter);
*counter += 1;
(name, false)
}
pub fn find_unconnected_pins(
&self,
primitives: &[SchRecord],
) -> Vec<(String, String, (i32, i32))> {
let netlist = self.build(primitives);
let mut unconnected = Vec::new();
for net in &netlist.nets {
let pins: Vec<_> = net
.connections
.iter()
.filter(|c| matches!(c.kind, ConnectionKind::Pin { .. }))
.collect();
if pins.len() == 1 {
let has_wires = net
.connections
.iter()
.any(|c| matches!(c.kind, ConnectionKind::WireEnd { .. }));
if !has_wires {
if let ConnectionKind::Pin { component, pin } = &pins[0].kind {
unconnected.push((component.clone(), pin.clone(), pins[0].location));
}
}
}
}
unconnected
}
pub fn find_missing_junctions(&self, primitives: &[SchRecord]) -> Vec<(i32, i32)> {
let mut wire_points: HashMap<(i32, i32), usize> = HashMap::new();
let mut junction_locations: HashSet<(i32, i32)> = HashSet::new();
for record in primitives {
if let SchRecord::Junction(j) = record {
junction_locations.insert((j.graphical.location_x, j.graphical.location_y));
}
}
for record in primitives {
if let SchRecord::Wire(wire) = record {
for i in 0..wire.vertices.len().saturating_sub(1) {
let start = wire.vertices[i];
let end = wire.vertices[i + 1];
*wire_points.entry(start).or_insert(0) += 1;
*wire_points.entry(end).or_insert(0) += 1;
for other_record in primitives {
if let SchRecord::Wire(other_wire) = other_record {
for j in 0..other_wire.vertices.len().saturating_sub(1) {
let other_start = other_wire.vertices[j];
let other_end = other_wire.vertices[j + 1];
if self.point_on_segment(start, other_start, other_end)
&& start != other_start
&& start != other_end
{
*wire_points.entry(start).or_insert(0) += 1;
}
if self.point_on_segment(end, other_start, other_end)
&& end != other_start
&& end != other_end
{
*wire_points.entry(end).or_insert(0) += 1;
}
}
}
}
}
}
}
let mut missing = Vec::new();
for (loc, count) in wire_points {
if count >= 3 && !junction_locations.contains(&loc) {
missing.push(loc);
}
}
missing
}
}
struct UnionFind {
parent: Vec<usize>,
rank: Vec<usize>,
}
impl UnionFind {
fn new(size: usize) -> Self {
Self {
parent: (0..size).collect(),
rank: vec![0; size],
}
}
fn find(&mut self, x: usize) -> usize {
if self.parent[x] != x {
self.parent[x] = self.find(self.parent[x]);
}
self.parent[x]
}
fn union(&mut self, x: usize, y: usize) {
let root_x = self.find(x);
let root_y = self.find(y);
if root_x != root_y {
if self.rank[root_x] < self.rank[root_y] {
self.parent[root_x] = root_y;
} else if self.rank[root_x] > self.rank[root_y] {
self.parent[root_y] = root_x;
} else {
self.parent[root_y] = root_x;
self.rank[root_x] += 1;
}
}
}
}