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 topola::{
|
||||
autorouter::{ratsnest::Ratsnest, selection::Selection},
|
||||
autorouter::{
|
||||
ratsnest::Ratsnest,
|
||||
selection::{BboxSelectionKind, Selection},
|
||||
},
|
||||
board::{mesadata::AccessMesadata, Board},
|
||||
drawing::{
|
||||
graph::{GetLayer, MakePrimitive},
|
||||
|
|
@ -53,7 +56,11 @@ impl Overlay {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -81,21 +88,21 @@ impl Overlay {
|
|||
}
|
||||
|
||||
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
|
||||
self.reselect_bbox = None;
|
||||
|
||||
match selmode {
|
||||
SelectionMode::Substitution => {
|
||||
self.selection = Selection::new();
|
||||
self.select_all_in_bbox(board, &aabb);
|
||||
self.select_all_in_bbox(board, &aabb, bsk);
|
||||
}
|
||||
SelectionMode::Addition => {
|
||||
self.select_all_in_bbox(board, &aabb);
|
||||
self.select_all_in_bbox(board, &aabb, bsk);
|
||||
}
|
||||
SelectionMode::Toggling => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -134,9 +141,10 @@ impl Overlay {
|
|||
&mut self,
|
||||
board: &Board<impl AccessMesadata>,
|
||||
aabb: &AABB<[f64; 2]>,
|
||||
bsk: BboxSelectionKind,
|
||||
) {
|
||||
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 {
|
||||
|
|
@ -148,10 +156,21 @@ impl Overlay {
|
|||
}
|
||||
|
||||
/// 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)| {
|
||||
(
|
||||
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()]),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -100,9 +100,17 @@ impl Viewport {
|
|||
);
|
||||
} else if response.drag_stopped_by(egui::PointerButton::Primary) {
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use rstar::AABB;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
board::{mesadata::AccessMesadata, BandName, Board},
|
||||
board::{mesadata::AccessMesadata, BandName, Board, ResolvedSelector},
|
||||
drawing::graph::{GetLayer, MakePrimitive, PrimitiveIndex},
|
||||
geometry::GenericNode,
|
||||
graph::{GenericIndex, GetPetgraphIndex},
|
||||
|
|
@ -55,6 +55,17 @@ impl PinSelector {
|
|||
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)]
|
||||
|
|
@ -109,10 +120,18 @@ impl BandSelector {
|
|||
_ => 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 {
|
||||
band: board
|
||||
.band_bandname(&board.layout().drawing().collect().loose_band_uid(loose))?
|
||||
.clone(),
|
||||
band: board.band_bandname(uid)?.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)]
|
||||
pub struct Selection {
|
||||
pub pin_selection: PinSelection,
|
||||
|
|
@ -151,20 +186,80 @@ impl Selection {
|
|||
board: &Board<impl AccessMesadata>,
|
||||
aabb: &AABB<[f64; 2]>,
|
||||
active_layer: usize,
|
||||
kind: BboxSelectionKind,
|
||||
) {
|
||||
use rstar::Envelope;
|
||||
const INF: f64 = f64::INFINITY;
|
||||
let layout = board.layout();
|
||||
for &geom in layout.drawing().rtree().locate_in_envelope_intersecting(
|
||||
&AABB::<[f64; 3]>::from_corners(
|
||||
[aabb.lower()[0], aabb.lower()[1], -f64::INFINITY],
|
||||
[aabb.upper()[0], aabb.upper()[1], f64::INFINITY],
|
||||
),
|
||||
) {
|
||||
let node = geom.data;
|
||||
if aabb.contains_envelope(&layout.node_bbox(node))
|
||||
&& layout.is_node_in_layer(node, active_layer)
|
||||
{
|
||||
self.select_at_node(board, node);
|
||||
|
||||
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(
|
||||
&AABB::<[f64; 3]>::from_corners(
|
||||
[aabb.lower()[0], aabb.lower()[1], -f64::INFINITY],
|
||||
[aabb.upper()[0], aabb.upper()[1], f64::INFINITY],
|
||||
),
|
||||
) {
|
||||
let node = geom.data;
|
||||
if layout.is_node_in_layer(node, active_layer)
|
||||
&& kind.matches(aabb, &layout.node_bbox(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.
|
||||
///
|
||||
/// The struct manages the relationships between board's layout,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use core::{cmp, hash};
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use petgraph::stable_graph::NodeIndex;
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ use super::{
|
|||
Drawing,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Ord, PartialOrd)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BandUid(pub BandTermsegIndex, pub BandTermsegIndex);
|
||||
|
||||
impl BandUid {
|
||||
|
|
@ -41,6 +42,30 @@ impl PartialEq 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)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub enum BandTermsegIndex {
|
||||
|
|
|
|||
Loading…
Reference in New Issue