From 1433c02d81a3614cad6ef7d533171401e15a127d Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Sun, 7 Jun 2026 20:24:57 +0200 Subject: [PATCH] Make it possible to hide debug overlay elements --- locales/en-US/main.ftl | 3 + topola-egui/src/actions.rs | 22 ++ topola-egui/src/debug_overlay.rs | 338 +++++++++++++++++++++++++++++++ topola-egui/src/display.rs | 322 ++--------------------------- topola-egui/src/main.rs | 1 + topola-egui/src/menu_bar.rs | 15 ++ topola-egui/src/viewport.rs | 2 +- 7 files changed, 402 insertions(+), 301 deletions(-) create mode 100644 topola-egui/src/debug_overlay.rs diff --git a/locales/en-US/main.ftl b/locales/en-US/main.ftl index f013cd7..efd4ab1 100644 --- a/locales/en-US/main.ftl +++ b/locales/en-US/main.ftl @@ -47,6 +47,9 @@ tr-menu-debug-show-triangulation = Show Triangulation tr-menu-debug-show-triangulation-constraints = Show Triangulation Constraints tr-menu-debug-show-pathfinding-scores = Show Pathfinding Scores tr-menu-debug-show-topo-navmesh = Show Topological Navmesh +tr-menu-debug-show-repulsions = Show Repulsions +tr-menu-debug-show-attractions = Show Attractions +tr-menu-debug-show-retentions = Show Retentions tr-menu-debug-show-bboxes = Show BBoxes tr-menu-debug-show-primitive-indices = Show Primitive Indices tr-menu-debug-show-bend-endpoint-tangents = Show Bend Endpoint Tangents diff --git a/topola-egui/src/actions.rs b/topola-egui/src/actions.rs index d0b177c..2637b34 100644 --- a/topola-egui/src/actions.rs +++ b/topola-egui/src/actions.rs @@ -101,6 +101,11 @@ impl RunActions { pub struct DebugActions { pub fix_step_rate: Switch, + pub show_repulsions: Switch, + pub show_attractions: Switch, + pub show_retentions: Switch, + pub show_bboxes: Switch, + pub show_navmeshes: Switch, } impl DebugActions { @@ -108,10 +113,27 @@ impl DebugActions { Self { fix_step_rate: Action::new_keyless(tr.text("tr-menu-debug-fix-step-rate")) .into_switch(), + show_repulsions: Action::new_keyless(tr.text("tr-menu-debug-show-repulsions")) + .into_switch(), + show_attractions: Action::new_keyless(tr.text("tr-menu-debug-show-attractions")) + .into_switch(), + show_retentions: Action::new_keyless(tr.text("tr-menu-debug-show-retentions")) + .into_switch(), + show_bboxes: Action::new_keyless(tr.text("tr-menu-debug-show-bboxes")).into_switch(), + show_navmeshes: Action::new_keyless(tr.text("tr-menu-debug-show-navmesh")) + .into_switch(), } } pub fn render_menu(&mut self, _ctx: &Context, ui: &mut Ui, menu_bar: &mut MenuBar) { self.fix_step_rate.checkbox(ui, &mut menu_bar.fix_step_rate); + + ui.separator(); + + self.show_repulsions.checkbox(ui, &mut menu_bar.show_repulsions); + self.show_attractions.checkbox(ui, &mut menu_bar.show_attractions); + self.show_retentions.checkbox(ui, &mut menu_bar.show_retentions); + self.show_bboxes.checkbox(ui, &mut menu_bar.show_bboxes); + self.show_navmeshes.checkbox(ui, &mut menu_bar.show_navmeshes); } } diff --git a/topola-egui/src/debug_overlay.rs b/topola-egui/src/debug_overlay.rs new file mode 100644 index 0000000..b39bd15 --- /dev/null +++ b/topola-egui/src/debug_overlay.rs @@ -0,0 +1,338 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{controller::Controller, viewport::Viewport}; +use topola::{Orientation, Vector2, Workspace}; + +pub struct DebugOverlay {} + +pub struct DebugOverlayOptions { + pub repulsions: bool, + pub attractions: bool, + pub retentions: bool, + pub bboxes: bool, + pub navmeshes: bool, +} + +impl DebugOverlay { + pub fn new() -> Self { + Self {} + } + + pub fn update( + &mut self, + ctx: &egui::Context, + ui: &egui::Ui, + viewport: &Viewport, + workspace: &Controller, + options: &DebugOverlayOptions, + ) { + if options.repulsions { + self.display_repulsions(ui, viewport, workspace); + } + if options.attractions { + self.display_attractions(ui, viewport, workspace); + } + if options.retentions { + self.display_retentions(ui, viewport, workspace); + } + if options.bboxes { + self.display_bboxes(ctx, ui, viewport, workspace); + } + if options.navmeshes { + self.display_navmeshes(ctx, ui, viewport, workspace); + } + } + + fn display_repulsions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { + crate::profile_function!(); + let board = workspace.workspace.board(); + let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::YELLOW); + + for selector in &workspace.workspace.selection().components.0 { + let Some(component_id) = board.component_id(&selector.component) else { + continue; + }; + + let Some(bbox) = board.layout().component_bbox2(component_id) else { + continue; + }; + + let origin = Vector2::new((bbox.min.x + bbox.max.x) / 2, (bbox.min.y + bbox.max.y) / 2); + + Self::paint_arrows( + ui, + origin, + board + .layout() + .locate_component_repulsions(component_id, Orientation::Oblique), + stroke, + ); + } + + for selector in &workspace.workspace.selection().pins.0 { + let Some(pin_id) = board.pin_id(&selector.pin) else { + continue; + }; + + Self::paint_arrows( + ui, + board.layout().pin_centroid(pin_id), + board + .layout() + .locate_pin_repulsions(pin_id, Orientation::Oblique), + stroke, + ); + } + } + + fn display_retentions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { + crate::profile_function!(); + let board = workspace.workspace.board(); + let layout = board.layout(); + let stroke = egui::Stroke::new( + 150.0 / viewport.scale_factor(), + egui::Color32::from_rgb(192, 64, 255), + ); + + for selector in &workspace.workspace.selection().components.0 { + let Some(component_id) = board.component_id(&selector.component) else { + continue; + }; + + let Some(bbox) = layout.component_bbox2(component_id) else { + continue; + }; + + let origin = Vector2::new((bbox.min.x + bbox.max.x) / 2, (bbox.min.y + bbox.max.y) / 2); + + Self::paint_arrows( + ui, + origin, + layout.component_retentions(component_id), + stroke, + ); + } + + for selector in &workspace.workspace.selection().pins.0 { + let Some(pin_id) = board.pin_id(&selector.pin) else { + continue; + }; + + Self::paint_arrows( + ui, + layout.pin_centroid(pin_id), + layout.pin_retentions(pin_id), + stroke, + ); + } + } + + fn display_attractions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { + crate::profile_function!(); + let board = workspace.workspace.board(); + let layout = board.layout(); + let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::BLUE); + + for selector in &workspace.workspace.selection().components.0 { + let Some(component_id) = board.component_id(&selector.component) else { + continue; + }; + + let Some(bbox) = layout.component_bbox2(component_id) else { + continue; + }; + + let origin = Vector2::new((bbox.min.x + bbox.max.x) / 2, (bbox.min.y + bbox.max.y) / 2); + + Self::paint_arrows( + ui, + origin, + layout.component_attractions(component_id), + stroke, + ); + } + + for selector in &workspace.workspace.selection().pins.0 { + let Some(pin_id) = board.pin_id(&selector.pin) else { + continue; + }; + + Self::paint_arrows( + ui, + layout.pin_centroid(pin_id), + layout.pin_attractions(pin_id), + stroke, + ); + } + } + + fn paint_arrows( + ui: &egui::Ui, + origin: Vector2, + repulsions: impl IntoIterator>, + stroke: egui::Stroke, + ) { + for repulsion in repulsions { + if repulsion.x == 0 && repulsion.y == 0 { + continue; + } + + ui.painter().arrow( + egui::pos2(origin.x as f32, origin.y as f32), + egui::vec2(repulsion.x as f32, repulsion.y as f32), + stroke, + ); + } + } + + fn display_bboxes( + &mut self, + ctx: &egui::Context, + ui: &egui::Ui, + viewport: &Viewport, + workspace: &Controller, + ) { + crate::profile_function!(); + let board = workspace.workspace.board(); + let layout = board.layout(); + + for layer in workspace + .appearance_panel + .layers_in_display_order(*layout.layer_count()) + { + if !workspace.appearance_panel.visible[layer.index()] { + continue; + } + + for joint_id in layout.layer_joints(layer) { + let bbox = layout.joint(joint_id).bbox(); + ui.painter().rect_stroke( + egui::Rect { + min: egui::pos2(bbox.min.x as f32, bbox.min.y as f32), + max: egui::pos2(bbox.max.x as f32, bbox.max.y as f32), + }, + egui::CornerRadius::ZERO, + egui::Stroke::new(5.0, egui::Color32::GRAY), + egui::StrokeKind::Middle, + ); + } + + for segment_id in layout.layer_segments(layer) { + let endpoints = layout.segment(segment_id).endpoints; + + ui.painter().rect_stroke( + egui::Rect::from_two_pos( + egui::pos2(endpoints[0].x as f32, endpoints[0].y as f32), + egui::pos2(endpoints[1].x as f32, endpoints[1].y as f32), + ), + egui::CornerRadius::ZERO, + egui::Stroke::new(5.0, egui::Color32::GRAY), + egui::StrokeKind::Middle, + ); + } + + for via_id in layout.layer_vias(layer) { + let via = layout.via(via_id); + let bbox = via.bbox(); + + ui.painter().rect_stroke( + egui::Rect { + min: egui::pos2(bbox.min.x as f32, bbox.min.y as f32), + max: egui::pos2(bbox.max.x as f32, bbox.max.y as f32), + }, + egui::CornerRadius::ZERO, + egui::Stroke::new(5.0, egui::Color32::GRAY), + egui::StrokeKind::Middle, + ); + } + + for polygon_id in layout.layer_polygons(layer) { + let polygon = layout.polygon(polygon_id); + let bbox = polygon.bbox(); + + ui.painter().rect_stroke( + egui::Rect { + min: egui::pos2(bbox.min.x as f32, bbox.min.y as f32), + max: egui::pos2(bbox.max.x as f32, bbox.max.y as f32), + }, + egui::CornerRadius::ZERO, + egui::Stroke::new(5.0, egui::Color32::GRAY), + egui::StrokeKind::Middle, + ); + } + } + } + + fn display_navmeshes( + &mut self, + ctx: &egui::Context, + ui: &egui::Ui, + viewport: &Viewport, + workspace: &Controller, + ) { + crate::profile_function!(); + let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else { + return; + }; + let autorouter = &autorouter_workspace.autorouter; + + for layer in workspace + .appearance_panel + .layers_in_display_order(*workspace.workspace.board().layout().layer_count()) + { + if workspace.appearance_panel.visible[layer.index()] { + for navmesh in autorouter + .router() + .navmesher_board() + .navmesher() + .layer_navmeshers()[layer.index()] + .navmeshes() + { + for edge_geom in navmesh + .triangulation() + .rtreed_dcel() + .edges_rtree() + .as_ref() + .iter() + { + let (from_vertex, to_vertex) = navmesh + .triangulation() + .rtreed_dcel() + .dcel() + .edge_endpoints(edge_geom.data); + let from = navmesh + .triangulation() + .rtreed_dcel() + .dcel() + .vertex_weight(from_vertex) + .position(); + let to = navmesh + .triangulation() + .rtreed_dcel() + .dcel() + .vertex_weight(to_vertex) + .position(); + ui.painter().line_segment( + [ + egui::pos2(*from.x() as f32, *from.y() as f32), + egui::pos2(*to.x() as f32, *to.y() as f32), + ], + egui::Stroke::new( + 10.0, + egui::Color32::WHITE, + /*workspace + .appearance_panel + .colors(ctx) + .layers + .color(workspace.autorouter.navmesher_board().board().layer_name(layer)) + .normal,*/ + ), + ); + } + } + } + } + } +} diff --git a/topola-egui/src/display.rs b/topola-egui/src/display.rs index f316caf..8f27f62 100644 --- a/topola-egui/src/display.rs +++ b/topola-egui/src/display.rs @@ -2,9 +2,12 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::{controller::Controller, viewport::Viewport}; +use crate::{ + controller::Controller, debug_overlay::{DebugOverlay, DebugOverlayOptions}, menu_bar::MenuBar, + viewport::Viewport, +}; use topola::{ - Orientation, Vector2, Workspace, + Workspace, layout::primitives::{Joint, Polygon, Segment, Via}, }; @@ -19,19 +22,29 @@ impl Display { &mut self, ctx: &egui::Context, ui: &egui::Ui, - //menu_bar: &MenuBar, viewport: &Viewport, workspace: &Controller, + menu_bar: &MenuBar, ) { crate::profile_function!(); - self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace); - self.display_repulsions(ui, viewport, workspace); - self.display_attractions(ui, viewport, workspace); - self.display_retentions(ui, viewport, workspace); - self.display_bboxes(ctx, ui, viewport, workspace); - self.display_navmeshes(ctx, ui, viewport, workspace); + self.display_layout(ctx, ui, viewport, workspace); self.display_ratsnest(ctx, ui, viewport, workspace); + + let mut debug_overlay = DebugOverlay::new(); + debug_overlay.update( + ctx, + ui, + viewport, + workspace, + &DebugOverlayOptions { + repulsions: menu_bar.show_repulsions, + attractions: menu_bar.show_attractions, + retentions: menu_bar.show_retentions, + bboxes: menu_bar.show_bboxes, + navmeshes: menu_bar.show_navmeshes, + }, + ); } fn display_layout( @@ -164,148 +177,6 @@ impl Display { ); } - fn display_repulsions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { - crate::profile_function!(); - let board = workspace.workspace.board(); - let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::YELLOW); - - for selector in &workspace.workspace.selection().components.0 { - let Some(component_id) = board.component_id(&selector.component) else { - continue; - }; - - let Some(bbox) = board.layout().component_bbox2(component_id) else { - continue; - }; - - let origin = Vector2::new((bbox.min.x + bbox.max.x) / 2, (bbox.min.y + bbox.max.y) / 2); - - Self::paint_arrows( - ui, - origin, - board - .layout() - .locate_component_repulsions(component_id, Orientation::Oblique), - stroke, - ); - } - - for selector in &workspace.workspace.selection().pins.0 { - let Some(pin_id) = board.pin_id(&selector.pin) else { - continue; - }; - - Self::paint_arrows( - ui, - board.layout().pin_centroid(pin_id), - board - .layout() - .locate_pin_repulsions(pin_id, Orientation::Oblique), - stroke, - ); - } - } - - fn display_retentions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { - crate::profile_function!(); - let board = workspace.workspace.board(); - let layout = board.layout(); - let stroke = egui::Stroke::new( - 150.0 / viewport.scale_factor(), - egui::Color32::from_rgb(192, 64, 255), - ); - - for selector in &workspace.workspace.selection().components.0 { - let Some(component_id) = board.component_id(&selector.component) else { - continue; - }; - - let Some(bbox) = layout.component_bbox2(component_id) else { - continue; - }; - - let origin = Vector2::new((bbox.min.x + bbox.max.x) / 2, (bbox.min.y + bbox.max.y) / 2); - - Self::paint_arrows( - ui, - origin, - layout.component_retentions(component_id), - stroke, - ); - } - - for selector in &workspace.workspace.selection().pins.0 { - let Some(pin_id) = board.pin_id(&selector.pin) else { - continue; - }; - - Self::paint_arrows( - ui, - layout.pin_centroid(pin_id), - layout.pin_retentions(pin_id), - stroke, - ); - } - } - - fn display_attractions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { - crate::profile_function!(); - let board = workspace.workspace.board(); - let layout = board.layout(); - let stroke = egui::Stroke::new(150.0 / viewport.scale_factor(), egui::Color32::BLUE); - - for selector in &workspace.workspace.selection().components.0 { - let Some(component_id) = board.component_id(&selector.component) else { - continue; - }; - - let Some(bbox) = layout.component_bbox2(component_id) else { - continue; - }; - - let origin = Vector2::new((bbox.min.x + bbox.max.x) / 2, (bbox.min.y + bbox.max.y) / 2); - - Self::paint_arrows( - ui, - origin, - layout.component_attractions(component_id), - stroke, - ); - } - - for selector in &workspace.workspace.selection().pins.0 { - let Some(pin_id) = board.pin_id(&selector.pin) else { - continue; - }; - - Self::paint_arrows( - ui, - layout.pin_centroid(pin_id), - layout.pin_attractions(pin_id), - stroke, - ); - } - } - - fn paint_arrows( - ui: &egui::Ui, - origin: Vector2, - repulsions: impl IntoIterator>, - stroke: egui::Stroke, - ) { - for repulsion in repulsions { - if repulsion.x == 0 && repulsion.y == 0 { - continue; - } - - ui.painter().arrow( - egui::pos2(origin.x as f32, origin.y as f32), - egui::vec2(repulsion.x as f32, repulsion.y as f32), - stroke, - ); - } - } - fn paint_joint( &mut self, ctx: &egui::Context, @@ -374,155 +245,6 @@ impl Display { )); } - fn display_bboxes( - &mut self, - ctx: &egui::Context, - ui: &egui::Ui, - viewport: &Viewport, - workspace: &Controller, - ) { - crate::profile_function!(); - let board = workspace.workspace.board(); - let layout = board.layout(); - - for layer in workspace - .appearance_panel - .layers_in_display_order(*layout.layer_count()) - { - if !workspace.appearance_panel.visible[layer.index()] { - continue; - } - - for joint_id in layout.layer_joints(layer) { - let bbox = layout.joint(joint_id).bbox(); - ui.painter().rect_stroke( - egui::Rect { - min: egui::pos2(bbox.min.x as f32, bbox.min.y as f32), - max: egui::pos2(bbox.max.x as f32, bbox.max.y as f32), - }, - egui::CornerRadius::ZERO, - egui::Stroke::new(5.0, egui::Color32::GRAY), - egui::StrokeKind::Middle, - ); - } - - for segment_id in layout.layer_segments(layer) { - let endpoints = layout.segment(segment_id).endpoints; - - ui.painter().rect_stroke( - egui::Rect::from_two_pos( - egui::pos2(endpoints[0].x as f32, endpoints[0].y as f32), - egui::pos2(endpoints[1].x as f32, endpoints[1].y as f32), - ), - egui::CornerRadius::ZERO, - egui::Stroke::new(5.0, egui::Color32::GRAY), - egui::StrokeKind::Middle, - ); - } - - for via_id in layout.layer_vias(layer) { - let via = layout.via(via_id); - let bbox = via.bbox(); - - ui.painter().rect_stroke( - egui::Rect { - min: egui::pos2(bbox.min.x as f32, bbox.min.y as f32), - max: egui::pos2(bbox.max.x as f32, bbox.max.y as f32), - }, - egui::CornerRadius::ZERO, - egui::Stroke::new(5.0, egui::Color32::GRAY), - egui::StrokeKind::Middle, - ); - } - - for polygon_id in layout.layer_polygons(layer) { - let polygon = layout.polygon(polygon_id); - let bbox = polygon.bbox(); - - ui.painter().rect_stroke( - egui::Rect { - min: egui::pos2(bbox.min.x as f32, bbox.min.y as f32), - max: egui::pos2(bbox.max.x as f32, bbox.max.y as f32), - }, - egui::CornerRadius::ZERO, - egui::Stroke::new(5.0, egui::Color32::GRAY), - egui::StrokeKind::Middle, - ); - } - } - } - - fn display_navmeshes( - &mut self, - ctx: &egui::Context, - ui: &egui::Ui, - viewport: &Viewport, - workspace: &Controller, - ) { - crate::profile_function!(); - let Workspace::Autorouter(autorouter_workspace) = &workspace.workspace else { - return; - }; - let autorouter = &autorouter_workspace.autorouter; - - for layer in workspace - .appearance_panel - .layers_in_display_order(*workspace.workspace.board().layout().layer_count()) - { - if workspace.appearance_panel.visible[layer.index()] { - for navmesh in autorouter - .router() - .navmesher_board() - .navmesher() - .layer_navmeshers()[layer.index()] - .navmeshes() - { - for edge_geom in navmesh - .triangulation() - .rtreed_dcel() - .edges_rtree() - .as_ref() - .iter() - { - let (from_vertex, to_vertex) = navmesh - .triangulation() - .rtreed_dcel() - .dcel() - .edge_endpoints(edge_geom.data); - let from = navmesh - .triangulation() - .rtreed_dcel() - .dcel() - .vertex_weight(from_vertex) - .position(); - let to = navmesh - .triangulation() - .rtreed_dcel() - .dcel() - .vertex_weight(to_vertex) - .position(); - ui.painter().line_segment( - [ - egui::pos2(*from.x() as f32, *from.y() as f32), - egui::pos2(*to.x() as f32, *to.y() as f32), - ], - egui::Stroke::new( - 10.0, - egui::Color32::WHITE, - /*workspace - .appearance_panel - .colors(ctx) - .layers - .color(workspace.autorouter.navmesher_board().board().layer_name(layer)) - .normal,*/ - ), - ); - } - } - } - } - } - fn display_ratsnest( &mut self, _ctx: &egui::Context, diff --git a/topola-egui/src/main.rs b/topola-egui/src/main.rs index 9f15bf2..91efa4b 100644 --- a/topola-egui/src/main.rs +++ b/topola-egui/src/main.rs @@ -8,6 +8,7 @@ mod action; mod actions; mod app; mod controller; +mod debug_overlay; mod display; mod layers_panel; mod menu_bar; diff --git a/topola-egui/src/menu_bar.rs b/topola-egui/src/menu_bar.rs index ee3d7bb..14a7e9a 100644 --- a/topola-egui/src/menu_bar.rs +++ b/topola-egui/src/menu_bar.rs @@ -29,6 +29,16 @@ pub struct MenuBar { pub step_rate: f64, #[serde(default)] pub show_profiler: bool, + #[serde(default)] + pub show_repulsions: bool, + #[serde(default)] + pub show_attractions: bool, + #[serde(default)] + pub show_retentions: bool, + #[serde(default)] + pub show_bboxes: bool, + #[serde(default)] + pub show_navmeshes: bool, } impl MenuBar { @@ -37,6 +47,11 @@ impl MenuBar { fix_step_rate: false, step_rate: 1.0, show_profiler: false, + show_repulsions: false, + show_attractions: false, + show_retentions: false, + show_bboxes: false, + show_navmeshes: false, } } diff --git a/topola-egui/src/viewport.rs b/topola-egui/src/viewport.rs index 6453ed5..de8f52b 100644 --- a/topola-egui/src/viewport.rs +++ b/topola-egui/src/viewport.rs @@ -45,7 +45,7 @@ impl Viewport { .show(ui, &mut scene_rect, |ui| { if let Some(ref controller) = controller { let mut display = Display::new(); - display.update(ctx, ui, &self, controller); + display.update(ctx, ui, &self, controller, menu_bar); } }) .response;