mirror of https://codeberg.org/topola/topola.git
feat: allow toggling of all nodes in selected bbox
The complication here is that we need to take extra care to avoid issuing self-vanishing/repeated togglings of the same pin/band, as multiple graph nodes correspond to the same one (at least in case of polygons) - refactor(layout): move reusable functions from Overlay to Layout
This commit is contained in:
parent
b070cd787b
commit
eaadc60265
|
|
@ -49,9 +49,12 @@ petgraph.workspace = true
|
||||||
rstar.workspace = true
|
rstar.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
spade.workspace = true
|
spade.workspace = true
|
||||||
specctra-core.path = "crates/specctra-core"
|
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
|
||||||
|
[dependencies.specctra-core]
|
||||||
|
path = "crates/specctra-core"
|
||||||
|
features = ["rstar"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,7 @@ serde.workspace = true
|
||||||
specctra_derive.path = "../specctra_derive"
|
specctra_derive.path = "../specctra_derive"
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
utf8-chars = "3.0"
|
utf8-chars = "3.0"
|
||||||
|
|
||||||
|
[dependencies.rstar]
|
||||||
|
workspace = true
|
||||||
|
optional = true
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,16 @@ impl Circle {
|
||||||
y: self.pos.0.y + self.r * phi.sin()
|
y: self.pos.0.y + self.r * phi.sin()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The (x,y) axis aligned bounding box for this circle.
|
||||||
|
#[cfg(feature = "rstar")]
|
||||||
|
pub fn bbox(&self, margin: f64) -> rstar::AABB<[f64; 2]> {
|
||||||
|
let r = self.r + margin;
|
||||||
|
rstar::AABB::from_corners(
|
||||||
|
[self.pos.0.x - r, self.pos.0.y - r],
|
||||||
|
[self.pos.0.x + r, self.pos.0.y + r],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sub for Circle {
|
impl Sub for Circle {
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ impl Trigger {
|
||||||
|
|
||||||
pub fn consume_key_triggered(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) -> bool {
|
pub fn consume_key_triggered(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) -> bool {
|
||||||
self.consume_key(ctx, ui);
|
self.consume_key(ctx, ui);
|
||||||
self.triggered()
|
self.triggered
|
||||||
}
|
}
|
||||||
|
|
||||||
fn consume_key(&mut self, ctx: &egui::Context, _ui: &mut egui::Ui) {
|
fn consume_key(&mut self, ctx: &egui::Context, _ui: &mut egui::Ui) {
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,10 @@ pub struct EditActions {
|
||||||
pub undo: Trigger,
|
pub undo: Trigger,
|
||||||
pub redo: Trigger,
|
pub redo: Trigger,
|
||||||
pub abort: Trigger,
|
pub abort: Trigger,
|
||||||
pub remove_bands: Trigger,
|
|
||||||
pub reset_bbox: Trigger,
|
pub reset_bbox: Trigger,
|
||||||
pub reselect_bbox: Trigger,
|
pub reselect_bbox: Trigger,
|
||||||
|
pub toggle_all_in_bbox: Trigger,
|
||||||
|
pub remove_bands: Trigger,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditActions {
|
impl EditActions {
|
||||||
|
|
@ -104,12 +105,6 @@ impl EditActions {
|
||||||
egui::Key::Escape,
|
egui::Key::Escape,
|
||||||
)
|
)
|
||||||
.into_trigger(),
|
.into_trigger(),
|
||||||
remove_bands: Action::new(
|
|
||||||
tr.text("tr-menu-edit-remove-bands"),
|
|
||||||
egui::Modifiers::NONE,
|
|
||||||
egui::Key::Delete,
|
|
||||||
)
|
|
||||||
.into_trigger(),
|
|
||||||
reset_bbox: Action::new(
|
reset_bbox: Action::new(
|
||||||
tr.text("tr-menu-edit-reset-bbox"),
|
tr.text("tr-menu-edit-reset-bbox"),
|
||||||
egui::Modifiers::CTRL,
|
egui::Modifiers::CTRL,
|
||||||
|
|
@ -122,6 +117,18 @@ impl EditActions {
|
||||||
egui::Key::B,
|
egui::Key::B,
|
||||||
)
|
)
|
||||||
.into_trigger(),
|
.into_trigger(),
|
||||||
|
toggle_all_in_bbox: Action::new(
|
||||||
|
tr.text("tr-menu-edit-toggle-all-in-bbox"),
|
||||||
|
egui::Modifiers::NONE,
|
||||||
|
egui::Key::A,
|
||||||
|
)
|
||||||
|
.into_trigger(),
|
||||||
|
remove_bands: Action::new(
|
||||||
|
tr.text("tr-menu-edit-remove-bands"),
|
||||||
|
egui::Modifiers::NONE,
|
||||||
|
egui::Key::Delete,
|
||||||
|
)
|
||||||
|
.into_trigger(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,6 +151,7 @@ impl EditActions {
|
||||||
|
|
||||||
self.reset_bbox.button(ctx, ui);
|
self.reset_bbox.button(ctx, ui);
|
||||||
self.reselect_bbox.button(ctx, ui);
|
self.reselect_bbox.button(ctx, ui);
|
||||||
|
self.toggle_all_in_bbox.button(ctx, ui);
|
||||||
|
|
||||||
//ui.add_enabled_ui(workspace_activities_enabled, |ui| {
|
//ui.add_enabled_ui(workspace_activities_enabled, |ui| {
|
||||||
self.remove_bands.button(ctx, ui);
|
self.remove_bands.button(ctx, ui);
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,13 @@ impl MenuBar {
|
||||||
workspace.overlay.reset_selected_bbox();
|
workspace.overlay.reset_selected_bbox();
|
||||||
} else if actions.edit.reselect_bbox.consume_key_triggered(ctx, ui) {
|
} else if actions.edit.reselect_bbox.consume_key_triggered(ctx, ui) {
|
||||||
workspace.overlay.start_bbox_reselect();
|
workspace.overlay.start_bbox_reselect();
|
||||||
|
} else if actions
|
||||||
|
.edit
|
||||||
|
.toggle_all_in_bbox
|
||||||
|
.consume_key_triggered(ctx, ui)
|
||||||
|
{
|
||||||
|
let board = workspace.interactor.invoker().autorouter().board();
|
||||||
|
workspace.overlay.toggle_all_in_selected_bbox(board);
|
||||||
} else if actions.place.place_via.consume_key_enabled(
|
} else if actions.place.place_via.consume_key_enabled(
|
||||||
ctx,
|
ctx,
|
||||||
ui,
|
ui,
|
||||||
|
|
@ -283,7 +290,7 @@ impl MenuBar {
|
||||||
|
|
||||||
pub fn update_view_menu(
|
pub fn update_view_menu(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &egui::Context,
|
_ctx: &egui::Context,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
tr: &Translator,
|
tr: &Translator,
|
||||||
viewport: &mut Viewport,
|
viewport: &mut Viewport,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use topola::{
|
||||||
layout::{
|
layout::{
|
||||||
poly::{MakePolyShape, PolyWeight},
|
poly::{MakePolyShape, PolyWeight},
|
||||||
via::ViaWeight,
|
via::ViaWeight,
|
||||||
CompoundWeight, NodeIndex,
|
CompoundWeight, Layout, NodeIndex,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -84,61 +84,36 @@ impl Overlay {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Some(geom) = geoms.iter().find(|&&geom| {
|
if let Some(geom) = geoms.iter().find(|&&geom| {
|
||||||
self.contains_point(board, geom.data, at)
|
board.layout().node_shape(geom.data).contains_point(at)
|
||||||
&& match geom.data {
|
&& board
|
||||||
NodeIndex::Primitive(primitive) => {
|
.layout()
|
||||||
primitive.primitive(board.layout().drawing()).layer() == self.active_layer
|
.is_node_in_layer(geom.data, self.active_layer)
|
||||||
}
|
|
||||||
NodeIndex::Compound(compound) => {
|
|
||||||
match board.layout().drawing().compound_weight(compound) {
|
|
||||||
CompoundWeight::Poly(_) => {
|
|
||||||
board
|
|
||||||
.layout()
|
|
||||||
.poly(GenericIndex::<PolyWeight>::new(
|
|
||||||
compound.petgraph_index(),
|
|
||||||
))
|
|
||||||
.layer()
|
|
||||||
== self.active_layer
|
|
||||||
}
|
|
||||||
CompoundWeight::Via(weight) => {
|
|
||||||
weight.from_layer >= self.active_layer
|
|
||||||
&& weight.to_layer <= self.active_layer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
}) {
|
||||||
self.selection.toggle_at_node(board, geom.data);
|
self.selection.toggle_at_node(board, geom.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_point(
|
pub fn toggle_all_in_selected_bbox(&mut self, board: &Board<impl AccessMesadata>) {
|
||||||
&self,
|
use rstar::Envelope;
|
||||||
board: &Board<impl AccessMesadata>,
|
let aabb = self.selected_bbox;
|
||||||
node: NodeIndex,
|
let mut temp_selection = Selection::new();
|
||||||
p: Point,
|
for node in board
|
||||||
) -> bool {
|
.layout()
|
||||||
let shape: Shape = match node {
|
.drawing()
|
||||||
NodeIndex::Primitive(primitive) => {
|
.rtree()
|
||||||
primitive.primitive(board.layout().drawing()).shape().into()
|
.locate_in_envelope_intersecting(&AABB::<[f64; 3]>::from_corners(
|
||||||
}
|
[aabb.lower()[0], aabb.lower()[1], -f64::INFINITY],
|
||||||
NodeIndex::Compound(compound) => {
|
[aabb.upper()[0], aabb.upper()[1], f64::INFINITY],
|
||||||
match board.layout().drawing().compound_weight(compound) {
|
))
|
||||||
CompoundWeight::Poly(_) => board
|
.map(|&geom| geom.data)
|
||||||
.layout()
|
.filter(|&node| {
|
||||||
.poly(GenericIndex::<PolyWeight>::new(compound.petgraph_index()))
|
aabb.contains_envelope(&board.layout().node_bbox(node))
|
||||||
.shape()
|
&& board.layout().is_node_in_layer(node, self.active_layer)
|
||||||
.into(),
|
})
|
||||||
CompoundWeight::Via(_) => board
|
{
|
||||||
.layout()
|
temp_selection.select_at_node(board, node);
|
||||||
.via(GenericIndex::<ViaWeight>::new(compound.petgraph_index()))
|
}
|
||||||
.shape()
|
self.selection ^= &temp_selection;
|
||||||
.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
shape.contains_point(p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ratsnest(&self) -> &Ratsnest {
|
pub fn ratsnest(&self) -> &Ratsnest {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ tr-menu-edit-redo = Redo
|
||||||
tr-menu-edit-abort = Abort
|
tr-menu-edit-abort = Abort
|
||||||
tr-menu-edit-reset-bbox = Reset selected BBox
|
tr-menu-edit-reset-bbox = Reset selected BBox
|
||||||
tr-menu-edit-reselect-bbox = (Re-)select BBox
|
tr-menu-edit-reselect-bbox = (Re-)select BBox
|
||||||
|
tr-menu-edit-toggle-all-in-bbox = Toggle all entries in selected BBox
|
||||||
tr-menu-edit-remove-bands = Remove Bands
|
tr-menu-edit-remove-bands = Remove Bands
|
||||||
|
|
||||||
tr-menu-view = View
|
tr-menu-view = View
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,14 @@ impl Selection {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_at_node(&mut self, board: &Board<impl AccessMesadata>, node: NodeIndex) {
|
||||||
|
if let Some(selector) = PinSelector::try_from_node(board, node) {
|
||||||
|
self.pin_selection.0.insert(selector);
|
||||||
|
} else if let Some(selector) = BandSelector::try_from_node(board, node) {
|
||||||
|
self.band_selection.0.insert(selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggle_at_node(&mut self, board: &Board<impl AccessMesadata>, node: NodeIndex) {
|
pub fn toggle_at_node(&mut self, board: &Board<impl AccessMesadata>, node: NodeIndex) {
|
||||||
if let Some(selector) = PinSelector::try_from_node(board, node) {
|
if let Some(selector) = PinSelector::try_from_node(board, node) {
|
||||||
if self.pin_selection.0.contains(&selector) {
|
if self.pin_selection.0.contains(&selector) {
|
||||||
|
|
@ -166,3 +174,10 @@ impl Selection {
|
||||||
|| self.band_selection.contains_node(board, node)
|
|| self.band_selection.contains_node(board, node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> core::ops::BitXorAssign<&'a Selection> for Selection {
|
||||||
|
fn bitxor_assign(&mut self, rhs: &'a Selection) {
|
||||||
|
self.pin_selection.0 = &self.pin_selection.0 ^ &rhs.pin_selection.0;
|
||||||
|
self.band_selection.0 = &self.band_selection.0 ^ &rhs.band_selection.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ use crate::{
|
||||||
cane::Cane,
|
cane::Cane,
|
||||||
dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight},
|
dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight},
|
||||||
gear::GearIndex,
|
gear::GearIndex,
|
||||||
graph::{GetMaybeNet, PrimitiveIndex, PrimitiveWeight},
|
graph::{GetMaybeNet, MakePrimitive, PrimitiveIndex, PrimitiveWeight},
|
||||||
|
primitive::MakePrimitiveShape,
|
||||||
rules::AccessRules,
|
rules::AccessRules,
|
||||||
seg::{
|
seg::{
|
||||||
FixedSegIndex, FixedSegWeight, LoneLooseSegIndex, LoneLooseSegWeight, SegIndex,
|
FixedSegIndex, FixedSegWeight, LoneLooseSegIndex, LoneLooseSegWeight, SegIndex,
|
||||||
|
|
@ -23,10 +24,10 @@ use crate::{
|
||||||
},
|
},
|
||||||
Drawing, DrawingEdit, DrawingException, Infringement,
|
Drawing, DrawingEdit, DrawingException, Infringement,
|
||||||
},
|
},
|
||||||
geometry::{edit::ApplyGeometryEdit, GenericNode},
|
geometry::{edit::ApplyGeometryEdit, shape::Shape, GenericNode},
|
||||||
graph::{GenericIndex, GetPetgraphIndex},
|
graph::{GenericIndex, GetPetgraphIndex},
|
||||||
layout::{
|
layout::{
|
||||||
poly::{Poly, PolyWeight},
|
poly::{MakePolyShape, Poly, PolyWeight},
|
||||||
via::{Via, ViaWeight},
|
via::{Via, ViaWeight},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -317,6 +318,67 @@ impl<R: AccessRules> Layout<R> {
|
||||||
.compound_members(GenericIndex::new(poly.petgraph_index()))
|
.compound_members(GenericIndex::new(poly.petgraph_index()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_node_in_layer(&self, index: NodeIndex, active_layer: usize) -> bool {
|
||||||
|
use crate::drawing::graph::GetLayer;
|
||||||
|
match index {
|
||||||
|
NodeIndex::Primitive(primitive) => {
|
||||||
|
primitive.primitive(&self.drawing).layer() == active_layer
|
||||||
|
}
|
||||||
|
NodeIndex::Compound(compound) => match self.drawing.compound_weight(compound) {
|
||||||
|
CompoundWeight::Poly(_) => {
|
||||||
|
self.poly(GenericIndex::<PolyWeight>::new(compound.petgraph_index()))
|
||||||
|
.layer()
|
||||||
|
== active_layer
|
||||||
|
}
|
||||||
|
CompoundWeight::Via(weight) => {
|
||||||
|
weight.from_layer >= active_layer && weight.to_layer <= active_layer
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node_shape(&self, index: NodeIndex) -> Shape {
|
||||||
|
match index {
|
||||||
|
NodeIndex::Primitive(primitive) => primitive.primitive(&self.drawing).shape().into(),
|
||||||
|
NodeIndex::Compound(compound) => match self.drawing.compound_weight(compound) {
|
||||||
|
CompoundWeight::Poly(_) => self
|
||||||
|
.poly(GenericIndex::<PolyWeight>::new(compound.petgraph_index()))
|
||||||
|
.shape()
|
||||||
|
.into(),
|
||||||
|
CompoundWeight::Via(_) => self
|
||||||
|
.via(GenericIndex::<ViaWeight>::new(compound.petgraph_index()))
|
||||||
|
.shape()
|
||||||
|
.into(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node_bbox(&self, index: NodeIndex) -> AABB<[f64; 2]> {
|
||||||
|
use crate::geometry::primitive::AccessPrimitiveShape;
|
||||||
|
match index {
|
||||||
|
NodeIndex::Primitive(primitive) => primitive.primitive(&self.drawing).shape().bbox(0.0),
|
||||||
|
NodeIndex::Compound(compound) => match self.drawing.compound_weight(compound) {
|
||||||
|
CompoundWeight::Poly(_) => {
|
||||||
|
let coord_string = self
|
||||||
|
.poly(GenericIndex::<PolyWeight>::new(compound.petgraph_index()))
|
||||||
|
.shape()
|
||||||
|
.polygon
|
||||||
|
.exterior()
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|coord| [coord.x, coord.y])
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
AABB::from_points(&coord_string[..])
|
||||||
|
}
|
||||||
|
CompoundWeight::Via(_) => self
|
||||||
|
.via(GenericIndex::<ViaWeight>::new(compound.petgraph_index()))
|
||||||
|
.shape()
|
||||||
|
.bbox(0.0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn rules(&self) -> &R {
|
pub fn rules(&self) -> &R {
|
||||||
self.drawing.rules()
|
self.drawing.rules()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue