Add capability to move components by dragging mouse

This commit is contained in:
Mikolaj Wielgus 2026-05-29 16:09:30 +02:00
parent 77950109fc
commit 1264c3183a
24 changed files with 417 additions and 231 deletions

View File

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

15
topola/src/board/bbox.rs Normal file
View File

@ -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<Rect2<i64>> {
self.resolved_components_bbox2(&self.resolve_components(selection).collect::<Vec<_>>())
}
pub fn resolved_components_bbox2(&self, selection: &[ComponentId]) -> Option<Rect2<i64>> {
self.layout.components_bbox2(selection.iter().copied())
}
}

View File

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

View File

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

View File

@ -36,19 +36,19 @@ impl MasterInteractor {
pub fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {
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() {

View File

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

View File

@ -2,6 +2,8 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0
mod bbox;
mod contains;
mod insert;
pub mod interactors;
mod layer;

View File

@ -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<ComponentSelector> {
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<NetSelector> {
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<PinSelector> {
let joint = self.layout.joint(id);

View File

@ -32,7 +32,7 @@ impl ComponentSelection {
}
}
pub fn xor(&mut self, selectors: impl IntoIterator<Item = ComponentSelector>) {
pub fn toggle(&mut self, selectors: impl IntoIterator<Item = ComponentSelector>) {
for selector in selectors {
if self.0.contains(&selector) {
self.0.remove(&selector);

View File

@ -38,7 +38,7 @@ impl NetSelection {
}
}
pub fn xor(&mut self, selectors: impl IntoIterator<Item = NetSelector>) {
pub fn toggle(&mut self, selectors: impl IntoIterator<Item = NetSelector>) {
for selector in selectors {
if self.0.contains(&selector) {
self.0.remove(&selector);

View File

@ -35,7 +35,7 @@ impl PinSelection {
}
}
pub fn xor(&mut self, selectors: impl IntoIterator<Item = PinSelector>) {
pub fn toggle(&mut self, selectors: impl IntoIterator<Item = PinSelector>) {
for selector in selectors {
if self.0.contains(&selector) {
self.0.remove(&selector);

55
topola/src/layout/bbox.rs Normal file
View File

@ -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<Rect2<i64>> {
let component = self.component(component_id);
let mut maybe_accum_bbox2 = None::<Rect2<i64>>;
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<Item = ComponentId>,
) -> Option<Rect2<i64>> {
let mut maybe_accum_bbox2 = None::<Rect2<i64>>;
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<Rect2<i64>>, bbox2_to_add: Rect2<i64>) {
*maybe_accum_bbox2 = Some(match maybe_accum_bbox2 {
None => bbox2_to_add,
Some(accum) => accum.union(bbox2_to_add),
})
}

View File

@ -30,7 +30,7 @@ impl ComponentId {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Default)]
pub struct Component {
pub joints: Vec<JointId>,
pub segments: Vec<SegmentId>,
@ -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()
}
}

View File

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

View File

@ -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| {

View File

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

View File

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

View File

@ -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<i64> {
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<i64>) -> bool {

View File

@ -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<i64> {
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<i64> {

View File

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

View File

@ -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<i64> {
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,
],
))
),
)
}
}

View File

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

View File

@ -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<T> {
}
impl<T: Copy + Ord> Rect2<T> {
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<Self> {
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<T> {
Rect3 {
min: Vector3::new(self.min.x, self.min.y, std::cmp::min(from, to)),
@ -233,6 +263,40 @@ impl<T: Ord + Copy> Rect3<T> {
),
}
}
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<Self> {
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<T: Copy + Ord> Rect3<T> {
@ -248,4 +312,8 @@ impl<T: RTreeNum> Rect3<T> {
[self.max.x, self.max.y, self.max.z],
)
}
pub fn rtree_rectangle(self) -> Rectangle<[T; 3]> {
Rectangle::from_aabb(self.aabb())
}
}

View File

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