diff --git a/src/autorouter/ratsnest.rs b/src/autorouter/ratsnest.rs index 14cecaf..a399e99 100644 --- a/src/autorouter/ratsnest.rs +++ b/src/autorouter/ratsnest.rs @@ -54,7 +54,7 @@ impl From for crate::layout::NodeIndex { #[derive(Debug, Clone, Copy)] pub struct RatvertexWeight { - vertex: RatvertexIndex, + pub(crate) vertex: RatvertexIndex, pub pos: Point, } diff --git a/src/drawing/graph.rs b/src/drawing/graph.rs index 3ecfdde..bc77efb 100644 --- a/src/drawing/graph.rs +++ b/src/drawing/graph.rs @@ -4,6 +4,7 @@ use enum_dispatch::enum_dispatch; use petgraph::stable_graph::NodeIndex; +use serde::{Deserialize, Serialize}; use crate::{drawing::Drawing, graph::GetPetgraphIndex}; @@ -89,7 +90,7 @@ macro_rules! impl_loose_weight { // TODO: This enum shouldn't exist: we shouldn't be carrying the tag around like this. Instead we // should be getting it from the graph when it's needed. #[enum_dispatch(GetPetgraphIndex, MakePrimitive)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum PrimitiveIndex { FixedDot(FixedDotIndex), LooseDot(LooseDotIndex), diff --git a/src/router/brute_force/mod.rs b/src/router/brute_force/mod.rs new file mode 100644 index 0000000..5ccefe8 --- /dev/null +++ b/src/router/brute_force/mod.rs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 Topola contributors +// +// SPDX-License-Identifier: MIT +// +//! A work-in-progress brute-force routing implementation +//! +//! This should always (except in pathological cases like a non-planar ratsnest) +//! be able to find an optimal solution, but potentially require extremely long +//! run/walltime to do so. It is intended as a benchmark for heuristic approximations +//! of such optimal routing (to compare to potentially suboptimal routing). + +pub mod topo_navmesh; diff --git a/src/router/brute_force/topo_navmesh.rs b/src/router/brute_force/topo_navmesh.rs new file mode 100644 index 0000000..7275c84 --- /dev/null +++ b/src/router/brute_force/topo_navmesh.rs @@ -0,0 +1,361 @@ +// SPDX-FileCopyrightText: 2024 Topola contributors +// +// SPDX-License-Identifier: MIT +// +//! A topological navmesh implmentation +// idea: see issue #132 + +use bimap::BiMap; +use geo::Point; +use petgraph::graph::{NodeIndex, UnGraph}; +use serde::{Deserialize, Serialize}; +use spade::HasPosition; + +use crate::{autorouter::ratsnest::Ratsnest, layout::NodeIndex as LayoutNodeIndex}; + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub struct NodeWeight { + /// Position of a node. Warning: multiple nodes might have the same position + pub pos: Point, + + /// can we pass through this point when constructing a route? + /// + /// e.g. primal nodes are not passable, nodes used by etched paths are not passable, + /// the remaining dual nodes are passable. + pub is_passable: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct EdgeWeight

{ + /// indicates if this is part of an etched path (`P` is the type of the path index) + pub path: Option

, +} + +/// A topological navmesh, +/// built upon the merging of the primary and dual graph, +/// and with an operation to create barriers. +/// +/// This is basically a planar graph embedding, and is used for enumeration of such embeddings. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Navmesh

{ + // a merging of the primary and dual graph + graph: UnGraph>, + + // association between primary graph and this one + primals: BiMap, +} + +/// Trianvertices are the vertices of the triangulation before it is converted +/// to the navmesh. +#[derive(Clone, Debug, PartialEq)] +struct Trianvertex { + pub idx: LayoutNodeIndex, + pub pos: Point, +} + +impl HasPosition for Trianvertex { + type Scalar = T; + + #[inline] + fn position(&self) -> spade::Point2 { + let pos = self.pos.0.clone(); + spade::Point2 { x: pos.x, y: pos.y } + } +} + +fn voronoi_vertex_get_weight( + vertex: &spade::handles::VoronoiVertex<'_, V, DE, UE, F>, +) -> NodeWeight +where + V: HasPosition, +{ + fn outer_halfspace_get_weight, DE, UE, F>( + face: &spade::handles::DirectedVoronoiEdge<'_, V, DE, UE, F>, + ) -> NodeWeight { + let delauney = face.as_delaunay_edge(); + let from = delauney.from().position(); + let from = geo::point! { x: from.x, y: from.y }; + let to = delauney.to().position(); + let to = geo::point! { x: to.x, y: to.y }; + let diff = to - from; + let orth = geo::point! { + x: -diff.y(), + y: diff.x() + }; + + NodeWeight { + pos: (from + to + orth) / 2.0, + is_passable: true, + } + } + + use spade::handles::VoronoiVertex as VV; + match vertex { + VV::Inner(face) => { + let cc = face.circumcenter(); + NodeWeight { + pos: geo::point! { x: cc.x, y: cc.y }, + is_passable: true, + } + } + VV::Outer(out) => outer_halfspace_get_weight(out), + } +} + +impl

Navmesh

{ + pub fn new() -> Self { + Self { + graph: UnGraph::new_undirected(), + primals: BiMap::new(), + } + } + + /// Create a navmesh based upon a ratsnest + pub fn from_ratsnest(ratsnest: &Ratsnest) -> Result { + use spade::Triangulation; + let mut triangulation = spade::DelaunayTriangulation::, ()>::new(); + + let rng = ratsnest.graph(); + for node in rng.node_indices() { + let weight = &rng[node]; + triangulation.insert(Trianvertex { + idx: LayoutNodeIndex::from(weight.vertex), + pos: weight.pos, + })?; + } + + Ok(Self::from_triangulation(&triangulation)) + } + + /// Create a navmesh based upon a triangulation + pub fn from_triangulation(triangulation: &T) -> Self + where + T: spade::Triangulation>, + { + let mut this = UnGraph::new_undirected(); + // `primals` maps between layout node indices and indices in this graph + let mut primals = BiMap::with_capacity(triangulation.num_vertices()); + // `voronoi_verts` maps between dual node indices + // (computed from face indices) and our navmesh graph indices + let mut voronoi_verts = std::collections::HashMap::::with_capacity( + triangulation.num_all_faces(), + ); + + let mut handle_voronoi_vertex = |this: &mut UnGraph>, + vertex: &spade::handles::VoronoiVertex< + '_, + Trianvertex, + T::DirectedEdge, + T::UndirectedEdge, + T::Face, + >| { + use spade::handles::VoronoiVertex as VV; + let index = match vertex { + VV::Inner(face) => face.index() << 1, + VV::Outer(face) => (face.index() << 1) + 1, + }; + *voronoi_verts + .entry(index) + .or_insert_with(|| this.add_node(voronoi_vertex_get_weight(vertex))) + }; + + for node in triangulation.vertices() { + let graph_idx = this.add_node(NodeWeight { + pos: node.data().pos, + is_passable: false, + }); + + if primals + .insert_no_overwrite(node.data().idx, graph_idx) + .is_err() + { + panic!("duplicate layout node index"); + } + + for edge in node.as_voronoi_face().adjacent_edges() { + // to convert dual edges around a node into dual nodes around a node, + // we use the dual nodes that the edges point to. + let graph_idx2 = handle_voronoi_vertex(&mut this, &edge.to()); + this.update_edge(graph_idx, graph_idx2, EdgeWeight { path: None }); + } + } + + for edge in triangulation.undirected_voronoi_edges() { + let [a, b] = edge.vertices(); + let gridx_a = handle_voronoi_vertex(&mut this, &a); + let gridx_b = handle_voronoi_vertex(&mut this, &b); + this.update_edge(gridx_a, gridx_b, EdgeWeight { path: None }); + } + + Self { + graph: this, + primals, + } + } + + #[inline(always)] + pub fn graph(&self) -> &UnGraph> { + &self.graph + } + + #[inline(always)] + pub fn primals(&self) -> &BiMap { + &self.primals + } + + #[inline(always)] + pub fn neighbors(&self, idx: NodeIndex) -> petgraph::graph::Neighbors<'_, EdgeWeight

, u32> { + self.graph.neighbors(idx) + } + + /// Advance "etching" by a single step. + /// + /// * `prev` is the previous node in the path (if there is any), and possibly + /// the companions from the previous step (edges to which should form a grid) + /// * `current` is the currently selected node + /// * `dest` is the next node in the path (edges to there should be duplicated) + fn etch_path_make_companions( + &mut self, + prev: Option<(NodeIndex, Option<(NodeIndex, NodeIndex)>)>, + current: NodeIndex, + next: Option, + ) -> Option<(NodeIndex, NodeIndex)> { + let node_cur = self.graph.node_weight(current)?.clone(); + if prev.is_none() && next.is_none() || node_cur.is_passable != true { + return None; + } + use std::collections::BTreeSet; + let cur_pos = node_cur.pos; + let mut link_angles: Vec<_> = self + .graph + .neighbors(current) + .map(|i| { + ( + i, + self.graph + .node_weight(i) + .expect("encountered invalid edge endpoint") + .pos + - cur_pos, + ) + }) + .map(|(i, oth_pos_rel)| (i, oth_pos_rel.y().atan2(oth_pos_rel.x()))) + .collect(); + link_angles.sort_unstable_by(|a, b| f64::total_cmp(&a.1, &b.1)); + + // positions in the list of angles + // make `prev` the first item in the link_angles + if let Some(xpos) = prev + .as_ref() + .and_then(|(i, _)| link_angles.iter().position(|(j, _)| i == j)) + { + let mut tmp: Vec<_> = link_angles.drain(..xpos).collect(); + link_angles.append(&mut tmp); + let prev_idx = prev.unwrap().0; + // the prev isn't duplicated + assert_eq!(link_angles[0].0, prev_idx); + assert_eq!( + link_angles[1..].iter().position(|(j, _)| &prev_idx == j), + None + ); + + // subtract prev angle from all angles, and get them into `[0, 2π)` + let prev_angle = link_angles[0].1; + link_angles.iter_mut().for_each(|(_, i)| { + *i -= prev_angle; + use core::f64::consts::PI; + if *i < 0.0 { + *i += 2.0 * PI; + } else if *i >= 2.0 * PI { + *i -= 2.0 * PI; + } + }); + assert!(link_angles.is_sorted_by(|a, b| &a.1 <= &b.1)); + } + + // split the list at the next, categorize links into left vs right + if let Some(xpos) = next + .as_ref() + .and_then(|i| link_angles.iter().position(|(j, _)| i == j)) + { + let (lhs, rhs) = link_angles.split_at(xpos); + // don't put xpos into either + let rhs = &rhs[1..]; + } else { + // split at π + let split_point = match link_angles + .binary_search_by(|x| f64::total_cmp(&x.1, &core::f64::consts::PI)) + { + Ok(x) => x, + Err(x) => x, + }; + let (lhs, rhs) = link_angles.split_at(split_point); + }; + + let mut link_lhs = BTreeSet::new(); + let mut link_rhs = BTreeSet::new(); + let mut link_middle = BTreeSet::new(); + + if let Some((prev, prev_links)) = prev { + match prev_links { + None => { + //assert!(link_middle_pre.contains(&prev)); + link_lhs.insert(prev); + link_rhs.insert(prev); + link_middle.insert(prev); + } + Some((old_lhs, old_rhs)) => { + //assert!(link_middle_pre.contains(&prev)); + link_lhs.insert(old_lhs); + link_rhs.insert(old_rhs); + link_middle.insert(prev); + } + } + } + + link_lhs.insert(current); + link_rhs.insert(current); + + if let Some(next) = next { + link_lhs.insert(next); + link_rhs.insert(next); + link_middle.insert(next); + } + + todo!() + } + + /// "etch" a taken route into the navmesh (marking it such that it doesn't get crossed later) + /// basically marking the path itself, and also creating new dual paths to the left and right of it + /// + /// @returns `false` if invalid edges or nodes were encountered + pub fn etch_path(&mut self, path: &[NodeIndex]) -> bool { + if path.len() < 2 { + return false; + } + + let mut it = path.windows(2).map(|i| (i[0], i[1])); + let mut prev = None; + + for (i0, i1) in it { + prev = Some((i0, self.etch_path_make_companions(prev, i0, Some(i1)))); + } + + self.etch_path_make_companions(prev, *path.last().unwrap(), None); + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn first_node() { + let fi = NodeIndex::FIRST; + assert!(fi.ty < NodeTy::Compound); + assert!(fi.ty < NodeTy::DualInner); + assert!(fi.ty < NodeTy::DualOuter); + } +} diff --git a/src/router/mod.rs b/src/router/mod.rs index 766ca2c..bd03bec 100644 --- a/src/router/mod.rs +++ b/src/router/mod.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT pub mod astar; +pub mod brute_force; pub mod draw; pub mod navcord; pub mod navcorder; diff --git a/tests/single_layer/test/test.dsn b/tests/single_layer/test/test.dsn new file mode 100644 index 0000000..d7b7747 --- /dev/null +++ b/tests/single_layer/test/test.dsn @@ -0,0 +1,138 @@ +(pcb test.dsn + (parser + (string_quote ") + (space_in_quoted_tokens on) + (host_cad "KiCad's Pcbnew") + (host_version "7.0.9") + ) + (resolution um 10) + (unit um) + (structure + (layer F.Cu + (type signal) + (property + (index 0) + ) + ) + (layer B.Cu + (type signal) + (property + (index 1) + ) + ) + (boundary + (path pcb 0 61846 -50400 2162.05 -50400 2162.05 -14754.8 61846 -14754.8 + 61846 -50400) + ) + (via "Via[0-1]_800:400_um") + (rule + (width 600) + (clearance 200.1) + (clearance 200.1 (type default_smd)) + (clearance 50 (type smd_smd)) + ) + ) + (placement + (component Connector_Pin:Pin_D1.0mm_L10.0mm + (place REF21 16510.000000 -46990.000000 front 0.000000 (PN Pin_D1.0mm_L10.0mm)) + (place REF11 53340.000000 -17653.000000 front 0.000000 (PN Pin_D1.0mm_L10.0mm)) + (place REF31 12700.000000 -40640.000000 front 0.000000 (PN Pin_D1.0mm_L10.0mm)) + (place REF22 45720.000000 -35560.000000 front 0.000000 (PN Pin_D1.0mm_L10.0mm)) + (place REF12 10668.000000 -32258.000000 front 0.000000 (PN Pin_D1.0mm_L10.0mm)) + (place REF32 35560.000000 -24130.000000 front 0.000000 (PN Pin_D1.0mm_L10.0mm)) + ) + ) + (library + (image Connector_Pin:Pin_D1.0mm_L10.0mm + (outline (path signal 120 1251 0 1231.99 -217.234 1175.56 -427.867 1083.4 -625.5 + 958.322 -804.127 804.127 -958.322 625.5 -1083.4 427.867 -1175.56 + 217.234 -1231.99 0 -1251 -217.234 -1231.99 -427.867 -1175.56 + -625.5 -1083.4 -804.127 -958.322 -958.322 -804.127 -1083.4 -625.5 + -1175.56 -427.867 -1231.99 -217.234 -1251 0 -1231.99 217.234 + -1175.56 427.867 -1083.4 625.5 -958.322 804.127 -804.127 958.322 + -625.5 1083.4 -427.867 1175.56 -217.234 1231.99 0 1251 217.234 1231.99 + 427.867 1175.56 625.5 1083.4 804.127 958.322 958.322 804.127 + 1083.4 625.5 1175.56 427.867 1231.99 217.234 1251 0)) + (outline (path signal 50 1500 0 1480.58 -240.617 1422.81 -475.002 1328.18 -697.085 + 1199.16 -901.113 1039.09 -1081.8 852.097 -1234.48 643.039 -1355.18 + 417.326 -1440.78 180.805 -1489.06 -60.399 -1498.78 -300.039 -1469.69 + -531.907 -1402.52 -750 -1299.04 -948.668 -1161.91 -1122.77 -994.684 + -1267.79 -801.699 -1379.97 -587.95 -1456.41 -358.973 -1495.14 -120.7 + -1495.14 120.7 -1456.41 358.973 -1379.97 587.95 -1267.79 801.699 + -1122.77 994.684 -948.668 1161.91 -750 1299.04 -531.907 1402.52 + -300.039 1469.69 -60.399 1498.78 180.805 1489.06 417.326 1440.78 + 643.039 1355.18 852.097 1234.48 1039.09 1081.8 1199.16 901.113 + 1328.18 697.085 1422.81 475.002 1480.58 240.617 1500 0)) + (outline (path signal 120 500 0 481.459 -134.898 427.21 -259.792 341.277 -365.418 + 230.033 -443.943 101.728 -489.542 -34.121 -498.834 -167.44 -471.13 + -288.34 -408.485 -387.856 -315.544 -458.606 -199.201 -495.343 -68.083 + -495.343 68.083 -458.606 199.201 -387.856 315.544 -288.34 408.485 + -167.44 471.13 -34.121 498.834 101.728 489.542 230.033 443.943 + 341.277 365.418 427.21 259.792 481.459 134.898 500 0)) + (outline (path signal 120 1000 0 980.785 -195.09 923.88 -382.683 831.47 -555.57 + 707.107 -707.107 555.57 -831.47 382.683 -923.88 195.09 -980.785 + 0 -1000 -195.09 -980.785 -382.683 -923.88 -555.57 -831.47 + -707.107 -707.107 -831.47 -555.57 -923.88 -382.683 -980.785 -195.09 + -1000 0 -980.785 195.09 -923.88 382.683 -831.47 555.57 -707.107 707.107 + -555.57 831.47 -382.683 923.88 -195.09 980.785 0 1000 195.09 980.785 + 382.683 923.88 555.57 831.47 707.107 707.107 831.47 555.57 + 923.88 382.683 980.785 195.09 1000 0)) + (pin Round[A]Pad_2000_um 1 0 0) + ) + (padstack Round[A]Pad_2000_um + (shape (circle F.Cu 2000)) + (shape (circle B.Cu 2000)) + (attach off) + ) + (padstack "Via[0-1]_800:400_um" + (shape (circle F.Cu 800)) + (shape (circle B.Cu 800)) + (attach off) + ) + ) + (network + (net 1 + (pins REF11-1 REF12-1) + ) + (net 2 + (pins REF21-1 REF22-1) + ) + (net 3 + (pins REF31-1 REF32-1) + ) + (class kicad_default "" 1 2 3 + (circuit + (use_via Via[0-1]_800:400_um) + ) + (rule + (width 600) + (clearance 200.1) + ) + ) + (class test unconnected + (circuit + (use_via Via[0-1]_800:400_um) + ) + (rule + (width 300) + (clearance 500.1) + ) + ) + ) + (wiring + (wire (path F.Cu 600 20000 -50000 60000 -50000)(net unconnected)(type route)) + (wire (path F.Cu 600 60000 -20000 40000 -20000 40000 -40000)(net unconnected)(type route)) + (wire (path F.Cu 600 50000 -40000 50000 -30000)(net unconnected)(type route)) + (wire (path F.Cu 600 60000 -50000 60000 -20000)(net unconnected)(type route)) + (wire (path F.Cu 600 20000 -20000 20000 -50000)(net unconnected)(type route)) + (wire (path F.Cu 600 40000 -40000 50000 -40000)(net unconnected)(type route)) + (via "Via[0-1]_800:400_um" 20000 -50000 (net unconnected)(type route)) + (via "Via[0-1]_800:400_um" 60000 -50000 (net unconnected)(type route)) + (via "Via[0-1]_800:400_um" 50000 -40000 (net unconnected)(type route)) + (via "Via[0-1]_800:400_um" 40000 -20000 (net unconnected)(type route)) + (via "Via[0-1]_800:400_um" 20000 -20000 (net unconnected)(type route)) + (via "Via[0-1]_800:400_um" 40000 -40000 (net unconnected)(type route)) + (via "Via[0-1]_800:400_um" 60000 -20000 (net unconnected)(type route)) + (via "Via[0-1]_800:400_um" 50000 -30000 (net unconnected)(type route)) + ) +)