Make it possible to hide debug overlay elements

This commit is contained in:
Mikolaj Wielgus 2026-06-07 20:24:57 +02:00
parent 18316e1a8f
commit 1433c02d81
7 changed files with 402 additions and 301 deletions

View File

@ -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

View File

@ -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);
}
}

View File

@ -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<i64>,
repulsions: impl IntoIterator<Item = Vector2<i64>>,
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,*/
),
);
}
}
}
}
}
}

View File

@ -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<i64>,
repulsions: impl IntoIterator<Item = Vector2<i64>>,
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,

View File

@ -8,6 +8,7 @@ mod action;
mod actions;
mod app;
mod controller;
mod debug_overlay;
mod display;
mod layers_panel;
mod menu_bar;

View File

@ -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,
}
}

View File

@ -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;