From 05108e964ded390e4ec49a8e7b9467e7cc3b5430 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Wed, 3 Jun 2026 02:20:45 +0200 Subject: [PATCH] Display repulsions and attractions of selected pins and components --- topola-egui/src/display.rs | 107 ++++++++++++++++++++++++++++- topola/src/layout/compounds/pin.rs | 18 +++++ topola/src/layout/infringement.rs | 56 ++++++++++++++- topola/src/layout/repulsion.rs | 48 ++++++++++++- topola/src/lib.rs | 1 + 5 files changed, 225 insertions(+), 5 deletions(-) diff --git a/topola-egui/src/display.rs b/topola-egui/src/display.rs index c745106..ce0b365 100644 --- a/topola-egui/src/display.rs +++ b/topola-egui/src/display.rs @@ -3,8 +3,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::{viewport::Viewport, workspace::GuiWorkspace}; -use topola::Workspace; use topola::primitives::{Joint, Polygon, Segment, Via}; +use topola::{Orientation, Vector2, Workspace}; pub struct Display {} @@ -22,6 +22,8 @@ impl Display { workspace: &GuiWorkspace, ) { self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace); + self.display_repulsions(ui, viewport, workspace); + self.display_attractions(ui, viewport, workspace); self.display_bboxes(ctx, ui, viewport, workspace); self.display_navmeshes(ctx, ui, viewport, workspace); self.display_ratsnest(ctx, ui, viewport, workspace); @@ -156,6 +158,109 @@ impl Display { ); } + fn display_repulsions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &GuiWorkspace) { + 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_attractions( + &mut self, + ui: &egui::Ui, + viewport: &Viewport, + workspace: &GuiWorkspace, + ) { + 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, diff --git a/topola/src/layout/compounds/pin.rs b/topola/src/layout/compounds/pin.rs index a608374..cd377a7 100644 --- a/topola/src/layout/compounds/pin.rs +++ b/topola/src/layout/compounds/pin.rs @@ -11,6 +11,7 @@ use crate::{ compounds::{ComponentId, NetId}, primitives::{JointId, PolygonId, SegmentId, ViaId}, }, + primitives::PrimitiveId, }; #[derive( @@ -61,6 +62,23 @@ impl Pin { polygons: Vec::new(), } } + + pub fn primitives(&self) -> impl Iterator + '_ { + self.joints + .iter() + .map(|&joint_id| PrimitiveId::Joint(joint_id)) + .chain( + self.segments + .iter() + .map(|&segment_id| PrimitiveId::Segment(segment_id)), + ) + .chain(self.vias.iter().map(|&via_id| PrimitiveId::Via(via_id))) + .chain( + self.polygons + .iter() + .map(|&polygon_id| PrimitiveId::Polygon(polygon_id)), + ) + } } impl Layout { diff --git a/topola/src/layout/infringement.rs b/topola/src/layout/infringement.rs index 93710d0..e9b6d46 100644 --- a/topola/src/layout/infringement.rs +++ b/topola/src/layout/infringement.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; use crate::primitives::PrimitiveId; use super::Layout; -use super::compounds::{ComponentId, NetId}; +use super::compounds::{ComponentId, NetId, PinId}; use super::primitives::{JointId, PolygonId, SegmentId, ViaId}; #[derive( @@ -140,6 +140,60 @@ impl Layout { .chain(polygon_infringements) } + pub fn locate_pin_infringements( + &self, + infringer: PinId, + ) -> impl Iterator> + '_ { + let mut infringee_pins = BTreeSet::new(); + + for infringement in self.locate_pin_primitive_infringements(infringer) { + let Some(infringee_pin) = self.primitive_pin(infringement.infringee()) else { + continue; + }; + + if infringee_pin == infringer { + continue; + } + + infringee_pins.insert(infringee_pin); + } + + infringee_pins + .into_iter() + .map(move |infringee| Infringement { + infringer, + infringee, + }) + } + + pub fn locate_pin_primitive_infringements( + &self, + infringer: PinId, + ) -> impl Iterator + '_ { + let pin = self.pin(infringer); + + pin.joints + .iter() + .copied() + .flat_map(|joint_id| self.locate_joint_infringements(joint_id).map(Into::into)) + .chain( + pin.segments.iter().copied().flat_map(|segment_id| { + self.locate_segment_infringements(segment_id) + .map(Into::into) + }), + ) + .chain( + pin.vias + .iter() + .copied() + .flat_map(|via_id| self.locate_via_infringements(via_id).map(Into::into)), + ) + .chain(pin.polygons.iter().copied().flat_map(|polygon_id| { + self.locate_polygon_infringements(polygon_id) + .map(Into::into) + })) + } + pub fn locate_joint_infringements( &self, infringer: JointId, diff --git a/topola/src/layout/repulsion.rs b/topola/src/layout/repulsion.rs index b3a5b17..09d58be 100644 --- a/topola/src/layout/repulsion.rs +++ b/topola/src/layout/repulsion.rs @@ -5,7 +5,10 @@ use crate::{ Rect2, Vector2, compass::CompassDirection, - layout::{Layout, compounds::ComponentId}, + layout::{ + Layout, + compounds::{ComponentId, PinId}, + }, orientation::Orientation, primitives::{JointId, PolygonId, PrimitiveId, SegmentId, ViaId}, }; @@ -35,8 +38,47 @@ impl Layout { let mut max_repulsion = Vector2::new(0, 0); let mut max_repulsion_magnitude = 0; - for infringer_primitive in self.component(infringer).primitives() { - for infringee_primitive in self.component(infringee).primitives() { + for &infringer_pin in self.component(infringer).pins.iter() { + for &infringee_pin in self.component(infringee).pins.iter() { + let repulsion = self.pin_pin_repulsion(infringer_pin, infringee_pin, orientation); + let repulsion_magnitude = repulsion.x.abs() + repulsion.y.abs(); + + if repulsion_magnitude > max_repulsion_magnitude { + max_repulsion = repulsion; + max_repulsion_magnitude = repulsion_magnitude; + } + } + } + + max_repulsion + } + + pub fn locate_pin_repulsions( + &self, + infringer: PinId, + orientation: Orientation, + ) -> impl Iterator> + '_ { + self.locate_pin_infringements(infringer) + .map(move |infringement| { + self.pin_pin_repulsion( + infringement.infringer(), + infringement.infringee(), + orientation, + ) + }) + } + + pub fn pin_pin_repulsion( + &self, + infringer: PinId, + infringee: PinId, + orientation: Orientation, + ) -> Vector2 { + let mut max_repulsion = Vector2::new(0, 0); + let mut max_repulsion_magnitude = 0; + + for infringer_primitive in self.pin(infringer).primitives() { + for infringee_primitive in self.pin(infringee).primitives() { let repulsion = self.primitive_primitive_repulsion( infringer_primitive, infringee_primitive, diff --git a/topola/src/lib.rs b/topola/src/lib.rs index d6557a7..b41dd25 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -32,6 +32,7 @@ pub use crate::layout::LayerId; pub use crate::layout::Layout; pub use crate::layout::compounds::{Pin, PinId}; pub use crate::layout::primitives; +pub use crate::orientation::Orientation; pub use crate::ratsnest::{Ratline, Ratsnest}; pub use crate::rect::{Rect2, Rect3}; pub use crate::vector::{Vector2, Vector3};