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
|
// 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);
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue