This commit is contained in:
Alain Emilia Anna Zscheile 2024-12-13 15:12:57 +01:00
parent 9c007a8ccb
commit 7b16328a6c
6 changed files with 515 additions and 2 deletions

View File

@ -54,7 +54,7 @@ impl From<RatvertexIndex> for crate::layout::NodeIndex {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct RatvertexWeight { pub struct RatvertexWeight {
vertex: RatvertexIndex, pub(crate) vertex: RatvertexIndex,
pub pos: Point, pub pos: Point,
} }

View File

@ -4,6 +4,7 @@
use enum_dispatch::enum_dispatch; use enum_dispatch::enum_dispatch;
use petgraph::stable_graph::NodeIndex; use petgraph::stable_graph::NodeIndex;
use serde::{Deserialize, Serialize};
use crate::{drawing::Drawing, graph::GetPetgraphIndex}; 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 // 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. // should be getting it from the graph when it's needed.
#[enum_dispatch(GetPetgraphIndex, MakePrimitive)] #[enum_dispatch(GetPetgraphIndex, MakePrimitive)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum PrimitiveIndex { pub enum PrimitiveIndex {
FixedDot(FixedDotIndex), FixedDot(FixedDotIndex),
LooseDot(LooseDotIndex), LooseDot(LooseDotIndex),

View File

@ -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;

View File

@ -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<P> {
/// indicates if this is part of an etched path (`P` is the type of the path index)
pub path: Option<P>,
}
/// 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<P> {
// a merging of the primary and dual graph
graph: UnGraph<NodeWeight, EdgeWeight<P>>,
// association between primary graph and this one
primals: BiMap<LayoutNodeIndex, NodeIndex>,
}
/// Trianvertices are the vertices of the triangulation before it is converted
/// to the navmesh.
#[derive(Clone, Debug, PartialEq)]
struct Trianvertex<T: geo::CoordNum> {
pub idx: LayoutNodeIndex,
pub pos: Point<T>,
}
impl<T: geo::CoordNum + spade::SpadeNum> HasPosition for Trianvertex<T> {
type Scalar = T;
#[inline]
fn position(&self) -> spade::Point2<T> {
let pos = self.pos.0.clone();
spade::Point2 { x: pos.x, y: pos.y }
}
}
fn voronoi_vertex_get_weight<V, DE, UE, F>(
vertex: &spade::handles::VoronoiVertex<'_, V, DE, UE, F>,
) -> NodeWeight
where
V: HasPosition<Scalar = f64>,
{
fn outer_halfspace_get_weight<V: HasPosition<Scalar = f64>, 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<P> Navmesh<P> {
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<Self, spade::InsertionError> {
use spade::Triangulation;
let mut triangulation = spade::DelaunayTriangulation::<Trianvertex<f64>, ()>::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<T>(triangulation: &T) -> Self
where
T: spade::Triangulation<Vertex = Trianvertex<f64>>,
{
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::<usize, NodeIndex>::with_capacity(
triangulation.num_all_faces(),
);
let mut handle_voronoi_vertex = |this: &mut UnGraph<NodeWeight, EdgeWeight<P>>,
vertex: &spade::handles::VoronoiVertex<
'_,
Trianvertex<f64>,
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<NodeWeight, EdgeWeight<P>> {
&self.graph
}
#[inline(always)]
pub fn primals(&self) -> &BiMap<LayoutNodeIndex, NodeIndex> {
&self.primals
}
#[inline(always)]
pub fn neighbors(&self, idx: NodeIndex) -> petgraph::graph::Neighbors<'_, EdgeWeight<P>, 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<NodeIndex>,
) -> 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);
}
}

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pub mod astar; pub mod astar;
pub mod brute_force;
pub mod draw; pub mod draw;
pub mod navcord; pub mod navcord;
pub mod navcorder; pub mod navcorder;

View File

@ -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))
)
)