Compare commits

...

4 Commits

Author SHA1 Message Date
Mikolaj Wielgus 13c7bbb061 Find pin selector of what is under mouse pointer 2026-03-14 22:04:08 +01:00
Mikolaj Wielgus 6992369041 Filter out bbox-located primitives for which hit-test fails 2026-03-14 18:55:19 +01:00
Mikolaj Wielgus 7aed12dda9 Add method to check if polygon contains point 2026-03-14 18:40:19 +01:00
Mikolaj Wielgus 2fc3b25fec Use `Vector2<i64>` instead of `[i64; 2]` 2026-03-14 18:33:58 +01:00
11 changed files with 326 additions and 125 deletions

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{viewport::Viewport, workspace::Workspace};
use topola::{Joint, Polygon, Segment, SegmentId};
use topola::{Joint, Polygon, Segment, SegmentId, Vector2};
pub struct Display {}
@ -132,7 +132,7 @@ impl Display {
color: egui::Color32,
) {
ui.painter().circle_filled(
egui::pos2(joint.position[0] as f32, joint.position[1] as f32),
egui::pos2(joint.position.x as f32, joint.position.y as f32),
joint.radius as f32,
color,
);
@ -144,13 +144,13 @@ impl Display {
ui: &egui::Ui,
viewport: &Viewport,
segment: &Segment,
endpoints: [[i64; 2]; 2],
endpoints: [Vector2<i64>; 2],
color: egui::Color32,
) {
ui.painter().line_segment(
[
egui::pos2(endpoints[0][0] as f32, endpoints[0][1] as f32),
egui::pos2(endpoints[1][0] as f32, endpoints[1][1] as f32),
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::Stroke::new(segment.half_width as f32 * 2.0, color),
);
@ -167,7 +167,7 @@ impl Display {
let points: Vec<egui::Pos2> = polygon
.vertices
.iter()
.map(|v| egui::pos2(v[0] as f32, v[1] as f32))
.map(|v| egui::pos2(v.x as f32, v.y as f32))
.collect();
ui.painter().add(egui::Shape::convex_polygon(
@ -226,8 +226,8 @@ impl Display {
ui.painter().rect_stroke(
egui::Rect::from_two_pos(
egui::pos2(endpoints[0][0] as f32, endpoints[0][1] as f32),
egui::pos2(endpoints[1][0] as f32, endpoints[1][1] as f32),
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),

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize};
use stable_vec::StableVec;
use undoredo::{ApplyDelta, Delta, FlushDelta, Recorder};
use crate::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId};
use crate::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Vector2, Via, ViaId};
#[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize,
@ -164,7 +164,7 @@ impl Layout {
polygon_id
}
pub fn segment_endpoints(&self, segment_id: SegmentId) -> [[i64; 2]; 2] {
pub fn segment_endpoints(&self, segment_id: SegmentId) -> [Vector2<i64>; 2] {
let endjoints = self.segments.get(&segment_id.id()).unwrap().endjoints;
[
self.joints.get(&endjoints[0].id()).unwrap().position,
@ -172,15 +172,28 @@ impl Layout {
]
}
pub fn segment_contains_point(&self, segment_id: SegmentId, point: Vector2<i64>) -> bool {
let endpoints = self.segment_endpoints(segment_id);
let segment = self.segments.get(&segment_id.id()).unwrap();
let vertices = crate::math::inflated_segment(
endpoints[0].x,
endpoints[0].y,
endpoints[1].x,
endpoints[1].y,
segment.half_width,
);
point.inside_polygon(&vertices)
}
pub fn segment_bbox(&self, segment_id: SegmentId) -> Rectangle<[i64; 3]> {
let endpoints = self.segment_endpoints(segment_id);
let layer = self.segments.get(&segment_id.id()).unwrap().layer as i64;
let half_width = self.segments.get(&segment_id.id()).unwrap().half_width as i64;
let min_x = std::cmp::min(endpoints[0][0], endpoints[1][0]) - half_width;
let min_y = std::cmp::min(endpoints[0][1], endpoints[1][1]) - half_width;
let max_x = std::cmp::max(endpoints[0][0], endpoints[1][0]) + half_width;
let max_y = std::cmp::max(endpoints[0][1], endpoints[1][1]) + half_width;
let min_x = std::cmp::min(endpoints[0].x, endpoints[1].x) - half_width;
let min_y = std::cmp::min(endpoints[0].y, endpoints[1].y) - half_width;
let max_x = std::cmp::max(endpoints[0].x, endpoints[1].x) + half_width;
let max_y = std::cmp::max(endpoints[0].y, endpoints[1].y) + half_width;
Rectangle::from_corners([min_x, min_y, layer], [max_x, max_y, layer])
}
@ -188,23 +201,30 @@ impl Layout {
pub fn locate_joints_at_point(
&self,
layer: usize,
point: [i64; 2],
point: Vector2<i64>,
) -> impl Iterator<Item = JointId> {
self.joints_rtree
.as_ref()
.locate_all_at_point(&[point[0], point[1], layer as i64])
.locate_all_at_point(&[point.x, point.y, layer as i64])
.map(|geom_with_data| geom_with_data.data)
.filter(move |joint_id| {
self.joints
.get(&joint_id.id())
.unwrap()
.contains_point(point)
})
}
pub fn locate_segments_at_point(
&self,
layer: usize,
point: [i64; 2],
point: Vector2<i64>,
) -> impl Iterator<Item = SegmentId> {
self.segments_rtree
.as_ref()
.locate_all_at_point(&[point[0], point[1], layer as i64])
.locate_all_at_point(&[point.x, point.y, layer as i64])
.map(|geom_with_data| geom_with_data.data)
.filter(move |segment_id| self.segment_contains_point(*segment_id, point))
}
// TODO: vias.
@ -212,16 +232,34 @@ impl Layout {
pub fn locate_polygons_at_point(
&self,
layer: usize,
point: [i64; 2],
point: Vector2<i64>,
) -> impl Iterator<Item = PolygonId> {
self.polygons_rtree
.as_ref()
.locate_all_at_point(&[point[0], point[1], layer as i64])
.locate_all_at_point(&[point.x, point.y, layer as i64])
.map(|geom_with_data| geom_with_data.data)
.filter(move |polygon_id| {
self.polygons
.get(&polygon_id.id())
.unwrap()
.contains_point(point)
})
}
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()]
}
}

View File

@ -12,5 +12,7 @@ mod specctra;
pub use crate::board::Board;
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};

View File

@ -4,12 +4,93 @@
use derive_more::{Add, Constructor, From, Into, Sub};
#[derive(Add, Clone, Constructor, Copy, Debug, From, Into, Sub)]
#[derive(Add, Clone, Constructor, Copy, Debug, Eq, From, Into, PartialEq, Sub)]
pub struct Vector2<T> {
pub x: T,
pub y: T,
}
impl<T: Copy> From<[T; 2]> for Vector2<T> {
fn from(from: [T; 2]) -> Self {
Self {
x: from[0],
y: from[1],
}
}
}
impl<T: Copy> From<Vector2<T>> for [T; 2] {
fn from(from: Vector2<T>) -> Self {
[from.x, from.y]
}
}
// 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 mut p1 = &polygon[n - 1];
for p2 in polygon.iter() {
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;
}
}
p1 = p2;
}
inside
}
}
};
}
impl_inside_polygon!(f32);
impl_inside_polygon!(f64);
impl_inside_polygon!(i32);
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] {
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;
// Perpendicular vector scaled to half-width.
let px = -dy * (half_width as i64) / approx_len;
let py = dx * (half_width as i64) / approx_len;
[
Vector2::new(x1 + px, y1 + py),
Vector2::new(x2 + px, y2 + py),
Vector2::new(x2 - px, y2 - py),
Vector2::new(x1 - px, y1 - py),
]
}
macro_rules! impl_rotate_around_point {
($t:ty) => {
impl Vector2<$t> {

View File

@ -6,6 +6,7 @@ use dearcut::RecordingTriangulator;
use derive_getters::Getters;
use crate::{
math,
Board,
primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId},
};
@ -129,8 +130,8 @@ impl NavmesherBoard {
}
fn joint_circumscribed_octagon(joint: Joint) -> [[i64; 2]; 8] {
let cx = joint.position[0];
let cy = joint.position[1];
let cx = joint.position.x;
let cy = joint.position.y;
let r = joint.radius as i64;
[
@ -162,35 +163,18 @@ impl NavmesherBoard {
navmesher.insert_polygon(
segment.layer,
Self::inflated_segment(
endpoints[0][0],
endpoints[0][1],
endpoints[1][0],
endpoints[1][1],
math::inflated_segment(
endpoints[0].x,
endpoints[0].y,
endpoints[1].x,
endpoints[1].y,
segment.half_width,
),
)
.into_iter()
.map(Into::into),
)
}
fn inflated_segment(x1: i64, y1: i64, x2: i64, y2: i64, half_width: u64) -> [[i64; 2]; 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;
// Perpendicular vector scaled to half-width.
let px = -dy * (half_width as i64) / approx_len;
let py = dx * (half_width as i64) / approx_len;
[
[x1 + px, y1 + py],
[x2 + px, y2 + py],
[x2 - px, y2 - py],
[x1 - px, y1 - py],
]
}
pub fn insert_via(&mut self, via: Via) -> ViaId {
// TODO: Insert into navmesh.
self.board.add_via(via)
@ -202,6 +186,9 @@ impl NavmesherBoard {
}
fn insert_polygon_in_navmesher(navmesher: &mut Navmesher, polygon: Polygon) {
navmesher.insert_polygon(polygon.layer, polygon.vertices);
navmesher.insert_polygon(
polygon.layer,
polygon.vertices.into_iter().map(Into::into),
);
}
}

View File

@ -7,6 +7,7 @@ use rstar::{AABB, Envelope, primitives::Rectangle};
use serde::{Deserialize, Serialize};
use crate::{
Vector2,
layout::{NetId, PinId},
selection::PinSelector,
};
@ -26,7 +27,7 @@ impl JointId {
#[derive(Clone, Copy, Debug)]
pub struct Joint {
pub position: [i64; 2],
pub position: Vector2<i64>,
pub layer: usize,
pub radius: u64,
pub net: NetId,
@ -37,23 +38,21 @@ impl Joint {
pub fn bbox(&self) -> Rectangle<[i64; 3]> {
Rectangle::from_aabb(AABB::from_corners(
[
self.position[0] - self.radius as i64,
self.position[1] - self.radius as i64,
self.position.x - self.radius as i64,
self.position.y - self.radius as i64,
self.layer as i64,
],
[
self.position[0] + self.radius as i64,
self.position[1] + self.radius as i64,
self.position.x + self.radius as i64,
self.position.y + self.radius as i64,
self.layer as i64,
],
))
}
pub fn pin_selector(&self) -> Option<PinSelector> {
Some(PinSelector {
pin: self.pin?,
layer: self.layer,
})
pub fn contains_point(&self, point: Vector2<i64>) -> bool {
(point.x - self.position.x).pow(2) as u64 + (point.y - self.position.y).pow(2) as u64
<= self.radius.pow(2)
}
}
@ -79,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,
)]
@ -114,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(
@ -138,7 +121,7 @@ impl PolygonId {
#[derive(Clone, Debug)]
pub struct Polygon {
pub vertices: Vec<[i64; 2]>,
pub vertices: Vec<Vector2<i64>>,
pub layer: usize,
pub net: NetId,
pub pin: Option<PinId>,
@ -146,18 +129,17 @@ pub struct Polygon {
impl Polygon {
pub fn bbox(&self) -> Rectangle<[i64; 3]> {
Rectangle::from_aabb(self.vertices.clone().into_iter().fold(
AABB::new_empty(),
|aabb, vertex| {
aabb.merged(&AABB::from_point([vertex[0], vertex[1], self.layer as i64]))
},
))
Rectangle::from_aabb(
self.vertices
.clone()
.into_iter()
.fold(AABB::new_empty(), |aabb, vertex| {
aabb.merged(&AABB::from_point([vertex.x, vertex.y, self.layer as i64]))
}),
)
}
pub fn pin_selector(&self) -> Option<PinSelector> {
Some(PinSelector {
pin: self.pin?,
layer: self.layer,
})
pub fn contains_point(&self, point: Vector2<i64>) -> bool {
dbg!(point.inside_polygon(&self.vertices))
}
}

View File

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

View File

@ -14,8 +14,8 @@ use crate::{
Segment,
board::Board,
layout::{NetId, PinId},
math::Vector2,
primitives::{Joint, Polygon},
Vector2,
};
impl Board {
@ -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,
@ -322,7 +322,7 @@ impl Board {
flip: bool,
) {
// Add the first coordinate in the wire path as a dot and save its index.
let mut prev_pos = Self::pos(place, pin_pos, coords[0].x, coords[0].y, flip);
let mut prev_pos: Vector2<i64> = Self::pos(place, pin_pos, coords[0].x, coords[0].y, flip);
let mut prev_joint = board.add_joint(Joint {
position: prev_pos,
layer,
@ -372,7 +372,7 @@ impl Board {
pin: Option<PinId>,
flip: bool,
) {
let vertices: Vec<[i64; 2]> = coords
let vertices: Vec<Vector2<i64>> = coords
.iter()
.map(|coord| Self::pos(place, pin_pos, coord.x, coord.y, flip))
.collect();
@ -400,13 +400,13 @@ impl Board {
x: f64,
y: f64,
flip: bool,
) -> [i64; 2] {
) -> Vector2<i64> {
let pos = (Vector2::new(x, y) + Vector2::new(pin.pos.x(), pin.pos.y()))
.rotate_around_point_degrees(pin.rot, Vector2::new(pin.pos.x(), pin.pos.y()));
let pos = (Vector2::new(place.pos.x(), place.pos.y())
+ flip.then_some(Vector2::new(-pos.x, pos.y)).unwrap_or(pos))
.rotate_around_point_degrees(place.rot, Vector2::new(place.pos.x(), place.pos.y()));
[pos.x as i64, pos.y as i64]
Vector2::new(pos.x as i64, pos.y as i64)
}
}