feat(autorouter/invoker): Create trait to display debug information on navmesh

This commit is contained in:
Mikolaj Wielgus 2025-05-01 01:58:03 +02:00
parent 83d9fce38c
commit f7cd817457
13 changed files with 233 additions and 59 deletions

View File

@ -114,4 +114,25 @@ impl<'a> Painter<'a> {
stroke,
));
}
pub fn paint_text(
&mut self,
pos: Point,
anchor: egui::Align2,
text: &str,
color: egui::epaint::Color32,
) {
let text = self.ui.painter().fonts(|fonts| {
egui::Shape::text(
fonts,
self.transform
.mul_pos([pos.x() as f32, -pos.y() as f32].into()),
anchor,
text,
egui::FontId::default(),
color,
)
});
self.ui.painter().add(text);
}
}

View File

@ -11,7 +11,9 @@ use rstar::{Envelope, AABB};
use topola::{
autorouter::{
execution::Command,
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles},
invoker::{
GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles,
},
},
board::AccessMesadata,
drawing::{
@ -22,6 +24,7 @@ use topola::{
graph::MakeRef,
layout::{poly::MakePolygon, via::ViaWeight},
math::{Circle, RotationSense},
router::navmesh::NavvertexIndex,
};
use crate::{config::Config, menu_bar::MenuBar, painter::Painter, workspace::Workspace};
@ -265,6 +268,50 @@ impl Viewport {
};
painter.paint_edge(from, to, stroke);
if let Some(text) = activity
.navedge_debug_text((edge.source(), edge.target()))
{
painter.paint_text(
(from + to) / 2.0,
egui::Align2::LEFT_BOTTOM,
text,
egui::Color32::from_rgb(255, 255, 255),
);
}
}
for index in navmesh.graph().node_indices() {
let navvertex = NavvertexIndex(index);
if let Some(text) = activity.navvertex_debug_text(navvertex)
{
let mut pos = PrimitiveIndex::from(
navmesh.node_weight(navvertex).unwrap().node,
)
.primitive(board.layout().drawing())
.shape()
.center();
pos += match navmesh
.node_weight(navvertex)
.unwrap()
.maybe_sense
{
Some(RotationSense::Counterclockwise) => {
[0.0, 150.0].into()
}
Some(RotationSense::Clockwise) => {
[-0.0, -150.0].into()
}
None => [0.0, 0.0].into(),
};
painter.paint_text(
pos,
egui::Align2::LEFT_BOTTOM,
text,
egui::Color32::from_rgb(255, 255, 255),
);
}
}
}
}

View File

@ -19,7 +19,7 @@ use crate::{
translator::Translator,
};
/// A loaded design and associated structures
/// A loaded design and associated structures.
pub struct Workspace {
pub design: SpecctraDesign,
pub appearance_panel: AppearancePanel,
@ -50,7 +50,7 @@ impl Workspace {
interactor: Interactor::new(board).map_err(|err| {
format!(
"{}; {}",
tr.text("tr-error_unable-to-initialize-overlay"),
tr.text("tr-error-unable-to-initialize-overlay"),
err
)
})?,

View File

@ -14,12 +14,16 @@ use crate::{
drawing::{band::BandTermsegIndex, graph::PrimitiveIndex, Collect},
geometry::primitive::PrimitiveShape,
layout::LayoutEdit,
router::{navcord::NavcordStepper, navmesh::Navmesh, RouteStepper, Router},
router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
RouteStepper, Router,
},
stepper::Step,
};
use super::{
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles},
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
Autorouter, AutorouterError, AutorouterOptions,
};
@ -159,29 +163,37 @@ impl<M: AccessMesadata> Step<Autorouter<M>, Option<LayoutEdit>, AutorouteContinu
}
impl GetMaybeNavmesh for AutorouteExecutionStepper {
/// Retrieves an optional reference to the navigation mesh from the current route.
fn maybe_navmesh(&self) -> Option<&Navmesh> {
self.route.as_ref().map(|route| route.navmesh())
}
}
impl GetMaybeNavcord for AutorouteExecutionStepper {
/// Retrieves an optional reference to the navcord from the current route.
fn maybe_navcord(&self) -> Option<&NavcordStepper> {
self.route.as_ref().map(|route| route.navcord())
}
}
impl GetGhosts for AutorouteExecutionStepper {
/// Retrieves ghost shapes from the current route.
fn ghosts(&self) -> &[PrimitiveShape] {
self.route.as_ref().map_or(&[], |route| route.ghosts())
}
}
impl GetObstacles for AutorouteExecutionStepper {
/// Retrieves obstacles encountered during routing.
fn obstacles(&self) -> &[PrimitiveIndex] {
self.route.as_ref().map_or(&[], |route| route.obstacles())
}
}
impl GetNavmeshDebugTexts for AutorouteExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
// Add debug text here.
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
// Add debug text here.
None
}
}

View File

@ -14,13 +14,17 @@ use crate::{
drawing::graph::PrimitiveIndex,
geometry::{primitive::PrimitiveShape, shape::MeasureLength},
graph::MakeRef,
router::{navcord::NavcordStepper, navmesh::Navmesh},
router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
stepper::Step,
};
use super::{
autoroute::{AutorouteContinueStatus, AutorouteExecutionStepper},
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles},
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
remove_bands::RemoveBandsExecutionStepper,
Autorouter, AutorouterError, AutorouterOptions,
};
@ -123,3 +127,13 @@ impl GetObstacles for CompareDetoursExecutionStepper {
self.autoroute.obstacles()
}
}
impl GetNavmeshDebugTexts for CompareDetoursExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
None
}
}

View File

@ -35,7 +35,13 @@ pub enum Command {
MeasureLength(BandSelection),
}
#[enum_dispatch(GetMaybeNavmesh, GetMaybeNavcord, GetGhosts, GetObstacles)]
#[enum_dispatch(
GetMaybeNavmesh,
GetMaybeNavcord,
GetGhosts,
GetObstacles,
GetNavmeshDebugTexts
)]
pub enum ExecutionStepper {
Autoroute(AutorouteExecutionStepper),
PlaceVia(PlaceViaExecutionStepper),

View File

@ -15,7 +15,10 @@ use crate::{
board::AccessMesadata,
drawing::graph::PrimitiveIndex,
geometry::{edit::ApplyGeometryEdit, primitive::PrimitiveShape},
router::{navcord::NavcordStepper, navmesh::Navmesh},
router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
stepper::Step,
};
@ -31,63 +34,59 @@ use super::{
};
#[enum_dispatch]
/// Getter trait to obtain Navigation Mesh
///
/// Navigation Mesh is possible routes between
/// two points
/// Trait for getting the navmesh to display it on the debug overlay.
pub trait GetMaybeNavmesh {
/// Returns Navigation Mesh if possible
fn maybe_navmesh(&self) -> Option<&Navmesh>;
}
#[enum_dispatch]
/// Getter for Navigation Cord
///
/// Navigation Cord is the possible path of
/// ongoing autorouting process
/// Trait for getting the navcord to display it on the debug overlay.
pub trait GetMaybeNavcord {
/// Gets the Navigation Cord if possible
fn maybe_navcord(&self) -> Option<&NavcordStepper>;
}
#[enum_dispatch]
/// Requires Ghosts implementations
///
/// Ghosts are possible shapes of routing
/// bands
/// Trait for getting ghosts to display on the debug overlay. Ghosts are the
/// shapes that Topola attempted to create but failed due to them infringing on
/// other shapes.
pub trait GetGhosts {
/// Retrieves the ghosts associated with the execution.
fn ghosts(&self) -> &[PrimitiveShape];
}
#[enum_dispatch]
/// Getter for the Obstacles
///
/// Obstacles are shapes of existing bands
/// to be avoided by the new band
/// Trait for getting the obstacles that prevented Topola from creating
/// new objects (the shapes of these objects can be obtained with the above
/// `GetGhosts` trait), for the purpose of displaying these obstacles on the
/// debug overlay.
pub trait GetObstacles {
/// Returns possible Obstacles
fn obstacles(&self) -> &[PrimitiveIndex];
}
/// Error types that can occur during the invocation of commands
#[enum_dispatch]
/// Trait for getting text strings with debug information attached to navmesh
/// edges and vertices.
pub trait GetNavmeshDebugTexts {
fn navvertex_debug_text(&self, navvertex: NavvertexIndex) -> Option<&str>;
fn navedge_debug_text(&self, navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str>;
}
/// Error types that can occur during the invocation of commands.
#[derive(Error, Debug, Clone)]
pub enum InvokerError {
/// Wraps errors related to command history operations
/// Wraps errors related to command history operations.
#[error(transparent)]
History(#[from] HistoryError),
/// Wraps errors related to autorouter operations
/// Wraps errors related to autorouter operations.
#[error(transparent)]
Autorouter(#[from] AutorouterError),
}
#[derive(Getters, Dissolve)]
/// Structure that manages the execution and history of commands within the autorouting system
pub struct Invoker<M> {
/// Instance for executing desired autorouting commands
/// Instance for executing desired autorouting commands.
pub(super) autorouter: Autorouter<M>,
/// History of executed commands
/// History of executed commands.
pub(super) history: History,
/// Currently ongoing command type.
pub(super) ongoing_command: Option<Command>,
@ -109,10 +108,10 @@ impl<M: AccessMesadata> Invoker<M> {
}
//#[debug_requires(self.ongoing_command.is_none())]
/// Executes a command, managing the command status and history
/// Executes a command, managing the command status and history.
///
/// This function is used to pass the [`Command`] to [`Invoker::execute_stepper`]
/// function, and control its execution status
/// function, and control its execution status.
pub fn execute(&mut self, command: Command) -> Result<(), InvokerError> {
let mut execute = self.execute_stepper(command)?;
@ -127,9 +126,9 @@ impl<M: AccessMesadata> Invoker<M> {
}
#[debug_requires(self.ongoing_command.is_none())]
/// Pass given command to be executed
/// Pass given command to be executed.
///
/// Function used to set given [`Command`] to ongoing state, dispatch and execute it
/// Function used to set given [`Command`] to ongoing state, dispatch and execute it.
pub fn execute_stepper(&mut self, command: Command) -> Result<ExecutionStepper, InvokerError> {
let execute = self.dispatch_command(&command);
self.ongoing_command = Some(command);
@ -174,7 +173,7 @@ impl<M: AccessMesadata> Invoker<M> {
}
#[debug_requires(self.ongoing_command.is_none())]
/// Undo last command
/// Undo last command.
pub fn undo(&mut self) -> Result<(), InvokerError> {
let last_done = self.history.last_done()?;
@ -186,7 +185,7 @@ impl<M: AccessMesadata> Invoker<M> {
}
//#[debug_requires(self.ongoing_command.is_none())]
/// Redo last command
/// Redo last command.
pub fn redo(&mut self) -> Result<(), InvokerError> {
let last_undone = self.history.last_undone()?;
@ -198,7 +197,7 @@ impl<M: AccessMesadata> Invoker<M> {
}
#[debug_requires(self.ongoing_command.is_none())]
/// Replay last command
/// Replay last command.
pub fn replay(&mut self, history: History) {
let (done, undone) = history.dissolve();

View File

@ -11,11 +11,15 @@ use crate::{
drawing::graph::PrimitiveIndex,
geometry::{primitive::PrimitiveShape, shape::MeasureLength as MeasureLengthTrait},
graph::MakeRef,
router::{navcord::NavcordStepper, navmesh::Navmesh},
router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
};
use super::{
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles},
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
remove_bands::RemoveBandsExecutionStepper,
selection::BandSelection,
Autorouter, AutorouterError,
};
@ -78,3 +82,13 @@ impl GetObstacles for MeasureLengthExecutionStepper {
&[]
}
}
impl GetNavmeshDebugTexts for MeasureLengthExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
None
}
}

View File

@ -11,11 +11,14 @@ use crate::{
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
layout::{via::ViaWeight, LayoutEdit},
router::{navcord::NavcordStepper, navmesh::Navmesh},
router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
};
use super::{
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles},
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
Autorouter, AutorouterError,
};
@ -75,3 +78,13 @@ impl GetObstacles for PlaceViaExecutionStepper {
&[]
}
}
impl GetNavmeshDebugTexts for PlaceViaExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
None
}
}

View File

@ -9,11 +9,14 @@ use crate::{
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
layout::LayoutEdit,
router::{navcord::NavcordStepper, navmesh::Navmesh},
router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
};
use super::{
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles},
invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles},
selection::BandSelection,
Autorouter, AutorouterError,
};
@ -74,3 +77,13 @@ impl GetObstacles for RemoveBandsExecutionStepper {
&[]
}
}
impl GetNavmeshDebugTexts for RemoveBandsExecutionStepper {
fn navvertex_debug_text(&self, _navvertex: NavvertexIndex) -> Option<&str> {
None
}
fn navedge_debug_text(&self, _navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
None
}
}

View File

@ -12,14 +12,18 @@ use crate::{
autorouter::{
execution::ExecutionStepper,
invoker::{
GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles, Invoker, InvokerError,
GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles,
Invoker, InvokerError,
},
},
board::AccessMesadata,
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
interactor::interaction::{InteractionError, InteractionStepper},
router::{navcord::NavcordStepper, navmesh::Navmesh},
router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
stepper::{Abort, Step},
};
@ -44,7 +48,13 @@ pub enum ActivityError {
}
/// An activity is either an interaction or an execution
#[enum_dispatch(GetMaybeNavmesh, GetMaybeNavcord, GetGhosts, GetObstacles)]
#[enum_dispatch(
GetMaybeNavmesh,
GetMaybeNavcord,
GetGhosts,
GetObstacles,
GetNavmeshDebugTexts
)]
pub enum ActivityStepper {
Interaction(InteractionStepper),
Execution(ExecutionStepper),
@ -137,3 +147,13 @@ impl GetObstacles for ActivityStepperWithStatus {
self.activity.obstacles()
}
}
impl GetNavmeshDebugTexts for ActivityStepperWithStatus {
fn navvertex_debug_text(&self, navvertex: NavvertexIndex) -> Option<&str> {
self.activity.navvertex_debug_text(navvertex)
}
fn navedge_debug_text(&self, navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
self.activity.navedge_debug_text(navedge)
}
}

View File

@ -7,11 +7,16 @@ use std::ops::ControlFlow;
use thiserror::Error;
use crate::{
autorouter::invoker::{GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetObstacles},
autorouter::invoker::{
GetGhosts, GetMaybeNavcord, GetMaybeNavmesh, GetNavmeshDebugTexts, GetObstacles,
},
board::AccessMesadata,
drawing::graph::PrimitiveIndex,
geometry::primitive::PrimitiveShape,
router::{navcord::NavcordStepper, navmesh::Navmesh},
router::{
navcord::NavcordStepper,
navmesh::{Navmesh, NavvertexIndex},
},
stepper::{Abort, Step},
};
@ -70,3 +75,13 @@ impl GetObstacles for InteractionStepper {
todo!()
}
}
impl GetNavmeshDebugTexts for InteractionStepper {
fn navvertex_debug_text(&self, navvertex: NavvertexIndex) -> Option<&str> {
todo!()
}
fn navedge_debug_text(&self, navedge: (NavvertexIndex, NavvertexIndex)) -> Option<&str> {
todo!()
}
}

View File

@ -39,7 +39,7 @@ use crate::{
use super::RouterOptions;
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
pub struct NavvertexIndex(NodeIndex<usize>);
pub struct NavvertexIndex(pub NodeIndex<usize>);
impl core::fmt::Debug for NavvertexIndex {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
@ -152,7 +152,7 @@ pub enum NavmeshError {
/// when going directly to destination) on the layout for each leap and
/// along-edge crossing.
///
/// The name "navmesh" is a shortening of "navigation mesh".
/// The name "navmesh" is a blend of "navigation mesh".
#[derive(Debug, Clone)]
pub struct Navmesh {
graph: UnGraph<NavvertexWeight, (), usize>,