feat(autorouter/anterouter): Fanout only in cardinal directions

This works around the falsely positively fillet exclusions our current
navmesh creation method causes.
This commit is contained in:
Mikolaj Wielgus 2025-10-03 13:09:21 +02:00
parent d0417926f5
commit 2ebd7ce82d
4 changed files with 109 additions and 72 deletions

View File

@ -10,7 +10,7 @@ use rstar::{Envelope, RTreeObject, AABB};
use specctra_core::mesadata::AccessMesadata; use specctra_core::mesadata::AccessMesadata;
use crate::{ use crate::{
autorouter::{compass_direction::CompassDirection8, ratline::RatlineIndex, Autorouter}, autorouter::{compass_direction::CardinalDirection, ratline::RatlineIndex, Autorouter},
board::edit::BoardEdit, board::edit::BoardEdit,
drawing::{ drawing::{
dot::FixedDotIndex, dot::FixedDotIndex,
@ -18,12 +18,8 @@ use crate::{
primitive::MakePrimitiveShape, primitive::MakePrimitiveShape,
}, },
geometry::{GenericNode, GetLayer}, geometry::{GenericNode, GetLayer},
graph::{GenericIndex, GetIndex, MakeRef}, graph::MakeRef,
layout::{ layout::{poly::MakePolygon, via::ViaWeight},
poly::{MakePolygon, PolyWeight},
via::ViaWeight,
CompoundWeight,
},
math::Circle, math::Circle,
}; };
@ -120,7 +116,7 @@ impl Anterouter {
ratline_delta = -ratline_delta; ratline_delta = -ratline_delta;
} }
let initial_compass_direction8 = CompassDirection8::nearest_to_vector(ratline_delta); let cardinal_direction = CardinalDirection::nearest_to_vector(ratline_delta);
if self if self
.anteroute_fanout_to_anchor( .anteroute_fanout_to_anchor(
@ -128,15 +124,15 @@ impl Anterouter {
ratvertex, ratvertex,
source_dot, source_dot,
target_layer, target_layer,
Point::from(initial_compass_direction8) * 1.2, Point::from(cardinal_direction) * 1.4,
) )
.is_ok() .is_ok()
{ {
return; return;
} }
let mut counterclockwise_turning = initial_compass_direction8; let mut counterclockwise_turning = cardinal_direction;
let mut clockwise_turning = initial_compass_direction8; let mut clockwise_turning = cardinal_direction;
loop { loop {
counterclockwise_turning = counterclockwise_turning.turn_counterclockwise(); counterclockwise_turning = counterclockwise_turning.turn_counterclockwise();
@ -147,7 +143,7 @@ impl Anterouter {
ratvertex, ratvertex,
source_dot, source_dot,
target_layer, target_layer,
Point::from(counterclockwise_turning) * 1.2, Point::from(counterclockwise_turning) * 1.4,
) )
.is_ok() .is_ok()
{ {
@ -162,15 +158,15 @@ impl Anterouter {
ratvertex, ratvertex,
source_dot, source_dot,
target_layer, target_layer,
Point::from(clockwise_turning) * 1.2, Point::from(clockwise_turning) * 1.4,
) )
.is_ok() .is_ok()
{ {
return; return;
} }
if counterclockwise_turning == initial_compass_direction8 if counterclockwise_turning == cardinal_direction
|| clockwise_turning == initial_compass_direction8 || clockwise_turning == cardinal_direction
{ {
break; break;
//panic!(); //panic!();
@ -211,25 +207,7 @@ impl Anterouter {
.primitive(dot) .primitive(dot)
.maybe_net(); .maybe_net();
let pin_bbox = if let Some(poly) = autorouter let pin_bbox = if let Some(poly) = autorouter.board().layout().primitive_poly(dot.into()) {
.board()
.layout()
.drawing()
.compounds(GenericIndex::<()>::new(dot.index()))
.find_map(|(_, compound)| {
if let CompoundWeight::Poly(_) = autorouter
.board()
.layout()
.drawing()
.compound_weight(compound)
{
Some(compound)
} else {
None
}
})
.map(|compound| GenericIndex::<PolyWeight>::new(compound.index()))
{
let bbox = poly.ref_(autorouter.board().layout()).shape().envelope(); let bbox = poly.ref_(autorouter.board().layout()).shape().envelope();
AABB::<[f64; 2]>::from_corners( AABB::<[f64; 2]>::from_corners(
[bbox.lower().x(), bbox.lower().y()], [bbox.lower().x(), bbox.lower().y()],

View File

@ -1,11 +1,66 @@
// SPDX-FileCopyrightText: 2025 Topola contributors // SPDX-FileCopyrightText: 2025 Topola contributors
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT
use geo::Point; use geo::Point;
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompassDirection8 { pub enum CardinalDirection {
North,
West,
South,
East,
}
impl From<CardinalDirection> for Point {
fn from(compass_direction: CardinalDirection) -> Point {
match compass_direction {
CardinalDirection::North => [0.0, -1.0].into(),
CardinalDirection::West => [-1.0, 0.0].into(),
CardinalDirection::South => [0.0, 1.0].into(),
CardinalDirection::East => [1.0, 0.0].into(),
}
}
}
impl CardinalDirection {
pub fn nearest_to_vector(vector: Point) -> Self {
if vector.x().abs() > vector.y().abs() {
if vector.x() > 0.0 {
Self::East
} else {
Self::West
}
} else {
if vector.y() > 0.0 {
Self::North
} else {
Self::South
}
}
}
pub fn turn_counterclockwise(self) -> Self {
match self {
Self::North => Self::West,
Self::West => Self::South,
Self::South => Self::East,
Self::East => Self::North,
}
}
pub fn turn_clockwise(self) -> Self {
match self {
Self::North => Self::East,
Self::East => Self::South,
Self::South => Self::West,
Self::West => Self::North,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PrincipalWind {
North, North,
NorthWest, NorthWest,
West, West,
@ -16,52 +71,41 @@ pub enum CompassDirection8 {
NorthEast, NorthEast,
} }
impl From<CompassDirection8> for Point { impl From<PrincipalWind> for Point {
fn from(compass_direction8: CompassDirection8) -> Point { fn from(principal_wind: PrincipalWind) -> Point {
match compass_direction8 { match principal_wind {
CompassDirection8::North => [0.0, -1.0].into(), PrincipalWind::North => [0.0, -1.0].into(),
CompassDirection8::NorthWest => [-1.0, -1.0].into(), PrincipalWind::NorthWest => [-1.0, -1.0].into(),
CompassDirection8::West => [-1.0, 0.0].into(), PrincipalWind::West => [-1.0, 0.0].into(),
CompassDirection8::SouthWest => [-1.0, 1.0].into(), PrincipalWind::SouthWest => [-1.0, 1.0].into(),
CompassDirection8::South => [0.0, 1.0].into(), PrincipalWind::South => [0.0, 1.0].into(),
CompassDirection8::SouthEast => [1.0, 1.0].into(), PrincipalWind::SouthEast => [1.0, 1.0].into(),
CompassDirection8::East => [1.0, 0.0].into(), PrincipalWind::East => [1.0, 0.0].into(),
CompassDirection8::NorthEast => [1.0, -1.0].into(), PrincipalWind::NorthEast => [1.0, -1.0].into(),
} }
} }
} }
impl CompassDirection8 { impl PrincipalWind {
pub fn nearest_to_vector(vector: Point) -> Self { pub fn nearest_to_vector(vector: Point) -> Self {
/*match (vector.x().signum(), vector.y().signum()) {
(0.0, -1.0) => Self::North,
(-1.0, -1.0) => Self::NorthWest,
(-1.0, 0.0) => Self::West,
(-1.0, 1.0) => Self::SouthWest,
(0.0, 1.0) => Self::South,
(1.0, 1.0) => Self::SouthEast,
(1.0, 0.0) => Self::East,
(1.0, -1.0) => Self::NorthEast,
(_, _) => panic!(),
}*/
if vector.x() == 0.0 && vector.y() == 0.0 { if vector.x() == 0.0 && vector.y() == 0.0 {
panic!("Zero vector has no direction"); panic!("Zero vector has no direction");
} }
let angle = vector.y().atan2(vector.x()); // atan2 gives angle in radians from -π to π let angle = vector.y().atan2(vector.x()); // atan2 gives angle in radians from -pi to pi.
let angle_deg = angle.to_degrees(); // Convert to degrees for easier reasoning let angle_deg = angle.to_degrees(); // Convert to degrees for easier reasoning.
let compass_angle = (450.0 - angle_deg) % 360.0; // Convert to compass heading (0° = North) let compass_angle = (450.0 - angle_deg) % 360.0; // Convert to compass heading (0 deg = North).
// Each direction is 45 degrees wide; round to nearest // Each direction is 45 degrees wide; round to nearest.
match ((compass_angle + 22.5) / 45.0).floor() as i32 % 8 { match ((compass_angle + 22.5) / 45.0).floor() as i32 % 8 {
0 => CompassDirection8::North, 0 => PrincipalWind::North,
1 => CompassDirection8::NorthEast, 1 => PrincipalWind::NorthEast,
2 => CompassDirection8::East, 2 => PrincipalWind::East,
3 => CompassDirection8::SouthEast, 3 => PrincipalWind::SouthEast,
4 => CompassDirection8::South, 4 => PrincipalWind::South,
5 => CompassDirection8::SouthWest, 5 => PrincipalWind::SouthWest,
6 => CompassDirection8::West, 6 => PrincipalWind::West,
7 => CompassDirection8::NorthWest, 7 => PrincipalWind::NorthWest,
_ => unreachable!(), _ => unreachable!(),
} }
} }

View File

@ -465,7 +465,9 @@ impl<
where where
I: Copy + GetIndex, I: Copy + GetIndex,
{ {
self.geometry.add_to_compound(primitive, label, compound); todo!();
// This is incorrect: R-tree bbox is not updated.
//self.geometry.add_to_compound(primitive, label, compound);
} }
fn compound_weight(&self, compound: GenericIndex<CW>) -> &CW { fn compound_weight(&self, compound: GenericIndex<CW>) -> &CW {

View File

@ -347,6 +347,19 @@ impl<R: AccessRules> Layout<R> {
} }
} }
pub fn primitive_poly(&self, primitive: PrimitiveIndex) -> Option<GenericIndex<PolyWeight>> {
self.drawing()
.compounds(GenericIndex::<()>::new(primitive.index()))
.find_map(|(_, compound)| {
if let CompoundWeight::Poly(_) = self.drawing().compound_weight(compound) {
Some(compound)
} else {
None
}
})
.map(|compound| GenericIndex::<PolyWeight>::new(compound.index()))
}
/// Checks if a node is not a primitive part of a compound, and if yes, returns its apex and center /// Checks if a node is not a primitive part of a compound, and if yes, returns its apex and center
pub fn apex_of_compoundless_node( pub fn apex_of_compoundless_node(
&self, &self,