1use std::collections::{HashMap, HashSet};
4
5use crate::records::sch::{SchPin, SchRecord};
6use crate::types::Coord;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct ConnectionPoint {
11 pub location: (i32, i32),
13 pub kind: ConnectionKind,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub enum ConnectionKind {
20 Pin { component: String, pin: String },
22 WireEnd { wire_index: usize },
24 Junction { index: usize },
26 NetLabel { name: String },
28 PowerPort { name: String },
30 Port { name: String },
32}
33
34#[derive(Debug, Clone)]
36pub struct Net {
37 pub name: String,
39 pub named: bool,
41 pub connections: Vec<ConnectionPoint>,
43}
44
45impl Net {
46 pub fn pins(&self) -> Vec<(&str, &str)> {
48 self.connections
49 .iter()
50 .filter_map(|c| match &c.kind {
51 ConnectionKind::Pin { component, pin } => Some((component.as_str(), pin.as_str())),
52 _ => None,
53 })
54 .collect()
55 }
56
57 pub fn connects_to(&self, component: &str) -> bool {
59 self.connections.iter().any(|c| match &c.kind {
60 ConnectionKind::Pin { component: c, .. } => c == component,
61 _ => false,
62 })
63 }
64}
65
66#[derive(Debug, Clone, Default)]
68pub struct Netlist {
69 pub nets: Vec<Net>,
71 location_to_net: HashMap<(i32, i32), usize>,
73}
74
75impl Netlist {
76 pub fn get_by_name(&self, name: &str) -> Option<&Net> {
78 self.nets.iter().find(|n| n.name == name)
79 }
80
81 pub fn nets_for_component(&self, designator: &str) -> Vec<&Net> {
83 self.nets
84 .iter()
85 .filter(|n| n.connects_to(designator))
86 .collect()
87 }
88
89 pub fn net_at(&self, x: i32, y: i32) -> Option<&Net> {
91 self.location_to_net.get(&(x, y)).map(|&i| &self.nets[i])
92 }
93}
94
95pub struct NetlistBuilder {
97 tolerance: i32,
99}
100
101impl Default for NetlistBuilder {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107impl NetlistBuilder {
108 pub fn new() -> Self {
110 Self {
111 tolerance: Coord::from_mils(1.0).to_raw(), }
113 }
114
115 pub fn with_tolerance(mut self, mils: f64) -> Self {
117 self.tolerance = Coord::from_mils(mils).to_raw();
118 self
119 }
120
121 pub fn build(&self, primitives: &[SchRecord]) -> Netlist {
123 let mut points: Vec<ConnectionPoint> = Vec::new();
125 let mut designators: HashMap<usize, String> = HashMap::new();
126
127 for record in primitives.iter() {
129 if let SchRecord::Designator(d) = record {
130 let owner = d.param.label.graphical.base.owner_index as usize;
131 designators.insert(owner, d.text().to_string());
132 }
133 }
134
135 for (i, record) in primitives.iter().enumerate() {
137 match record {
138 SchRecord::Pin(pin) => {
139 let owner = pin.graphical.base.owner_index as usize;
140 let component = designators.get(&owner).cloned().unwrap_or_default();
141 let endpoint = self.get_pin_endpoint(pin);
142
143 points.push(ConnectionPoint {
144 location: endpoint,
145 kind: ConnectionKind::Pin {
146 component,
147 pin: pin.designator.clone(),
148 },
149 });
150 }
151 SchRecord::Wire(wire) => {
152 for (j, vertex) in wire.vertices.iter().enumerate() {
154 if j == 0 || j == wire.vertices.len() - 1 {
155 points.push(ConnectionPoint {
156 location: *vertex,
157 kind: ConnectionKind::WireEnd { wire_index: i },
158 });
159 }
160 }
161 }
162 SchRecord::Junction(junction) => {
163 points.push(ConnectionPoint {
164 location: (junction.graphical.location_x, junction.graphical.location_y),
165 kind: ConnectionKind::Junction { index: i },
166 });
167 }
168 SchRecord::NetLabel(label) => {
169 points.push(ConnectionPoint {
170 location: (
171 label.label.graphical.location_x,
172 label.label.graphical.location_y,
173 ),
174 kind: ConnectionKind::NetLabel {
175 name: label.label.text.clone(),
176 },
177 });
178 }
179 SchRecord::PowerObject(power) => {
180 points.push(ConnectionPoint {
181 location: (power.graphical.location_x, power.graphical.location_y),
182 kind: ConnectionKind::PowerPort {
183 name: power.text.clone(),
184 },
185 });
186 }
187 SchRecord::Port(port) => {
188 points.push(ConnectionPoint {
189 location: (port.graphical.location_x, port.graphical.location_y),
190 kind: ConnectionKind::Port {
191 name: port.name.clone(),
192 },
193 });
194 }
195 _ => {}
196 }
197 }
198
199 let mut wire_segments: Vec<((i32, i32), (i32, i32))> = Vec::new();
201 for record in primitives {
202 if let SchRecord::Wire(wire) = record {
203 for i in 0..wire.vertices.len().saturating_sub(1) {
204 wire_segments.push((wire.vertices[i], wire.vertices[i + 1]));
205 }
206 }
207 }
208
209 let mut union_find = UnionFind::new(points.len());
211
212 let mut location_map: HashMap<(i32, i32), Vec<usize>> = HashMap::new();
214 for (i, point) in points.iter().enumerate() {
215 let snapped = self.snap_location(point.location);
217 location_map.entry(snapped).or_default().push(i);
218 }
219
220 for indices in location_map.values() {
221 for i in 1..indices.len() {
222 union_find.union(indices[0], indices[i]);
223 }
224 }
225
226 for (start, end) in &wire_segments {
228 let snapped_start = self.snap_location(*start);
229 let snapped_end = self.snap_location(*end);
230
231 if let (Some(start_indices), Some(end_indices)) = (
232 location_map.get(&snapped_start),
233 location_map.get(&snapped_end),
234 ) {
235 if !start_indices.is_empty() && !end_indices.is_empty() {
236 union_find.union(start_indices[0], end_indices[0]);
237 }
238 }
239
240 for (loc, indices) in &location_map {
242 if self.point_on_segment(*loc, *start, *end) && !indices.is_empty() {
243 if let Some(start_idx) =
244 location_map.get(&snapped_start).and_then(|v| v.first())
245 {
246 union_find.union(*start_idx, indices[0]);
247 }
248 }
249 }
250 }
251
252 let mut net_groups: HashMap<usize, Vec<usize>> = HashMap::new();
254 for i in 0..points.len() {
255 let root = union_find.find(i);
256 net_groups.entry(root).or_default().push(i);
257 }
258
259 let mut nets = Vec::new();
261 let mut net_counter = 1;
262
263 for (_, group) in net_groups {
264 let connections: Vec<ConnectionPoint> =
265 group.iter().map(|&i| points[i].clone()).collect();
266
267 let (name, named) = self.find_net_name(&connections, &mut net_counter);
269
270 nets.push(Net {
271 name,
272 named,
273 connections,
274 });
275 }
276
277 nets.sort_by(|a, b| a.name.cmp(&b.name));
279
280 let mut location_to_net = HashMap::new();
282 for (i, net) in nets.iter().enumerate() {
283 for conn in &net.connections {
284 location_to_net.insert(conn.location, i);
285 }
286 }
287
288 Netlist {
289 nets,
290 location_to_net,
291 }
292 }
293
294 fn snap_location(&self, loc: (i32, i32)) -> (i32, i32) {
296 let tol = self.tolerance.max(1);
297 ((loc.0 / tol) * tol, (loc.1 / tol) * tol)
298 }
299
300 fn get_pin_endpoint(&self, pin: &SchPin) -> (i32, i32) {
302 use crate::records::sch::PinConglomerateFlags;
303
304 let base_x = pin.graphical.location_x;
305 let base_y = pin.graphical.location_y;
306 let length = pin.pin_length;
307
308 let rotated = pin.pin_conglomerate.contains(PinConglomerateFlags::ROTATED);
309 let flipped = pin.pin_conglomerate.contains(PinConglomerateFlags::FLIPPED);
310
311 let (dx, dy) = match (rotated, flipped) {
312 (false, false) => (length, 0),
313 (true, false) => (0, length),
314 (false, true) => (-length, 0),
315 (true, true) => (0, -length),
316 };
317
318 (base_x + dx, base_y + dy)
319 }
320
321 fn point_on_segment(&self, point: (i32, i32), start: (i32, i32), end: (i32, i32)) -> bool {
323 let (px, py) = point;
324 let (sx, sy) = start;
325 let (ex, ey) = end;
326
327 let min_x = sx.min(ex) - self.tolerance;
329 let max_x = sx.max(ex) + self.tolerance;
330 let min_y = sy.min(ey) - self.tolerance;
331 let max_y = sy.max(ey) + self.tolerance;
332
333 if px < min_x || px > max_x || py < min_y || py > max_y {
334 return false;
335 }
336
337 if sy == ey && (py - sy).abs() <= self.tolerance {
339 return true;
340 }
341
342 if sx == ex && (px - sx).abs() <= self.tolerance {
344 return true;
345 }
346
347 false
348 }
349
350 fn find_net_name(&self, connections: &[ConnectionPoint], counter: &mut u32) -> (String, bool) {
352 for conn in connections {
356 if let ConnectionKind::NetLabel { name } = &conn.kind {
357 if !name.is_empty() {
358 return (name.clone(), true);
359 }
360 }
361 }
362
363 for conn in connections {
365 if let ConnectionKind::PowerPort { name } = &conn.kind {
366 if !name.is_empty() {
367 return (name.clone(), true);
368 }
369 }
370 }
371
372 for conn in connections {
374 if let ConnectionKind::Port { name } = &conn.kind {
375 if !name.is_empty() {
376 return (name.clone(), true);
377 }
378 }
379 }
380
381 let name = format!("Net{}", counter);
383 *counter += 1;
384 (name, false)
385 }
386
387 pub fn find_unconnected_pins(
389 &self,
390 primitives: &[SchRecord],
391 ) -> Vec<(String, String, (i32, i32))> {
392 let netlist = self.build(primitives);
393 let mut unconnected = Vec::new();
394
395 for net in &netlist.nets {
397 let pins: Vec<_> = net
398 .connections
399 .iter()
400 .filter(|c| matches!(c.kind, ConnectionKind::Pin { .. }))
401 .collect();
402
403 if pins.len() == 1 {
404 let has_wires = net
406 .connections
407 .iter()
408 .any(|c| matches!(c.kind, ConnectionKind::WireEnd { .. }));
409
410 if !has_wires {
411 if let ConnectionKind::Pin { component, pin } = &pins[0].kind {
412 unconnected.push((component.clone(), pin.clone(), pins[0].location));
413 }
414 }
415 }
416 }
417
418 unconnected
419 }
420
421 pub fn find_missing_junctions(&self, primitives: &[SchRecord]) -> Vec<(i32, i32)> {
423 let mut wire_points: HashMap<(i32, i32), usize> = HashMap::new();
424 let mut junction_locations: HashSet<(i32, i32)> = HashSet::new();
425
426 for record in primitives {
428 if let SchRecord::Junction(j) = record {
429 junction_locations.insert((j.graphical.location_x, j.graphical.location_y));
430 }
431 }
432
433 for record in primitives {
435 if let SchRecord::Wire(wire) = record {
436 for i in 0..wire.vertices.len().saturating_sub(1) {
437 let start = wire.vertices[i];
438 let end = wire.vertices[i + 1];
439
440 *wire_points.entry(start).or_insert(0) += 1;
442 *wire_points.entry(end).or_insert(0) += 1;
443
444 for other_record in primitives {
446 if let SchRecord::Wire(other_wire) = other_record {
447 for j in 0..other_wire.vertices.len().saturating_sub(1) {
448 let other_start = other_wire.vertices[j];
449 let other_end = other_wire.vertices[j + 1];
450
451 if self.point_on_segment(start, other_start, other_end)
453 && start != other_start
454 && start != other_end
455 {
456 *wire_points.entry(start).or_insert(0) += 1;
457 }
458 if self.point_on_segment(end, other_start, other_end)
459 && end != other_start
460 && end != other_end
461 {
462 *wire_points.entry(end).or_insert(0) += 1;
463 }
464 }
465 }
466 }
467 }
468 }
469 }
470
471 let mut missing = Vec::new();
473 for (loc, count) in wire_points {
474 if count >= 3 && !junction_locations.contains(&loc) {
475 missing.push(loc);
476 }
477 }
478
479 missing
480 }
481}
482
483struct UnionFind {
485 parent: Vec<usize>,
486 rank: Vec<usize>,
487}
488
489impl UnionFind {
490 fn new(size: usize) -> Self {
491 Self {
492 parent: (0..size).collect(),
493 rank: vec![0; size],
494 }
495 }
496
497 fn find(&mut self, x: usize) -> usize {
498 if self.parent[x] != x {
499 self.parent[x] = self.find(self.parent[x]);
500 }
501 self.parent[x]
502 }
503
504 fn union(&mut self, x: usize, y: usize) {
505 let root_x = self.find(x);
506 let root_y = self.find(y);
507
508 if root_x != root_y {
509 if self.rank[root_x] < self.rank[root_y] {
510 self.parent[root_x] = root_y;
511 } else if self.rank[root_x] > self.rank[root_y] {
512 self.parent[root_y] = root_x;
513 } else {
514 self.parent[root_y] = root_x;
515 self.rank[root_x] += 1;
516 }
517 }
518 }
519}