mirror of https://codeberg.org/topola/topola.git
Find pin selector of what is under mouse pointer
This commit is contained in:
parent
6992369041
commit
13c7bbb061
|
|
@ -3,6 +3,7 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use egui::Pos2;
|
||||
use topola::Vector2;
|
||||
|
||||
use crate::{display::Display, workspace::Workspace};
|
||||
|
||||
|
|
@ -23,25 +24,74 @@ impl Viewport {
|
|||
|
||||
pub fn update(&mut self, ctx: &egui::Context, workspace: Option<&mut Workspace>) {
|
||||
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()
|
||||
.zoom_range(0.00001..=10000.0)
|
||||
.show(ui, &mut scene_rect, |ui| {
|
||||
if let Some(ref workspace) = workspace {
|
||||
let mut display = Display::new();
|
||||
display.update(ctx, ui, &self, workspace);
|
||||
}
|
||||
});
|
||||
let viewport_rect = ui.available_rect_before_wrap();
|
||||
let mut scene_rect = self.scene_rect.clone();
|
||||
|
||||
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.zoom_to_fit_if_scheduled(workspace);
|
||||
}
|
||||
self.scene_rect = scene_rect;
|
||||
|
||||
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) {
|
||||
if self.scheduled_zoom_to_fit {
|
||||
self.scene_rect = Self::boundary_bounding_box(workspace);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use topola::{Board, NavmesherBoard};
|
||||
use topola::{Board, NavmesherBoard, PinSelection};
|
||||
|
||||
use crate::{appearance_panel::AppearancePanel, translator::Translator};
|
||||
|
||||
pub struct Workspace {
|
||||
pub navmesher_board: NavmesherBoard,
|
||||
pub appearance_panel: AppearancePanel,
|
||||
pub pin_selection: PinSelection,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
|
|
@ -18,6 +19,7 @@ impl Workspace {
|
|||
Self {
|
||||
navmesher_board: NavmesherBoard::with_board(board),
|
||||
appearance_panel,
|
||||
pin_selection: PinSelection::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,41 +8,43 @@ use undoredo::{ApplyDelta, Delta, FlushDelta};
|
|||
|
||||
use crate::{
|
||||
layout::{Layout, LayoutHalfDelta, NetId, PinId},
|
||||
math::Vector2,
|
||||
primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId},
|
||||
selection::PinSelector,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Getters)]
|
||||
pub struct Board {
|
||||
layout: Layout,
|
||||
#[getter(skip)]
|
||||
pin_names: BiBTreeMap<PinId, String>,
|
||||
#[getter(skip)]
|
||||
layer_names: BiBTreeMap<usize, String>,
|
||||
#[getter(skip)]
|
||||
net_names: BiBTreeMap<NetId, String>,
|
||||
#[getter(skip)]
|
||||
pin_names: BiBTreeMap<PinId, String>,
|
||||
}
|
||||
|
||||
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 {
|
||||
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(),
|
||||
net_names: BiBTreeMap::new(),
|
||||
pin_names: BiBTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_names(
|
||||
boundary: Vec<[i64; 2]>,
|
||||
boundary: Vec<Vector2<i64>>,
|
||||
layer_count: usize,
|
||||
layer_names: BiBTreeMap<usize, String>,
|
||||
net_names: BiBTreeMap<NetId, String>,
|
||||
) -> 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,
|
||||
net_names,
|
||||
pin_names: BiBTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,20 +75,75 @@ impl Board {
|
|||
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> {
|
||||
self.layer_names.get_by_left(&layer).map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn layer_id(&self, name: &str) -> Option<usize> {
|
||||
self.layer_names.get_by_right(name).copied()
|
||||
pub fn layer_id(&self, layer_name: &str) -> Option<usize> {
|
||||
self.layer_names.get_by_right(layer_name).copied()
|
||||
}
|
||||
|
||||
pub fn net_name(&self, net: NetId) -> Option<&str> {
|
||||
self.net_names.get_by_left(&net).map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn net_id(&self, name: &str) -> Option<NetId> {
|
||||
self.net_names.get_by_right(name).copied()
|
||||
pub fn net_id(&self, net_name: &str) -> Option<NetId> {
|
||||
self.net_names.get_by_right(net_name).copied()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -246,8 +246,20 @@ impl Layout {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn pin(&self, pin: PinId) -> &Pin {
|
||||
&self.pins[pin.id()]
|
||||
pub fn joint(&self, joint_id: JointId) -> &Joint {
|
||||
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()]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,3 +15,4 @@ pub use crate::layout::Layout;
|
|||
pub use crate::math::Vector2;
|
||||
pub use crate::navmesher::NavmesherBoard;
|
||||
pub use crate::primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId};
|
||||
pub use crate::selection::{PinSelection, PinSelector};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// algorithm.
|
||||
// Checks if the point (px, py) is inside a polygon using the ray-casting
|
||||
// algorithm. Division is not used to avoid integer truncation errors.
|
||||
macro_rules! impl_inside_polygon {
|
||||
($type:ty) => {
|
||||
impl Vector2<$type> {
|
||||
pub fn inside_polygon(&self, polygon: &[Vector2<$type>]) -> bool {
|
||||
let mut inside = false;
|
||||
let n = polygon.len();
|
||||
let px = &self.x;
|
||||
let py = &self.y;
|
||||
let px = self.x;
|
||||
let py = self.y;
|
||||
|
||||
let mut p1 = &polygon[n - 1];
|
||||
|
||||
for p2 in polygon.iter() {
|
||||
if (*py > p1.y) != (*py > p2.y) {
|
||||
if *px < (p2.x - p1.x) * (*py - p1.y) / (p2.y - p1.y) + p1.x {
|
||||
let dy = p2.y - p1.y;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -59,18 +73,11 @@ impl_inside_polygon!(i64);
|
|||
|
||||
/// Returns the four vertices of a segment inflated by `half_width`, forming a convex
|
||||
/// quadrilateral. The segment goes from (x1, y1) to (x2, y2).
|
||||
pub fn inflated_segment(
|
||||
x1: i64,
|
||||
y1: i64,
|
||||
x2: i64,
|
||||
y2: i64,
|
||||
half_width: u64,
|
||||
) -> [Vector2<i64>; 4] {
|
||||
pub fn inflated_segment(x1: i64, y1: i64, x2: i64, y2: i64, half_width: u64) -> [Vector2<i64>; 4] {
|
||||
let dx = x2 - x1;
|
||||
let dy = y2 - y1;
|
||||
|
||||
let approx_len =
|
||||
std::cmp::max(dx.abs(), dy.abs()) + 3 * std::cmp::min(dx.abs(), dy.abs()) / 8;
|
||||
let approx_len = std::cmp::max(dx.abs(), dy.abs()) + 3 * std::cmp::min(dx.abs(), dy.abs()) / 8;
|
||||
|
||||
// Perpendicular vector scaled to half-width.
|
||||
let px = -dy * (half_width as i64) / approx_len;
|
||||
|
|
|
|||
|
|
@ -54,13 +54,6 @@ impl Joint {
|
|||
(point.x - self.position.x).pow(2) as u64 + (point.y - self.position.y).pow(2) as u64
|
||||
<= self.radius.pow(2)
|
||||
}
|
||||
|
||||
pub fn pin_selector(&self) -> Option<PinSelector> {
|
||||
Some(PinSelector {
|
||||
pin: self.pin?,
|
||||
layer: self.layer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
|
@ -85,15 +78,6 @@ pub struct Segment {
|
|||
pub pin: Option<PinId>,
|
||||
}
|
||||
|
||||
impl Segment {
|
||||
pub fn pin_selector(&self) -> Option<PinSelector> {
|
||||
Some(PinSelector {
|
||||
pin: self.pin?,
|
||||
layer: self.layer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
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 pin_selector(&self) -> Option<PinSelector> {
|
||||
Some(PinSelector {
|
||||
pin: self.pin?,
|
||||
layer: self.layer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
|
@ -163,13 +140,6 @@ impl Polygon {
|
|||
}
|
||||
|
||||
pub fn contains_point(&self, point: Vector2<i64>) -> bool {
|
||||
point.inside_polygon(&self.vertices)
|
||||
}
|
||||
|
||||
pub fn pin_selector(&self) -> Option<PinSelector> {
|
||||
Some(PinSelector {
|
||||
pin: self.pin?,
|
||||
layer: self.layer,
|
||||
})
|
||||
dbg!(point.inside_polygon(&self.vertices))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,20 @@ use std::collections::BTreeSet;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::layout::PinId;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct PinSelector {
|
||||
pub pin: PinId,
|
||||
pub layer: usize,
|
||||
pub pin: String,
|
||||
pub layer: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct PinSelection(pub BTreeSet<PinSelector>);
|
||||
|
||||
impl PinSelection {
|
||||
pub fn new() -> Self {
|
||||
Self(BTreeSet::new())
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self, pin_selector: PinSelector) {
|
||||
if self.0.contains(&pin_selector) {
|
||||
self.0.remove(&pin_selector);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ impl Board {
|
|||
.into_iter()
|
||||
.skip(1)
|
||||
.rev()
|
||||
.map(|p| [p.x as i64, p.y as i64])
|
||||
.map(|p| Vector2::new(p.x as i64, p.y as i64))
|
||||
.collect(),
|
||||
dsn.pcb.structure.layers.len(),
|
||||
layer_names,
|
||||
|
|
|
|||
Loading…
Reference in New Issue