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

View File

@ -1,11 +1,66 @@
// SPDX-FileCopyrightText: 2025 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
// SPDX-License-Identifier: MIT
use geo::Point;
#[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,
NorthWest,
West,
@ -16,52 +71,41 @@ pub enum CompassDirection8 {
NorthEast,
}
impl From<CompassDirection8> for Point {
fn from(compass_direction8: CompassDirection8) -> Point {
match compass_direction8 {
CompassDirection8::North => [0.0, -1.0].into(),
CompassDirection8::NorthWest => [-1.0, -1.0].into(),
CompassDirection8::West => [-1.0, 0.0].into(),
CompassDirection8::SouthWest => [-1.0, 1.0].into(),
CompassDirection8::South => [0.0, 1.0].into(),
CompassDirection8::SouthEast => [1.0, 1.0].into(),
CompassDirection8::East => [1.0, 0.0].into(),
CompassDirection8::NorthEast => [1.0, -1.0].into(),
impl From<PrincipalWind> for Point {
fn from(principal_wind: PrincipalWind) -> Point {
match principal_wind {
PrincipalWind::North => [0.0, -1.0].into(),
PrincipalWind::NorthWest => [-1.0, -1.0].into(),
PrincipalWind::West => [-1.0, 0.0].into(),
PrincipalWind::SouthWest => [-1.0, 1.0].into(),
PrincipalWind::South => [0.0, 1.0].into(),
PrincipalWind::SouthEast => [1.0, 1.0].into(),
PrincipalWind::East => [1.0, 0.0].into(),
PrincipalWind::NorthEast => [1.0, -1.0].into(),
}
}
}
impl CompassDirection8 {
impl PrincipalWind {
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 {
panic!("Zero vector has no direction");
}
let angle = vector.y().atan2(vector.x()); // atan2 gives angle in radians from -π to π
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 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 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 {
0 => CompassDirection8::North,
1 => CompassDirection8::NorthEast,
2 => CompassDirection8::East,
3 => CompassDirection8::SouthEast,
4 => CompassDirection8::South,
5 => CompassDirection8::SouthWest,
6 => CompassDirection8::West,
7 => CompassDirection8::NorthWest,
0 => PrincipalWind::North,
1 => PrincipalWind::NorthEast,
2 => PrincipalWind::East,
3 => PrincipalWind::SouthEast,
4 => PrincipalWind::South,
5 => PrincipalWind::SouthWest,
6 => PrincipalWind::West,
7 => PrincipalWind::NorthWest,
_ => unreachable!(),
}
}

View File

@ -465,7 +465,9 @@ impl<
where
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 {

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
pub fn apex_of_compoundless_node(
&self,