feat(board,layout,drawing): implement edit recording

Not stored in the undo/redo objects yet.
This commit is contained in:
Mikolaj Wielgus 2024-10-29 16:58:59 +01:00 committed by mikolaj
parent ad1b43b806
commit d6fe67a373
20 changed files with 490 additions and 223 deletions

View File

@ -2,6 +2,7 @@ use std::fs::File;
use std::io::BufReader;
use topola::autorouter::invoker::Invoker;
use topola::autorouter::Autorouter;
use topola::layout::LayoutEdit;
use topola::specctra::design::SpecctraDesign;
fn main() -> Result<(), std::io::Error> {
@ -9,7 +10,7 @@ fn main() -> Result<(), std::io::Error> {
let design_bufread = BufReader::new(design_file);
let design = SpecctraDesign::load(design_bufread).unwrap();
let board = design.make_board();
let board = design.make_board(&mut LayoutEdit::new());
let invoker = Invoker::new(Autorouter::new(board).unwrap());

View File

@ -8,7 +8,7 @@ use thiserror::Error;
use crate::{
board::{mesadata::AccessMesadata, Board},
drawing::{band::BandTermsegIndex, dot::FixedDotIndex, Infringement},
layout::via::ViaWeight,
layout::{via::ViaWeight, LayoutEdit},
router::{astar::AstarError, navmesh::NavmeshError, RouterOptions},
triangulation::GetTrianvertexNodeIndex,
};
@ -73,7 +73,7 @@ impl<M: AccessMesadata> Autorouter<M> {
.node_index()
{
RatvertexIndex::FixedDot(dot) => dot,
RatvertexIndex::Poly(poly) => self.board.poly_apex(poly),
RatvertexIndex::Poly(poly) => self.board.poly_apex(&mut LayoutEdit::new(), poly),
};
PointrouteExecutionStepper::new(self, origin_dot, point, options)
@ -82,7 +82,7 @@ impl<M: AccessMesadata> Autorouter<M> {
pub fn undo_pointroute(&mut self, band: BandTermsegIndex) -> Result<(), AutorouterError> {
self.board
.layout_mut()
.remove_band(band)
.remove_band(&mut LayoutEdit::new(), band)
.map_err(|_| AutorouterError::CouldNotRemoveBand(band))
}
@ -120,7 +120,7 @@ impl<M: AccessMesadata> Autorouter<M> {
.unwrap();
self.board
.layout_mut()
.remove_band(band)
.remove_band(&mut LayoutEdit::new(), band)
.map_err(|_| AutorouterError::CouldNotRemoveBand(band))?;
}
@ -191,7 +191,7 @@ impl<M: AccessMesadata> Autorouter<M> {
.node_index()
{
RatvertexIndex::FixedDot(dot) => dot,
RatvertexIndex::Poly(poly) => self.board.poly_apex(poly),
RatvertexIndex::Poly(poly) => self.board.poly_apex(&mut LayoutEdit::new(), poly),
};
let target_dot = match self
@ -202,7 +202,7 @@ impl<M: AccessMesadata> Autorouter<M> {
.node_index()
{
RatvertexIndex::FixedDot(dot) => dot,
RatvertexIndex::Poly(poly) => self.board.poly_apex(poly),
RatvertexIndex::Poly(poly) => self.board.poly_apex(&mut LayoutEdit::new(), poly),
};
(source_dot, target_dot)

View File

@ -6,7 +6,7 @@ use crate::{
board::mesadata::AccessMesadata,
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
layout::via::ViaWeight,
layout::{via::ViaWeight, LayoutEdit},
router::{navcord::NavcordStepper, navmesh::Navmesh},
};
@ -35,7 +35,10 @@ impl PlaceViaExecutionStepper {
) -> Result<(), AutorouterError> {
if !self.done {
self.done = true;
autorouter.board.layout_mut().add_via(self.weight)?;
autorouter
.board
.layout_mut()
.add_via(&mut LayoutEdit::new(), self.weight)?;
Ok(())
} else {
Ok(())

View File

@ -8,6 +8,7 @@ use crate::{
band::BandTermsegIndex,
dot::{FixedDotIndex, FixedDotWeight},
},
layout::LayoutEdit,
math::Circle,
router::{route::RouteStepper, Router},
stepper::Step,
@ -29,6 +30,7 @@ impl PointrouteExecutionStepper {
options: AutorouterOptions,
) -> Result<Self, AutorouterError> {
let destination = autorouter.board.add_fixed_dot_infringably(
&mut LayoutEdit::new(),
FixedDotWeight {
circle: Circle {
pos: point,

View File

@ -4,6 +4,7 @@ use crate::{
board::mesadata::AccessMesadata,
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
layout::LayoutEdit,
router::{navcord::NavcordStepper, navmesh::Navmesh},
};
@ -36,7 +37,10 @@ impl RemoveBandsExecutionStepper {
for selector in self.selection.selectors() {
let band = autorouter.board.bandname_band(&selector.band).unwrap().0;
autorouter.board.layout_mut().remove_band(band);
autorouter
.board
.layout_mut()
.remove_band(&mut LayoutEdit::new(), band);
}
Ok(())
} else {

View File

@ -9,6 +9,7 @@ use topola::{
activity::{ActivityContext, ActivityStepperWithStatus, InteractiveInput},
Interactor,
},
layout::LayoutEdit,
specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata},
stepper::Step,
};
@ -30,7 +31,7 @@ pub struct Workspace {
impl Workspace {
pub fn new(design: SpecctraDesign, tr: &Translator) -> Result<Self, String> {
let board = design.make_board();
let board = design.make_board(&mut LayoutEdit::new());
let layers = Layers::new(&board);
let overlay = Overlay::new(&board).map_err(|err| {
format!(

View File

@ -7,6 +7,7 @@ use topola::autorouter::invoker::Invoker;
use topola::autorouter::selection::PinSelection;
use topola::autorouter::Autorouter;
use topola::autorouter::AutorouterOptions;
use topola::layout::LayoutEdit;
use topola::router::RouterOptions;
use topola::specctra::design::SpecctraDesign;
@ -19,7 +20,7 @@ fn main() -> Result<(), std::io::Error> {
let mut design_bufread = BufReader::new(design_file);
let design = SpecctraDesign::load(design_bufread).unwrap();
let board = design.make_board();
let board = design.make_board(&mut LayoutEdit::new());
let history = if let Some(commands_filename) = args.commands {
let command_file = File::open(commands_filename)?;

View File

@ -16,7 +16,7 @@ use crate::{
graph::GenericIndex,
layout::{
poly::{GetMaybeApex, MakePolyShape, PolyWeight},
Layout, NodeIndex,
Layout, LayoutEdit, NodeIndex,
},
math::Circle,
};
@ -67,10 +67,11 @@ 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.
pub fn add_fixed_dot_infringably(
&mut self,
recorder: &mut LayoutEdit,
weight: FixedDotWeight,
maybe_pin: Option<String>,
) -> FixedDotIndex {
let dot = self.layout.add_fixed_dot_infringably(weight);
let dot = self.layout.add_fixed_dot_infringably(recorder, weight);
if let Some(ref pin) = maybe_pin {
self.node_to_pinname
@ -85,10 +86,13 @@ impl<M: AccessMesadata> Board<M> {
/// Adds the segment to the layout and maps the pin name to the created segment if provided.
pub fn add_poly_fixed_dot_infringably(
&mut self,
recorder: &mut LayoutEdit,
weight: FixedDotWeight,
poly: GenericIndex<PolyWeight>,
) -> FixedDotIndex {
let dot = self.layout.add_poly_fixed_dot_infringably(weight, poly);
let dot = self
.layout
.add_poly_fixed_dot_infringably(recorder, weight, poly);
if let Some(pin) = self.node_pinname(&GenericNode::Compound(poly.into())) {
self.node_to_pinname
@ -103,12 +107,15 @@ impl<M: AccessMesadata> Board<M> {
/// Adds the segment to the layout and updates the internal mapping if necessary.
pub fn add_fixed_seg_infringably(
&mut self,
recorder: &mut LayoutEdit,
from: FixedDotIndex,
to: FixedDotIndex,
weight: FixedSegWeight,
maybe_pin: Option<String>,
) -> FixedSegIndex {
let seg = self.layout.add_fixed_seg_infringably(from, to, weight);
let seg = self
.layout
.add_fixed_seg_infringably(recorder, from, to, weight);
if let Some(pin) = maybe_pin {
self.node_to_pinname
@ -123,6 +130,7 @@ impl<M: AccessMesadata> Board<M> {
/// Adds the segment to the layout and updates the internal mapping if necessary.
pub fn add_poly_fixed_seg_infringably(
&mut self,
recorder: &mut LayoutEdit,
from: FixedDotIndex,
to: FixedDotIndex,
weight: FixedSegWeight,
@ -130,7 +138,7 @@ impl<M: AccessMesadata> Board<M> {
) -> FixedSegIndex {
let seg = self
.layout
.add_poly_fixed_seg_infringably(from, to, weight, poly);
.add_poly_fixed_seg_infringably(recorder, from, to, weight, poly);
if let Some(pin) = self.node_pinname(&GenericNode::Compound(poly.into())) {
self.node_to_pinname
@ -145,10 +153,11 @@ 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.
pub fn add_poly(
&mut self,
recorder: &mut LayoutEdit,
weight: PolyWeight,
maybe_pin: Option<String>,
) -> GenericIndex<PolyWeight> {
let poly = self.layout.add_poly(weight);
let poly = self.layout.add_poly(recorder, weight);
if let Some(pin) = maybe_pin {
self.node_to_pinname
@ -161,11 +170,16 @@ impl<M: AccessMesadata> Board<M> {
/// Retrieves or creates the apex (top point) of a polygon in the layout.
///
/// If the polygon already has an apex, returns it. Otherwise, creates and returns a new fixed dot as the apex.
pub fn poly_apex(&mut self, poly: GenericIndex<PolyWeight>) -> FixedDotIndex {
pub fn poly_apex(
&mut self,
recorder: &mut LayoutEdit,
poly: GenericIndex<PolyWeight>,
) -> FixedDotIndex {
if let Some(apex) = self.layout.poly(poly).maybe_apex() {
apex
} else {
self.add_poly_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle: Circle {
pos: self.layout.poly(poly).shape().center(),

View File

@ -14,7 +14,7 @@ use crate::{
use petgraph::stable_graph::NodeIndex;
#[enum_dispatch(GetPetgraphIndex, MakePrimitive)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BendIndex {
Fixed(FixedBendIndex),
Loose(LooseBendIndex),

View File

@ -16,7 +16,7 @@ use crate::{
};
#[enum_dispatch(GetPetgraphIndex, MakePrimitive)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DotIndex {
Fixed(FixedDotIndex),
Loose(LooseDotIndex),

View File

@ -7,9 +7,9 @@ use rstar::{RTree, AABB};
use thiserror::Error;
use crate::geometry::{
compound::ManageCompounds,
primitive::{AccessPrimitiveShape, PrimitiveShape},
with_rtree::{BboxedIndex, GeometryWithRtree},
recording_with_rtree::{GeometryEdit, RecordingGeometryWithRtree},
with_rtree::BboxedIndex,
AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel,
GetOffset, GetPos, GetWidth,
};
@ -65,9 +65,21 @@ pub struct Collision(pub PrimitiveShape, pub PrimitiveIndex);
#[error("{1:?} is already connected to net {0}")]
pub struct AlreadyConnected(pub usize, pub PrimitiveIndex);
pub type DrawingEdit<CW: Copy> = GeometryEdit<
PrimitiveWeight,
DotWeight,
SegWeight,
BendWeight,
CW,
PrimitiveIndex,
DotIndex,
SegIndex,
BendIndex,
>;
#[derive(Debug, Getters)]
pub struct Drawing<CW: Copy, R: AccessRules> {
geometry_with_rtree: GeometryWithRtree<
recording_geometry_with_rtree: RecordingGeometryWithRtree<
PrimitiveWeight,
DotWeight,
SegWeight,
@ -85,15 +97,20 @@ pub struct Drawing<CW: Copy, R: AccessRules> {
impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
pub fn new(rules: R, layer_count: usize) -> Self {
Self {
geometry_with_rtree: GeometryWithRtree::new(layer_count),
recording_geometry_with_rtree: RecordingGeometryWithRtree::new(layer_count),
rules,
}
}
pub fn remove_band(&mut self, band: BandTermsegIndex) -> Result<(), DrawingException> {
pub fn remove_band(
&mut self,
recorder: &mut DrawingEdit<CW>,
band: BandTermsegIndex,
) -> Result<(), DrawingException> {
match band {
BandTermsegIndex::Straight(seg) => {
self.geometry_with_rtree.remove_seg(seg.into());
self.recording_geometry_with_rtree
.remove_seg(recorder, seg.into());
}
BandTermsegIndex::Bended(first_loose_seg) => {
let mut dots = vec![];
@ -110,7 +127,8 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
dots.push(dot);
}
LooseIndex::LoneSeg(seg) => {
self.geometry_with_rtree.remove_seg(seg.into());
self.recording_geometry_with_rtree
.remove_seg(recorder, seg.into());
break;
}
LooseIndex::SeqSeg(seg) => {
@ -121,7 +139,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
if let Some(outer) = self.primitive(bend).outer() {
outers.push(outer);
self.reattach_bend(outer, self.primitive(bend).inner());
self.reattach_bend(recorder, outer, self.primitive(bend).inner());
}
}
}
@ -132,22 +150,25 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
}
for bend in bends {
self.geometry_with_rtree.remove_bend(bend.into());
self.recording_geometry_with_rtree
.remove_bend(recorder, bend.into());
}
for seg in segs {
self.geometry_with_rtree.remove_seg(seg.into());
self.recording_geometry_with_rtree
.remove_seg(recorder, seg.into());
}
// We must remove the dots only after the segs and bends because we need dots to calculate
// the shapes, which we first need unchanged to remove the segs and bends from the R-tree.
for dot in dots {
self.geometry_with_rtree.remove_dot(dot.into());
self.recording_geometry_with_rtree
.remove_dot(recorder, dot.into());
}
for outer in outers {
self.update_this_and_outward_bows(outer)?;
self.update_this_and_outward_bows(recorder, outer)?;
}
}
}
@ -158,34 +179,44 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_ok() -> self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() + 1))]
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count()))]
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn add_fixed_dot(&mut self, weight: FixedDotWeight) -> Result<FixedDotIndex, Infringement> {
self.add_dot_with_infringables(weight, Some(&[]))
pub fn add_fixed_dot(
&mut self,
recorder: &mut DrawingEdit<CW>,
weight: FixedDotWeight,
) -> Result<FixedDotIndex, Infringement> {
self.add_dot_with_infringables(recorder, weight, Some(&[]))
}
#[debug_ensures(self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() - 1))]
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn remove_fixed_dot(&mut self, dot: FixedDotIndex) {
self.geometry_with_rtree.remove_dot(dot.into());
pub fn remove_fixed_dot(&mut self, recorder: &mut DrawingEdit<CW>, dot: FixedDotIndex) {
self.recording_geometry_with_rtree
.remove_dot(recorder, dot.into());
}
#[debug_ensures(self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() + 1))]
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn add_fixed_dot_infringably(&mut self, weight: FixedDotWeight) -> FixedDotIndex {
self.add_dot_infringably(weight)
pub fn add_fixed_dot_infringably(
&mut self,
recorder: &mut DrawingEdit<CW>,
weight: FixedDotWeight,
) -> FixedDotIndex {
self.add_dot_infringably(recorder, weight)
}
#[debug_ensures(ret.is_ok() -> self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() + 1))]
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count()))]
fn add_dot_with_infringables<W: AccessDotWeight<PrimitiveWeight> + GetLayer>(
&mut self,
recorder: &mut DrawingEdit<CW>,
weight: W,
infringables: Option<&[PrimitiveIndex]>,
) -> Result<GenericIndex<W>, Infringement>
where
GenericIndex<W>: Into<PrimitiveIndex> + Copy,
{
let dot = self.add_dot_infringably(weight);
self.fail_and_remove_if_infringes_except(dot.into(), infringables)?;
let dot = self.add_dot_infringably(recorder, weight);
self.fail_and_remove_if_infringes_except(recorder, dot.into(), infringables)?;
Ok(dot)
}
@ -195,22 +226,24 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn add_fixed_seg(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: FixedDotIndex,
to: FixedDotIndex,
weight: FixedSegWeight,
) -> Result<FixedSegIndex, Infringement> {
self.add_seg_with_infringables(from.into(), to.into(), weight, Some(&[]))
self.add_seg_with_infringables(recorder, from.into(), to.into(), weight, Some(&[]))
}
#[debug_ensures(self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() + 1))]
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count() + 2))]
pub fn add_fixed_seg_infringably(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: FixedDotIndex,
to: FixedDotIndex,
weight: FixedSegWeight,
) -> FixedSegIndex {
self.add_seg_infringably(from.into(), to.into(), weight)
self.add_seg_infringably(recorder, from.into(), to.into(), weight)
}
#[debug_ensures(ret.is_ok() -> self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() + 1))]
@ -219,11 +252,13 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn add_lone_loose_seg(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: FixedDotIndex,
to: FixedDotIndex,
weight: LoneLooseSegWeight,
) -> Result<LoneLooseSegIndex, Infringement> {
let seg = self.add_seg_with_infringables(from.into(), to.into(), weight, Some(&[]))?;
let seg =
self.add_seg_with_infringables(recorder, from.into(), to.into(), weight, Some(&[]))?;
Ok(seg)
}
@ -233,11 +268,12 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn add_seq_loose_seg(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: DotIndex,
to: LooseDotIndex,
weight: SeqLooseSegWeight,
) -> Result<SeqLooseSegIndex, Infringement> {
let seg = self.add_seg_with_infringables(from, to.into(), weight, Some(&[]))?;
let seg = self.add_seg_with_infringables(recorder, from, to.into(), weight, Some(&[]))?;
Ok(seg)
}
@ -247,6 +283,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
fn add_seg_with_infringables<W: AccessSegWeight<PrimitiveWeight> + GetLayer>(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: DotIndex,
to: DotIndex,
weight: W,
@ -255,8 +292,8 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
where
GenericIndex<W>: Into<PrimitiveIndex> + Copy,
{
let seg = self.add_seg_infringably(from, to, weight);
self.fail_and_remove_if_infringes_except(seg.into(), infringables)?;
let seg = self.add_seg_infringably(recorder, from, to, weight);
self.fail_and_remove_if_infringes_except(recorder, seg.into(), infringables)?;
Ok(seg)
}
@ -268,6 +305,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
fn add_loose_bend_with_infringables(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: LooseDotIndex,
to: LooseDotIndex,
around: GearIndex,
@ -294,13 +332,34 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
match around {
GearIndex::FixedDot(core) => self
.add_core_bend_with_infringables(from.into(), to.into(), core, weight, infringables)
.add_core_bend_with_infringables(
recorder,
from.into(),
to.into(),
core,
weight,
infringables,
)
.map_err(Into::into),
GearIndex::FixedBend(around) => self
.add_outer_bend_with_infringables(from, to, around.into(), weight, infringables)
.add_outer_bend_with_infringables(
recorder,
from,
to,
around.into(),
weight,
infringables,
)
.map_err(Into::into),
GearIndex::LooseBend(around) => self
.add_outer_bend_with_infringables(from, to, around.into(), weight, infringables)
.add_outer_bend_with_infringables(
recorder,
from,
to,
around.into(),
weight,
infringables,
)
.map_err(Into::into),
}
}
@ -311,6 +370,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
fn add_core_bend_with_infringables<W: AccessBendWeight<PrimitiveWeight> + GetLayer>(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: DotIndex,
to: DotIndex,
core: FixedDotIndex,
@ -320,11 +380,11 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
where
GenericIndex<W>: Into<PrimitiveIndex> + Copy,
{
let bend = self
.geometry_with_rtree
.add_bend(from, to, core.into(), weight);
let bend =
self.recording_geometry_with_rtree
.add_bend(recorder, from, to, core.into(), weight);
self.fail_and_remove_if_infringes_except(bend.into(), infringables)?;
self.fail_and_remove_if_infringes_except(recorder, bend.into(), infringables)?;
Ok(bend)
}
@ -334,6 +394,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
fn add_outer_bend_with_infringables(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: LooseDotIndex,
to: LooseDotIndex,
inner: BendIndex,
@ -341,15 +402,15 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
infringables: Option<&[PrimitiveIndex]>,
) -> Result<GenericIndex<LooseBendWeight>, Infringement> {
let core = *self
.geometry_with_rtree
.recording_geometry_with_rtree
.graph()
.neighbors(inner.petgraph_index())
.filter(|ni| {
matches!(
self.geometry_with_rtree
self.recording_geometry_with_rtree
.graph()
.edge_weight(
self.geometry_with_rtree
self.recording_geometry_with_rtree
.graph()
.find_edge(inner.petgraph_index(), *ni)
.unwrap()
@ -363,29 +424,42 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
.first()
.unwrap();
let bend = self
.geometry_with_rtree
.add_bend(from.into(), to.into(), core.into(), weight);
self.geometry_with_rtree
.reattach_bend(bend.into(), Some(inner));
let bend = self.recording_geometry_with_rtree.add_bend(
recorder,
from.into(),
to.into(),
core.into(),
weight,
);
self.recording_geometry_with_rtree
.reattach_bend(recorder, bend.into(), Some(inner));
self.fail_and_remove_if_infringes_except(bend.into(), infringables)?;
self.fail_and_remove_if_infringes_except(recorder, bend.into(), infringables)?;
Ok(bend)
}
#[debug_ensures(self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count()))]
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn flip_bend(&mut self, bend: FixedBendIndex) {
self.geometry_with_rtree.flip_bend(bend.into());
pub fn flip_bend(&mut self, recorder: &mut DrawingEdit<CW>, bend: FixedBendIndex) {
self.recording_geometry_with_rtree
.flip_bend(recorder, bend.into());
}
#[debug_ensures(self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count()))]
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count())
|| self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count() - 1)
|| self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count() + 1))]
fn reattach_bend(&mut self, bend: LooseBendIndex, maybe_new_inner: Option<LooseBendIndex>) {
self.geometry_with_rtree
.reattach_bend(bend.into(), maybe_new_inner.map(Into::into));
fn reattach_bend(
&mut self,
recorder: &mut DrawingEdit<CW>,
bend: LooseBendIndex,
maybe_new_inner: Option<LooseBendIndex>,
) {
self.recording_geometry_with_rtree.reattach_bend(
recorder,
bend.into(),
maybe_new_inner.map(Into::into),
);
}
#[debug_ensures(ret.is_ok() -> self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() + 4))]
@ -394,6 +468,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn insert_cane(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: DotIndex,
around: GearIndex,
dot_weight: LooseDotWeight,
@ -403,6 +478,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
) -> Result<Cane, DrawingException> {
let maybe_next_gear = around.ref_(self).next_gear();
let cane = self.add_cane_with_infringables(
recorder,
from,
around,
dot_weight,
@ -413,21 +489,22 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
)?;
if let Some(next_gear) = maybe_next_gear {
self.reattach_bend(next_gear, Some(cane.bend));
self.reattach_bend(recorder, next_gear, Some(cane.bend));
}
if let Some(outer) = self.primitive(cane.bend).outer() {
self.update_this_and_outward_bows(outer).map_err(|err| {
let joint = self.primitive(cane.bend).other_joint(cane.dot);
self.remove_cane(&cane, joint);
err
})?;
self.update_this_and_outward_bows(recorder, outer)
.map_err(|err| {
let joint = self.primitive(cane.bend).other_joint(cane.dot);
self.remove_cane(recorder, &cane, joint);
err
})?;
}
// Segs must not cross.
if let Some(collision) = self.detect_collision(cane.seg.into()) {
let joint = self.primitive(cane.bend).other_joint(cane.dot);
self.remove_cane(&cane, joint);
self.remove_cane(recorder, &cane, joint);
Err(collision.into())
} else {
Ok(cane)
@ -438,6 +515,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
fn update_this_and_outward_bows(
&mut self,
recorder: &mut DrawingEdit<CW>,
around: LooseBendIndex,
) -> Result<(), DrawingException> {
// FIXME: Fail gracefully on infringement.
@ -475,17 +553,20 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
);
self.move_dot_with_infringables(
recorder,
joints.0.into(),
from,
Some(&self.collect().bend_outer_bows(rail)),
)?;
self.move_dot_with_infringables(
recorder,
joints.1.into(),
to,
Some(&self.collect().bend_outer_bows(rail)),
)?;
self.shift_bend_with_infringables(
recorder,
rail.into(),
offset,
Some(&self.collect().bend_outer_bows(rail)),
@ -517,17 +598,20 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
);
self.move_dot_with_infringables(
recorder,
joints.0.into(),
from,
Some(&self.collect().bend_outer_bows(rail)),
)?;
self.move_dot_with_infringables(
recorder,
joints.1.into(),
to,
Some(&self.collect().bend_outer_bows(rail)),
)?;
self.shift_bend_with_infringables(
recorder,
rail.into(),
offset,
Some(&self.collect().bend_outer_bows(rail)),
@ -546,6 +630,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn add_cane(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: DotIndex,
around: GearIndex,
dot_weight: LooseDotWeight,
@ -554,6 +639,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
cw: bool,
) -> Result<Cane, DrawingException> {
self.add_cane_with_infringables(
recorder,
from,
around,
dot_weight,
@ -570,6 +656,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
fn add_cane_with_infringables(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: DotIndex,
around: GearIndex,
dot_weight: LooseDotWeight,
@ -578,30 +665,43 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
cw: bool,
infringables: Option<&[PrimitiveIndex]>,
) -> Result<Cane, DrawingException> {
let seg_to = self.add_dot_with_infringables(dot_weight, infringables)?;
let seg_to = self.add_dot_with_infringables(recorder, dot_weight, infringables)?;
let seg = self
.add_seg_with_infringables(from, seg_to.into(), seg_weight, infringables)
.add_seg_with_infringables(recorder, from, seg_to.into(), seg_weight, infringables)
.map_err(|err| {
self.geometry_with_rtree.remove_dot(seg_to.into());
self.recording_geometry_with_rtree
.remove_dot(recorder, seg_to.into());
err
})?;
let to = self
.add_dot_with_infringables(dot_weight, infringables)
.add_dot_with_infringables(recorder, dot_weight, infringables)
.map_err(|err| {
self.geometry_with_rtree.remove_seg(seg.into());
self.geometry_with_rtree.remove_dot(seg_to.into());
self.recording_geometry_with_rtree
.remove_seg(recorder, seg.into());
self.recording_geometry_with_rtree
.remove_dot(recorder, seg_to.into());
err
})?;
let (bend_from, bend_to) = if cw { (to, seg_to) } else { (seg_to, to) };
let bend = self
.add_loose_bend_with_infringables(bend_from, bend_to, around, bend_weight, infringables)
.add_loose_bend_with_infringables(
recorder,
bend_from,
bend_to,
around,
bend_weight,
infringables,
)
.map_err(|err| {
self.geometry_with_rtree.remove_dot(to.into());
self.geometry_with_rtree.remove_seg(seg.into());
self.geometry_with_rtree.remove_dot(seg_to.into());
self.recording_geometry_with_rtree
.remove_dot(recorder, to.into());
self.recording_geometry_with_rtree
.remove_seg(recorder, seg.into());
self.recording_geometry_with_rtree
.remove_dot(recorder, seg_to.into());
err
})?;
@ -613,25 +713,34 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
}
#[debug_ensures(self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() - 4))]
pub fn remove_cane(&mut self, cane: &Cane, face: LooseDotIndex) {
pub fn remove_cane(
&mut self,
recorder: &mut DrawingEdit<CW>,
cane: &Cane,
face: LooseDotIndex,
) {
let maybe_outer = self.primitive(cane.bend).outer();
// Removing a loose bend affects its outer bends.
if let Some(outer) = maybe_outer {
self.reattach_bend(outer, self.primitive(cane.bend).inner());
self.reattach_bend(recorder, outer, self.primitive(cane.bend).inner());
}
self.geometry_with_rtree.remove_bend(cane.bend.into());
self.geometry_with_rtree.remove_seg(cane.seg.into());
self.recording_geometry_with_rtree
.remove_bend(recorder, cane.bend.into());
self.recording_geometry_with_rtree
.remove_seg(recorder, cane.seg.into());
// We must remove the dots only after the segs and bends because we need dots to calculate
// the shapes, which we first need unchanged to remove the segs and bends from the R-tree.
self.geometry_with_rtree.remove_dot(face.into());
self.geometry_with_rtree.remove_dot(cane.dot.into());
self.recording_geometry_with_rtree
.remove_dot(recorder, face.into());
self.recording_geometry_with_rtree
.remove_dot(recorder, cane.dot.into());
if let Some(outer) = maybe_outer {
self.update_this_and_outward_bows(outer).unwrap(); // Must never fail.
self.update_this_and_outward_bows(recorder, outer).unwrap(); // Must never fail.
}
}
@ -641,10 +750,15 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count()))]
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
pub fn move_dot(&mut self, dot: DotIndex, to: Point) -> Result<(), Infringement> {
pub fn move_dot(
&mut self,
recorder: &mut DrawingEdit<CW>,
dot: DotIndex,
to: Point,
) -> Result<(), Infringement> {
match dot {
DotIndex::Fixed(..) => self.move_dot_with_infringables(dot, to, Some(&[])),
DotIndex::Loose(..) => self.move_dot_with_infringables(dot, to, Some(&[])),
DotIndex::Fixed(..) => self.move_dot_with_infringables(recorder, dot, to, Some(&[])),
DotIndex::Loose(..) => self.move_dot_with_infringables(recorder, dot, to, Some(&[])),
}
}
@ -652,24 +766,32 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
fn move_dot_with_infringables(
&mut self,
recorder: &mut DrawingEdit<CW>,
dot: DotIndex,
to: Point,
infringables: Option<&[PrimitiveIndex]>,
) -> Result<(), Infringement> {
let old_pos = self.geometry_with_rtree.geometry().dot_weight(dot).pos();
self.geometry_with_rtree.move_dot(dot, to);
let old_pos = self
.recording_geometry_with_rtree
.geometry()
.dot_weight(dot)
.pos();
self.recording_geometry_with_rtree
.move_dot(recorder, dot, to);
for limb in dot.primitive(self).limbs() {
if let Some(infringement) = self.detect_infringement_except(limb, infringables) {
// Restore original state.
self.geometry_with_rtree.move_dot(dot, old_pos);
self.recording_geometry_with_rtree
.move_dot(recorder, dot, old_pos);
return Err(infringement);
}
}
if let Some(infringement) = self.detect_infringement_except(dot.into(), infringables) {
// Restore original state.
self.geometry_with_rtree.move_dot(dot, old_pos);
self.recording_geometry_with_rtree
.move_dot(recorder, dot, old_pos);
return Err(infringement);
}
@ -680,20 +802,23 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
fn shift_bend_with_infringables(
&mut self,
recorder: &mut DrawingEdit<CW>,
bend: BendIndex,
offset: f64,
infringables: Option<&[PrimitiveIndex]>,
) -> Result<(), Infringement> {
let old_offset = self
.geometry_with_rtree
.recording_geometry_with_rtree
.geometry()
.bend_weight(bend)
.offset();
self.geometry_with_rtree.shift_bend(bend, offset);
self.recording_geometry_with_rtree
.shift_bend(recorder, bend, offset);
if let Some(infringement) = self.detect_infringement_except(bend.into(), infringables) {
// Restore original state.
self.geometry_with_rtree.shift_bend(bend, old_offset);
self.recording_geometry_with_rtree
.shift_bend(recorder, bend, old_offset);
return Err(infringement);
}
@ -703,7 +828,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
fn detect_collision(&self, node: PrimitiveIndex) -> Option<Collision> {
let shape = node.primitive(self).shape();
self.geometry_with_rtree
self.recording_geometry_with_rtree
.rtree()
.locate_in_envelope_intersecting(&shape.full_height_envelope_3d(0.0, 2))
.filter_map(|wrapper| {
@ -724,18 +849,20 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count()))]
fn add_dot_infringably<W: AccessDotWeight<PrimitiveWeight> + GetLayer>(
&mut self,
recorder: &mut DrawingEdit<CW>,
weight: W,
) -> GenericIndex<W>
where
GenericIndex<W>: Into<PrimitiveIndex> + Copy,
{
self.geometry_with_rtree.add_dot(weight)
self.recording_geometry_with_rtree.add_dot(recorder, weight)
}
#[debug_ensures(self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() + 1))]
#[debug_ensures(self.geometry_with_rtree.graph().edge_count() == old(self.geometry_with_rtree.graph().edge_count() + 2))]
fn add_seg_infringably<W: AccessSegWeight<PrimitiveWeight> + GetLayer>(
&mut self,
recorder: &mut DrawingEdit<CW>,
from: DotIndex,
to: DotIndex,
weight: W,
@ -743,7 +870,28 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
where
GenericIndex<W>: Into<PrimitiveIndex>,
{
self.geometry_with_rtree.add_seg(from, to, weight)
self.recording_geometry_with_rtree
.add_seg(recorder, from, to, weight)
}
pub fn add_compound(&mut self, recorder: &mut DrawingEdit<CW>, weight: CW) -> GenericIndex<CW> {
self.recording_geometry_with_rtree
.add_compound(recorder, weight)
}
pub fn remove_compound(&mut self, recorder: &mut DrawingEdit<CW>, compound: GenericIndex<CW>) {
self.recording_geometry_with_rtree
.remove_compound(recorder, compound);
}
pub fn add_to_compound<W>(
&mut self,
recorder: &mut DrawingEdit<CW>,
primitive: GenericIndex<W>,
compound: GenericIndex<CW>,
) {
self.recording_geometry_with_rtree
.add_to_compound(recorder, primitive, compound);
}
#[debug_ensures(ret.is_ok() -> self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count()))]
@ -751,16 +899,18 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
#[debug_ensures(ret.is_err() -> self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count() - 1))]
fn fail_and_remove_if_infringes_except(
&mut self,
recorder: &mut DrawingEdit<CW>,
node: PrimitiveIndex,
maybe_except: Option<&[PrimitiveIndex]>,
) -> Result<(), Infringement> {
if let Some(infringement) = self.detect_infringement_except(node, maybe_except) {
if let Ok(dot) = node.try_into() {
self.geometry_with_rtree.remove_dot(dot);
self.recording_geometry_with_rtree.remove_dot(recorder, dot);
} else if let Ok(seg) = node.try_into() {
self.geometry_with_rtree.remove_seg(seg);
self.recording_geometry_with_rtree.remove_seg(recorder, seg);
} else if let Ok(bend) = node.try_into() {
self.geometry_with_rtree.remove_bend(bend);
self.recording_geometry_with_rtree
.remove_bend(recorder, bend);
}
return Err(infringement);
}
@ -799,7 +949,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
.unwrap_or(0.0),
);
self.geometry_with_rtree
self.recording_geometry_with_rtree
.rtree()
.locate_in_envelope_intersecting(
&limiting_shape.envelope_3d(0.0, node.primitive(self).layer()),
@ -832,7 +982,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
}
pub fn primitive_nodes(&self) -> impl Iterator<Item = PrimitiveIndex> + '_ {
self.geometry_with_rtree
self.recording_geometry_with_rtree
.rtree()
.iter()
.filter_map(|wrapper| {
@ -845,7 +995,7 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
}
pub fn layer_primitive_nodes(&self, layer: usize) -> impl Iterator<Item = PrimitiveIndex> + '_ {
self.geometry_with_rtree
self.recording_geometry_with_rtree
.rtree()
.locate_in_envelope_intersecting(&AABB::from_corners(
[-f64::INFINITY, -f64::INFINITY, layer as f64],
@ -860,6 +1010,17 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
})
}
pub fn compound_weight(&self, compound: GenericIndex<CW>) -> CW {
self.recording_geometry_with_rtree.compound_weight(compound)
}
pub fn compounds<'a, W: 'a>(
&'a self,
node: GenericIndex<W>,
) -> impl Iterator<Item = GenericIndex<CW>> + 'a {
self.recording_geometry_with_rtree.compounds(node)
}
fn are_connectable(&self, node1: PrimitiveIndex, node2: PrimitiveIndex) -> bool {
if let (Some(node1_net_id), Some(node2_net_id)) = (
node1.primitive(self).maybe_net(),
@ -884,11 +1045,11 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
SegIndex,
BendIndex,
> {
self.geometry_with_rtree.geometry()
self.recording_geometry_with_rtree.geometry()
}
pub fn rtree(&self) -> &RTree<BboxedIndex<GenericNode<PrimitiveIndex, GenericIndex<CW>>>> {
self.geometry_with_rtree.rtree()
self.recording_geometry_with_rtree.rtree()
}
#[debug_ensures(self.geometry_with_rtree.graph().node_count() == old(self.geometry_with_rtree.graph().node_count()))]
@ -914,11 +1075,11 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
}
pub fn layer_count(&self) -> usize {
*self.geometry_with_rtree.layer_count()
self.recording_geometry_with_rtree.layer_count()
}
pub fn node_count(&self) -> usize {
self.geometry_with_rtree.graph().node_count()
self.recording_geometry_with_rtree.graph().node_count()
}
fn test_if_looses_dont_infringe_each_other(&self) -> bool {
@ -958,26 +1119,3 @@ impl<CW: Copy, R: AccessRules> Drawing<CW, R> {
})
}
}
impl<CW: Copy, R: AccessRules> ManageCompounds<CW, GenericIndex<CW>> for Drawing<CW, R> {
fn add_compound(&mut self, weight: CW) -> GenericIndex<CW> {
self.geometry_with_rtree.add_compound(weight)
}
fn remove_compound(&mut self, compound: GenericIndex<CW>) {
self.geometry_with_rtree.remove_compound(compound);
}
fn add_to_compound<W>(&mut self, primitive: GenericIndex<W>, compound: GenericIndex<CW>) {
self.geometry_with_rtree
.add_to_compound(primitive, compound);
}
fn compound_weight(&self, compound: GenericIndex<CW>) -> CW {
self.geometry_with_rtree.compound_weight(compound)
}
fn compounds<W>(&self, node: GenericIndex<W>) -> impl Iterator<Item = GenericIndex<CW>> {
self.geometry_with_rtree.compounds(node)
}
}

View File

@ -14,7 +14,7 @@ use crate::{
use petgraph::stable_graph::NodeIndex;
#[enum_dispatch(GetPetgraphIndex, MakePrimitive)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SegIndex {
Fixed(FixedSegIndex),
LoneLoose(LoneLooseSegIndex),

View File

@ -2,6 +2,7 @@ use std::{collections::HashMap, hash::Hash, marker::PhantomData};
use geo::Point;
use petgraph::stable_graph::StableDiGraph;
use rstar::RTree;
use crate::{
drawing::graph::{GetLayer, Retag},
@ -9,10 +10,13 @@ use crate::{
};
use super::{
compound::ManageCompounds, with_rtree::GeometryWithRtree, AccessBendWeight, AccessDotWeight,
AccessSegWeight, GenericNode, GeometryLabel, GetWidth,
compound::ManageCompounds,
with_rtree::{BboxedIndex, GeometryWithRtree},
AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel,
GetWidth,
};
#[derive(Debug)]
pub struct GeometryEdit<
PW: GetWidth + GetLayer + TryInto<DW> + TryInto<SW> + TryInto<BW> + Retag<PI> + Copy,
DW: AccessDotWeight<PW> + GetLayer,
@ -31,6 +35,30 @@ pub struct GeometryEdit<
primitive_weight_marker: PhantomData<PW>,
}
impl<
PW: GetWidth + GetLayer + TryInto<DW> + TryInto<SW> + TryInto<BW> + Retag<PI> + Copy,
DW: AccessDotWeight<PW> + GetLayer,
SW: AccessSegWeight<PW> + GetLayer,
BW: AccessBendWeight<PW> + GetLayer,
CW: Copy,
PI: GetPetgraphIndex + TryInto<DI> + TryInto<SI> + TryInto<BI> + Eq + Hash + Copy,
DI: GetPetgraphIndex + Into<PI> + Eq + Hash + Copy,
SI: GetPetgraphIndex + Into<PI> + Eq + Hash + Copy,
BI: GetPetgraphIndex + Into<PI> + Eq + Hash + Copy,
> GeometryEdit<PW, DW, SW, BW, CW, PI, DI, SI, BI>
{
pub fn new() -> Self {
Self {
dots: HashMap::new(),
segs: HashMap::new(),
bends: HashMap::new(),
compounds: HashMap::new(),
primitive_weight_marker: PhantomData,
}
}
}
#[derive(Debug)]
pub struct RecordingGeometryWithRtree<
PW: GetWidth + GetLayer + TryInto<DW> + TryInto<SW> + TryInto<BW> + Retag<PI> + Copy,
DW: AccessDotWeight<PW> + GetLayer,
@ -57,6 +85,14 @@ impl<
BI: GetPetgraphIndex + Into<PI> + Eq + Hash + Copy,
> RecordingGeometryWithRtree<PW, DW, SW, BW, CW, PI, DI, SI, BI>
{
pub fn new(layer_count: usize) -> Self {
Self {
geometry_with_rtree: GeometryWithRtree::<PW, DW, SW, BW, CW, PI, DI, SI, BI>::new(
layer_count,
),
}
}
pub fn add_dot<W: AccessDotWeight<PW> + GetLayer>(
&mut self,
recorder: &mut GeometryEdit<PW, DW, SW, BW, CW, PI, DI, SI, BI>,
@ -349,6 +385,18 @@ impl<
self.geometry_with_rtree.compounds(node)
}
pub fn geometry(&self) -> &Geometry<PW, DW, SW, BW, CW, PI, DI, SI, BI> {
self.geometry_with_rtree.geometry()
}
pub fn rtree(&self) -> &RTree<BboxedIndex<GenericNode<PI, GenericIndex<CW>>>> {
self.geometry_with_rtree.rtree()
}
pub fn layer_count(&self) -> usize {
*self.geometry_with_rtree.layer_count()
}
pub fn graph(&self) -> &StableDiGraph<GenericNode<PW, CW>, GeometryLabel, usize> {
self.geometry_with_rtree.graph()
}

View File

@ -17,9 +17,9 @@ use crate::{
FixedSegIndex, FixedSegWeight, LoneLooseSegIndex, LoneLooseSegWeight, SeqLooseSegIndex,
SeqLooseSegWeight,
},
Drawing, DrawingException, Infringement,
Drawing, DrawingEdit, DrawingException, Infringement,
},
geometry::{compound::ManageCompounds, GenericNode},
geometry::GenericNode,
graph::{GenericIndex, GetPetgraphIndex},
layout::{
poly::{Poly, PolyWeight},
@ -39,6 +39,7 @@ pub enum CompoundWeight {
/// The alias to differ node types
pub type NodeIndex = GenericNode<PrimitiveIndex, GenericIndex<CompoundWeight>>;
pub type LayoutEdit = DrawingEdit<CompoundWeight>;
#[derive(Debug, Getters)]
/// Structure for managing the Layout design
@ -54,46 +55,61 @@ impl<R: AccessRules> Layout<R> {
/// Insert [`Cane`] object into the [`Layout`]
pub fn insert_cane(
&mut self,
from: DotIndex,
recorder: &mut LayoutEdit,
from: DotIndex,
around: GearIndex,
dot_weight: LooseDotWeight,
seg_weight: SeqLooseSegWeight,
bend_weight: LooseBendWeight,
cw: bool,
) -> Result<Cane, DrawingException> {
self.drawing
.insert_cane(from, around, dot_weight, seg_weight, bend_weight, cw)
self.drawing.insert_cane(
recorder,
from,
around,
dot_weight,
seg_weight,
bend_weight,
cw,
)
}
/// Remove [`Cane`] object from the [`Layout`]
pub fn remove_cane(&mut self, cane: &Cane, face: LooseDotIndex) {
self.drawing.remove_cane(cane, face)
pub fn remove_cane(&mut self, recorder: &mut LayoutEdit, cane: &Cane, face: LooseDotIndex) {
self.drawing.remove_cane(recorder, cane, face)
}
#[debug_ensures(ret.is_ok() -> self.drawing.node_count() == old(self.drawing.node_count()) + weight.to_layer - weight.from_layer + 2)]
#[debug_ensures(ret.is_err() -> self.drawing.node_count() == old(self.drawing.node_count()))]
/// Insert [`Via`] into the [`Layout`]
pub fn add_via(&mut self, weight: ViaWeight) -> Result<GenericIndex<ViaWeight>, Infringement> {
let compound = self.drawing.add_compound(weight.into());
pub fn add_via(
&mut self,
recorder: &mut LayoutEdit,
weight: ViaWeight,
) -> Result<GenericIndex<ViaWeight>, Infringement> {
let compound = self.drawing.add_compound(recorder, weight.into());
let mut dots = vec![];
for layer in weight.from_layer..=weight.to_layer {
match self.drawing.add_fixed_dot(FixedDotWeight {
circle: weight.circle,
layer,
maybe_net: weight.maybe_net,
}) {
match self.drawing.add_fixed_dot(
recorder,
FixedDotWeight {
circle: weight.circle,
layer,
maybe_net: weight.maybe_net,
},
) {
Ok(dot) => {
self.drawing.add_to_compound(dot, compound);
self.drawing.add_to_compound(recorder, dot, compound);
dots.push(dot);
}
Err(err) => {
// Remove inserted dots.
self.drawing.remove_compound(compound);
self.drawing.remove_compound(recorder, compound);
for dot in dots.iter().rev() {
self.drawing.remove_fixed_dot(*dot);
self.drawing.remove_fixed_dot(recorder, *dot);
}
return Err(err);
@ -104,24 +120,32 @@ impl<R: AccessRules> Layout<R> {
Ok(GenericIndex::<ViaWeight>::new(compound.petgraph_index()))
}
pub fn add_fixed_dot(&mut self, weight: FixedDotWeight) -> Result<FixedDotIndex, Infringement> {
self.drawing.add_fixed_dot(weight)
pub fn add_fixed_dot(
&mut self,
recorder: &mut LayoutEdit,
weight: FixedDotWeight,
) -> Result<FixedDotIndex, Infringement> {
self.drawing.add_fixed_dot(recorder, weight)
}
pub fn add_fixed_dot_infringably(&mut self, weight: FixedDotWeight) -> FixedDotIndex {
self.drawing.add_fixed_dot_infringably(weight)
pub fn add_fixed_dot_infringably(
&mut self,
recorder: &mut LayoutEdit,
weight: FixedDotWeight,
) -> FixedDotIndex {
self.drawing.add_fixed_dot_infringably(recorder, weight)
}
pub fn add_poly_fixed_dot(
&mut self,
recorder: &mut LayoutEdit,
weight: FixedDotWeight,
poly: GenericIndex<PolyWeight>,
) -> Result<FixedDotIndex, Infringement> {
let maybe_dot = self.drawing.add_fixed_dot(weight);
let maybe_dot = self.drawing.add_fixed_dot(recorder, weight);
if let Ok(dot) = maybe_dot {
self.drawing.add_to_compound(dot, poly.into());
self.drawing.add_to_compound(recorder, dot, poly.into());
}
maybe_dot
@ -129,43 +153,48 @@ impl<R: AccessRules> Layout<R> {
pub fn add_poly_fixed_dot_infringably(
&mut self,
recorder: &mut LayoutEdit,
weight: FixedDotWeight,
poly: GenericIndex<PolyWeight>,
) -> FixedDotIndex {
let dot = self.drawing.add_fixed_dot_infringably(weight);
self.drawing.add_to_compound(dot, poly.into());
let dot = self.drawing.add_fixed_dot_infringably(recorder, weight);
self.drawing.add_to_compound(recorder, dot, poly.into());
dot
}
pub fn add_fixed_seg(
&mut self,
recorder: &mut LayoutEdit,
from: FixedDotIndex,
to: FixedDotIndex,
weight: FixedSegWeight,
) -> Result<FixedSegIndex, Infringement> {
self.drawing.add_fixed_seg(from, to, weight)
self.drawing.add_fixed_seg(recorder, from, to, weight)
}
pub fn add_fixed_seg_infringably(
&mut self,
recorder: &mut LayoutEdit,
from: FixedDotIndex,
to: FixedDotIndex,
weight: FixedSegWeight,
) -> FixedSegIndex {
self.drawing.add_fixed_seg_infringably(from, to, weight)
self.drawing
.add_fixed_seg_infringably(recorder, from, to, weight)
}
pub fn add_poly_fixed_seg(
&mut self,
recorder: &mut LayoutEdit,
from: FixedDotIndex,
to: FixedDotIndex,
weight: FixedSegWeight,
poly: GenericIndex<PolyWeight>,
) -> Result<FixedSegIndex, Infringement> {
let maybe_seg = self.add_fixed_seg(from, to, weight);
let maybe_seg = self.add_fixed_seg(recorder, from, to, weight);
if let Ok(seg) = maybe_seg {
self.drawing.add_to_compound(seg, poly.into());
self.drawing.add_to_compound(recorder, seg, poly.into());
}
maybe_seg
@ -173,48 +202,64 @@ impl<R: AccessRules> Layout<R> {
pub fn add_poly_fixed_seg_infringably(
&mut self,
recorder: &mut LayoutEdit,
from: FixedDotIndex,
to: FixedDotIndex,
weight: FixedSegWeight,
poly: GenericIndex<PolyWeight>,
) -> FixedSegIndex {
let seg = self.add_fixed_seg_infringably(from, to, weight);
self.drawing.add_to_compound(seg, poly.into());
let seg = self.add_fixed_seg_infringably(recorder, from, to, weight);
self.drawing.add_to_compound(recorder, seg, poly.into());
seg
}
pub fn add_lone_loose_seg(
&mut self,
recorder: &mut LayoutEdit,
from: FixedDotIndex,
to: FixedDotIndex,
weight: LoneLooseSegWeight,
) -> Result<LoneLooseSegIndex, Infringement> {
self.drawing.add_lone_loose_seg(from, to, weight)
self.drawing.add_lone_loose_seg(recorder, from, to, weight)
}
pub fn add_seq_loose_seg(
&mut self,
recorder: &mut LayoutEdit,
from: DotIndex,
to: LooseDotIndex,
weight: SeqLooseSegWeight,
) -> Result<SeqLooseSegIndex, Infringement> {
self.drawing.add_seq_loose_seg(from, to, weight)
self.drawing.add_seq_loose_seg(recorder, from, to, weight)
}
pub fn move_dot(&mut self, dot: DotIndex, to: Point) -> Result<(), Infringement> {
self.drawing.move_dot(dot, to)
pub fn move_dot(
&mut self,
recorder: &mut LayoutEdit,
dot: DotIndex,
to: Point,
) -> Result<(), Infringement> {
self.drawing.move_dot(recorder, dot, to)
}
pub fn add_poly(&mut self, weight: PolyWeight) -> GenericIndex<PolyWeight> {
pub fn add_poly(
&mut self,
recorder: &mut LayoutEdit,
weight: PolyWeight,
) -> GenericIndex<PolyWeight> {
GenericIndex::<PolyWeight>::new(
self.drawing
.add_compound(CompoundWeight::Poly(weight))
.add_compound(recorder, CompoundWeight::Poly(weight))
.petgraph_index(),
)
}
pub fn remove_band(&mut self, band: BandTermsegIndex) -> Result<(), DrawingException> {
self.drawing.remove_band(band)
pub fn remove_band(
&mut self,
recorder: &mut LayoutEdit,
band: BandTermsegIndex,
) -> Result<(), DrawingException> {
self.drawing.remove_band(recorder, band)
}
pub fn polys<W: 'static>(

View File

@ -12,7 +12,7 @@ use crate::{
rules::AccessRules,
seg::SegIndex,
},
geometry::{compound::ManageCompounds, poly::PolyShape, GetPos},
geometry::{poly::PolyShape, GetPos},
graph::{GenericIndex, GetPetgraphIndex},
layout::{CompoundWeight, Layout},
};

View File

@ -4,10 +4,7 @@ use serde::{Deserialize, Serialize};
use crate::{
drawing::{graph::GetMaybeNet, primitive::MakePrimitiveShape, rules::AccessRules},
geometry::{
compound::ManageCompounds,
primitive::{DotShape, PrimitiveShape},
},
geometry::primitive::{DotShape, PrimitiveShape},
graph::{GenericIndex, GetPetgraphIndex},
layout::{CompoundWeight, Layout},
math::Circle,

View File

@ -16,7 +16,7 @@ use crate::{
seg::{LoneLooseSegWeight, SeqLooseSegWeight},
DrawingException, Infringement,
},
layout::Layout,
layout::{Layout, LayoutEdit},
math::{Circle, NoTangents},
};
@ -66,6 +66,7 @@ impl<'a, R: AccessRules> Draw<'a, R> {
DotIndex::Fixed(dot) => BandTermsegIndex::Straight(
self.layout
.add_lone_loose_seg(
&mut LayoutEdit::new(),
dot,
into,
LoneLooseSegWeight {
@ -79,6 +80,7 @@ impl<'a, R: AccessRules> Draw<'a, R> {
DotIndex::Loose(dot) => BandTermsegIndex::Bended(
self.layout
.add_seq_loose_seg(
&mut LayoutEdit::new(),
into.into(),
dot,
SeqLooseSegWeight {
@ -164,7 +166,8 @@ impl<'a, R: AccessRules> Draw<'a, R> {
#[debug_ensures(self.layout.drawing().node_count() == old(self.layout.drawing().node_count()))]
fn extend_head(&mut self, head: Head, to: Point) -> Result<Head, Infringement> {
if let Head::Cane(head) = head {
self.layout.move_dot(head.face.into(), to)?;
self.layout
.move_dot(&mut LayoutEdit::new(), head.face.into(), to)?;
Ok(Head::Cane(head))
} else {
Ok(head)
@ -185,6 +188,7 @@ impl<'a, R: AccessRules> Draw<'a, R> {
let layer = head.face().primitive(self.layout.drawing()).layer();
let maybe_net = head.face().primitive(self.layout.drawing()).maybe_net();
let cane = self.layout.insert_cane(
&mut LayoutEdit::new(),
head.face(),
around,
LooseDotWeight {
@ -227,7 +231,8 @@ impl<'a, R: AccessRules> Draw<'a, R> {
.primitive(head.cane.seg)
.other_joint(head.cane.dot.into());
self.layout.remove_cane(&head.cane, head.face);
self.layout
.remove_cane(&mut LayoutEdit::new(), &head.cane, head.face);
Some(self.guide().head(prev_dot))
}

View File

@ -16,7 +16,7 @@ use crate::{
Drawing,
},
geometry::{primitive::PrimitiveShape, GetWidth},
layout::{poly::SolidPolyWeight, Layout},
layout::{poly::SolidPolyWeight, Layout, LayoutEdit},
math::{Circle, PointWithRotation},
specctra::{
mesadata::SpecctraMesadata,
@ -185,7 +185,7 @@ impl SpecctraDesign {
/// 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
/// from the PCB definition.
pub fn make_board(&self) -> Board<SpecctraMesadata> {
pub fn make_board(&self, recorder: &mut LayoutEdit) -> Board<SpecctraMesadata> {
let mesadata = SpecctraMesadata::from_pcb(&self.pcb);
let mut board = Board::new(Layout::new(Drawing::new(
mesadata,
@ -210,11 +210,7 @@ impl SpecctraDesign {
net_pin_assignments.pins.as_ref().and_then(|pins| {
// take the list of pins
// and for each pin output (pin name, net id)
Some(pins
.names
.iter()
.map(move |pinname| (pinname.clone(), net))
)
Some(pins.names.iter().map(move |pinname| (pinname.clone(), net)))
})
})
// flatten the nested iters into a single stream of tuples
@ -248,6 +244,7 @@ impl SpecctraDesign {
Shape::Circle(circle) => {
let layer = get_layer(&board, &circle.layer);
Self::add_circle(
recorder,
&mut board,
place.point_with_rotation(),
pin.point_with_rotation(),
@ -260,6 +257,7 @@ impl SpecctraDesign {
Shape::Rect(rect) => {
let layer = get_layer(&board, &rect.layer);
Self::add_rect(
recorder,
&mut board,
place.point_with_rotation(),
pin.point_with_rotation(),
@ -275,6 +273,7 @@ impl SpecctraDesign {
Shape::Path(path) => {
let layer = get_layer(&board, &path.layer);
Self::add_path(
recorder,
&mut board,
place.point_with_rotation(),
pin.point_with_rotation(),
@ -288,6 +287,7 @@ impl SpecctraDesign {
Shape::Polygon(polygon) => {
let layer = get_layer(&board, &polygon.layer);
Self::add_polygon(
recorder,
&mut board,
place.point_with_rotation(),
pin.point_with_rotation(),
@ -305,11 +305,7 @@ impl SpecctraDesign {
}
for via in &self.pcb.wiring.vias {
let net = board
.layout()
.drawing()
.rules()
.netname_net(&via.net);
let net = board.layout().drawing().rules().netname_net(&via.net);
let padstack = self.pcb.library.find_padstack_by_name(&via.name).unwrap();
@ -322,6 +318,7 @@ impl SpecctraDesign {
Shape::Circle(circle) => {
let layer = get_layer(&board, &circle.layer);
Self::add_circle(
recorder,
&mut board,
// TODO: refactor?
// should this call take PointWithRotation?
@ -336,6 +333,7 @@ impl SpecctraDesign {
Shape::Rect(rect) => {
let layer = get_layer(&board, &rect.layer);
Self::add_rect(
recorder,
&mut board,
PointWithRotation::from_xy(via.x, via.y),
PointWithRotation::default(),
@ -351,6 +349,7 @@ impl SpecctraDesign {
Shape::Path(path) => {
let layer = get_layer(&board, &path.layer);
Self::add_path(
recorder,
&mut board,
PointWithRotation::from_xy(via.x, via.y),
PointWithRotation::default(),
@ -364,6 +363,7 @@ impl SpecctraDesign {
Shape::Polygon(polygon) => {
let layer = get_layer(&board, &polygon.layer);
Self::add_polygon(
recorder,
&mut board,
PointWithRotation::from_xy(via.x, via.y),
PointWithRotation::default(),
@ -385,13 +385,10 @@ impl SpecctraDesign {
.rules()
.layername_layer(&wire.path.layer)
.unwrap();
let net = board
.layout()
.drawing()
.rules()
.netname_net(&wire.net);
let net = board.layout().drawing().rules().netname_net(&wire.net);
Self::add_path(
recorder,
&mut board,
PointWithRotation::default(),
PointWithRotation::default(),
@ -427,6 +424,7 @@ impl SpecctraDesign {
}
fn add_circle(
recorder: &mut LayoutEdit,
board: &mut Board<SpecctraMesadata>,
place: PointWithRotation,
pin: PointWithRotation,
@ -441,6 +439,7 @@ impl SpecctraDesign {
};
board.add_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle,
layer,
@ -451,6 +450,7 @@ impl SpecctraDesign {
}
fn add_rect(
recorder: &mut LayoutEdit,
board: &mut Board<SpecctraMesadata>,
place: PointWithRotation,
pin: PointWithRotation,
@ -463,16 +463,14 @@ impl SpecctraDesign {
maybe_pin: Option<String>,
) {
let poly = board.add_poly(
SolidPolyWeight {
layer,
maybe_net,
}
.into(),
recorder,
SolidPolyWeight { layer, maybe_net }.into(),
maybe_pin.clone(),
);
// Corners.
let dot_1_1 = board.add_poly_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle: Circle {
pos: Self::pos(place, pin, x1, y1),
@ -484,6 +482,7 @@ impl SpecctraDesign {
poly,
);
let dot_2_1 = board.add_poly_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle: Circle {
pos: Self::pos(place, pin, x2, y1),
@ -495,6 +494,7 @@ impl SpecctraDesign {
poly,
);
let dot_2_2 = board.add_poly_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle: Circle {
pos: Self::pos(place, pin, x2, y2),
@ -506,6 +506,7 @@ impl SpecctraDesign {
poly,
);
let dot_1_2 = board.add_poly_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle: Circle {
pos: Self::pos(place, pin, x1, y2),
@ -518,6 +519,7 @@ impl SpecctraDesign {
);
// Sides.
board.add_poly_fixed_seg_infringably(
recorder,
dot_1_1,
dot_2_1,
FixedSegWeight {
@ -528,6 +530,7 @@ impl SpecctraDesign {
poly,
);
board.add_poly_fixed_seg_infringably(
recorder,
dot_2_1,
dot_2_2,
FixedSegWeight {
@ -538,6 +541,7 @@ impl SpecctraDesign {
poly,
);
board.add_poly_fixed_seg_infringably(
recorder,
dot_2_2,
dot_1_2,
FixedSegWeight {
@ -548,6 +552,7 @@ impl SpecctraDesign {
poly,
);
board.add_poly_fixed_seg_infringably(
recorder,
dot_1_2,
dot_1_1,
FixedSegWeight {
@ -560,6 +565,7 @@ impl SpecctraDesign {
}
fn add_path(
recorder: &mut LayoutEdit,
board: &mut Board<SpecctraMesadata>,
place: PointWithRotation,
pin: PointWithRotation,
@ -572,6 +578,7 @@ impl SpecctraDesign {
// add the first coordinate in the wire path as a dot and save its index
let mut prev_pos = Self::pos(place, pin, coords[0].x, coords[0].y);
let mut prev_index = board.add_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle: Circle {
pos: prev_pos,
@ -592,6 +599,7 @@ impl SpecctraDesign {
}
let index = board.add_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle: Circle {
pos,
@ -605,6 +613,7 @@ impl SpecctraDesign {
// add a seg between the current and previous coords
let _ = board.add_fixed_seg_infringably(
recorder,
prev_index,
index,
FixedSegWeight {
@ -621,6 +630,7 @@ impl SpecctraDesign {
}
fn add_polygon(
recorder: &mut LayoutEdit,
board: &mut Board<SpecctraMesadata>,
place: PointWithRotation,
pin: PointWithRotation,
@ -631,16 +641,14 @@ impl SpecctraDesign {
maybe_pin: Option<String>,
) {
let poly = board.add_poly(
SolidPolyWeight {
layer,
maybe_net,
}
.into(),
recorder,
SolidPolyWeight { layer, maybe_net }.into(),
maybe_pin.clone(),
);
// add the first coordinate in the wire path as a dot and save its index
let mut prev_index = board.add_poly_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle: Circle {
pos: Self::pos(place, pin, coords[0].x, coords[0].y),
@ -657,6 +665,7 @@ impl SpecctraDesign {
// iterate through path coords starting from the second
for coord in coords.iter().skip(1) {
let index = board.add_poly_fixed_dot_infringably(
recorder,
FixedDotWeight {
circle: Circle {
pos: Self::pos(place, pin, coord.x, coord.y),
@ -671,6 +680,7 @@ impl SpecctraDesign {
// add a seg between the current and previous coords
let _ = board.add_poly_fixed_seg_infringably(
recorder,
prev_index,
index,
FixedSegWeight {

View File

@ -86,11 +86,7 @@ impl SpecctraMesadata {
.classes
.iter()
.flat_map(|class| &class.nets)
.chain(
pcb.network.nets
.iter()
.map(|net| &net.name)
)
.chain(pcb.network.nets.iter().map(|net| &net.name))
.enumerate()
.map(|(net, netname)| (net, netname.clone())),
);

View File

@ -11,6 +11,7 @@ use topola::{
drawing::graph::{GetLayer, GetMaybeNet},
geometry::shape::MeasureLength,
graph::{GetPetgraphIndex, MakeRef},
layout::LayoutEdit,
specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata},
};
@ -18,7 +19,8 @@ pub fn load_design_and_assert(filename: &str) -> Invoker<SpecctraMesadata> {
let design_file = File::open(filename).unwrap();
let design_bufread = BufReader::new(design_file);
let design = SpecctraDesign::load(design_bufread).unwrap();
let mut invoker = Invoker::new(Autorouter::new(design.make_board()).unwrap());
let mut invoker =
Invoker::new(Autorouter::new(design.make_board(&mut LayoutEdit::new())).unwrap());
assert!(matches!(
invoker.undo(),