Find pin selector of what is under mouse pointer

This commit is contained in:
Mikolaj Wielgus 2026-03-14 22:04:08 +01:00
parent 6992369041
commit 13c7bbb061
9 changed files with 180 additions and 79 deletions

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use egui::Pos2; use egui::Pos2;
use topola::Vector2;
use crate::{display::Display, workspace::Workspace}; use crate::{display::Display, workspace::Workspace};
@ -23,25 +24,74 @@ impl Viewport {
pub fn update(&mut self, ctx: &egui::Context, workspace: Option<&mut Workspace>) { pub fn update(&mut self, ctx: &egui::Context, workspace: Option<&mut Workspace>) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
let mut scene_rect = self.scene_rect.clone(); egui::Frame::canvas(ui.style()).show(ui, |ui| {
let zoom_range = 0.00001..=10000.0;
egui::Scene::new() let viewport_rect = ui.available_rect_before_wrap();
.zoom_range(0.00001..=10000.0) let mut scene_rect = self.scene_rect.clone();
.show(ui, &mut scene_rect, |ui| {
if let Some(ref workspace) = workspace {
let mut display = Display::new();
display.update(ctx, ui, &self, workspace);
}
});
self.scene_rect = scene_rect; egui::Scene::new()
.zoom_range(zoom_range.clone())
.show(ui, &mut scene_rect, |ui| {
if let Some(ref workspace) = workspace {
let mut display = Display::new();
display.update(ctx, ui, &self, workspace);
}
});
if let Some(workspace) = workspace { self.scene_rect = scene_rect;
self.zoom_to_fit_if_scheduled(workspace);
} let scene_to_viewport =
Self::fit_to_rect_in_scene(viewport_rect, scene_rect, zoom_range.into());
let response = ui.interact(viewport_rect, ui.id(), egui::Sense::click_and_drag());
let pointer_scene_pos = scene_to_viewport.inverse()
* (response.interact_pointer_pos().unwrap_or_else(|| {
ctx.input(|i| i.pointer.interact_pos().unwrap_or_default())
}));
if let Some(workspace) = workspace {
dbg!(workspace.navmesher_board.board().point_pin_selector(
0,
Vector2::new(pointer_scene_pos.x as i64, pointer_scene_pos.y as i64)
));
self.zoom_to_fit_if_scheduled(workspace);
}
})
}); });
} }
/// Copied from egui/containers/scene.rs and modified.
///
/// Creates a transformation that fits a given scene rectangle into the available screen size.
///
/// The resulting visual scene bounds can be larger, due to letterboxing.
///
/// Returns the transformation from `scene` to `global` coordinates.
fn fit_to_rect_in_scene(
rect_in_viewport: egui::Rect,
rect_in_scene: egui::Rect,
zoom_range: egui::Rangef,
) -> egui::emath::TSTransform {
// Compute the scale factor to fit the bounding rectangle into the available screen size:
let scale = rect_in_viewport.size() / rect_in_scene.size();
// Use the smaller of the two scales to ensure the whole rectangle fits on the screen:
let scale = scale.min_elem();
// Clamp scale to what is allowed
let scale = zoom_range.clamp(scale);
// Compute the translation to center the bounding rect in the screen:
let center_in_global = rect_in_viewport.center().to_vec2();
let center_scene = rect_in_scene.center().to_vec2();
// Set the transformation to scale and then translate to center.
egui::emath::TSTransform::from_translation(center_in_global - scale * center_scene)
* egui::emath::TSTransform::from_scaling(scale)
}
fn zoom_to_fit_if_scheduled(&mut self, workspace: &Workspace) { fn zoom_to_fit_if_scheduled(&mut self, workspace: &Workspace) {
if self.scheduled_zoom_to_fit { if self.scheduled_zoom_to_fit {
self.scene_rect = Self::boundary_bounding_box(workspace); self.scene_rect = Self::boundary_bounding_box(workspace);

View File

@ -2,13 +2,14 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use topola::{Board, NavmesherBoard}; use topola::{Board, NavmesherBoard, PinSelection};
use crate::{appearance_panel::AppearancePanel, translator::Translator}; use crate::{appearance_panel::AppearancePanel, translator::Translator};
pub struct Workspace { pub struct Workspace {
pub navmesher_board: NavmesherBoard, pub navmesher_board: NavmesherBoard,
pub appearance_panel: AppearancePanel, pub appearance_panel: AppearancePanel,
pub pin_selection: PinSelection,
} }
impl Workspace { impl Workspace {
@ -18,6 +19,7 @@ impl Workspace {
Self { Self {
navmesher_board: NavmesherBoard::with_board(board), navmesher_board: NavmesherBoard::with_board(board),
appearance_panel, appearance_panel,
pin_selection: PinSelection::new(),
} }
} }

View File

@ -8,41 +8,43 @@ use undoredo::{ApplyDelta, Delta, FlushDelta};
use crate::{ use crate::{
layout::{Layout, LayoutHalfDelta, NetId, PinId}, layout::{Layout, LayoutHalfDelta, NetId, PinId},
math::Vector2,
primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId}, primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId},
selection::PinSelector,
}; };
#[derive(Clone, Debug, Getters)] #[derive(Clone, Debug, Getters)]
pub struct Board { pub struct Board {
layout: Layout, layout: Layout,
#[getter(skip)] #[getter(skip)]
pin_names: BiBTreeMap<PinId, String>,
#[getter(skip)]
layer_names: BiBTreeMap<usize, String>, layer_names: BiBTreeMap<usize, String>,
#[getter(skip)] #[getter(skip)]
net_names: BiBTreeMap<NetId, String>, net_names: BiBTreeMap<NetId, String>,
#[getter(skip)]
pin_names: BiBTreeMap<PinId, String>,
} }
impl Board { impl Board {
pub fn new(boundary: Vec<[i64; 2]>, layer_count: usize) -> Self { pub fn new(boundary: Vec<Vector2<i64>>, layer_count: usize) -> Self {
Self { Self {
layout: Layout::new(boundary, layer_count), layout: Layout::new(boundary.into_iter().map(Into::into).collect(), layer_count),
pin_names: BiBTreeMap::new(),
layer_names: BiBTreeMap::new(), layer_names: BiBTreeMap::new(),
net_names: BiBTreeMap::new(), net_names: BiBTreeMap::new(),
pin_names: BiBTreeMap::new(),
} }
} }
pub fn with_names( pub fn with_names(
boundary: Vec<[i64; 2]>, boundary: Vec<Vector2<i64>>,
layer_count: usize, layer_count: usize,
layer_names: BiBTreeMap<usize, String>, layer_names: BiBTreeMap<usize, String>,
net_names: BiBTreeMap<NetId, String>, net_names: BiBTreeMap<NetId, String>,
) -> Self { ) -> Self {
Self { Self {
layout: Layout::new(boundary, layer_count), layout: Layout::new(boundary.into_iter().map(Into::into).collect(), layer_count),
pin_names: BiBTreeMap::new(),
layer_names, layer_names,
net_names, net_names,
pin_names: BiBTreeMap::new(),
} }
} }
@ -73,20 +75,75 @@ impl Board {
self.layout.add_polygon(polygon) self.layout.add_polygon(polygon)
} }
pub fn joint_pin_selector(&self, joint_id: JointId) -> Option<PinSelector> {
let joint = self.layout.joint(joint_id);
Some(PinSelector {
pin: self.pin_name(joint.pin?)?.to_string(),
layer: self.layer_name(joint.layer)?.to_string(),
})
}
pub fn segment_pin_selector(&self, segment_id: SegmentId) -> Option<PinSelector> {
let segment = self.layout.segment(segment_id);
Some(PinSelector {
pin: self.pin_name(segment.pin?)?.to_string(),
layer: self.layer_name(segment.layer)?.to_string(),
})
}
// TODO: Vias.
pub fn polygon_pin_selector(&self, polygon_id: PolygonId) -> Option<PinSelector> {
let polygon = self.layout.polygon(polygon_id);
Some(PinSelector {
pin: self.pin_name(polygon.pin?)?.to_string(),
layer: self.layer_name(polygon.layer)?.to_string(),
})
}
pub fn point_pin_selector(&self, layer: usize, point: Vector2<i64>) -> Option<PinSelector> {
if let Some(joint_id) = self.layout.locate_joints_at_point(layer, point).next() {
return self.joint_pin_selector(joint_id);
}
if let Some(segment_id) = self.layout.locate_segments_at_point(layer, point).next() {
return self.segment_pin_selector(segment_id);
}
// TODO: Vias.
if let Some(polygon_id) = self.layout.locate_polygons_at_point(layer, point).next() {
return self.polygon_pin_selector(polygon_id);
}
None
}
pub fn pin_name(&self, pin: PinId) -> Option<&str> {
self.pin_names.get_by_left(&pin).map(String::as_str)
}
pub fn pin_id(&self, pin_name: &str) -> Option<PinId> {
self.pin_names.get_by_right(pin_name).copied()
}
pub fn layer_name(&self, layer: usize) -> Option<&str> { pub fn layer_name(&self, layer: usize) -> Option<&str> {
self.layer_names.get_by_left(&layer).map(String::as_str) self.layer_names.get_by_left(&layer).map(String::as_str)
} }
pub fn layer_id(&self, name: &str) -> Option<usize> { pub fn layer_id(&self, layer_name: &str) -> Option<usize> {
self.layer_names.get_by_right(name).copied() self.layer_names.get_by_right(layer_name).copied()
} }
pub fn net_name(&self, net: NetId) -> Option<&str> { pub fn net_name(&self, net: NetId) -> Option<&str> {
self.net_names.get_by_left(&net).map(String::as_str) self.net_names.get_by_left(&net).map(String::as_str)
} }
pub fn net_id(&self, name: &str) -> Option<NetId> { pub fn net_id(&self, net_name: &str) -> Option<NetId> {
self.net_names.get_by_right(name).copied() self.net_names.get_by_right(net_name).copied()
} }
} }

View File

@ -246,8 +246,20 @@ impl Layout {
}) })
} }
pub fn pin(&self, pin: PinId) -> &Pin { pub fn joint(&self, joint_id: JointId) -> &Joint {
&self.pins[pin.id()] self.joints.get(&joint_id.id()).unwrap()
}
pub fn segment(&self, segment_id: SegmentId) -> &Segment {
self.segments.get(&segment_id.id()).unwrap()
}
pub fn polygon(&self, polygon_id: PolygonId) -> &Polygon {
self.polygons.get(&polygon_id.id()).unwrap()
}
pub fn pin(&self, pin_id: PinId) -> &Pin {
&self.pins[pin_id.id()]
} }
} }

View File

@ -15,3 +15,4 @@ pub use crate::layout::Layout;
pub use crate::math::Vector2; pub use crate::math::Vector2;
pub use crate::navmesher::NavmesherBoard; pub use crate::navmesher::NavmesherBoard;
pub use crate::primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId}; pub use crate::primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId};
pub use crate::selection::{PinSelection, PinSelector};

View File

@ -25,21 +25,35 @@ impl<T: Copy> From<Vector2<T>> for [T; 2] {
} }
} }
// Check if the point (px, py) is inside a polygon using the ray-casting // Checks if the point (px, py) is inside a polygon using the ray-casting
// algorithm. // algorithm. Division is not used to avoid integer truncation errors.
macro_rules! impl_inside_polygon { macro_rules! impl_inside_polygon {
($type:ty) => { ($type:ty) => {
impl Vector2<$type> { impl Vector2<$type> {
pub fn inside_polygon(&self, polygon: &[Vector2<$type>]) -> bool { pub fn inside_polygon(&self, polygon: &[Vector2<$type>]) -> bool {
let mut inside = false; let mut inside = false;
let n = polygon.len(); let n = polygon.len();
let px = &self.x; let px = self.x;
let py = &self.y; let py = self.y;
let mut p1 = &polygon[n - 1]; let mut p1 = &polygon[n - 1];
for p2 in polygon.iter() { for p2 in polygon.iter() {
if (*py > p1.y) != (*py > p2.y) { let dy = p2.y - p1.y;
if *px < (p2.x - p1.x) * (*py - p1.y) / (p2.y - p1.y) + p1.x { let zero = 0 as $type;
if dy != zero && (py > p1.y) != (py > p2.y) {
let dx = p2.x - p1.x;
let t = py - p1.y;
let s = px - p1.x;
let crosses = if dy > zero {
s * dy < dx * t
} else {
s * dy > dx * t
};
if crosses {
inside = !inside; inside = !inside;
} }
} }
@ -59,18 +73,11 @@ impl_inside_polygon!(i64);
/// Returns the four vertices of a segment inflated by `half_width`, forming a convex /// Returns the four vertices of a segment inflated by `half_width`, forming a convex
/// quadrilateral. The segment goes from (x1, y1) to (x2, y2). /// quadrilateral. The segment goes from (x1, y1) to (x2, y2).
pub fn inflated_segment( pub fn inflated_segment(x1: i64, y1: i64, x2: i64, y2: i64, half_width: u64) -> [Vector2<i64>; 4] {
x1: i64,
y1: i64,
x2: i64,
y2: i64,
half_width: u64,
) -> [Vector2<i64>; 4] {
let dx = x2 - x1; let dx = x2 - x1;
let dy = y2 - y1; let dy = y2 - y1;
let approx_len = let approx_len = std::cmp::max(dx.abs(), dy.abs()) + 3 * std::cmp::min(dx.abs(), dy.abs()) / 8;
std::cmp::max(dx.abs(), dy.abs()) + 3 * std::cmp::min(dx.abs(), dy.abs()) / 8;
// Perpendicular vector scaled to half-width. // Perpendicular vector scaled to half-width.
let px = -dy * (half_width as i64) / approx_len; let px = -dy * (half_width as i64) / approx_len;

View File

@ -54,13 +54,6 @@ impl Joint {
(point.x - self.position.x).pow(2) as u64 + (point.y - self.position.y).pow(2) as u64 (point.x - self.position.x).pow(2) as u64 + (point.y - self.position.y).pow(2) as u64
<= self.radius.pow(2) <= self.radius.pow(2)
} }
pub fn pin_selector(&self) -> Option<PinSelector> {
Some(PinSelector {
pin: self.pin?,
layer: self.layer,
})
}
} }
#[derive( #[derive(
@ -85,15 +78,6 @@ pub struct Segment {
pub pin: Option<PinId>, pub pin: Option<PinId>,
} }
impl Segment {
pub fn pin_selector(&self) -> Option<PinSelector> {
Some(PinSelector {
pin: self.pin?,
layer: self.layer,
})
}
}
#[derive( #[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize, Clone, Constructor, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize,
)] )]
@ -120,13 +104,6 @@ impl Via {
/*pub fn bbox(&self) -> Rectangle<[i64; 3]> { /*pub fn bbox(&self) -> Rectangle<[i64; 3]> {
// //
}*/ }*/
pub fn pin_selector(&self) -> Option<PinSelector> {
Some(PinSelector {
pin: self.pin?,
layer: self.layer,
})
}
} }
#[derive( #[derive(
@ -163,13 +140,6 @@ impl Polygon {
} }
pub fn contains_point(&self, point: Vector2<i64>) -> bool { pub fn contains_point(&self, point: Vector2<i64>) -> bool {
point.inside_polygon(&self.vertices) dbg!(point.inside_polygon(&self.vertices))
}
pub fn pin_selector(&self) -> Option<PinSelector> {
Some(PinSelector {
pin: self.pin?,
layer: self.layer,
})
} }
} }

View File

@ -6,18 +6,20 @@ use std::collections::BTreeSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::layout::PinId;
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct PinSelector { pub struct PinSelector {
pub pin: PinId, pub pin: String,
pub layer: usize, pub layer: String,
} }
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PinSelection(pub BTreeSet<PinSelector>); pub struct PinSelection(pub BTreeSet<PinSelector>);
impl PinSelection { impl PinSelection {
pub fn new() -> Self {
Self(BTreeSet::new())
}
pub fn toggle(&mut self, pin_selector: PinSelector) { pub fn toggle(&mut self, pin_selector: PinSelector) {
if self.0.contains(&pin_selector) { if self.0.contains(&pin_selector) {
self.0.remove(&pin_selector); self.0.remove(&pin_selector);

View File

@ -60,7 +60,7 @@ impl Board {
.into_iter() .into_iter()
.skip(1) .skip(1)
.rev() .rev()
.map(|p| [p.x as i64, p.y as i64]) .map(|p| Vector2::new(p.x as i64, p.y as i64))
.collect(), .collect(),
dsn.pcb.structure.layers.len(), dsn.pcb.structure.layers.len(),
layer_names, layer_names,