feat(autorouter/autoroute): Record band assigns so undo actually works

Aborting the autoroute stepper was unimplemented and it would make the
autorouting job finish instead. This is fixed now.
This commit is contained in:
Mikolaj Wielgus 2025-07-13 22:49:30 +02:00
parent 1cc75a79e8
commit d7129354a1
16 changed files with 237 additions and 123 deletions

View File

@ -12,7 +12,7 @@ use topola::autorouter::selection::PinSelection;
use topola::autorouter::Autorouter; use topola::autorouter::Autorouter;
use topola::autorouter::AutorouterOptions; use topola::autorouter::AutorouterOptions;
use topola::autorouter::PresortBy; use topola::autorouter::PresortBy;
use topola::layout::LayoutEdit; use topola::board::edit::BoardEdit;
use topola::router::RouterOptions; use topola::router::RouterOptions;
use topola::specctra::design::SpecctraDesign; use topola::specctra::design::SpecctraDesign;
@ -27,7 +27,7 @@ fn main() -> Result<(), std::io::Error> {
let design = let design =
SpecctraDesign::load(design_bufread).expect("File failed to parse as Specctra DSN"); SpecctraDesign::load(design_bufread).expect("File failed to parse as Specctra DSN");
let board = design.make_board(&mut LayoutEdit::new()); let board = design.make_board(&mut BoardEdit::new());
let history = if let Some(commands_filename) = args.commands { let history = if let Some(commands_filename) = args.commands {
let command_file = File::open(commands_filename)?; let command_file = File::open(commands_filename)?;

View File

@ -9,11 +9,12 @@ use std::{
use topola::{ use topola::{
autorouter::{execution::Command, history::History}, autorouter::{execution::Command, history::History},
board::edit::BoardEdit,
interactor::{ interactor::{
activity::{InteractiveEvent, InteractiveEventKind, InteractiveInput}, activity::{InteractiveEvent, InteractiveEventKind, InteractiveInput},
Interactor, Interactor,
}, },
layout::{via::ViaWeight, LayoutEdit}, layout::via::ViaWeight,
math::Circle, math::Circle,
specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata}, specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata},
}; };
@ -40,7 +41,7 @@ pub struct Workspace {
impl Workspace { impl Workspace {
pub fn new(design: SpecctraDesign, tr: &Translator) -> Result<Self, String> { pub fn new(design: SpecctraDesign, tr: &Translator) -> Result<Self, String> {
let board = design.make_board(&mut LayoutEdit::new()); let board = design.make_board(&mut BoardEdit::new());
let appearance_panel = AppearancePanel::new(&board); let appearance_panel = AppearancePanel::new(&board);
let overlay = Overlay::new(&board).map_err(|err| { let overlay = Overlay::new(&board).map_err(|err| {
format!( format!(

View File

@ -8,9 +8,12 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use crate::{ use crate::{
board::AccessMesadata, board::{
edit::{BoardDataEdit, BoardEdit},
AccessMesadata,
},
drawing::{band::BandTermsegIndex, graph::PrimitiveIndex, Collect}, drawing::{band::BandTermsegIndex, graph::PrimitiveIndex, Collect},
geometry::{edit::ApplyGeometryEdit, primitive::PrimitiveShape}, geometry::primitive::PrimitiveShape,
graph::MakeRef, graph::MakeRef,
layout::LayoutEdit, layout::LayoutEdit,
router::{ router::{
@ -42,6 +45,9 @@ pub struct AutorouteExecutionStepper {
curr_ratline_index: usize, curr_ratline_index: usize,
/// Stores the current route being processed, if any. /// Stores the current route being processed, if any.
route: Option<RouteStepper>, route: Option<RouteStepper>,
/// Records the changes to the board data (changes to layout data are
/// recorded in navcord in route stepper).
data_edit: BoardDataEdit,
/// The options for the autorouting process, defining how routing should be carried out. /// The options for the autorouting process, defining how routing should be carried out.
options: AutorouterOptions, options: AutorouterOptions,
} }
@ -73,12 +79,22 @@ impl AutorouteExecutionStepper {
destination, destination,
options.router_options.routed_band_width, options.router_options.routed_band_width,
)?), )?),
data_edit: BoardDataEdit::new(),
options, options,
}) })
} }
fn dissolve_route_stepper_into_layout_edit(&mut self) -> LayoutEdit {
if let Some(taken_route) = self.route.take() {
let (_thetastar, navcord, ..) = taken_route.dissolve();
navcord.recorder
} else {
LayoutEdit::new()
}
}
} }
impl<M: AccessMesadata> Step<Autorouter<M>, Option<LayoutEdit>, AutorouteContinueStatus> impl<M: AccessMesadata> Step<Autorouter<M>, Option<BoardEdit>, AutorouteContinueStatus>
for AutorouteExecutionStepper for AutorouteExecutionStepper
{ {
type Error = AutorouterError; type Error = AutorouterError;
@ -86,20 +102,17 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<LayoutEdit>, AutorouteContinu
fn step( fn step(
&mut self, &mut self,
autorouter: &mut Autorouter<M>, autorouter: &mut Autorouter<M>,
) -> Result<ControlFlow<Option<LayoutEdit>, AutorouteContinueStatus>, AutorouterError> { ) -> Result<ControlFlow<Option<BoardEdit>, AutorouteContinueStatus>, AutorouterError> {
if self.curr_ratline_index >= self.ratlines.len() { if self.curr_ratline_index >= self.ratlines.len() {
let recorder = if let Some(taken_route) = self.route.take() { let recorder = self.dissolve_route_stepper_into_layout_edit();
let (_thetastar, navcord, ..) = taken_route.dissolve(); return Ok(ControlFlow::Break(Some(BoardEdit::new_from_edits(
navcord.recorder self.data_edit.clone(),
} else { recorder,
LayoutEdit::new() ))));
};
return Ok(ControlFlow::Break(Some(recorder)));
} }
let Some(ref mut route) = self.route else { let Some(ref mut route) = self.route else {
// Shouldn't happen. // May happen if stepper was aborted.
return Ok(ControlFlow::Break(None)); return Ok(ControlFlow::Break(None));
}; };
@ -134,7 +147,7 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<LayoutEdit>, AutorouteContinu
autorouter autorouter
.board .board
.try_set_band_between_nodes(source, target, band); .try_set_band_between_nodes(&mut self.data_edit, source, target, band);
AutorouteContinueStatus::Routed(band_termseg) AutorouteContinueStatus::Routed(band_termseg)
}; };
@ -145,13 +158,7 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<LayoutEdit>, AutorouteContinu
let (source, target) = new_ratline.ref_(autorouter).endpoint_dots(); let (source, target) = new_ratline.ref_(autorouter).endpoint_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);
let recorder = self.dissolve_route_stepper_into_layout_edit();
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( self.route = Some(router.route(
recorder, recorder,
@ -167,12 +174,13 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<LayoutEdit>, AutorouteContinu
impl<M: AccessMesadata> Abort<Autorouter<M>> for AutorouteExecutionStepper { impl<M: AccessMesadata> Abort<Autorouter<M>> for AutorouteExecutionStepper {
fn abort(&mut self, autorouter: &mut Autorouter<M>) { fn abort(&mut self, autorouter: &mut Autorouter<M>) {
if let Some(ref route) = self.route { let layout_edit = self.dissolve_route_stepper_into_layout_edit();
autorouter.board.apply(&route.navcord().recorder.reverse()); let board_edit = BoardEdit::new_from_edits(self.data_edit.clone(), layout_edit);
autorouter.board.apply_edit(&board_edit.reverse());
self.curr_ratline_index = self.ratlines.len(); self.curr_ratline_index = self.ratlines.len();
} }
} }
}
impl EstimateProgress for AutorouteExecutionStepper { impl EstimateProgress for AutorouteExecutionStepper {
type Value = f64; type Value = f64;

View File

@ -8,8 +8,8 @@ use enum_dispatch::enum_dispatch;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
board::AccessMesadata, board::{edit::BoardEdit, AccessMesadata},
layout::{via::ViaWeight, LayoutEdit}, layout::via::ViaWeight,
router::ng, router::ng,
stepper::{Abort, EstimateProgress, Step}, stepper::{Abort, EstimateProgress, Step},
}; };
@ -57,7 +57,7 @@ impl<M: AccessMesadata + Clone> ExecutionStepper<M> {
fn step_catch_err( fn step_catch_err(
&mut self, &mut self,
autorouter: &mut Autorouter<M>, autorouter: &mut Autorouter<M>,
) -> Result<ControlFlow<(Option<LayoutEdit>, String)>, InvokerError> { ) -> Result<ControlFlow<(Option<BoardEdit>, String)>, InvokerError> {
Ok(match self { Ok(match self {
ExecutionStepper::Autoroute(autoroute) => match autoroute.step(autorouter)? { ExecutionStepper::Autoroute(autoroute) => match autoroute.step(autorouter)? {
ControlFlow::Continue(..) => ControlFlow::Continue(()), ControlFlow::Continue(..) => ControlFlow::Continue(()),
@ -74,9 +74,12 @@ impl<M: AccessMesadata + Clone> ExecutionStepper<M> {
ControlFlow::Break(true) => { ControlFlow::Break(true) => {
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 autorouter.board.try_set_band_between_nodes(
.board &mut autoroute.last_recorder.data_edit,
.try_set_band_between_nodes(source, target, *band); source,
target,
*band,
);
} }
let topo_navmesh = autoroute.maybe_topo_navmesh().unwrap().to_owned(); let topo_navmesh = autoroute.maybe_topo_navmesh().unwrap().to_owned();

View File

@ -10,7 +10,7 @@ use derive_getters::{Dissolve, Getters};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use crate::{autorouter::execution::Command, layout::LayoutEdit}; use crate::{autorouter::execution::Command, board::edit::BoardEdit};
#[derive(Error, Debug, Clone)] #[derive(Error, Debug, Clone)]
pub enum HistoryError { pub enum HistoryError {
@ -25,7 +25,7 @@ pub enum HistoryError {
pub struct HistoryEntry { pub struct HistoryEntry {
command: Command, command: Command,
#[serde(skip)] #[serde(skip)]
edit: Option<LayoutEdit>, edit: Option<BoardEdit>,
} }
#[derive(Debug, Default, Clone, Getters, Dissolve, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Getters, Dissolve, Serialize, Deserialize)]
@ -39,7 +39,7 @@ impl History {
Self::default() Self::default()
} }
pub fn do_(&mut self, command: Command, edit: Option<LayoutEdit>) { pub fn do_(&mut self, command: Command, edit: Option<BoardEdit>) {
self.done.push(HistoryEntry { command, edit }); self.done.push(HistoryEntry { command, edit });
} }

View File

@ -15,7 +15,7 @@ use thiserror::Error;
use crate::{ use crate::{
board::AccessMesadata, board::AccessMesadata,
drawing::graph::PrimitiveIndex, drawing::graph::PrimitiveIndex,
geometry::{edit::ApplyGeometryEdit, primitive::PrimitiveShape, shape::MeasureLength}, geometry::{primitive::PrimitiveShape, shape::MeasureLength},
graph::{GenericIndex, MakeRef}, graph::{GenericIndex, MakeRef},
layout::poly::PolyWeight, layout::poly::PolyWeight,
router::{ router::{
@ -256,7 +256,7 @@ impl<M: AccessMesadata + Clone> Invoker<M> {
let last_done = self.history.last_done()?; let last_done = self.history.last_done()?;
if let Some(edit) = last_done.edit() { if let Some(edit) = last_done.edit() {
self.autorouter.board.apply(&edit.reverse()); self.autorouter.board.apply_edit(&edit.reverse());
} }
Ok(self.history.undo()?) Ok(self.history.undo()?)
@ -268,7 +268,7 @@ impl<M: AccessMesadata + Clone> Invoker<M> {
let last_undone = self.history.last_undone()?; let last_undone = self.history.last_undone()?;
if let Some(edit) = last_undone.edit() { if let Some(edit) = last_undone.edit() {
self.autorouter.board.apply(edit); self.autorouter.board.apply_edit(edit);
} }
Ok(self.history.redo()?) Ok(self.history.redo()?)

View File

@ -7,7 +7,10 @@
//! checks if the via has already been placed. //! checks if the via has already been placed.
use crate::{ use crate::{
board::AccessMesadata, board::{
edit::{BoardDataEdit, BoardEdit},
AccessMesadata,
},
layout::{via::ViaWeight, LayoutEdit}, layout::{via::ViaWeight, LayoutEdit},
stepper::EstimateProgress, stepper::EstimateProgress,
}; };
@ -31,16 +34,19 @@ impl PlaceViaExecutionStepper {
pub fn doit( pub fn doit(
&mut self, &mut self,
autorouter: &mut Autorouter<impl AccessMesadata>, autorouter: &mut Autorouter<impl AccessMesadata>,
) -> Result<Option<LayoutEdit>, AutorouterError> { ) -> Result<Option<BoardEdit>, AutorouterError> {
if !self.done { if !self.done {
self.done = true; self.done = true;
let mut edit = LayoutEdit::new(); let mut layout_edit = LayoutEdit::new();
autorouter autorouter
.board .board
.layout_mut() .layout_mut()
.add_via(&mut edit, self.weight)?; .add_via(&mut layout_edit, self.weight)?;
Ok(Some(edit)) Ok(Some(BoardEdit::new_from_edits(
BoardDataEdit::new(),
layout_edit,
)))
} else { } else {
Ok(None) Ok(None)
} }

View File

@ -7,7 +7,7 @@ use std::ops::ControlFlow;
use geo::Point; use geo::Point;
use crate::{ use crate::{
board::AccessMesadata, board::{edit::BoardEdit, AccessMesadata},
drawing::{ drawing::{
band::BandTermsegIndex, band::BandTermsegIndex,
dot::{FixedDotIndex, FixedDotWeight, GeneralDotWeight}, dot::{FixedDotIndex, FixedDotWeight, GeneralDotWeight},
@ -33,7 +33,7 @@ impl PointrouteExecutionStepper {
options: AutorouterOptions, options: AutorouterOptions,
) -> Result<Self, AutorouterError> { ) -> Result<Self, AutorouterError> {
let destination = autorouter.board.add_fixed_dot_infringably( let destination = autorouter.board.add_fixed_dot_infringably(
&mut LayoutEdit::new(), &mut BoardEdit::new(), // TODO?
FixedDotWeight(GeneralDotWeight { FixedDotWeight(GeneralDotWeight {
circle: Circle { circle: Circle {
pos: point, pos: point,
@ -49,7 +49,7 @@ impl PointrouteExecutionStepper {
Ok(Self { Ok(Self {
route: router.route( route: router.route(
LayoutEdit::new(), LayoutEdit::new(), // TODO?
origin, origin,
destination, destination,
options.router_options.routed_band_width, options.router_options.routed_band_width,

View File

@ -4,7 +4,10 @@
//! Provides functionality to remove bands from the layout. //! Provides functionality to remove bands from the layout.
use crate::{board::AccessMesadata, layout::LayoutEdit, stepper::EstimateProgress}; use crate::{
board::{edit::BoardEdit, AccessMesadata},
stepper::EstimateProgress,
};
use super::{invoker::GetDebugOverlayData, selection::BandSelection, Autorouter, AutorouterError}; use super::{invoker::GetDebugOverlayData, selection::BandSelection, Autorouter, AutorouterError};
@ -25,11 +28,12 @@ impl RemoveBandsExecutionStepper {
pub fn doit( pub fn doit(
&mut self, &mut self,
autorouter: &mut Autorouter<impl AccessMesadata>, autorouter: &mut Autorouter<impl AccessMesadata>,
) -> Result<Option<LayoutEdit>, AutorouterError> { ) -> Result<Option<BoardEdit>, AutorouterError> {
if !self.done { if !self.done {
self.done = true; self.done = true;
let mut edit = LayoutEdit::new(); let mut edit = BoardEdit::new();
for selector in self.selection.selectors() { for selector in self.selection.selectors() {
let band = *autorouter.board.bandname_band(&selector.band).unwrap(); let band = *autorouter.board.bandname_band(&selector.band).unwrap();
autorouter autorouter

62
src/board/edit.rs Normal file
View File

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2025 Topola contributors
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use std::collections::BTreeMap;
use crate::{drawing::band::BandUid, layout::LayoutEdit};
use super::BandName;
#[derive(Debug, Clone)]
pub struct BoardDataEdit {
pub(super) bands: BTreeMap<BandName, (Option<BandUid>, Option<BandUid>)>,
}
impl BoardDataEdit {
pub fn new() -> Self {
Self {
bands: BTreeMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct BoardEdit {
pub data_edit: BoardDataEdit,
pub layout_edit: LayoutEdit,
}
impl BoardEdit {
pub fn new() -> Self {
Self {
data_edit: BoardDataEdit::new(),
layout_edit: LayoutEdit::new(),
}
}
pub fn new_from_edits(data_edit: BoardDataEdit, layout_edit: LayoutEdit) -> Self {
Self {
data_edit,
layout_edit,
}
}
pub fn reverse_inplace(&mut self) {
self.data_edit
.bands
.values_mut()
.for_each(Self::swap_tuple_inplace);
self.layout_edit.reverse_inplace();
}
fn swap_tuple_inplace<D>(x: &mut (D, D)) {
core::mem::swap(&mut x.0, &mut x.1);
}
pub fn reverse(&self) -> Self {
let mut rev = self.clone();
rev.reverse_inplace();
rev
}
}

View File

@ -2,10 +2,12 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
//! Provides the functionality to create and manage relationships //! Manages relationship between pins and bands and the primitives and compounds
//! between nodes, pins, and bands, as well as handle metadata and geometric data //! that consitutite them on the layout. And some more.
//! for layout construction.
pub mod edit;
use edit::{BoardDataEdit, BoardEdit};
pub use specctra_core::mesadata::AccessMesadata; pub use specctra_core::mesadata::AccessMesadata;
use bimap::BiBTreeMap; use bimap::BiBTreeMap;
@ -16,15 +18,14 @@ use std::collections::BTreeMap;
use crate::{ use crate::{
drawing::{ drawing::{
band::BandUid, band::BandUid,
bend::{BendIndex, BendWeight}, dot::{FixedDotIndex, FixedDotWeight},
dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight},
graph::PrimitiveIndex, graph::PrimitiveIndex,
seg::{FixedSegIndex, FixedSegWeight, SegIndex, SegWeight}, seg::{FixedSegIndex, FixedSegWeight},
Collect, DrawingException, Collect, DrawingException,
}, },
geometry::{edit::ApplyGeometryEdit, GenericNode, GetLayer}, geometry::{edit::ApplyGeometryEdit, GenericNode, GetLayer},
graph::{GenericIndex, MakeRef}, graph::{GenericIndex, MakeRef},
layout::{poly::PolyWeight, CompoundEntryLabel, CompoundWeight, Layout, LayoutEdit, NodeIndex}, layout::{poly::PolyWeight, CompoundWeight, Layout, NodeIndex},
router::ng::EtchedPath, router::ng::EtchedPath,
}; };
@ -109,11 +110,13 @@ impl<M: AccessMesadata> Board<M> {
/// 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.
pub fn add_fixed_dot_infringably( pub fn add_fixed_dot_infringably(
&mut self, &mut self,
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
weight: FixedDotWeight, weight: FixedDotWeight,
maybe_pin: Option<String>, maybe_pin: Option<String>,
) -> FixedDotIndex { ) -> FixedDotIndex {
let dot = self.layout.add_fixed_dot_infringably(recorder, weight); let dot = self
.layout
.add_fixed_dot_infringably(&mut recorder.layout_edit, weight);
if let Some(pin) = maybe_pin { if let Some(pin) = maybe_pin {
self.node_to_pinname self.node_to_pinname
@ -128,15 +131,15 @@ impl<M: AccessMesadata> Board<M> {
/// Adds the segment to the layout and updates the internal mapping if necessary. /// Adds the segment to the layout and updates the internal mapping if necessary.
pub fn add_fixed_seg_infringably( pub fn add_fixed_seg_infringably(
&mut self, &mut self,
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
from: FixedDotIndex, from: FixedDotIndex,
to: FixedDotIndex, to: FixedDotIndex,
weight: FixedSegWeight, weight: FixedSegWeight,
maybe_pin: Option<String>, maybe_pin: Option<String>,
) -> FixedSegIndex { ) -> FixedSegIndex {
let seg = self let seg =
.layout self.layout
.add_fixed_seg_infringably(recorder, from, to, weight); .add_fixed_seg_infringably(&mut recorder.layout_edit, from, to, weight);
if let Some(pin) = maybe_pin { if let Some(pin) = maybe_pin {
self.node_to_pinname self.node_to_pinname
@ -151,12 +154,14 @@ impl<M: AccessMesadata> Board<M> {
/// Inserts the polygon into the layout and, if a pin name is provided, maps it to the created polygon's node. /// Inserts the polygon into the layout and, if a pin name is provided, maps it to the created polygon's node.
pub fn add_poly_with_nodes( pub fn add_poly_with_nodes(
&mut self, &mut self,
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
weight: PolyWeight, weight: PolyWeight,
maybe_pin: Option<String>, maybe_pin: Option<String>,
nodes: &[PrimitiveIndex], nodes: &[PrimitiveIndex],
) -> GenericIndex<PolyWeight> { ) -> GenericIndex<PolyWeight> {
let (poly, apex) = self.layout.add_poly_with_nodes(recorder, weight, nodes); let (poly, apex) =
self.layout
.add_poly_with_nodes(&mut recorder.layout_edit, weight, nodes);
if let Some(pin) = maybe_pin { if let Some(pin) = maybe_pin {
for i in nodes { for i in nodes {
@ -203,6 +208,7 @@ impl<M: AccessMesadata> Board<M> {
/// Creates band between the two nodes /// Creates band between the two nodes
pub fn try_set_band_between_nodes( pub fn try_set_band_between_nodes(
&mut self, &mut self,
recorder: &mut BoardDataEdit,
source: FixedDotIndex, source: FixedDotIndex,
target: FixedDotIndex, target: FixedDotIndex,
band: BandUid, band: BandUid,
@ -216,6 +222,7 @@ impl<M: AccessMesadata> Board<M> {
.unwrap() .unwrap()
.to_string(); .to_string();
let bandname = BandName::from((source_pinname, target_pinname)); let bandname = BandName::from((source_pinname, target_pinname));
if self.band_bandname.get_by_right(&bandname).is_some() { if self.band_bandname.get_by_right(&bandname).is_some() {
false false
} else { } else {
@ -223,7 +230,9 @@ impl<M: AccessMesadata> Board<M> {
end_points: (source, target).into(), end_points: (source, target).into(),
}; };
self.bands_by_id.insert(ep, band); self.bands_by_id.insert(ep, band);
self.band_bandname.insert(band, bandname); self.band_bandname.insert(band, bandname.clone());
recorder.bands.insert(bandname, (None, Some(band)));
true true
} }
} }
@ -242,10 +251,10 @@ impl<M: AccessMesadata> Board<M> {
self.band_between_pins(source_pinname, target_pinname) self.band_between_pins(source_pinname, target_pinname)
} }
/// Removes the band between the two nodes /// Removes the band between the two nodes.
pub fn remove_band_between_nodes( pub fn remove_band_between_nodes(
&mut self, &mut self,
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
source: FixedDotIndex, source: FixedDotIndex,
target: FixedDotIndex, target: FixedDotIndex,
) -> Result<(), DrawingException> { ) -> Result<(), DrawingException> {
@ -260,25 +269,35 @@ impl<M: AccessMesadata> Board<M> {
.node_pinname(&GenericNode::Primitive(target.into())) .node_pinname(&GenericNode::Primitive(target.into()))
.unwrap() .unwrap()
.to_string(); .to_string();
self.band_bandname
.remove_by_right(&BandName::from((source_pinname, target_pinname))); let bandname = BandName::from((source_pinname, target_pinname));
let maybe_band = self.band_bandname.get_by_right(&bandname).cloned();
self.band_bandname.remove_by_right(&bandname);
if let Some((_, uid)) = self.bands_by_id.remove_by_left(&ep) { if let Some((_, uid)) = self.bands_by_id.remove_by_left(&ep) {
let (from, _) = uid.into(); let (from, _) = uid.into();
self.layout.remove_band(recorder, from)?; self.layout.remove_band(&mut recorder.layout_edit, from)?;
} }
recorder
.data_edit
.bands
.insert(bandname, (maybe_band, None));
Ok(()) Ok(())
} }
/// Removes the band between two nodes given by [`BandUid`] /// Removes the band between two nodes given by [`BandUid`].
pub fn remove_band_by_id( pub fn remove_band_by_id(
&mut self, &mut self,
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
uid: BandUid, uid: BandUid,
) -> Result<(), DrawingException> { ) -> Result<(), DrawingException> {
if let Some(ep) = self.bands_by_id.get_by_right(&uid) { if let Some(ep) = self.bands_by_id.get_by_right(&uid) {
let (source, target) = ep.end_points.into(); let (source, target) = ep.end_points.into();
self.remove_band_between_nodes(recorder, source, target)?; self.remove_band_between_nodes(recorder, source, target)?;
} }
Ok(()) Ok(())
} }
@ -293,26 +312,24 @@ impl<M: AccessMesadata> Board<M> {
.copied() .copied()
} }
pub fn apply_edit(&mut self, edit: &BoardEdit) {
for (bandname, (maybe_old_band_uid, ..)) in &edit.data_edit.bands {
if maybe_old_band_uid.is_some() {
self.band_bandname.remove_by_right(bandname);
}
}
self.layout_mut().apply(&edit.layout_edit);
for (bandname, (.., maybe_new_band_uid)) in &edit.data_edit.bands {
if let Some(band_uid) = maybe_new_band_uid {
self.band_bandname.insert(*band_uid, bandname.clone());
}
}
}
/// Returns the mesadata associated with the layout's drawing rules. /// Returns the mesadata associated with the layout's drawing rules.
pub fn mesadata(&self) -> &M { pub fn mesadata(&self) -> &M {
self.layout.drawing().rules() self.layout.drawing().rules()
} }
} }
impl<M: AccessMesadata>
ApplyGeometryEdit<
DotWeight,
SegWeight,
BendWeight,
CompoundWeight,
CompoundEntryLabel,
PrimitiveIndex,
DotIndex,
SegIndex,
BendIndex,
> for Board<M>
{
fn apply(&mut self, edit: &LayoutEdit) {
self.layout.apply(edit);
}
}

View File

@ -145,7 +145,12 @@ impl AstarContext {
} }
} }
let fin = layout.finish_in_dot(&mut recorder, sub.active_head, prim, width)?; let fin = layout.finish_in_dot(
&mut recorder.layout_edit,
sub.active_head,
prim,
width,
)?;
length += sub length += sub
.active_head .active_head
.maybe_cane() .maybe_cane()

View File

@ -16,7 +16,7 @@ use std::{
}; };
use crate::{ use crate::{
board::Board, board::{edit::BoardEdit, Board},
drawing::{ drawing::{
band::BandUid, band::BandUid,
bend::BendIndex, bend::BendIndex,
@ -34,7 +34,7 @@ use crate::{
GenericNode, GenericNode,
}, },
graph::GetPetgraphIndex as _, graph::GetPetgraphIndex as _,
layout::{Layout, LayoutEdit}, layout::Layout,
math::{CachedPolyExt, RotationSense}, math::{CachedPolyExt, RotationSense},
router::draw::{Draw, DrawException}, router::draw::{Draw, DrawException},
}; };
@ -122,7 +122,7 @@ pub struct Common<R> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct AstarContext { pub struct AstarContext {
/// TODO: make sure we can trust the `LayoutEdit` /// TODO: make sure we can trust the `LayoutEdit`
pub recorder: LayoutEdit, pub recorder: BoardEdit,
pub bands: BTreeMap<EtchedPath, BandUid>, pub bands: BTreeMap<EtchedPath, BandUid>,
@ -135,7 +135,7 @@ pub struct AstarContext {
impl AstarContext { impl AstarContext {
pub fn last_layout<R: AccessRules + Clone>(&self, common: &Common<R>) -> Layout<R> { pub fn last_layout<R: AccessRules + Clone>(&self, common: &Common<R>) -> Layout<R> {
let mut layout = common.layout.clone(); let mut layout = common.layout.clone();
layout.apply(&self.recorder); layout.apply(&self.recorder.layout_edit);
layout layout
} }
} }
@ -557,7 +557,7 @@ impl SubContext {
fn cane_around<R: AccessRules>( fn cane_around<R: AccessRules>(
layout: &mut Layout<R>, layout: &mut Layout<R>,
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
route_length: &mut f64, route_length: &mut f64,
old_head: Head, old_head: Head,
core: FixedDotIndex, core: FixedDotIndex,
@ -574,7 +574,7 @@ fn cane_around<R: AccessRules>(
); );
let ret = match inner { let ret = match inner {
None => layout.cane_around_dot(recorder, old_head, core, sense, width), None => layout.cane_around_dot(&mut recorder.layout_edit, old_head, core, sense, width),
Some(inner) => { Some(inner) => {
// now, inner is expected to be a bend. // now, inner is expected to be a bend.
// TODO: handle the case that the same path wraps multiple times around the same core // TODO: handle the case that the same path wraps multiple times around the same core
@ -595,7 +595,13 @@ fn cane_around<R: AccessRules>(
}) })
.next(); .next();
if let Some(inner_bend) = inner_bend { if let Some(inner_bend) = inner_bend {
layout.cane_around_bend(recorder, old_head, inner_bend.into(), sense, width) layout.cane_around_bend(
&mut recorder.layout_edit,
old_head,
inner_bend.into(),
sense,
width,
)
} else { } else {
return Err(EvalException::BendNotFound { return Err(EvalException::BendNotFound {
core: core, core: core,

View File

@ -6,6 +6,7 @@ use geo::Point;
use specctra_core::rules::AccessRules; use specctra_core::rules::AccessRules;
use crate::{ use crate::{
board::edit::BoardEdit,
drawing::{ drawing::{
band::BandUid, band::BandUid,
dot::FixedDotIndex, dot::FixedDotIndex,
@ -15,7 +16,7 @@ use crate::{
}, },
geometry::{compound::ManageCompounds, shape::AccessShape as _, GetSetPos as _}, geometry::{compound::ManageCompounds, shape::AccessShape as _, GetSetPos as _},
graph::{GenericIndex, GetPetgraphIndex as _}, graph::{GenericIndex, GetPetgraphIndex as _},
layout::{poly::PolyWeight, CompoundEntryLabel, Layout, LayoutEdit}, layout::{poly::PolyWeight, CompoundEntryLabel, Layout},
math::{is_poly_convex_hull_cw, CachedPolyExt, RotationSense}, math::{is_poly_convex_hull_cw, CachedPolyExt, RotationSense},
router::ng::{ router::ng::{
pie::{mayrev, utils::rotate_iter}, pie::{mayrev, utils::rotate_iter},
@ -112,7 +113,7 @@ impl PolygonRouting {
fn route_next<R: AccessRules>( fn route_next<R: AccessRules>(
&self, &self,
layout: &mut Layout<R>, layout: &mut Layout<R>,
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
route_length: &mut f64, route_length: &mut f64,
old_head: Head, old_head: Head,
ext_core: FixedDotIndex, ext_core: FixedDotIndex,
@ -133,7 +134,7 @@ impl PolygonRouting {
pub fn route_to_entry<R: AccessRules>( pub fn route_to_entry<R: AccessRules>(
&self, &self,
layout: &mut Layout<R>, layout: &mut Layout<R>,
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
old_head: Head, old_head: Head,
entry_point: FixedDotIndex, entry_point: FixedDotIndex,
width: f64, width: f64,
@ -153,7 +154,7 @@ impl PolygonRouting {
pub fn route_to_exit<R: AccessRules>( pub fn route_to_exit<R: AccessRules>(
&self, &self,
layout: &mut Layout<R>, layout: &mut Layout<R>,
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
mut active_head: Head, mut active_head: Head,
exit: FixedDotIndex, exit: FixedDotIndex,
width: f64, width: f64,

View File

@ -11,10 +11,11 @@ use std::{
use crate::{ use crate::{
autorouter::invoker::GetDebugOverlayData, autorouter::invoker::GetDebugOverlayData,
board::edit::BoardEdit,
drawing::{band::BandUid, dot::FixedDotIndex, graph::PrimitiveIndex, rules::AccessRules}, drawing::{band::BandUid, dot::FixedDotIndex, graph::PrimitiveIndex, rules::AccessRules},
geometry::primitive::PrimitiveShape, geometry::primitive::PrimitiveShape,
graph::GenericIndex, graph::GenericIndex,
layout::{poly::PolyWeight, Layout, LayoutEdit}, layout::{poly::PolyWeight, Layout},
stepper::{Abort, EstimateProgress}, stepper::{Abort, EstimateProgress},
}; };
@ -68,7 +69,7 @@ pub struct AutorouteExecutionStepper<R> {
original_edge_paths: Box<[EdgePaths<EtchedPath, ()>]>, original_edge_paths: Box<[EdgePaths<EtchedPath, ()>]>,
pub last_layout: Layout<R>, pub last_layout: Layout<R>,
pub last_recorder: LayoutEdit, pub last_recorder: BoardEdit,
pub last_edge_paths: Box<[EdgePaths<EtchedPath, ()>]>, pub last_edge_paths: Box<[EdgePaths<EtchedPath, ()>]>,
pub last_bands: BTreeMap<EtchedPath, BandUid>, pub last_bands: BTreeMap<EtchedPath, BandUid>,
@ -90,7 +91,7 @@ impl<R: Clone> AutorouteExecutionStepper<R> {
impl<M: Clone + std::panic::RefUnwindSafe> Abort<()> for AutorouteExecutionStepper<M> { impl<M: Clone + std::panic::RefUnwindSafe> Abort<()> for AutorouteExecutionStepper<M> {
fn abort(&mut self, _: &mut ()) { fn abort(&mut self, _: &mut ()) {
self.last_layout = self.common.layout.clone(); self.last_layout = self.common.layout.clone();
self.last_recorder = LayoutEdit::new(); self.last_recorder = BoardEdit::new();
self.last_edge_paths = self.original_edge_paths.clone(); self.last_edge_paths = self.original_edge_paths.clone();
self.last_bands = BTreeMap::new(); self.last_bands = BTreeMap::new();
self.finish(); self.finish();
@ -121,7 +122,7 @@ impl<R: AccessRules + Clone + std::panic::RefUnwindSafe> AutorouteExecutionStepp
} }
let context = AstarContext { let context = AstarContext {
recorder: LayoutEdit::new(), recorder: BoardEdit::new(),
bands, bands,
length: 0.0, length: 0.0,
sub: None, sub: None,
@ -154,7 +155,7 @@ impl<R: AccessRules + Clone + std::panic::RefUnwindSafe> AutorouteExecutionStepp
common, common,
original_edge_paths: navmesh.edge_paths.clone(), original_edge_paths: navmesh.edge_paths.clone(),
last_layout: layout.clone(), last_layout: layout.clone(),
last_recorder: LayoutEdit::new(), last_recorder: BoardEdit::new(),
last_edge_paths: navmesh.edge_paths.clone(), last_edge_paths: navmesh.edge_paths.clone(),
last_bands: context.bands.clone(), last_bands: context.bands.clone(),
active_polygons: Vec::new(), active_polygons: Vec::new(),
@ -204,7 +205,7 @@ impl<R: AccessRules + Clone + std::panic::RefUnwindSafe> AutorouteExecutionStepp
// no valid result found // no valid result found
ControlFlow::Break(None) => { ControlFlow::Break(None) => {
self.last_layout = self.common.layout.clone(); self.last_layout = self.common.layout.clone();
self.last_recorder = LayoutEdit::new(); self.last_recorder = BoardEdit::new();
self.last_edge_paths = self.original_edge_paths.clone(); self.last_edge_paths = self.original_edge_paths.clone();
self.last_bands = BTreeMap::new(); self.last_bands = BTreeMap::new();
self.finish(); self.finish();
@ -286,7 +287,7 @@ pub struct ManualrouteExecutionStepper<R> {
// results // results
pub last_layout: Layout<R>, pub last_layout: Layout<R>,
pub last_recorder: LayoutEdit, pub last_recorder: BoardEdit,
// visualization / debug // visualization / debug
pub active_polygons: Vec<GenericIndex<PolyWeight>>, pub active_polygons: Vec<GenericIndex<PolyWeight>>,
@ -321,7 +322,7 @@ impl<M: Clone> ManualrouteExecutionStepper<M> {
impl<M: Clone + std::panic::RefUnwindSafe> Abort<()> for ManualrouteExecutionStepper<M> { impl<M: Clone + std::panic::RefUnwindSafe> Abort<()> for ManualrouteExecutionStepper<M> {
fn abort(&mut self, _: &mut ()) { fn abort(&mut self, _: &mut ()) {
self.last_layout = self.common.layout.clone(); self.last_layout = self.common.layout.clone();
self.last_recorder = LayoutEdit::new(); self.last_recorder = BoardEdit::new();
self.context.bands = BTreeMap::new(); self.context.bands = BTreeMap::new();
self.finish(); self.finish();
self.aborted = true; self.aborted = true;
@ -365,7 +366,7 @@ impl<R: AccessRules + Clone + std::panic::RefUnwindSafe> ManualrouteExecutionSte
} }
let context = AstarContext { let context = AstarContext {
recorder: LayoutEdit::new(), recorder: BoardEdit::new(),
bands, bands,
length: 0.0, length: 0.0,
sub: None, sub: None,
@ -382,7 +383,7 @@ impl<R: AccessRules + Clone + std::panic::RefUnwindSafe> ManualrouteExecutionSte
original_edge_paths: tmp_navmesh_edge_paths, original_edge_paths: tmp_navmesh_edge_paths,
last_layout: layout.clone(), last_layout: layout.clone(),
last_recorder: LayoutEdit::new(), last_recorder: BoardEdit::new(),
active_polygons: Vec::new(), active_polygons: Vec::new(),
ghosts: Vec::new(), ghosts: Vec::new(),
polygonal_blockers: Vec::new(), polygonal_blockers: Vec::new(),
@ -514,7 +515,7 @@ impl<R: AccessRules + Clone + std::panic::RefUnwindSafe> ManualrouteExecutionSte
// no valid result found // no valid result found
self.last_layout = self.common.layout.clone(); self.last_layout = self.common.layout.clone();
self.last_recorder = LayoutEdit::new(); self.last_recorder = BoardEdit::new();
self.context.bands = BTreeMap::new(); self.context.bands = BTreeMap::new();
self.finish(); self.finish();
ControlFlow::Break(false) ControlFlow::Break(false)

View File

@ -11,7 +11,7 @@ use std::collections::{btree_map::Entry as BTreeMapEntry, BTreeMap};
use geo::{point, Point, Rotate}; use geo::{point, Point, Rotate};
use crate::{ use crate::{
board::{AccessMesadata, Board}, board::{edit::BoardEdit, AccessMesadata, Board},
drawing::{ drawing::{
dot::{FixedDotWeight, GeneralDotWeight}, dot::{FixedDotWeight, GeneralDotWeight},
graph::{GetMaybeNet, MakePrimitive}, graph::{GetMaybeNet, MakePrimitive},
@ -20,7 +20,7 @@ use crate::{
Drawing, Drawing,
}, },
geometry::{primitive::PrimitiveShape, GetLayer, GetWidth}, geometry::{primitive::PrimitiveShape, GetLayer, GetWidth},
layout::{poly::SolidPolyWeight, Layout, LayoutEdit}, layout::{poly::SolidPolyWeight, Layout},
math::{Circle, PointWithRotation}, math::{Circle, PointWithRotation},
specctra::{ specctra::{
mesadata::SpecctraMesadata, mesadata::SpecctraMesadata,
@ -179,7 +179,7 @@ impl SpecctraDesign {
/// which is used for layout and routing operations. The board is initialized with [`SpecctraMesadata`], /// which is used for layout and routing operations. The board is initialized with [`SpecctraMesadata`],
/// which includes layer and net mappings, and is populated with components, pins, vias, and wires /// which includes layer and net mappings, and is populated with components, pins, vias, and wires
/// from the PCB definition. /// from the PCB definition.
pub fn make_board(&self, recorder: &mut LayoutEdit) -> Board<SpecctraMesadata> { pub fn make_board(&self, recorder: &mut BoardEdit) -> Board<SpecctraMesadata> {
let mesadata = SpecctraMesadata::from_pcb(&self.pcb); let mesadata = SpecctraMesadata::from_pcb(&self.pcb);
let mut board = Board::new(Layout::new(Drawing::new( let mut board = Board::new(Layout::new(Drawing::new(
mesadata, mesadata,
@ -424,7 +424,7 @@ impl SpecctraDesign {
} }
fn add_circle( fn add_circle(
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
board: &mut Board<SpecctraMesadata>, board: &mut Board<SpecctraMesadata>,
place: PointWithRotation, place: PointWithRotation,
pin: PointWithRotation, pin: PointWithRotation,
@ -450,7 +450,7 @@ impl SpecctraDesign {
} }
fn add_rect( fn add_rect(
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
board: &mut Board<SpecctraMesadata>, board: &mut Board<SpecctraMesadata>,
place: PointWithRotation, place: PointWithRotation,
pin: PointWithRotation, pin: PointWithRotation,
@ -575,7 +575,7 @@ impl SpecctraDesign {
} }
fn add_path( fn add_path(
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
board: &mut Board<SpecctraMesadata>, board: &mut Board<SpecctraMesadata>,
place: PointWithRotation, place: PointWithRotation,
pin: PointWithRotation, pin: PointWithRotation,
@ -640,7 +640,7 @@ impl SpecctraDesign {
} }
fn add_polygon( fn add_polygon(
recorder: &mut LayoutEdit, recorder: &mut BoardEdit,
board: &mut Board<SpecctraMesadata>, board: &mut Board<SpecctraMesadata>,
place: PointWithRotation, place: PointWithRotation,
pin: PointWithRotation, pin: PointWithRotation,