use crate::parser::schema::*;
use std::collections::{HashMap, HashSet};
use std::f64;
const INTERSECTION_TOLERANCE: f64 = 1.0;
const PIN_TO_WIRE_TOLERANCE: f64 = 15.0;
#[derive(Debug, Clone)]
struct WireSegment {
start: Position,
end: Position,
net_id: String,
}
pub struct NetlistBuilder;
impl NetlistBuilder {
pub fn build_netlist(schematic: &Schematic) -> HashMap<String, Vec<PinNetConnection>> {
let mut pin_to_net = HashMap::new();
let segments = Self::build_connectivity_graph(schematic);
if segments.is_empty() {
let pin_positions = Self::calculate_pin_positions(schematic);
Self::add_pins_near_labels(schematic, &pin_positions, &mut pin_to_net);
return pin_to_net;
}
let group_ids = Self::build_segment_groups(&segments);
let group_net_names = Self::assign_net_names_to_groups(schematic, &segments, &group_ids);
let segment_to_net_name: HashMap<String, String> = segments
.iter()
.enumerate()
.map(|(i, seg)| {
let g = group_ids[i];
let name = group_net_names
.get(&g)
.cloned()
.unwrap_or_else(|| format!("Net-{}", g));
(seg.net_id.clone(), name)
})
.collect();
let pin_positions = Self::calculate_pin_positions(schematic);
for (component_ref, pins) in &pin_positions {
for (pin_number, pin_pos) in pins {
if let Some(net_name) =
Self::find_net_name_for_position(pin_pos, &segments, &segment_to_net_name)
{
let key = format!("{}:{}", component_ref, pin_number);
pin_to_net.insert(key, vec![PinNetConnection {
component_ref: component_ref.clone(),
pin_number: pin_number.clone(),
net_name: net_name.clone(),
}]);
}
}
}
Self::add_pins_near_labels(schematic, &pin_positions, &mut pin_to_net);
pin_to_net
}
fn build_connectivity_graph(schematic: &Schematic) -> Vec<WireSegment> {
let mut segments = Vec::new();
for wire in &schematic.wires {
if wire.points.len() < 2 {
continue;
}
for i in 0..(wire.points.len() - 1) {
let start = wire.points[i].clone();
let end = wire.points[i + 1].clone();
let net_id = format!("Net-W{}-{}", wire.uuid, i);
segments.push(WireSegment {
start,
end,
net_id,
});
}
}
segments
}
fn build_segment_groups(segments: &[WireSegment]) -> Vec<usize> {
let n = segments.len();
let mut parent: Vec<usize> = (0..n).collect();
fn find(parent: &mut [usize], i: usize) -> usize {
if parent[i] != i {
parent[i] = find(parent, parent[i]);
}
parent[i]
}
fn unite(parent: &mut [usize], i: usize, j: usize) {
let pi = find(parent, i);
let pj = find(parent, j);
if pi != pj {
parent[pi] = pj;
}
}
let tol = INTERSECTION_TOLERANCE;
for i in 0..n {
for j in (i + 1)..n {
let a = &segments[i];
let b = &segments[j];
let share = Self::distance(&a.start, &b.start) < tol
|| Self::distance(&a.start, &b.end) < tol
|| Self::distance(&a.end, &b.start) < tol
|| Self::distance(&a.end, &b.end) < tol;
if share {
unite(&mut parent, i, j);
}
}
}
(0..n).map(|i| find(&mut parent, i)).collect()
}
fn assign_net_names_to_groups(
schematic: &Schematic,
segments: &[WireSegment],
group_ids: &[usize],
) -> HashMap<usize, String> {
let mut group_net_names: HashMap<usize, String> = HashMap::new();
let tol = PIN_TO_WIRE_TOLERANCE;
for label in &schematic.labels {
let net_name = match &label.label_type {
LabelType::Global => label.text.clone(),
LabelType::Local => format!("Net-({})", label.text),
LabelType::Hierarchical => format!("Hier-{}", label.text),
};
for (seg_idx, seg) in segments.iter().enumerate() {
let on_segment = Self::distance_point_to_segment(&label.position, &seg.start, &seg.end) < tol;
let near_end = Self::distance(&label.position, &seg.start) < tol
|| Self::distance(&label.position, &seg.end) < tol;
if on_segment || near_end {
let g = group_ids[seg_idx];
group_net_names.insert(g, net_name.clone());
break;
}
}
}
let unique_groups: HashSet<usize> = group_ids.iter().copied().collect();
for g in unique_groups {
group_net_names.entry(g).or_insert_with(|| format!("Net-{}", g));
}
group_net_names
}
fn distance_point_to_segment(point: &Position, start: &Position, end: &Position) -> f64 {
let dx = end.x - start.x;
let dy = end.y - start.y;
let length_sq = dx * dx + dy * dy;
if length_sq < 1e-12 {
return Self::distance(point, start);
}
let t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / length_sq;
let t = t.clamp(0.0, 1.0);
let proj = Position {
x: start.x + t * dx,
y: start.y + t * dy,
};
Self::distance(point, &proj)
}
fn find_net_name_for_position(
position: &Position,
segments: &[WireSegment],
segment_to_net_name: &HashMap<String, String>,
) -> Option<String> {
let mut best_net_id: Option<String> = None;
let mut best_dist = PIN_TO_WIRE_TOLERANCE;
for seg in segments {
let d = Self::distance_point_to_segment(position, &seg.start, &seg.end);
if d < best_dist {
best_dist = d;
best_net_id = Some(seg.net_id.clone());
}
}
best_net_id.and_then(|id| segment_to_net_name.get(&id).cloned())
}
fn add_pins_near_labels(
schematic: &Schematic,
pin_positions: &HashMap<String, HashMap<String, Position>>,
pin_to_net: &mut HashMap<String, Vec<PinNetConnection>>,
) {
let tol = PIN_TO_WIRE_TOLERANCE;
for label in &schematic.labels {
let net_name = match &label.label_type {
LabelType::Global => label.text.clone(),
LabelType::Local => format!("Net-({})", label.text),
LabelType::Hierarchical => format!("Hier-{}", label.text),
};
for (component_ref, pins) in pin_positions {
for (pin_number, pin_pos) in pins {
if Self::distance(pin_pos, &label.position) < tol {
let key = format!("{}:{}", component_ref, pin_number);
pin_to_net.entry(key).or_insert_with(Vec::new).push(PinNetConnection {
component_ref: component_ref.clone(),
pin_number: pin_number.clone(),
net_name: net_name.clone(),
});
}
}
}
}
}
fn calculate_pin_positions(schematic: &Schematic) -> HashMap<String, HashMap<String, Position>> {
let mut pin_positions = HashMap::new();
for component in &schematic.components {
let mut component_pins = HashMap::new();
if component.pins.is_empty() {
component_pins.insert("1".to_string(), component.position.clone());
} else {
let pin_spacing = 2.54;
let cos_r = component.rotation.to_radians().cos();
let sin_r = component.rotation.to_radians().sin();
for (idx, pin) in component.pins.iter().enumerate() {
let pin_offset_x = (idx as f64 % 2.0) * pin_spacing - pin_spacing / 2.0;
let pin_offset_y = (idx as f64 / 2.0).floor() * pin_spacing - pin_spacing / 2.0;
let rotated_x = pin_offset_x * cos_r - pin_offset_y * sin_r;
let rotated_y = pin_offset_x * sin_r + pin_offset_y * cos_r;
let pin_pos = Position {
x: component.position.x + rotated_x,
y: component.position.y + rotated_y,
};
component_pins.insert(pin.number.clone(), pin_pos);
}
}
pin_positions.insert(component.reference.clone(), component_pins);
}
for component in &schematic.power_symbols {
let mut component_pins = HashMap::new();
if component.pins.is_empty() {
component_pins.insert("1".to_string(), component.position.clone());
} else {
for pin in &component.pins {
component_pins.insert(pin.number.clone(), component.position.clone());
}
}
pin_positions.insert(component.reference.clone(), component_pins);
}
pin_positions
}
#[allow(dead_code)]
fn point_on_segment(point: &Position, start: &Position, end: &Position) -> bool {
let dx = end.x - start.x;
let dy = end.y - start.y;
let length_sq = dx * dx + dy * dy;
if length_sq < 1e-6 {
return Self::distance(point, start) < INTERSECTION_TOLERANCE;
}
let t = ((point.x - start.x) * dx + (point.y - start.y) * dy) / length_sq;
if t < 0.0 || t > 1.0 {
return false;
}
let proj_x = start.x + t * dx;
let proj_y = start.y + t * dy;
let dist = ((point.x - proj_x).powi(2) + (point.y - proj_y).powi(2)).sqrt();
dist < INTERSECTION_TOLERANCE
}
fn distance(p1: &Position, p2: &Position) -> f64 {
let dx = p1.x - p2.x;
let dy = p1.y - p2.y;
(dx * dx + dy * dy).sqrt()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PinNetConnection {
pub component_ref: String,
pub pin_number: String,
pub net_name: String,
}
#[derive(Debug, Clone)]
pub struct EnhancedNet {
pub name: String,
pub pin_connections: Vec<PinNetConnection>,
}
impl EnhancedNet {
pub fn connected_components(&self) -> Vec<&str> {
self.pin_connections
.iter()
.map(|c| c.component_ref.as_str())
.collect()
}
pub fn has_component(&self, component_ref: &str) -> bool {
self.pin_connections
.iter()
.any(|c| c.component_ref == component_ref)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point_on_segment() {
let start = Position { x: 0.0, y: 0.0 };
let end = Position { x: 10.0, y: 0.0 };
let point = Position { x: 5.0, y: 0.1 };
assert!(NetlistBuilder::point_on_segment(&point, &start, &end));
let point_far = Position { x: 5.0, y: 2.0 };
assert!(!NetlistBuilder::point_on_segment(&point_far, &start, &end));
}
#[test]
fn test_distance() {
let p1 = Position { x: 0.0, y: 0.0 };
let p2 = Position { x: 3.0, y: 4.0 };
assert!((NetlistBuilder::distance(&p1, &p2) - 5.0).abs() < 1e-6);
}
}