mirror of https://codeberg.org/topola/topola.git
feat(board,layout,drawing): implement edit recording
Not stored in the undo/redo objects yet.
This commit is contained in:
parent
ad1b43b806
commit
d6fe67a373
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 +55,7 @@ impl<R: AccessRules> Layout<R> {
|
|||
/// Insert [`Cane`] object into the [`Layout`]
|
||||
pub fn insert_cane(
|
||||
&mut self,
|
||||
recorder: &mut LayoutEdit,
|
||||
from: DotIndex,
|
||||
around: GearIndex,
|
||||
dot_weight: LooseDotWeight,
|
||||
|
|
@ -61,39 +63,53 @@ impl<R: AccessRules> Layout<R> {
|
|||
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>(
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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())),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue