feat(autorouter/ratsnest): Have one terminating dot for each layer of ratvertex

Some routes projects still crash while autorouting, but we are
progressing.
This commit is contained in:
Mikolaj Wielgus 2025-09-29 18:16:27 +02:00
parent d53aa2a678
commit b50e58b0fa
11 changed files with 113 additions and 53 deletions

View File

@ -11,17 +11,18 @@ use specctra_core::mesadata::AccessMesadata;
use crate::{ use crate::{
autorouter::{ratline::RatlineIndex, Autorouter}, autorouter::{ratline::RatlineIndex, Autorouter},
board::edit::BoardEdit,
drawing::{ drawing::{
dot::FixedDotIndex, dot::FixedDotIndex,
graph::{GetMaybeNet, MakePrimitiveRef}, graph::{GetMaybeNet, MakePrimitiveRef},
primitive::MakePrimitiveShape, primitive::MakePrimitiveShape,
}, },
geometry::GetLayer, geometry::{GenericNode, GetLayer},
graph::{GenericIndex, GetIndex, MakeRef}, graph::{GenericIndex, GetIndex, MakeRef},
layout::{ layout::{
poly::{MakePolygon, PolyWeight}, poly::{MakePolygon, PolyWeight},
via::ViaWeight, via::ViaWeight,
CompoundWeight, LayoutEdit, CompoundWeight,
}, },
math::Circle, math::Circle,
}; };
@ -53,15 +54,23 @@ impl Anterouter {
let endpoint_indices = ratline.ref_(autorouter).endpoint_indices(); let endpoint_indices = ratline.ref_(autorouter).endpoint_indices();
let endpoint_dots = ratline.ref_(autorouter).endpoint_dots(); let endpoint_dots = ratline.ref_(autorouter).endpoint_dots();
autorouter
.ratsnest
.assign_layer_to_ratline(*ratline, *layer);
if let Some(terminating_scheme) = self if let Some(terminating_scheme) = self
.plan .plan
.ratline_endpoint_dot_to_terminating_scheme .ratline_endpoint_dot_to_terminating_scheme
.get(&endpoint_dots.0) .get(&endpoint_dots.0)
{ {
match terminating_scheme { match terminating_scheme {
TerminatingScheme::ExistingFixedDot(terminating_dot) => autorouter TerminatingScheme::ExistingFixedDot(terminating_dot) => {
.ratsnest autorouter.ratsnest.assign_terminating_dot_to_ratvertex(
.assign_terminating_dot_to_ratvertex(endpoint_indices.0, *terminating_dot), endpoint_indices.0,
*layer,
*terminating_dot,
)
}
TerminatingScheme::Anteroute(pin_bbox_to_anchor) => self TerminatingScheme::Anteroute(pin_bbox_to_anchor) => self
.anteroute_dot_to_anchor( .anteroute_dot_to_anchor(
autorouter, autorouter,
@ -79,9 +88,13 @@ impl Anterouter {
.get(&endpoint_dots.1) .get(&endpoint_dots.1)
{ {
match terminating_scheme { match terminating_scheme {
TerminatingScheme::ExistingFixedDot(terminating_dot) => autorouter TerminatingScheme::ExistingFixedDot(terminating_dot) => {
.ratsnest autorouter.ratsnest.assign_terminating_dot_to_ratvertex(
.assign_terminating_dot_to_ratvertex(endpoint_indices.1, *terminating_dot), endpoint_indices.1,
*layer,
*terminating_dot,
)
}
TerminatingScheme::Anteroute(pin_bbox_to_anchor) => self TerminatingScheme::Anteroute(pin_bbox_to_anchor) => self
.anteroute_dot_to_anchor( .anteroute_dot_to_anchor(
autorouter, autorouter,
@ -170,10 +183,10 @@ impl Anterouter {
//let via_bbox_to_anchor = [-pin_bbox_to_anchor[0], -pin_bbox_to_anchor[1]]; //let via_bbox_to_anchor = [-pin_bbox_to_anchor[0], -pin_bbox_to_anchor[1]];
let mut layout_edit = LayoutEdit::new(); let mut board_edit = BoardEdit::new();
if let Ok((.., dots)) = autorouter.board.layout_mut().add_via( if let Ok((.., dots)) = autorouter.board.add_via(
&mut layout_edit, &mut board_edit,
ViaWeight { ViaWeight {
from_layer: std::cmp::min(pin_layer, to_layer), from_layer: std::cmp::min(pin_layer, to_layer),
to_layer: std::cmp::max(pin_layer, to_layer), to_layer: std::cmp::max(pin_layer, to_layer),
@ -183,6 +196,10 @@ impl Anterouter {
}, },
maybe_net: pin_maybe_net, maybe_net: pin_maybe_net,
}, },
autorouter
.board()
.node_pinname(&GenericNode::Primitive(dot.into()))
.cloned(),
) { ) {
let terminating_dot = dots let terminating_dot = dots
.iter() .iter()
@ -193,9 +210,11 @@ impl Anterouter {
.layer() .layer()
}) })
.unwrap(); .unwrap();
autorouter autorouter.ratsnest.assign_terminating_dot_to_ratvertex(
.ratsnest ratvertex,
.assign_terminating_dot_to_ratvertex(ratvertex, *terminating_dot); to_layer,
*terminating_dot,
);
} }
/*let bbox = if let Some(poly) = autorouter.board().layout().drawing().geometry().compounds(dot).find(|(_, compound_weight)| { /*let bbox = if let Some(poly) = autorouter.board().layout().drawing().geometry().compounds(dot).find(|(_, compound_weight)| {
matches!(compound_weight, CompoundWeight::Poly(..)) matches!(compound_weight, CompoundWeight::Poly(..))

View File

@ -7,12 +7,12 @@ use geo::Point;
use petgraph::graph::NodeIndex; use petgraph::graph::NodeIndex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use spade::InsertionError; use spade::InsertionError;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::BTreeSet;
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
autorouter::{ autorouter::{
anterouter::AnterouterPlan, multilayer_autoroute::MultilayerAutorouteExecutionStepper, multilayer_autoroute::MultilayerAutorouteExecutionStepper,
permutator::PlanarAutorouteExecutionPermutator, planner::Planner, permutator::PlanarAutorouteExecutionPermutator, planner::Planner,
}, },
board::{AccessMesadata, Board}, board::{AccessMesadata, Board},
@ -213,22 +213,22 @@ impl<M: AccessMesadata> Autorouter<M> {
active_layer, active_layer,
allowed_edges, allowed_edges,
ratlines.into_iter().filter_map(|ratline| { ratlines.into_iter().filter_map(|ratline| {
let (source, target) = ratline.ref_(self).terminating_dots(); let (origin, destination) = ratline.ref_(self).terminating_dots();
if navmesh if navmesh
.as_ref() .as_ref()
.node_data(&NavmeshIndex::Primal(source)) .node_data(&NavmeshIndex::Primal(origin))
.is_none() .is_none()
|| navmesh || navmesh
.as_ref() .as_ref()
.node_data(&NavmeshIndex::Primal(target)) .node_data(&NavmeshIndex::Primal(destination))
.is_none() .is_none()
{ {
// e.g. due to wrong active layer // e.g. due to wrong active layer
return None; return None;
} }
if self.board.band_between_nodes(source, target).is_some() { if self.board.band_between_nodes(origin, destination).is_some() {
// already connected // already connected
return None; return None;
} }
@ -236,8 +236,8 @@ impl<M: AccessMesadata> Autorouter<M> {
got_any_valid_goals = true; got_any_valid_goals = true;
Some(ng::Goal { Some(ng::Goal {
source, source: origin,
target, target: destination,
width, width,
}) })
}), }),

View File

@ -86,7 +86,7 @@ impl<M: AccessMesadata + Clone> ExecutionStepper<M> {
for (ep, band) in &autoroute.last_bands { for (ep, band) in &autoroute.last_bands {
let (source, target) = ep.end_points.into(); let (source, target) = ep.end_points.into();
autorouter.board.try_set_band_between_nodes( autorouter.board.try_set_band_between_nodes(
&mut autoroute.last_recorder.data_edit, &mut autoroute.last_recorder.board_data_edit,
source, source,
target, target,
*band, *band,

View File

@ -207,7 +207,7 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, PlanarAutorouteCo
self.curr_ratline_index += 1; self.curr_ratline_index += 1;
if let Some(new_ratline) = self.ratlines.get(self.curr_ratline_index) { if let Some(new_ratline) = self.ratlines.get(self.curr_ratline_index) {
let (source, target) = new_ratline.ref_(autorouter).terminating_dots(); let (origin, destination) = new_ratline.ref_(autorouter).terminating_dots();
let mut router = let mut router =
Router::new(autorouter.board.layout_mut(), self.options.router_options); Router::new(autorouter.board.layout_mut(), self.options.router_options);
@ -216,8 +216,8 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, PlanarAutorouteCo
self.route = Some(router.route( self.route = Some(router.route(
recorder, recorder,
source, origin,
target, destination,
self.options.router_options.routed_band_width, self.options.router_options.routed_band_width,
)?); )?);
} }

View File

@ -99,7 +99,7 @@ impl Planner {
None None
} }
}) })
.map_or(TerminatingScheme::Anteroute([-1.0, -1.0]), |dot| { .map_or(TerminatingScheme::Anteroute([-2.0, 0.0]), |dot| {
TerminatingScheme::ExistingFixedDot(dot) TerminatingScheme::ExistingFixedDot(dot)
}), }),
) )

View File

@ -12,7 +12,7 @@ use crate::{
dot::FixedDotIndex, dot::FixedDotIndex,
graph::{GetMaybeNet, MakePrimitiveRef, PrimitiveIndex}, graph::{GetMaybeNet, MakePrimitiveRef, PrimitiveIndex},
}, },
geometry::{shape::MeasureLength, GetLayer}, geometry::shape::MeasureLength,
graph::MakeRef, graph::MakeRef,
triangulation::GetTrianvertexNodeIndex, triangulation::GetTrianvertexNodeIndex,
}; };
@ -23,6 +23,7 @@ pub type RatlineIndex = EdgeIndex<usize>;
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
pub struct RatlineWeight { pub struct RatlineWeight {
pub layer: usize,
pub band_termseg: Option<BandTermsegIndex>, pub band_termseg: Option<BandTermsegIndex>,
} }
@ -102,7 +103,9 @@ impl<'a, M: AccessMesadata> RatlineRef<'a, M> {
.graph() .graph()
.node_weight(source) .node_weight(source)
.unwrap() .unwrap()
.maybe_terminating_dot .layer_terminating_dots
.get(&self.layer())
.copied()
.unwrap_or(self.endpoint_dots().0); .unwrap_or(self.endpoint_dots().0);
let target_dot = self let target_dot = self
.autorouter .autorouter
@ -110,17 +113,25 @@ impl<'a, M: AccessMesadata> RatlineRef<'a, M> {
.graph() .graph()
.node_weight(target) .node_weight(target)
.unwrap() .unwrap()
.maybe_terminating_dot .layer_terminating_dots
.get(&self.layer())
.copied()
.unwrap_or(self.endpoint_dots().1); .unwrap_or(self.endpoint_dots().1);
(source_dot, target_dot) (source_dot, target_dot)
} }
pub fn layer(&self) -> usize { pub fn layer(&self) -> usize {
self.endpoint_dots() self.autorouter
.0 .ratsnest()
.primitive_ref(self.autorouter.board().layout().drawing()) .graph()
.layer() .edge_weight(self.index)
.unwrap()
.layer
/*self.endpoint_dots()
.0
.primitive_ref(self.autorouter.board().layout().drawing())
.layer()*/
} }
pub fn net(&self) -> usize { pub fn net(&self) -> usize {

View File

@ -48,11 +48,11 @@ impl From<RatvertexNodeIndex> for crate::layout::NodeIndex {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub struct RatvertexWeight { pub struct RatvertexWeight {
vertex: RatvertexNodeIndex, vertex: RatvertexNodeIndex,
pub pos: Point, pub pos: Point,
pub maybe_terminating_dot: Option<FixedDotIndex>, pub layer_terminating_dots: BTreeMap<usize, FixedDotIndex>,
} }
impl GetTrianvertexNodeIndex<RatvertexNodeIndex> for RatvertexWeight { impl GetTrianvertexNodeIndex<RatvertexNodeIndex> for RatvertexWeight {
@ -118,11 +118,11 @@ impl Ratsnest {
let mut triangulations = BTreeMap::new(); let mut triangulations = BTreeMap::new();
this.add_layer_to_ratsnest(board, &mut triangulations, principal_layer); this.add_layer_to_ratsnest_triangulations(board, &mut triangulations, principal_layer);
for layer in 0..board.layout().drawing().layer_count() { for layer in 0..board.layout().drawing().layer_count() {
if layer != principal_layer { if layer != principal_layer {
this.add_layer_to_ratsnest(board, &mut triangulations, layer); this.add_layer_to_ratsnest_triangulations(board, &mut triangulations, layer);
} }
} }
@ -158,7 +158,7 @@ impl Ratsnest {
Ok(this) Ok(this)
} }
fn add_layer_to_ratsnest( fn add_layer_to_ratsnest_triangulations(
&mut self, &mut self,
board: &Board<impl AccessMesadata>, board: &Board<impl AccessMesadata>,
triangulations: &mut BTreeMap< triangulations: &mut BTreeMap<
@ -190,7 +190,7 @@ impl Ratsnest {
triangulation.add_vertex(RatvertexWeight { triangulation.add_vertex(RatvertexWeight {
vertex, vertex,
pos, pos,
maybe_terminating_dot: None, layer_terminating_dots: BTreeMap::new(),
})?; })?;
Ok(()) Ok(())
}; };
@ -229,12 +229,18 @@ impl Ratsnest {
pub fn assign_terminating_dot_to_ratvertex( pub fn assign_terminating_dot_to_ratvertex(
&mut self, &mut self,
node_index: NodeIndex<usize>, node_index: NodeIndex<usize>,
layer: usize,
terminating_dot: FixedDotIndex, terminating_dot: FixedDotIndex,
) { ) {
self.graph self.graph
.node_weight_mut(node_index) .node_weight_mut(node_index)
.unwrap() .unwrap()
.maybe_terminating_dot = Some(terminating_dot) .layer_terminating_dots
.insert(layer, terminating_dot);
}
pub fn assign_layer_to_ratline(&mut self, ratline: RatlineIndex, layer: usize) {
self.graph.edge_weight_mut(ratline).unwrap().layer = layer;
} }
pub fn assign_band_termseg_to_ratline( pub fn assign_band_termseg_to_ratline(

View File

@ -29,7 +29,7 @@ impl Edit for BoardDataEdit {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct BoardEdit { pub struct BoardEdit {
pub data_edit: BoardDataEdit, pub board_data_edit: BoardDataEdit,
pub layout_edit: LayoutEdit, pub layout_edit: LayoutEdit,
} }
@ -40,7 +40,7 @@ impl BoardEdit {
pub fn new_from_edits(data_edit: BoardDataEdit, layout_edit: LayoutEdit) -> Self { pub fn new_from_edits(data_edit: BoardDataEdit, layout_edit: LayoutEdit) -> Self {
Self { Self {
data_edit, board_data_edit: data_edit,
layout_edit, layout_edit,
} }
} }
@ -48,12 +48,12 @@ impl BoardEdit {
impl Edit for BoardEdit { impl Edit for BoardEdit {
fn reverse_inplace(&mut self) { fn reverse_inplace(&mut self) {
self.data_edit.reverse_inplace(); self.board_data_edit.reverse_inplace();
self.layout_edit.reverse_inplace(); self.layout_edit.reverse_inplace();
} }
fn merge(&mut self, edit: Self) { fn merge(&mut self, edit: Self) {
self.data_edit.merge(edit.data_edit); self.board_data_edit.merge(edit.board_data_edit);
self.layout_edit.merge(edit.layout_edit); self.layout_edit.merge(edit.layout_edit);
} }
} }

View File

@ -21,11 +21,11 @@ use crate::{
dot::{FixedDotIndex, FixedDotWeight}, dot::{FixedDotIndex, FixedDotWeight},
graph::PrimitiveIndex, graph::PrimitiveIndex,
seg::{FixedSegIndex, FixedSegWeight}, seg::{FixedSegIndex, FixedSegWeight},
DrawingException, DrawingException, Infringement,
}, },
geometry::{edit::ApplyGeometryEdit, GenericNode, GetLayer}, geometry::{edit::ApplyGeometryEdit, GenericNode, GetLayer},
graph::{GenericIndex, MakeRef}, graph::{GenericIndex, MakeRef},
layout::{poly::PolyWeight, CompoundWeight, Layout, NodeIndex}, layout::{poly::PolyWeight, via::ViaWeight, CompoundWeight, Layout, NodeIndex},
router::ng::EtchedPath, router::ng::EtchedPath,
}; };
@ -105,6 +105,24 @@ impl<M> Board<M> {
} }
impl<M: AccessMesadata> Board<M> { impl<M: AccessMesadata> Board<M> {
pub fn add_via(
&mut self,
recorder: &mut BoardEdit,
weight: ViaWeight,
maybe_pin: Option<String>,
) -> Result<(GenericIndex<ViaWeight>, Vec<FixedDotIndex>), Infringement> {
let (weight, dots) = self.layout.add_via(&mut recorder.layout_edit, weight)?;
if let Some(pin) = maybe_pin {
for dot in dots.clone() {
self.pinname_nodes
.insert(pin.clone(), GenericNode::Primitive(dot.into()));
}
}
Ok((weight, dots))
}
/// Adds a new fixed dot with an optional pin name. /// Adds a new fixed dot with an optional pin name.
/// ///
/// Inserts the dot into the layout and, if a pin name is provided, maps it to the created dot's node. /// Inserts the dot into the layout and, if a pin name is provided, maps it to the created dot's node.
@ -295,7 +313,7 @@ impl<M: AccessMesadata> Board<M> {
} }
recorder recorder
.data_edit .board_data_edit
.bands .bands
.insert(bandname, (maybe_band, None)); .insert(bandname, (maybe_band, None));
@ -328,7 +346,7 @@ impl<M: AccessMesadata> Board<M> {
} }
pub fn apply_edit(&mut self, edit: &BoardEdit) { pub fn apply_edit(&mut self, edit: &BoardEdit) {
for (bandname, (maybe_old_band_uid, ..)) in &edit.data_edit.bands { for (bandname, (maybe_old_band_uid, ..)) in &edit.board_data_edit.bands {
if maybe_old_band_uid.is_some() { if maybe_old_band_uid.is_some() {
self.band_bandname.remove_by_right(bandname); self.band_bandname.remove_by_right(bandname);
} }
@ -336,7 +354,7 @@ impl<M: AccessMesadata> Board<M> {
self.layout_mut().apply(&edit.layout_edit); self.layout_mut().apply(&edit.layout_edit);
for (bandname, (.., maybe_new_band_uid)) in &edit.data_edit.bands { for (bandname, (.., maybe_new_band_uid)) in &edit.board_data_edit.bands {
if let Some(band_uid) = maybe_new_band_uid { if let Some(band_uid) = maybe_new_band_uid {
self.band_bandname.insert(*band_uid, bandname.clone()); self.band_bandname.insert(*band_uid, bandname.clone());
} }

View File

@ -308,7 +308,7 @@ impl<
pub fn remove_primitive(&mut self, primitive: PI) { pub fn remove_primitive(&mut self, primitive: PI) {
let maybe_removed = self.graph.remove_node(primitive.index().into()); let maybe_removed = self.graph.remove_node(primitive.index().into());
debug_assert!(maybe_removed.is_some()); assert!(maybe_removed.is_some());
} }
pub fn move_dot(&mut self, dot: DI, to: Point) { pub fn move_dot(&mut self, dot: DI, to: Point) {
@ -356,7 +356,7 @@ impl<
.find(|edge| matches!(edge.weight(), GeometryLabel::Outer)) .find(|edge| matches!(edge.weight(), GeometryLabel::Outer))
{ {
let maybe_removed = self.graph.remove_edge(old_inner_edge.id()); let maybe_removed = self.graph.remove_edge(old_inner_edge.id());
debug_assert!(maybe_removed.is_some()); assert!(maybe_removed.is_some());
} }
if let Some(new_inner) = maybe_new_inner { if let Some(new_inner) = maybe_new_inner {
@ -656,7 +656,7 @@ impl<PW: Copy + Retag<Index = PI>, DW, SW, BW, CW: Clone, Cel: Copy, PI: Copy, D
fn remove_compound(&mut self, compound: GenericIndex<CW>) { fn remove_compound(&mut self, compound: GenericIndex<CW>) {
let maybe_removed = self.graph.remove_node(compound.index().into()); let maybe_removed = self.graph.remove_node(compound.index().into());
debug_assert!(maybe_removed.is_some()); assert!(maybe_removed.is_some());
} }
fn add_to_compound<I>(&mut self, primitive: I, entry_label: Cel, compound: GenericIndex<CW>) fn add_to_compound<I>(&mut self, primitive: I, entry_label: Cel, compound: GenericIndex<CW>)

View File

@ -192,6 +192,8 @@ impl Prenavmesh {
let primitive = node.primitive_ref(layout.drawing()); let primitive = node.primitive_ref(layout.drawing());
let Some(primitive_net) = primitive.maybe_net() else { let Some(primitive_net) = primitive.maybe_net() else {
assert_ne!(node, origin.into());
assert_ne!(node, destination.into());
continue; continue;
}; };
@ -215,6 +217,10 @@ impl Prenavmesh {
// false positives in some cases, so in the future, instead of this, // false positives in some cases, so in the future, instead of this,
// create a fillet compound type and check for compound membership. // create a fillet compound type and check for compound membership.
if Self::is_fixed_dot_filleted(layout, dot) { if Self::is_fixed_dot_filleted(layout, dot) {
// FIXME: anteroute dot may get skipped here, which
// results in a panic.
assert_ne!(origin, dot);
assert_ne!(destination, dot);
continue; continue;
} }