topola/src/autorouter/autoroute.rs

190 lines
6.3 KiB
Rust

// SPDX-FileCopyrightText: 2024 Topola contributors
//
// SPDX-License-Identifier: MIT
//! Manages autorouting of ratlines in a layout, tracking status and processed
//! routing steps.
use std::ops::ControlFlow;
use petgraph::graph::EdgeIndex;
use crate::{
board::AccessMesadata,
drawing::{band::BandTermsegIndex, graph::PrimitiveIndex, Collect},
geometry::primitive::PrimitiveShape,
layout::LayoutEdit,
router::{
navcord::Navcord, navmesh::Navmesh, thetastar::ThetastarStepper, RouteStepper, Router,
},
stepper::Step,
};
use super::{invoker::GetDebugOverlayData, Autorouter, AutorouterError, AutorouterOptions};
/// Represents the current status of the autoroute operation.
pub enum AutorouteContinueStatus {
/// The autoroute is currently running and in progress.
Running,
/// A specific segment has been successfully routed.
Routed(BandTermsegIndex),
/// A specific segment was already routed and has been skipped.
Skipped(BandTermsegIndex),
}
/// Manages the autorouting process across multiple ratlines.
pub struct AutorouteExecutionStepper {
/// An iterator over ratlines that tracks which segments still need to be routed.
ratlines_iter: Box<dyn Iterator<Item = EdgeIndex<usize>>>,
/// The layer on which the routing should be done.
layer: usize,
/// The options for the autorouting process, defining how routing should be carried out.
options: AutorouterOptions,
/// Stores the current route being processed, if any.
route: Option<RouteStepper>,
/// Keeps track of the current ratline being routed, if one is active.
curr_ratline: Option<EdgeIndex<usize>>,
}
impl AutorouteExecutionStepper {
/// Initializes a new [`AutorouteExecutionStepper`] instance.
///
/// This method sets up the routing process by accepting the execution properties.
/// It prepares the first ratline to route
/// and stores the associated data for future routing steps.
pub fn new(
autorouter: &mut Autorouter<impl AccessMesadata>,
ratlines: impl IntoIterator<Item = EdgeIndex<usize>> + 'static,
layer: usize,
options: AutorouterOptions,
) -> Result<Self, AutorouterError> {
let mut ratlines_iter = Box::new(ratlines.into_iter());
let Some(curr_ratline) = ratlines_iter.next() else {
return Err(AutorouterError::NothingToRoute);
};
let (origin, destination) = autorouter.ratline_endpoints(curr_ratline);
let mut router = Router::new(autorouter.board.layout_mut(), options.router_options);
Ok(Self {
ratlines_iter,
layer,
options,
route: Some(router.route(
LayoutEdit::new(),
origin,
destination,
layer,
options.router_options.routed_band_width,
)?),
curr_ratline: Some(curr_ratline),
})
}
}
impl<M: AccessMesadata> Step<Autorouter<M>, Option<LayoutEdit>, AutorouteContinueStatus>
for AutorouteExecutionStepper
{
type Error = AutorouterError;
fn step(
&mut self,
autorouter: &mut Autorouter<M>,
) -> Result<ControlFlow<Option<LayoutEdit>, AutorouteContinueStatus>, AutorouterError> {
let Some(curr_ratline) = self.curr_ratline else {
let recorder = if let Some(taken_route) = self.route.take() {
let (_thetastar, navcord, ..) = taken_route.dissolve();
navcord.recorder
} else {
LayoutEdit::new()
};
return Ok(ControlFlow::Break(Some(recorder)));
};
let Some(ref mut route) = self.route else {
// Shouldn't happen.
return Ok(ControlFlow::Break(None));
};
let (source, target) = autorouter.ratline_endpoints(curr_ratline);
let ret = if let Some(band_termseg) = autorouter.board.band_between_nodes(source, target) {
AutorouteContinueStatus::Skipped(band_termseg[false])
} else {
let band_termseg = {
let mut router =
Router::new(autorouter.board.layout_mut(), self.options.router_options);
let ControlFlow::Break(band_termseg) = route.step(&mut router)? else {
return Ok(ControlFlow::Continue(AutorouteContinueStatus::Running));
};
band_termseg
};
let band = autorouter
.board
.layout()
.drawing()
.loose_band_uid(band_termseg.into())
.expect("a completely routed band should've Seg's as ends");
autorouter
.ratsnest
.assign_band_termseg_to_ratline(self.curr_ratline.unwrap(), band_termseg);
autorouter
.board
.try_set_band_between_nodes(source, target, band);
AutorouteContinueStatus::Routed(band_termseg)
};
if let Some(new_ratline) = self.ratlines_iter.next() {
let (source, target) = autorouter.ratline_endpoints(new_ratline);
let mut router =
Router::new(autorouter.board.layout_mut(), self.options.router_options);
self.curr_ratline = Some(new_ratline);
let recorder = if let Some(taken_route) = self.route.take() {
let (_thetastar, navcord, ..) = taken_route.dissolve();
navcord.recorder
} else {
LayoutEdit::new()
};
self.route = Some(router.route(
recorder,
source,
target,
self.layer,
self.options.router_options.routed_band_width,
)?);
} else {
self.curr_ratline = None;
}
Ok(ControlFlow::Continue(ret))
}
}
impl GetDebugOverlayData for AutorouteExecutionStepper {
fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> {
self.route.as_ref().map(|route| route.thetastar())
}
fn maybe_navcord(&self) -> Option<&Navcord> {
self.route.as_ref().map(|route| route.navcord())
}
fn ghosts(&self) -> &[PrimitiveShape] {
self.route.as_ref().map_or(&[], |route| route.ghosts())
}
fn obstacles(&self) -> &[PrimitiveIndex] {
self.route.as_ref().map_or(&[], |route| route.obstacles())
}
}