mirror of https://codeberg.org/topola/topola.git
feat(selection): BboxSelectionKind (completely inside vs. merely intersects)
This commit is contained in:
parent
4c2f9e3984
commit
740019e2e5
|
|
@ -7,7 +7,10 @@ use rstar::{Point as _, AABB};
|
||||||
use spade::InsertionError;
|
use spade::InsertionError;
|
||||||
|
|
||||||
use topola::{
|
use topola::{
|
||||||
autorouter::{ratsnest::Ratsnest, selection::Selection},
|
autorouter::{
|
||||||
|
ratsnest::Ratsnest,
|
||||||
|
selection::{BboxSelectionKind, Selection},
|
||||||
|
},
|
||||||
board::{mesadata::AccessMesadata, Board},
|
board::{mesadata::AccessMesadata, Board},
|
||||||
drawing::{
|
drawing::{
|
||||||
graph::{GetLayer, MakePrimitive},
|
graph::{GetLayer, MakePrimitive},
|
||||||
|
|
@ -53,7 +56,11 @@ impl Overlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_all(&mut self, board: &Board<impl AccessMesadata>) {
|
pub fn select_all(&mut self, board: &Board<impl AccessMesadata>) {
|
||||||
self.select_all_in_bbox(board, &AABB::from_corners([-INF, -INF], [INF, INF]));
|
self.select_all_in_bbox(
|
||||||
|
board,
|
||||||
|
&AABB::from_corners([-INF, -INF], [INF, INF]),
|
||||||
|
BboxSelectionKind::CompletelyInside,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unselect_all(&mut self) {
|
pub fn unselect_all(&mut self) {
|
||||||
|
|
@ -81,21 +88,21 @@ impl Overlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drag_stop(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
|
pub fn drag_stop(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
|
||||||
if let Some((selmode, aabb)) = self.get_bbox_reselect(at) {
|
if let Some((selmode, bsk, aabb)) = self.get_bbox_reselect(at) {
|
||||||
// handle bounding box selection
|
// handle bounding box selection
|
||||||
self.reselect_bbox = None;
|
self.reselect_bbox = None;
|
||||||
|
|
||||||
match selmode {
|
match selmode {
|
||||||
SelectionMode::Substitution => {
|
SelectionMode::Substitution => {
|
||||||
self.selection = Selection::new();
|
self.selection = Selection::new();
|
||||||
self.select_all_in_bbox(board, &aabb);
|
self.select_all_in_bbox(board, &aabb, bsk);
|
||||||
}
|
}
|
||||||
SelectionMode::Addition => {
|
SelectionMode::Addition => {
|
||||||
self.select_all_in_bbox(board, &aabb);
|
self.select_all_in_bbox(board, &aabb, bsk);
|
||||||
}
|
}
|
||||||
SelectionMode::Toggling => {
|
SelectionMode::Toggling => {
|
||||||
let old_selection = self.take_selection();
|
let old_selection = self.take_selection();
|
||||||
self.select_all_in_bbox(board, &aabb);
|
self.select_all_in_bbox(board, &aabb, bsk);
|
||||||
self.selection ^= &old_selection;
|
self.selection ^= &old_selection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,9 +141,10 @@ impl Overlay {
|
||||||
&mut self,
|
&mut self,
|
||||||
board: &Board<impl AccessMesadata>,
|
board: &Board<impl AccessMesadata>,
|
||||||
aabb: &AABB<[f64; 2]>,
|
aabb: &AABB<[f64; 2]>,
|
||||||
|
bsk: BboxSelectionKind,
|
||||||
) {
|
) {
|
||||||
self.selection
|
self.selection
|
||||||
.select_all_in_bbox(board, aabb, self.active_layer);
|
.select_all_in_bbox(board, aabb, self.active_layer, bsk);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ratsnest(&self) -> &Ratsnest {
|
pub fn ratsnest(&self) -> &Ratsnest {
|
||||||
|
|
@ -148,10 +156,21 @@ impl Overlay {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the currently selected bounding box of a bounding-box reselect
|
/// Returns the currently selected bounding box of a bounding-box reselect
|
||||||
pub fn get_bbox_reselect(&self, at: Point) -> Option<(SelectionMode, AABB<[f64; 2]>)> {
|
pub fn get_bbox_reselect(
|
||||||
|
&self,
|
||||||
|
at: Point,
|
||||||
|
) -> Option<(SelectionMode, BboxSelectionKind, AABB<[f64; 2]>)> {
|
||||||
self.reselect_bbox.map(|(selmode, pt)| {
|
self.reselect_bbox.map(|(selmode, pt)| {
|
||||||
(
|
(
|
||||||
selmode,
|
selmode,
|
||||||
|
// Δx = at.x() - pt.x()
|
||||||
|
if at.x() <= pt.x() {
|
||||||
|
// Δx ≤ 0
|
||||||
|
BboxSelectionKind::MerelyIntersects
|
||||||
|
} else {
|
||||||
|
// Δx > 0
|
||||||
|
BboxSelectionKind::CompletelyInside
|
||||||
|
},
|
||||||
AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]),
|
AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -100,9 +100,17 @@ impl Viewport {
|
||||||
);
|
);
|
||||||
} else if response.drag_stopped_by(egui::PointerButton::Primary) {
|
} else if response.drag_stopped_by(egui::PointerButton::Primary) {
|
||||||
overlay.drag_stop(board, latest_point);
|
overlay.drag_stop(board, latest_point);
|
||||||
} else if let Some((_, cur_bbox)) = overlay.get_bbox_reselect(latest_point)
|
} else if let Some((_, bsk, cur_bbox)) =
|
||||||
|
overlay.get_bbox_reselect(latest_point)
|
||||||
{
|
{
|
||||||
painter.paint_bbox_with_color(cur_bbox, egui::Color32::RED);
|
use topola::autorouter::selection::BboxSelectionKind;
|
||||||
|
painter.paint_bbox_with_color(
|
||||||
|
cur_bbox,
|
||||||
|
match bsk {
|
||||||
|
BboxSelectionKind::CompletelyInside => egui::Color32::YELLOW,
|
||||||
|
BboxSelectionKind::MerelyIntersects => egui::Color32::BLUE,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let board = workspace.interactor.invoker().autorouter().board();
|
let board = workspace.interactor.invoker().autorouter().board();
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use rstar::AABB;
|
use rstar::AABB;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{mesadata::AccessMesadata, BandName, Board},
|
board::{mesadata::AccessMesadata, BandName, Board, ResolvedSelector},
|
||||||
drawing::graph::{GetLayer, MakePrimitive, PrimitiveIndex},
|
drawing::graph::{GetLayer, MakePrimitive, PrimitiveIndex},
|
||||||
geometry::GenericNode,
|
geometry::GenericNode,
|
||||||
graph::{GenericIndex, GetPetgraphIndex},
|
graph::{GenericIndex, GetPetgraphIndex},
|
||||||
|
|
@ -55,6 +55,17 @@ impl PinSelector {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn try_from_pin_and_layer_id(
|
||||||
|
board: &Board<impl AccessMesadata>,
|
||||||
|
pin: &str,
|
||||||
|
layer: usize,
|
||||||
|
) -> Option<PinSelector> {
|
||||||
|
Some(PinSelector {
|
||||||
|
pin: pin.to_string(),
|
||||||
|
layer: board.layout().rules().layer_layername(layer)?.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
|
|
@ -109,10 +120,18 @@ impl BandSelector {
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Self::try_from_uid(
|
||||||
|
board,
|
||||||
|
&board.layout().drawing().collect().loose_band_uid(loose),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_from_uid(
|
||||||
|
board: &Board<impl AccessMesadata>,
|
||||||
|
uid: &crate::drawing::band::BandUid,
|
||||||
|
) -> Option<BandSelector> {
|
||||||
Some(BandSelector {
|
Some(BandSelector {
|
||||||
band: board
|
band: board.band_bandname(uid)?.clone(),
|
||||||
.band_bandname(&board.layout().drawing().collect().loose_band_uid(loose))?
|
|
||||||
.clone(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +154,22 @@ impl BandSelection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum BboxSelectionKind {
|
||||||
|
CompletelyInside,
|
||||||
|
MerelyIntersects,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BboxSelectionKind {
|
||||||
|
pub fn matches(&self, bigger: &AABB<[f64; 2]>, smaller: &AABB<[f64; 2]>) -> bool {
|
||||||
|
use rstar::Envelope;
|
||||||
|
match self {
|
||||||
|
Self::CompletelyInside => bigger.contains_envelope(&smaller),
|
||||||
|
Self::MerelyIntersects => bigger.intersection_area(&smaller) > 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
pub struct Selection {
|
pub struct Selection {
|
||||||
pub pin_selection: PinSelection,
|
pub pin_selection: PinSelection,
|
||||||
|
|
@ -151,9 +186,46 @@ impl Selection {
|
||||||
board: &Board<impl AccessMesadata>,
|
board: &Board<impl AccessMesadata>,
|
||||||
aabb: &AABB<[f64; 2]>,
|
aabb: &AABB<[f64; 2]>,
|
||||||
active_layer: usize,
|
active_layer: usize,
|
||||||
|
kind: BboxSelectionKind,
|
||||||
) {
|
) {
|
||||||
use rstar::Envelope;
|
const INF: f64 = f64::INFINITY;
|
||||||
let layout = board.layout();
|
let layout = board.layout();
|
||||||
|
|
||||||
|
let resolved_selectors =
|
||||||
|
match kind {
|
||||||
|
BboxSelectionKind::CompletelyInside => {
|
||||||
|
// 1. gather relevant node indices, and group them by resolved selectors
|
||||||
|
// .0 collects all nodes per resolved selection
|
||||||
|
// .1 collects only nodes which are actively selected here
|
||||||
|
let mut selectors = BTreeMap::<
|
||||||
|
ResolvedSelector,
|
||||||
|
(BTreeSet<NodeIndex>, BTreeSet<NodeIndex>),
|
||||||
|
>::new();
|
||||||
|
for &geom in layout.drawing().rtree().locate_in_envelope_intersecting(
|
||||||
|
&AABB::<[f64; 3]>::from_corners([-INF, -INF, -INF], [INF, INF, INF]),
|
||||||
|
) {
|
||||||
|
let node = geom.data;
|
||||||
|
if layout.is_node_in_layer(node, active_layer) {
|
||||||
|
if let Some(rsel) = ResolvedSelector::try_from_node(board, node) {
|
||||||
|
let rseli = selectors.entry(rsel).or_default();
|
||||||
|
rseli.0.insert(node);
|
||||||
|
if kind.matches(aabb, &layout.node_bbox(node)) {
|
||||||
|
rseli.1.insert(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. restrict to complete matches, return associated keys
|
||||||
|
selectors
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(_, nis)| &nis.0 == &nis.1)
|
||||||
|
.map(|(k, _)| k)
|
||||||
|
.collect::<BTreeSet<_>>()
|
||||||
|
}
|
||||||
|
BboxSelectionKind::MerelyIntersects => {
|
||||||
|
// 1. gather relevant resolved selectors
|
||||||
|
let mut selectors = BTreeSet::<ResolvedSelector>::new();
|
||||||
for &geom in layout.drawing().rtree().locate_in_envelope_intersecting(
|
for &geom in layout.drawing().rtree().locate_in_envelope_intersecting(
|
||||||
&AABB::<[f64; 3]>::from_corners(
|
&AABB::<[f64; 3]>::from_corners(
|
||||||
[aabb.lower()[0], aabb.lower()[1], -f64::INFINITY],
|
[aabb.lower()[0], aabb.lower()[1], -f64::INFINITY],
|
||||||
|
|
@ -161,10 +233,33 @@ impl Selection {
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
let node = geom.data;
|
let node = geom.data;
|
||||||
if aabb.contains_envelope(&layout.node_bbox(node))
|
if layout.is_node_in_layer(node, active_layer)
|
||||||
&& layout.is_node_in_layer(node, active_layer)
|
&& kind.matches(aabb, &layout.node_bbox(node))
|
||||||
{
|
{
|
||||||
self.select_at_node(board, node);
|
if let Some(rsel) = ResolvedSelector::try_from_node(board, node) {
|
||||||
|
selectors.insert(rsel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. nothing to restrict
|
||||||
|
selectors
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 3. convert resolved selectors to actual selections
|
||||||
|
for i in resolved_selectors {
|
||||||
|
match i {
|
||||||
|
ResolvedSelector::Band { band_uid } => {
|
||||||
|
if let Some(x) = BandSelector::try_from_uid(board, &band_uid) {
|
||||||
|
self.band_selection.0.insert(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResolvedSelector::Pin { pin_name, layer } => {
|
||||||
|
if let Some(x) = PinSelector::try_from_pin_and_layer_id(board, pin_name, layer)
|
||||||
|
{
|
||||||
|
self.pin_selection.0.insert(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,53 @@ impl BandName {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum ResolvedSelector<'a> {
|
||||||
|
Band { band_uid: BandUid },
|
||||||
|
Pin { pin_name: &'a str, layer: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ResolvedSelector<'a> {
|
||||||
|
pub fn try_from_node(board: &'a Board<impl AccessMesadata>, node: NodeIndex) -> Option<Self> {
|
||||||
|
use crate::{drawing::graph::MakePrimitive, graph::GetPetgraphIndex};
|
||||||
|
|
||||||
|
let (layer, loose) = match node {
|
||||||
|
NodeIndex::Primitive(primitive) => (
|
||||||
|
primitive.primitive(board.layout().drawing()).layer(),
|
||||||
|
match primitive {
|
||||||
|
PrimitiveIndex::LooseDot(dot) => Some(dot.into()),
|
||||||
|
PrimitiveIndex::LoneLooseSeg(seg) => Some(seg.into()),
|
||||||
|
PrimitiveIndex::SeqLooseSeg(seg) => Some(seg.into()),
|
||||||
|
PrimitiveIndex::LooseBend(bend) => Some(bend.into()),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
NodeIndex::Compound(compound) => {
|
||||||
|
match board.layout().drawing().compound_weight(compound) {
|
||||||
|
CompoundWeight::Poly(..) => (
|
||||||
|
board
|
||||||
|
.layout()
|
||||||
|
.poly(GenericIndex::<PolyWeight>::new(compound.petgraph_index()))
|
||||||
|
.layer(),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(pin_name) = board.node_pinname(&node) {
|
||||||
|
Some(ResolvedSelector::Pin { pin_name, layer })
|
||||||
|
} else if let Some(loose) = loose {
|
||||||
|
Some(ResolvedSelector::Band {
|
||||||
|
band_uid: board.layout().drawing().collect().loose_band_uid(loose),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a board layout and its associated metadata.
|
/// Represents a board layout and its associated metadata.
|
||||||
///
|
///
|
||||||
/// The struct manages the relationships between board's layout,
|
/// The struct manages the relationships between board's layout,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use core::{cmp, hash};
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use petgraph::stable_graph::NodeIndex;
|
use petgraph::stable_graph::NodeIndex;
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ use super::{
|
||||||
Drawing,
|
Drawing,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Ord, PartialOrd)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct BandUid(pub BandTermsegIndex, pub BandTermsegIndex);
|
pub struct BandUid(pub BandTermsegIndex, pub BandTermsegIndex);
|
||||||
|
|
||||||
impl BandUid {
|
impl BandUid {
|
||||||
|
|
@ -41,6 +42,30 @@ impl PartialEq for BandUid {
|
||||||
|
|
||||||
impl Eq for BandUid {}
|
impl Eq for BandUid {}
|
||||||
|
|
||||||
|
impl hash::Hash for BandUid {
|
||||||
|
fn hash<H: hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.0.petgraph_index().hash(state);
|
||||||
|
self.1.petgraph_index().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cmp::PartialOrd for BandUid {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl cmp::Ord for BandUid {
|
||||||
|
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||||
|
use cmp::Ordering as O;
|
||||||
|
match self.0.petgraph_index().cmp(&other.0.petgraph_index()) {
|
||||||
|
O::Less => O::Less,
|
||||||
|
O::Greater => O::Greater,
|
||||||
|
O::Equal => self.1.petgraph_index().cmp(&other.1.petgraph_index()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[enum_dispatch(GetPetgraphIndex)]
|
#[enum_dispatch(GetPetgraphIndex)]
|
||||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
pub enum BandTermsegIndex {
|
pub enum BandTermsegIndex {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue