From 7f4d3eb420887166ab20d22d6a01641c0b44af2a Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Thu, 19 Mar 2026 21:43:51 +0100 Subject: [PATCH] Add ratsnest generation (not used yet) --- topola/src/layout.rs | 6 ++ topola/src/lib.rs | 7 ++- topola/src/primitives.rs | 8 +++ topola/src/ratsnest.rs | 123 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 topola/src/ratsnest.rs diff --git a/topola/src/layout.rs b/topola/src/layout.rs index ee44e31..34c26f4 100644 --- a/topola/src/layout.rs +++ b/topola/src/layout.rs @@ -164,6 +164,12 @@ impl Layout { polygon_id } + pub fn segment_center(&self, segment_id: SegmentId) -> Vector2 { + let endpoints = self.segment_endpoints(segment_id); + + (endpoints[0] + endpoints[1]) / 2 + } + pub fn segment_endpoints(&self, segment_id: SegmentId) -> [Vector2; 2] { let endjoints = self.segments.get(&segment_id.index()).unwrap().endjoints; [ diff --git a/topola/src/lib.rs b/topola/src/lib.rs index 059f491..dada36a 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -7,7 +7,7 @@ mod layout; mod math; mod navmesher; mod primitives; -//mod ratsnests; +mod ratsnest; mod selection; mod specctra; @@ -15,5 +15,8 @@ pub use crate::board::Board; pub use crate::layout::Layout; pub use crate::math::Vector2; pub use crate::navmesher::NavmesherBoard; -pub use crate::primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId}; +pub use crate::primitives::{ + Joint, JointId, Polygon, PolygonId, PrimitiveId, Segment, SegmentId, Via, ViaId, +}; +pub use crate::ratsnest::{Ratline, Ratsnest}; pub use crate::selection::{PinSelection, PinSelector}; diff --git a/topola/src/primitives.rs b/topola/src/primitives.rs index 7c89133..c09ab12 100644 --- a/topola/src/primitives.rs +++ b/topola/src/primitives.rs @@ -56,6 +56,10 @@ impl Joint { )) } + pub fn center(&self) -> Vector2 { + self.position + } + pub fn contains_point(&self, point: Vector2) -> bool { (point.x - self.position.x).pow(2) as u64 + (point.y - self.position.y).pow(2) as u64 <= self.radius.pow(2) @@ -145,6 +149,10 @@ impl Polygon { ) } + pub fn center(&self) -> Vector2 { + Vector2::::polygon_centroid(&self.vertices) + } + pub fn contains_point(&self, point: Vector2) -> bool { point.inside_polygon(&self.vertices) } diff --git a/topola/src/ratsnest.rs b/topola/src/ratsnest.rs new file mode 100644 index 0000000..5abac69 --- /dev/null +++ b/topola/src/ratsnest.rs @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use derive_getters::Getters; +use serde::{Deserialize, Serialize}; +use spade::{DelaunayTriangulation, HasPosition, Triangulation, handles::FixedVertexHandle}; + +use crate::{Board, JointId, PolygonId, SegmentId, Vector2, primitives::PrimitiveId}; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)] +pub struct Ratline { + endpoint_primitive_ids: [PrimitiveId; 2], + endpoint_layers: [usize; 2], + endpoints: [Vector2; 2], +} + +struct DelaunayVertex { + pub layer: usize, + pub center: Vector2, + pub position: spade::Point2, + pub primitive_id: PrimitiveId, +} + +impl HasPosition for DelaunayVertex { + type Scalar = f64; + + fn position(&self) -> spade::Point2 { + self.position + } +} + +#[derive(Clone, Debug, Deserialize, Getters, Serialize)] +pub struct Ratsnest { + ratlines: Vec, +} + +impl Ratsnest { + pub fn new(board: &Board) -> Self { + let mut ratlines = Vec::new(); + + for layer in 0..*board.layout().layer_count() { + let mut triangulation: DelaunayTriangulation = + DelaunayTriangulation::new(); + + for layer in 0..*board.layout().layer_count() { + for (i, joint) in board.layout().joints().collection() { + if joint.layer == layer { + triangulation.insert(DelaunayVertex { + layer, + center: joint.center(), + position: spade::Point2::new( + joint.center().x as f64, + joint.center().y as f64, + ), + primitive_id: PrimitiveId::Joint(JointId::new(i)), + }); + } + } + + for (i, segment) in board.layout().segments().collection() { + if segment.layer == layer { + let segment_center = board.layout().segment_center(SegmentId::new(i)); + triangulation.insert(DelaunayVertex { + layer, + center: segment_center, + position: spade::Point2::new( + segment_center.x as f64, + segment_center.y as f64, + ), + primitive_id: PrimitiveId::Segment(SegmentId::new(i)), + }); + } + } + + for (i, polygon) in board.layout().polygons().collection() { + if polygon.layer == layer { + triangulation.insert(DelaunayVertex { + layer, + center: polygon.center(), + position: spade::Point2::new( + polygon.center().x as f64, + polygon.center().y as f64, + ), + primitive_id: PrimitiveId::Polygon(PolygonId::new(i)), + }); + } + } + } + + let mut weighted_edges: Vec<(u64, [usize; 2])> = Vec::new(); + + for edge in triangulation.undirected_edges() { + let vertices = edge.vertices(); + weighted_edges.push(( + edge.length_2() as u64, + [vertices[0].index(), vertices[1].index()], + )); + } + + for [index0, index1] in + crate::math::kruskal_mst(triangulation.num_vertices(), &weighted_edges) + { + let vertex0 = triangulation + .get_vertex(FixedVertexHandle::from_index(index0)) + .unwrap(); + let vertex0 = vertex0.data(); + let vertex1 = triangulation + .get_vertex(FixedVertexHandle::from_index(index1)) + .unwrap(); + let vertex1 = vertex1.data(); + + ratlines.push(Ratline { + endpoint_primitive_ids: [vertex0.primitive_id, vertex1.primitive_id], + endpoint_layers: [vertex0.layer, vertex1.layer], + endpoints: [vertex0.center, vertex1.center], + }); + } + } + + Self { ratlines } + } +}