From 1264c3183a76a04950d9d8c54173e16d9c99ce6a Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Fri, 29 May 2026 16:09:30 +0200 Subject: [PATCH] Add capability to move components by dragging mouse --- topola-egui/src/display.rs | 20 +-- topola/src/board/bbox.rs | 15 +++ topola/src/board/contains.rs | 139 ++++++++++++++++++++ topola/src/board/interactors/drag_select.rs | 6 +- topola/src/board/interactors/master.rs | 18 +-- topola/src/board/interactors/select.rs | 6 +- topola/src/board/mod.rs | 2 + topola/src/board/select.rs | 119 +---------------- topola/src/board/selections/component.rs | 2 +- topola/src/board/selections/net.rs | 2 +- topola/src/board/selections/pin.rs | 2 +- topola/src/layout/bbox.rs | 55 ++++++++ topola/src/layout/compounds/component.rs | 9 +- topola/src/layout/delete.rs | 10 +- topola/src/layout/insert.rs | 9 +- topola/src/layout/mod.rs | 19 ++- topola/src/layout/modify.rs | 24 ++-- topola/src/layout/primitives/joint.rs | 32 ++--- topola/src/layout/primitives/polygon.rs | 34 +++-- topola/src/layout/primitives/segment.rs | 23 ++-- topola/src/layout/primitives/via.rs | 17 ++- topola/src/layout/transforms/move_by.rs | 9 ++ topola/src/rect.rs | 70 +++++++++- topola/src/workspace.rs | 6 +- 24 files changed, 417 insertions(+), 231 deletions(-) create mode 100644 topola/src/board/bbox.rs create mode 100644 topola/src/board/contains.rs create mode 100644 topola/src/layout/bbox.rs diff --git a/topola-egui/src/display.rs b/topola-egui/src/display.rs index daaaccf..c745106 100644 --- a/topola-egui/src/display.rs +++ b/topola-egui/src/display.rs @@ -243,17 +243,11 @@ impl Display { } for joint_id in layout.layer_joints(layer) { - let joint = layout.joint(joint_id); + let bbox = layout.joint(joint_id).bbox(); ui.painter().rect_stroke( egui::Rect { - min: egui::pos2( - joint.bbox().lower()[0] as f32, - joint.bbox().lower()[1] as f32, - ), - max: egui::pos2( - joint.bbox().upper()[0] as f32, - joint.bbox().upper()[1] as f32, - ), + min: egui::pos2(bbox.min.x as f32, bbox.min.y as f32), + max: egui::pos2(bbox.max.x as f32, bbox.max.y as f32), }, egui::CornerRadius::ZERO, egui::Stroke::new(5.0, egui::Color32::GRAY), @@ -281,8 +275,8 @@ impl Display { ui.painter().rect_stroke( egui::Rect { - min: egui::pos2(bbox.lower()[0] as f32, bbox.lower()[1] as f32), - max: egui::pos2(bbox.upper()[0] as f32, bbox.upper()[1] as f32), + min: egui::pos2(bbox.min.x as f32, bbox.min.y as f32), + max: egui::pos2(bbox.max.x as f32, bbox.max.y as f32), }, egui::CornerRadius::ZERO, egui::Stroke::new(5.0, egui::Color32::GRAY), @@ -296,8 +290,8 @@ impl Display { ui.painter().rect_stroke( egui::Rect { - min: egui::pos2(bbox.lower()[0] as f32, bbox.lower()[1] as f32), - max: egui::pos2(bbox.upper()[0] as f32, bbox.upper()[1] as f32), + min: egui::pos2(bbox.min.x as f32, bbox.min.y as f32), + max: egui::pos2(bbox.max.x as f32, bbox.max.y as f32), }, egui::CornerRadius::ZERO, egui::Stroke::new(5.0, egui::Color32::GRAY), diff --git a/topola/src/board/bbox.rs b/topola/src/board/bbox.rs new file mode 100644 index 0000000..b0391f4 --- /dev/null +++ b/topola/src/board/bbox.rs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{Board, Rect2, layout::compounds::ComponentId, selections::ComponentSelection}; + +impl Board { + pub fn components_bbox2(&self, selection: ComponentSelection) -> Option> { + self.resolved_components_bbox2(&self.resolve_components(selection).collect::>()) + } + + pub fn resolved_components_bbox2(&self, selection: &[ComponentId]) -> Option> { + self.layout.components_bbox2(selection.iter().copied()) + } +} diff --git a/topola/src/board/contains.rs b/topola/src/board/contains.rs new file mode 100644 index 0000000..c0c09ec --- /dev/null +++ b/topola/src/board/contains.rs @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{ + Board, Vector2, + primitives::{JointId, PolygonId, SegmentId, ViaId}, + selections::{ComponentSelection, NetSelection, NetSelector, PinSelection}, +}; + +impl Board { + pub fn components_contain_point( + &self, + selection: &ComponentSelection, + point: Vector2, + ) -> bool { + let Some(bbox) = self.components_bbox2(selection.clone()) else { + return false; + }; + + bbox.contains_point(point) + } + + pub fn components_contain_joint(&self, selection: &ComponentSelection, id: JointId) -> bool { + let Some(selector) = self.joint_component_selector(id) else { + return false; + }; + + selection.0.contains(&selector) + } + + pub fn components_contain_segment( + &self, + selection: &ComponentSelection, + id: SegmentId, + ) -> bool { + let Some(selector) = self.segment_component_selector(id) else { + return false; + }; + + selection.0.contains(&selector) + } + + pub fn components_contain_via(&self, selection: &ComponentSelection, id: ViaId) -> bool { + let Some(selector) = self.via_component_selector(id) else { + return false; + }; + + selection.0.contains(&selector) + } + + pub fn components_contain_polygon( + &self, + selection: &ComponentSelection, + id: PolygonId, + ) -> bool { + let Some(selector) = self.polygon_component_selector(id) else { + return false; + }; + + selection.0.contains(&selector) + } + + pub fn nets_contain_joint(&self, selection: &NetSelection, id: JointId) -> bool { + let joint = self.layout.joint(id); + let Some(net_name) = joint.spec.net.and_then(|net| self.net_name(net)) else { + return false; + }; + + selection + .0 + .contains(&NetSelector::new(net_name.to_string())) + } + + pub fn nets_contain_segment(&self, selection: &NetSelection, id: SegmentId) -> bool { + let segment = self.layout.segment(id); + let Some(net_name) = segment.net.and_then(|net| self.net_name(net)) else { + return false; + }; + + selection + .0 + .contains(&NetSelector::new(net_name.to_string())) + } + + pub fn nets_contain_via(&self, selection: &NetSelection, id: ViaId) -> bool { + let via = self.layout.via(id); + let Some(net_name) = via.net.and_then(|net| self.net_name(net)) else { + return false; + }; + + selection + .0 + .contains(&NetSelector::new(net_name.to_string())) + } + + pub fn nets_contain_polygon(&self, selection: &NetSelection, id: PolygonId) -> bool { + let polygon = self.layout.polygon(id); + let Some(net_name) = polygon.net.and_then(|net| self.net_name(net)) else { + return false; + }; + + selection + .0 + .contains(&NetSelector::new(net_name.to_string())) + } + + pub fn pins_contain_joint(&self, selection: &PinSelection, id: JointId) -> bool { + let Some(selector) = self.joint_pin_selector(id) else { + return false; + }; + + selection.0.contains(&selector) + } + + pub fn pins_contain_segment(&self, selection: &PinSelection, id: SegmentId) -> bool { + let Some(selector) = self.segment_pin_selector(id) else { + return false; + }; + + selection.0.contains(&selector) + } + + pub fn pins_contain_via(&self, selection: &PinSelection, id: ViaId) -> bool { + let Some(selector) = self.via_pin_selector(id) else { + return false; + }; + + selection.0.contains(&selector) + } + + pub fn pins_contain_polygon(&self, selection: &PinSelection, id: PolygonId) -> bool { + let Some(selector) = self.polygon_pin_selector(id) else { + return false; + }; + + selection.0.contains(&selector) + } +} diff --git a/topola/src/board/interactors/drag_select.rs b/topola/src/board/interactors/drag_select.rs index d7d5c11..9c9382d 100644 --- a/topola/src/board/interactors/drag_select.rs +++ b/topola/src/board/interactors/drag_select.rs @@ -172,13 +172,13 @@ impl DragSelectInteractor { SelectionCombineMode::Toggle => { combined_selection .components - .xor(self.selection.components.0.iter().cloned()); + .toggle(self.selection.components.0.iter().cloned()); combined_selection .nets - .xor(self.selection.nets.0.iter().cloned()); + .toggle(self.selection.nets.0.iter().cloned()); combined_selection .pins - .xor(self.selection.pins.0.iter().cloned()); + .toggle(self.selection.pins.0.iter().cloned()); } } diff --git a/topola/src/board/interactors/master.rs b/topola/src/board/interactors/master.rs index 3c0750c..d032fba 100644 --- a/topola/src/board/interactors/master.rs +++ b/topola/src/board/interactors/master.rs @@ -36,19 +36,19 @@ impl MasterInteractor { pub fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { if self.select_interactor.is_none() && self.drag_move_interactor.is_none() { - /*if board.selected_components_contain_point(&self.selection.components, input.pointer) { + if board.components_contain_point(&self.selection.components, pointer) { self.drag_move_interactor = Some(DragMoveInteractor::new( - input.pointer, + pointer, layer, self.selection.components.clone(), )); - } else {*/ - self.select_interactor = Some(SelectInteractor::new( - pointer, - self.selection.clone(), - SelectionCombineMode::Replace, - )); - //} + } else { + self.select_interactor = Some(SelectInteractor::new( + pointer, + self.selection.clone(), + SelectionCombineMode::Replace, + )); + } } if let Some(drag_move_interactor) = self.drag_move_interactor.as_mut() { diff --git a/topola/src/board/interactors/select.rs b/topola/src/board/interactors/select.rs index e2a5692..b5b8904 100644 --- a/topola/src/board/interactors/select.rs +++ b/topola/src/board/interactors/select.rs @@ -64,16 +64,16 @@ impl SelectInteractor { // Pins have intentional precedence over nets and components. if let Some(pin_selector) = board.locate_pins_prefer_layer_at_point(point).next() { - selection.pins.xor(std::iter::once(pin_selector)); + selection.pins.toggle(std::iter::once(pin_selector)); } else if let Some(net_selector) = board.locate_nets_prefer_layer_at_point(point).next() { - selection.nets.xor(std::iter::once(net_selector)); + selection.nets.toggle(std::iter::once(net_selector)); } else if let Some(component_selector) = board.locate_components_prefer_layer_at_point(point).next() { selection .components - .xor(std::iter::once(component_selector)); + .toggle(std::iter::once(component_selector)); } self.selection = selection; diff --git a/topola/src/board/mod.rs b/topola/src/board/mod.rs index 20b721d..5d2d077 100644 --- a/topola/src/board/mod.rs +++ b/topola/src/board/mod.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +mod bbox; +mod contains; mod insert; pub mod interactors; mod layer; diff --git a/topola/src/board/select.rs b/topola/src/board/select.rs index 8f80c20..d003308 100644 --- a/topola/src/board/select.rs +++ b/topola/src/board/select.rs @@ -6,8 +6,7 @@ use crate::{ board::{ Board, selections::{ - ComponentSelection, ComponentSelector, NetSelection, NetSelector, PinSelection, - PinSelector, + ComponentSelection, ComponentSelector, NetSelector, PinSelection, PinSelector, }, }, primitives::{JointId, PolygonId, SegmentId, ViaId}, @@ -78,46 +77,6 @@ impl Board { component_selection } - pub fn components_contain_joint(&self, selection: &ComponentSelection, id: JointId) -> bool { - let Some(selector) = self.joint_component_selector(id) else { - return false; - }; - - selection.0.contains(&selector) - } - - pub fn components_contain_segment( - &self, - selection: &ComponentSelection, - id: SegmentId, - ) -> bool { - let Some(selector) = self.segment_component_selector(id) else { - return false; - }; - - selection.0.contains(&selector) - } - - pub fn components_contain_via(&self, selection: &ComponentSelection, id: ViaId) -> bool { - let Some(selector) = self.via_component_selector(id) else { - return false; - }; - - selection.0.contains(&selector) - } - - pub fn components_contain_polygon( - &self, - selection: &ComponentSelection, - id: PolygonId, - ) -> bool { - let Some(selector) = self.polygon_component_selector(id) else { - return false; - }; - - selection.0.contains(&selector) - } - pub fn joint_component_selector(&self, id: JointId) -> Option { let joint = self.layout.joint(id); @@ -150,50 +109,6 @@ impl Board { }) } - pub fn nets_contain_joint(&self, selection: &NetSelection, id: JointId) -> bool { - let joint = self.layout.joint(id); - let Some(net_name) = joint.spec.net.and_then(|net| self.net_name(net)) else { - return false; - }; - - selection - .0 - .contains(&NetSelector::new(net_name.to_string())) - } - - pub fn nets_contain_segment(&self, selection: &NetSelection, id: SegmentId) -> bool { - let segment = self.layout.segment(id); - let Some(net_name) = segment.net.and_then(|net| self.net_name(net)) else { - return false; - }; - - selection - .0 - .contains(&NetSelector::new(net_name.to_string())) - } - - pub fn nets_contain_via(&self, selection: &NetSelection, id: ViaId) -> bool { - let via = self.layout.via(id); - let Some(net_name) = via.net.and_then(|net| self.net_name(net)) else { - return false; - }; - - selection - .0 - .contains(&NetSelector::new(net_name.to_string())) - } - - pub fn nets_contain_polygon(&self, selection: &NetSelection, id: PolygonId) -> bool { - let polygon = self.layout.polygon(id); - let Some(net_name) = polygon.net.and_then(|net| self.net_name(net)) else { - return false; - }; - - selection - .0 - .contains(&NetSelector::new(net_name.to_string())) - } - pub fn joint_net_selector(&self, id: JointId) -> Option { let joint = self.layout.joint(id); @@ -226,38 +141,6 @@ impl Board { }) } - pub fn pins_contain_joint(&self, selection: &PinSelection, id: JointId) -> bool { - let Some(selector) = self.joint_pin_selector(id) else { - return false; - }; - - selection.0.contains(&selector) - } - - pub fn pins_contain_segment(&self, selection: &PinSelection, id: SegmentId) -> bool { - let Some(selector) = self.segment_pin_selector(id) else { - return false; - }; - - selection.0.contains(&selector) - } - - pub fn pins_contain_via(&self, selection: &PinSelection, id: ViaId) -> bool { - let Some(selector) = self.via_pin_selector(id) else { - return false; - }; - - selection.0.contains(&selector) - } - - pub fn pins_contain_polygon(&self, selection: &PinSelection, id: PolygonId) -> bool { - let Some(selector) = self.polygon_pin_selector(id) else { - return false; - }; - - selection.0.contains(&selector) - } - pub fn joint_pin_selector(&self, id: JointId) -> Option { let joint = self.layout.joint(id); diff --git a/topola/src/board/selections/component.rs b/topola/src/board/selections/component.rs index 38a8c7c..a25fc27 100644 --- a/topola/src/board/selections/component.rs +++ b/topola/src/board/selections/component.rs @@ -32,7 +32,7 @@ impl ComponentSelection { } } - pub fn xor(&mut self, selectors: impl IntoIterator) { + pub fn toggle(&mut self, selectors: impl IntoIterator) { for selector in selectors { if self.0.contains(&selector) { self.0.remove(&selector); diff --git a/topola/src/board/selections/net.rs b/topola/src/board/selections/net.rs index d34676e..aa25bbc 100644 --- a/topola/src/board/selections/net.rs +++ b/topola/src/board/selections/net.rs @@ -38,7 +38,7 @@ impl NetSelection { } } - pub fn xor(&mut self, selectors: impl IntoIterator) { + pub fn toggle(&mut self, selectors: impl IntoIterator) { for selector in selectors { if self.0.contains(&selector) { self.0.remove(&selector); diff --git a/topola/src/board/selections/pin.rs b/topola/src/board/selections/pin.rs index d124182..3d22b1c 100644 --- a/topola/src/board/selections/pin.rs +++ b/topola/src/board/selections/pin.rs @@ -35,7 +35,7 @@ impl PinSelection { } } - pub fn xor(&mut self, selectors: impl IntoIterator) { + pub fn toggle(&mut self, selectors: impl IntoIterator) { for selector in selectors { if self.0.contains(&selector) { self.0.remove(&selector); diff --git a/topola/src/layout/bbox.rs b/topola/src/layout/bbox.rs new file mode 100644 index 0000000..95d2891 --- /dev/null +++ b/topola/src/layout/bbox.rs @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{ + Rect2, + layout::{Layout, compounds::ComponentId}, +}; + +impl Layout { + pub fn component_bbox2(&self, component_id: ComponentId) -> Option> { + let component = self.component(component_id); + let mut maybe_accum_bbox2 = None::>; + + for &joint_id in &component.joints { + unionize(&mut maybe_accum_bbox2, self.joint(joint_id).bbox().xy()); + } + + for &segment_id in &component.segments { + unionize(&mut maybe_accum_bbox2, self.segment(segment_id).bbox().xy()); + } + + for &via_id in &component.vias { + unionize(&mut maybe_accum_bbox2, self.via(via_id).bbox().xy()); + } + + for &polygon_id in &component.polygons { + unionize(&mut maybe_accum_bbox2, self.polygon(polygon_id).bbox().xy()); + } + + maybe_accum_bbox2 + } + + pub fn components_bbox2( + &self, + component_ids: impl IntoIterator, + ) -> Option> { + let mut maybe_accum_bbox2 = None::>; + + for component_id in component_ids { + if let Some(component_bbox2) = self.component_bbox2(component_id) { + unionize(&mut maybe_accum_bbox2, component_bbox2); + } + } + + maybe_accum_bbox2 + } +} + +fn unionize(maybe_accum_bbox2: &mut Option>, bbox2_to_add: Rect2) { + *maybe_accum_bbox2 = Some(match maybe_accum_bbox2 { + None => bbox2_to_add, + Some(accum) => accum.union(bbox2_to_add), + }) +} diff --git a/topola/src/layout/compounds/component.rs b/topola/src/layout/compounds/component.rs index 6012197..39e92ed 100644 --- a/topola/src/layout/compounds/component.rs +++ b/topola/src/layout/compounds/component.rs @@ -30,7 +30,7 @@ impl ComponentId { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Component { pub joints: Vec, pub segments: Vec, @@ -40,11 +40,6 @@ pub struct Component { impl Component { pub fn new() -> Self { - Self { - joints: Vec::new(), - segments: Vec::new(), - vias: Vec::new(), - polygons: Vec::new(), - } + Default::default() } } diff --git a/topola/src/layout/delete.rs b/topola/src/layout/delete.rs index 3f369ff..d2d3682 100644 --- a/topola/src/layout/delete.rs +++ b/topola/src/layout/delete.rs @@ -32,7 +32,8 @@ impl Layout { }); } - self.joints_rtree.remove(&GeomWithData::new(bbox, joint_id)); + self.joints_rtree + .remove(&GeomWithData::new(bbox.rtree_rectangle(), joint_id)); self.joints.remove(&joint_id.index()); } @@ -63,7 +64,7 @@ impl Layout { } self.segments_rtree - .remove(&GeomWithData::new(bbox, segment_id)); + .remove(&GeomWithData::new(bbox.rtree_rectangle(), segment_id)); self.segments.remove(&segment_id.index()); } @@ -89,7 +90,8 @@ impl Layout { }); } - self.vias_rtree.remove(&GeomWithData::new(bbox, via_id)); + self.vias_rtree + .remove(&GeomWithData::new(bbox.rtree_rectangle(), via_id)); self.vias.remove(&via_id.index()); } @@ -120,7 +122,7 @@ impl Layout { } self.polygons_rtree - .remove(&GeomWithData::new(bbox, polygon_id)); + .remove(&GeomWithData::new(bbox.rtree_rectangle(), polygon_id)); self.polygons.remove(&polygon_id.index()); } } diff --git a/topola/src/layout/insert.rs b/topola/src/layout/insert.rs index cf0da53..0167ae2 100644 --- a/topola/src/layout/insert.rs +++ b/topola/src/layout/insert.rs @@ -37,7 +37,7 @@ impl Layout { let joint_id = JointId::new(self.joints.push(joint)); self.joints_rtree - .insert(GeomWithData::new(bbox, joint_id), ()); + .insert(GeomWithData::new(bbox.rtree_rectangle(), joint_id), ()); if let Some(component_id) = component_id { self.components.modify(component_id.index(), |component| { @@ -81,7 +81,7 @@ impl Layout { }); self.segments_rtree - .insert(GeomWithData::new(bbox, segment_id), ()); + .insert(GeomWithData::new(bbox.rtree_rectangle(), segment_id), ()); if let Some(component_id) = component_id { self.components.modify(component_id.index(), |component| { @@ -122,7 +122,8 @@ impl Layout { joint.vias.push(via_id) }); - self.vias_rtree.insert(GeomWithData::new(bbox, via_id), ()); + self.vias_rtree + .insert(GeomWithData::new(bbox.rtree_rectangle(), via_id), ()); if let Some(component_id) = component_id { self.components.modify(component_id.index(), |component| { @@ -145,7 +146,7 @@ impl Layout { let polygon_id = PolygonId::new(self.polygons.push(polygon)); self.polygons_rtree - .insert(GeomWithData::new(bbox, polygon_id), ()); + .insert(GeomWithData::new(bbox.rtree_rectangle(), polygon_id), ()); if let Some(component_id) = component_id { self.components.modify(component_id.index(), |component| { diff --git a/topola/src/layout/mod.rs b/topola/src/layout/mod.rs index a5e2ed3..371734f 100644 --- a/topola/src/layout/mod.rs +++ b/topola/src/layout/mod.rs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +mod bbox; pub mod compounds; mod delete; mod insert; @@ -21,9 +22,9 @@ use stable_vec::StableVec; use undoredo::aliases::RTreeHalfDelta; use undoredo::{Delta, Recorder}; -use crate::{ - layout::compounds::{Component, Pin, PinId}, - layout::primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId}, +use crate::layout::{ + compounds::{Component, ComponentId, Pin, PinId}, + primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId}, }; #[derive( @@ -152,6 +153,14 @@ impl Layout { ) } + pub fn component(&self, component_id: ComponentId) -> &Component { + &self.components[component_id.index()] + } + + pub fn pin(&self, pin_id: PinId) -> &Pin { + &self.pins[pin_id.index()] + } + pub fn joint(&self, joint_id: JointId) -> &Joint { &self.joints[joint_id.index()] } @@ -167,8 +176,4 @@ impl Layout { pub fn polygon(&self, polygon_id: PolygonId) -> &Polygon { &self.polygons[polygon_id.index()] } - - pub fn pin(&self, pin_id: PinId) -> &Pin { - &self.pins[pin_id.index()] - } } diff --git a/topola/src/layout/modify.rs b/topola/src/layout/modify.rs index 5132319..638cc29 100644 --- a/topola/src/layout/modify.rs +++ b/topola/src/layout/modify.rs @@ -34,13 +34,13 @@ impl Layout { { let old_joint = &self.joints[id.index()]; self.joints_rtree - .remove(&GeomWithData::new(old_joint.bbox(), id)); + .remove(&GeomWithData::new(old_joint.bbox().rtree_rectangle(), id)); self.joints.modify(id.index(), |joint| f(joint)); let new_joint = self.joints[id.index()].clone(); self.joints_rtree - .insert(GeomWithData::new(new_joint.bbox(), id), ()); + .insert(GeomWithData::new(new_joint.bbox().rtree_rectangle(), id), ()); } pub fn modify_segment(&mut self, id: SegmentId, f: F) @@ -49,20 +49,20 @@ impl Layout { { let old_segment = &self.segments[id.index()]; self.segments_rtree - .remove(&GeomWithData::new(old_segment.bbox(), id)); + .remove(&GeomWithData::new(old_segment.bbox().rtree_rectangle(), id)); self.segments .modify(id.index(), |segment| f(&mut segment.spec)); let new_segment = &self.segments[id.index()]; self.segments_rtree - .insert(GeomWithData::new(new_segment.bbox(), id), ()); + .insert(GeomWithData::new(new_segment.bbox().rtree_rectangle(), id), ()); } pub(super) fn update_segment(&mut self, id: SegmentId) { let old_segment = &self.segments[id.index()]; self.segments_rtree - .remove(&GeomWithData::new(old_segment.bbox(), id)); + .remove(&GeomWithData::new(old_segment.bbox().rtree_rectangle(), id)); let endjoint_ids = old_segment.spec.endjoints; let endjoint_specs = [ @@ -77,7 +77,7 @@ impl Layout { let new_segment = &self.segments[id.index()]; self.segments_rtree - .insert(GeomWithData::new(new_segment.bbox(), id), ()); + .insert(GeomWithData::new(new_segment.bbox().rtree_rectangle(), id), ()); } pub fn modify_via(&mut self, id: ViaId, f: F) @@ -86,19 +86,19 @@ impl Layout { { let old_via = &self.vias[id.index()]; self.vias_rtree - .remove(&GeomWithData::new(old_via.bbox(), id)); + .remove(&GeomWithData::new(old_via.bbox().rtree_rectangle(), id)); self.vias.modify(id.index(), |via| f(&mut via.spec)); let new_via = &self.vias[id.index()]; self.vias_rtree - .insert(GeomWithData::new(new_via.bbox(), id), ()); + .insert(GeomWithData::new(new_via.bbox().rtree_rectangle(), id), ()); } pub(super) fn update_via(&mut self, id: ViaId) { let old_via = &self.vias[id.index()]; self.vias_rtree - .remove(&GeomWithData::new(old_via.bbox(), id)); + .remove(&GeomWithData::new(old_via.bbox().rtree_rectangle(), id)); let endjoint_ids = old_via.spec.endjoints; let endjoint_specs = [ @@ -114,7 +114,7 @@ impl Layout { let new_via = &self.vias[id.index()]; self.vias_rtree - .insert(GeomWithData::new(new_via.bbox(), id), ()); + .insert(GeomWithData::new(new_via.bbox().rtree_rectangle(), id), ()); } pub fn modify_polygon(&mut self, id: PolygonId, f: F) @@ -123,12 +123,12 @@ impl Layout { { let old_polygon = &self.polygons[id.index()]; self.polygons_rtree - .remove(&GeomWithData::new(old_polygon.bbox(), id)); + .remove(&GeomWithData::new(old_polygon.bbox().rtree_rectangle(), id)); self.polygons.modify(id.index(), |polygon| f(polygon)); let new_polygon = &self.polygons[id.index()]; self.polygons_rtree - .insert(GeomWithData::new(new_polygon.bbox(), id), ()); + .insert(GeomWithData::new(new_polygon.bbox().rtree_rectangle(), id), ()); } } diff --git a/topola/src/layout/primitives/joint.rs b/topola/src/layout/primitives/joint.rs index f68bc4b..74b6c30 100644 --- a/topola/src/layout/primitives/joint.rs +++ b/topola/src/layout/primitives/joint.rs @@ -3,10 +3,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use derive_more::{Constructor, From}; -use rstar::{AABB, primitives::Rectangle}; use serde::{Deserialize, Serialize}; -use crate::layout::LayerId; +use crate::{Rect3, Vector3, layout::LayerId}; use crate::layout::compounds::{ComponentId, NetId, PinId}; use crate::primitives::{SegmentId, ViaId}; use crate::vector::Vector2; @@ -57,19 +56,22 @@ impl Joint { self.spec.position } - pub fn bbox(&self) -> Rectangle<[i64; 3]> { - Rectangle::from_aabb(AABB::from_corners( - [ - self.spec.position.x - self.spec.radius as i64, - self.spec.position.y - self.spec.radius as i64, - self.spec.layer.index() as i64, - ], - [ - self.spec.position.x + self.spec.radius as i64, - self.spec.position.y + self.spec.radius as i64, - self.spec.layer.index() as i64, - ], - )) + pub fn bbox(&self) -> Rect3 { + let radius = self.spec.radius as i64; + let layer = self.spec.layer.index() as i64; + + Rect3::new( + Vector3::new( + self.spec.position.x - radius, + self.spec.position.y - radius, + layer, + ), + Vector3::new( + self.spec.position.x + radius, + self.spec.position.y + radius, + layer, + ), + ) } pub fn contains_point2(&self, point: Vector2) -> bool { diff --git a/topola/src/layout/primitives/polygon.rs b/topola/src/layout/primitives/polygon.rs index ef6bbf5..94167d1 100644 --- a/topola/src/layout/primitives/polygon.rs +++ b/topola/src/layout/primitives/polygon.rs @@ -3,10 +3,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use derive_more::{Constructor, From}; -use rstar::{AABB, Envelope, primitives::Rectangle}; use serde::{Deserialize, Serialize}; -use crate::layout::LayerId; +use crate::{Rect3, Vector3, layout::LayerId}; use crate::layout::compounds::{ComponentId, NetId, PinId}; use crate::vector::Vector2; @@ -44,17 +43,26 @@ 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.x, - vertex.y, - self.layer.index() as i64, - ])) - }, - )) + pub fn bbox(&self) -> Rect3 { + let layer = self.layer.index() as i64; + let mut min = Vector2::new(i64::MAX, i64::MAX); + let mut max = Vector2::new(i64::MIN, i64::MIN); + + for vertex in &self.vertices { + min.x = std::cmp::min(min.x, vertex.x); + min.y = std::cmp::min(min.y, vertex.y); + max.x = std::cmp::max(max.x, vertex.x); + max.y = std::cmp::max(max.y, vertex.y); + } + + if self.vertices.is_empty() { + return Rect3::new(Vector3::new(0, 0, layer), Vector3::new(0, 0, layer)); + } + + Rect3::new( + Vector3::new(min.x, min.y, layer), + Vector3::new(max.x, max.y, layer), + ) } pub fn center(&self) -> Vector2 { diff --git a/topola/src/layout/primitives/segment.rs b/topola/src/layout/primitives/segment.rs index 39c02ec..6bf4260 100644 --- a/topola/src/layout/primitives/segment.rs +++ b/topola/src/layout/primitives/segment.rs @@ -3,10 +3,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use derive_more::{Constructor, From}; -use rstar::primitives::Rectangle; use serde::{Deserialize, Serialize}; -use crate::layout::LayerId; +use crate::{Rect3, Vector3, layout::LayerId}; use crate::layout::compounds::{ComponentId, NetId, PinId}; use crate::vector::Vector2; @@ -80,16 +79,22 @@ impl Segment { ) } - pub fn bbox(&self) -> Rectangle<[i64; 3]> { + pub fn bbox(&self) -> Rect3 { let endpoints = self.endpoints; let layer = self.layer.index() as i64; let half_width = self.spec.half_width as i64; - 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]) + Rect3::new( + Vector3::new( + std::cmp::min(endpoints[0].x, endpoints[1].x) - half_width, + std::cmp::min(endpoints[0].y, endpoints[1].y) - half_width, + layer, + ), + Vector3::new( + std::cmp::max(endpoints[0].x, endpoints[1].x) + half_width, + std::cmp::max(endpoints[0].y, endpoints[1].y) + half_width, + layer, + ), + ) } } diff --git a/topola/src/layout/primitives/via.rs b/topola/src/layout/primitives/via.rs index 9381ed2..81b3487 100644 --- a/topola/src/layout/primitives/via.rs +++ b/topola/src/layout/primitives/via.rs @@ -3,10 +3,9 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use derive_more::{Constructor, From}; -use rstar::{AABB, primitives::Rectangle}; use serde::{Deserialize, Serialize}; -use crate::layout::LayerId; +use crate::{Rect3, Vector3, layout::LayerId}; use crate::layout::compounds::{ComponentId, NetId, PinId}; use crate::vector::Vector2; @@ -59,20 +58,20 @@ impl Via { <= self.spec.radius.pow(2) } - pub fn bbox(&self) -> Rectangle<[i64; 3]> { + pub fn bbox(&self) -> Rect3 { let radius = self.spec.radius as i64; - Rectangle::from_aabb(AABB::from_corners( - [ + Rect3::new( + Vector3::new( self.position.x - radius, self.position.y - radius, self.min_layer.index() as i64, - ], - [ + ), + Vector3::new( self.position.x + radius, self.position.y + radius, self.max_layer.index() as i64, - ], - )) + ), + ) } } diff --git a/topola/src/layout/transforms/move_by.rs b/topola/src/layout/transforms/move_by.rs index 072abfd..b8c8d29 100644 --- a/topola/src/layout/transforms/move_by.rs +++ b/topola/src/layout/transforms/move_by.rs @@ -24,6 +24,15 @@ impl Layout { for &via_id in &component.vias { self.update_via(via_id); } + + for &polygon_id in &component.polygons { + self.modify_polygon(polygon_id, |polygon| { + polygon + .vertices + .iter_mut() + .for_each(|vertex| *vertex += translation) + }); + } } } } diff --git a/topola/src/rect.rs b/topola/src/rect.rs index 6f13269..18845fd 100644 --- a/topola/src/rect.rs +++ b/topola/src/rect.rs @@ -5,7 +5,7 @@ use derive_getters::Getters; use derive_more::Constructor; use num_traits::Bounded; -use rstar::{AABB, RTreeNum}; +use rstar::{AABB, RTreeNum, primitives::Rectangle}; use serde::{Deserialize, Serialize}; use crate::{Vector2, Vector3}; @@ -19,6 +19,36 @@ pub struct Rect2 { } impl Rect2 { + pub fn union(self, other: Self) -> Self { + Self { + min: Vector2::new( + std::cmp::min(self.min.x, other.min.x), + std::cmp::min(self.min.y, other.min.y), + ), + max: Vector2::new( + std::cmp::max(self.max.x, other.max.x), + std::cmp::max(self.max.y, other.max.y), + ), + } + } + + pub fn intersection(self, other: Self) -> Option { + let min = Vector2::new( + std::cmp::max(self.min.x, other.min.x), + std::cmp::max(self.min.y, other.min.y), + ); + let max = Vector2::new( + std::cmp::min(self.max.x, other.max.x), + std::cmp::min(self.max.y, other.max.y), + ); + + if min.x > max.x || min.y > max.y { + return None; + } + + Some(Self { min, max }) + } + pub fn z_extruded(self, from: T, to: T) -> Rect3 { Rect3 { min: Vector3::new(self.min.x, self.min.y, std::cmp::min(from, to)), @@ -233,6 +263,40 @@ impl Rect3 { ), } } + + pub fn union(self, other: Self) -> Self { + Self { + min: Vector3::new( + std::cmp::min(self.min.x, other.min.x), + std::cmp::min(self.min.y, other.min.y), + std::cmp::min(self.min.z, other.min.z), + ), + max: Vector3::new( + std::cmp::max(self.max.x, other.max.x), + std::cmp::max(self.max.y, other.max.y), + std::cmp::max(self.max.z, other.max.z), + ), + } + } + + pub fn intersection(self, other: Self) -> Option { + let min = Vector3::new( + std::cmp::max(self.min.x, other.min.x), + std::cmp::max(self.min.y, other.min.y), + std::cmp::max(self.min.z, other.min.z), + ); + let max = Vector3::new( + std::cmp::min(self.max.x, other.max.x), + std::cmp::min(self.max.y, other.max.y), + std::cmp::min(self.max.z, other.max.z), + ); + + if min.x > max.x || min.y > max.y || min.z > max.z { + return None; + } + + Some(Self { min, max }) + } } impl Rect3 { @@ -248,4 +312,8 @@ impl Rect3 { [self.max.x, self.max.y, self.max.z], ) } + + pub fn rtree_rectangle(self) -> Rectangle<[T; 3]> { + Rectangle::from_aabb(self.aabb()) + } } diff --git a/topola/src/workspace.rs b/topola/src/workspace.rs index 596952d..7362853 100644 --- a/topola/src/workspace.rs +++ b/topola/src/workspace.rs @@ -2,6 +2,8 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use undoredo::FlushDelta; + use crate::{Autorouter, Board, selections::PersistableSelection}; pub enum Workspace { @@ -10,7 +12,9 @@ pub enum Workspace { } impl Workspace { - pub fn new_board(board: Board) -> Self { + pub fn new_board(mut board: Board) -> Self { + board.flush_delta(); + Self::Board(BoardWorkspace::new(board)) }