From 4a4f18f558366575b06fda1ae301e3ee7fff13c9 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Wed, 15 May 2024 03:40:48 +0200 Subject: [PATCH] egui,autorouter: implement undo/redo in GUI --- src/autorouter/autorouter.rs | 55 ++++++++++++++++++++++-------------- src/autorouter/invoker.rs | 38 ++++++++++++++++++------- src/autorouter/ratsnest.rs | 14 +++++---- src/bin/topola-egui/app.rs | 29 ++++++++++++++++--- src/router/router.rs | 1 - tests/0603_breakout.rs | 2 +- 6 files changed, 97 insertions(+), 42 deletions(-) diff --git a/src/autorouter/autorouter.rs b/src/autorouter/autorouter.rs index f7830b9..616a422 100644 --- a/src/autorouter/autorouter.rs +++ b/src/autorouter/autorouter.rs @@ -29,6 +29,7 @@ use crate::{ pub struct Autoroute { ratlines_iter: Box>>, navmesh: Option, // Useful for debugging. + cur_ratline: Option>, } impl Autoroute { @@ -38,17 +39,18 @@ impl Autoroute { ) -> Option { let mut ratlines_iter = Box::new(ratlines.into_iter()); - let Some(cur_edge) = ratlines_iter.next() else { + let Some(cur_ratline) = ratlines_iter.next() else { return None; }; - let (from, to) = Self::edge_from_to(autorouter, cur_edge); + let (source, target) = Self::ratline_endpoints(autorouter, cur_ratline); let layout = autorouter.layout.lock().unwrap(); - let navmesh = Some(Navmesh::new(&layout, from, to).ok()?); + let navmesh = Some(Navmesh::new(&layout, source, target).ok()?); let this = Self { ratlines_iter, navmesh, + cur_ratline: Some(cur_ratline), }; Some(this) @@ -59,35 +61,46 @@ impl Autoroute { autorouter: &mut Autorouter, observer: &mut impl RouterObserverTrait, ) -> bool { - let new_navmesh = if let Some(cur_edge) = self.ratlines_iter.next() { - let (from, to) = Self::edge_from_to(autorouter, cur_edge); + let (new_navmesh, new_ratline) = if let Some(cur_ratline) = self.ratlines_iter.next() { + let (source, target) = Self::ratline_endpoints(autorouter, cur_ratline); let layout = autorouter.layout.lock().unwrap(); - Some(Navmesh::new(&layout, from, to).ok().unwrap()) + ( + Some(Navmesh::new(&layout, source, target).ok().unwrap()), + Some(cur_ratline), + ) } else { - None + (None, None) }; let router = Router::new_from_navmesh( &mut autorouter.layout, std::mem::replace(&mut self.navmesh, new_navmesh).unwrap(), ); - router.unwrap().route_band(100.0, observer); + + let Ok(band) = router.unwrap().route_band(100.0, observer) else { + return false; + }; + + autorouter + .ratsnest + .assign_band_to_ratline(self.cur_ratline.unwrap(), band); + self.cur_ratline = new_ratline; self.navmesh.is_some() } - fn edge_from_to( + fn ratline_endpoints( autorouter: &Autorouter, - edge: EdgeIndex, + ratline: EdgeIndex, ) -> (FixedDotIndex, FixedDotIndex) { let mut layout = autorouter.layout.lock().unwrap(); - let (from, to) = autorouter.ratsnest.graph().edge_endpoints(edge).unwrap(); + let (source, target) = autorouter.ratsnest.graph().edge_endpoints(ratline).unwrap(); - let from_dot = match autorouter + let source_dot = match autorouter .ratsnest .graph() - .node_weight(from) + .node_weight(source) .unwrap() .vertex_index() { @@ -95,10 +108,10 @@ impl Autoroute { RatsnestVertexIndex::Zone(zone) => layout.zone_apex(zone), }; - let to_dot = match autorouter + let target_dot = match autorouter .ratsnest .graph() - .node_weight(to) + .node_weight(target) .unwrap() .vertex_index() { @@ -106,7 +119,7 @@ impl Autoroute { RatsnestVertexIndex::Zone(zone) => layout.zone_apex(zone), }; - (from_dot, to_dot) + (source_dot, target_dot) } pub fn navmesh(&self) -> &Option { @@ -155,22 +168,22 @@ impl Autorouter { .graph() .edge_indices() .filter(|ratline| { - let (from, to) = self.ratsnest.graph().edge_endpoints(*ratline).unwrap(); + let (source, target) = self.ratsnest.graph().edge_endpoints(*ratline).unwrap(); - let from_vertex = self + let source_vertex = self .ratsnest .graph() - .node_weight(from) + .node_weight(source) .unwrap() .vertex_index(); let to_vertex = self .ratsnest .graph() - .node_weight(to) + .node_weight(target) .unwrap() .vertex_index(); - selection.contains(&from_vertex.into()) && selection.contains(&to_vertex.into()) + selection.contains(&source_vertex.into()) && selection.contains(&to_vertex.into()) }) .collect() } diff --git a/src/autorouter/invoker.rs b/src/autorouter/invoker.rs index a0ba83b..d785069 100644 --- a/src/autorouter/invoker.rs +++ b/src/autorouter/invoker.rs @@ -26,14 +26,20 @@ impl Execute { pub struct Invoker { autorouter: Autorouter, + history: Vec, + undone_history: Vec, } impl Invoker { pub fn new(autorouter: Autorouter) -> Self { - Self { autorouter } + Self { + autorouter, + history: vec![], + undone_history: vec![], + } } - pub fn execute(&mut self, command: &Command, observer: &mut impl RouterObserverTrait) { + pub fn execute(&mut self, command: Command, observer: &mut impl RouterObserverTrait) { let mut execute = self.execute_walk(command); while execute.next(self, observer) { @@ -41,20 +47,32 @@ impl Invoker { } } - pub fn execute_walk(&mut self, command: &Command) -> Execute { - match command { - Command::Autoroute(selection) => { - Execute::Autoroute(self.autorouter.autoroute_walk(&selection).unwrap()) + pub fn execute_walk(&mut self, command: Command) -> Execute { + let execute = match command { + Command::Autoroute(ref selection) => { + Execute::Autoroute(self.autorouter.autoroute_walk(selection).unwrap()) } - } + }; + + self.history.push(command); + execute } pub fn undo(&mut self) { - todo!(); + let command = self.history.pop().unwrap(); + + match command { + Command::Autoroute(ref selection) => { + self.autorouter.undo_autoroute(selection); + } + } + + self.undone_history.push(command); } - pub fn redo(&mut self) { - todo!(); + pub fn redo(&mut self, observer: &mut impl RouterObserverTrait) { + let command = self.undone_history.pop().unwrap(); + self.execute(command, observer); } pub fn autorouter(&self) -> &Autorouter { diff --git a/src/autorouter/ratsnest.rs b/src/autorouter/ratsnest.rs index 7a67b88..2549158 100644 --- a/src/autorouter/ratsnest.rs +++ b/src/autorouter/ratsnest.rs @@ -4,7 +4,7 @@ use enum_dispatch::enum_dispatch; use geo::Point; use petgraph::{ data::{Element, FromElements}, - graph::{NodeIndex, UnGraph}, + graph::{EdgeIndex, NodeIndex, UnGraph}, unionfind::UnionFind, visit::{EdgeRef, IntoEdgeReferences, NodeIndexable}, }; @@ -153,10 +153,10 @@ impl Ratsnest { } this.graph.retain_edges(|g, i| { - if let Some((from, to)) = g.edge_endpoints(i) { - let from_index = g.node_weight(from).unwrap().vertex_index().node_index(); - let to_index = g.node_weight(to).unwrap().vertex_index().node_index(); - !unionfind.equiv(from_index, to_index) + if let Some((source, target)) = g.edge_endpoints(i) { + let source_index = g.node_weight(source).unwrap().vertex_index().node_index(); + let target_index = g.node_weight(target).unwrap().vertex_index().node_index(); + !unionfind.equiv(source_index, target_index) } else { true } @@ -165,6 +165,10 @@ impl Ratsnest { Ok(this) } + pub fn assign_band_to_ratline(&mut self, ratline: EdgeIndex, band: BandIndex) { + self.graph.edge_weight_mut(ratline).unwrap().band = Some(band); + } + pub fn graph(&self) -> &UnGraph { &self.graph } diff --git a/src/bin/topola-egui/app.rs b/src/bin/topola-egui/app.rs index 8725853..b49e288 100644 --- a/src/bin/topola-egui/app.rs +++ b/src/bin/topola-egui/app.rs @@ -34,7 +34,7 @@ use topola::{ draw::DrawException, navmesh::{Navmesh, NavmeshEdgeReference, VertexIndex}, tracer::{Trace, Tracer}, - RouterObserverTrait, + EmptyRouterObserver, RouterObserverTrait, }, }; @@ -205,13 +205,13 @@ impl eframe::App for App { if ui.button("Autoroute").clicked() { if let (Some(invoker_arc_mutex), Some(overlay)) = (&self.invoker, &self.overlay) { - let invoker = invoker_arc_mutex.clone(); + let invoker_arc_mutex = invoker_arc_mutex.clone(); let shared_data_arc_mutex = self.shared_data.clone(); let selection = overlay.selection().clone(); execute(async move { - let mut invoker = invoker.lock().unwrap(); - let mut execute = invoker.execute_walk(&Command::Autoroute(selection)); + let mut invoker = invoker_arc_mutex.lock().unwrap(); + let mut execute = invoker.execute_walk(Command::Autoroute(selection)); if let Execute::Autoroute(ref mut autoroute) = execute { let from = autoroute.navmesh().as_ref().unwrap().from(); @@ -240,6 +240,27 @@ impl eframe::App for App { } } + if ui.button("Undo").clicked() { + if let Some(invoker_arc_mutex) = &self.invoker { + let invoker_arc_mutex = invoker_arc_mutex.clone(); + execute(async move { + invoker_arc_mutex.lock().unwrap().undo(); + }); + } + } + + if ui.button("Redo").clicked() { + if let Some(invoker_arc_mutex) = &self.invoker { + let invoker_arc_mutex = invoker_arc_mutex.clone(); + execute(async move { + invoker_arc_mutex + .lock() + .unwrap() + .redo(&mut EmptyRouterObserver); + }); + } + } + ui.separator(); egui::widgets::global_dark_light_mode_buttons(ui); diff --git a/src/router/router.rs b/src/router/router.rs index 0c15433..e7cf3c3 100644 --- a/src/router/router.rs +++ b/src/router/router.rs @@ -190,7 +190,6 @@ impl<'a, R: RulesTrait> Router<'a, R> { observer: &mut impl RouterObserverTrait, ) -> Result { let mut tracer = self.tracer(); - let trace = tracer.start(self.navmesh.from(), width); let (_cost, _path, band) = astar( diff --git a/tests/0603_breakout.rs b/tests/0603_breakout.rs index 0af2bc7..877b38a 100644 --- a/tests/0603_breakout.rs +++ b/tests/0603_breakout.rs @@ -15,7 +15,7 @@ fn test() { let layout_arc_mutex = Arc::new(Mutex::new(design.make_layout())); let mut autorouter = Autorouter::new(layout_arc_mutex.clone()).unwrap(); - autorouter.autoroute(0, &mut EmptyRouterObserver); + autorouter.autoroute(&mut EmptyRouterObserver); let layout = layout_arc_mutex.lock().unwrap(); let mut unionfind = UnionFind::new(layout.drawing().geometry().graph().node_bound());