mirror of https://codeberg.org/topola/topola.git
feat: implement bounding box selection (#139)
Fixes #138 Reviewed-on: https://codeberg.org/topola/topola/pulls/139 Co-authored-by: Alain Emilia Anna Zscheile <fogti+devel@ytrizja.de> Co-committed-by: Alain Emilia Anna Zscheile <fogti+devel@ytrizja.de>
This commit is contained in:
parent
bbf54c9eb5
commit
53b964b865
|
|
@ -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,6 +78,8 @@ pub struct EditActions {
|
||||||
pub undo: Trigger,
|
pub undo: Trigger,
|
||||||
pub redo: Trigger,
|
pub redo: Trigger,
|
||||||
pub abort: Trigger,
|
pub abort: Trigger,
|
||||||
|
pub select_all: Trigger,
|
||||||
|
pub unselect_all: Trigger,
|
||||||
pub remove_bands: Trigger,
|
pub remove_bands: Trigger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +104,18 @@ impl EditActions {
|
||||||
egui::Key::Escape,
|
egui::Key::Escape,
|
||||||
)
|
)
|
||||||
.into_trigger(),
|
.into_trigger(),
|
||||||
|
select_all: Action::new(
|
||||||
|
tr.text("tr-menu-edit-select-all"),
|
||||||
|
egui::Modifiers::CTRL,
|
||||||
|
egui::Key::A, // taken from KiCAD
|
||||||
|
)
|
||||||
|
.into_trigger(),
|
||||||
|
unselect_all: Action::new(
|
||||||
|
tr.text("tr-menu-edit-unselect-all"),
|
||||||
|
egui::Modifiers::CTRL | egui::Modifiers::SHIFT,
|
||||||
|
egui::Key::A,
|
||||||
|
)
|
||||||
|
.into_trigger(),
|
||||||
remove_bands: Action::new(
|
remove_bands: Action::new(
|
||||||
tr.text("tr-menu-edit-remove-bands"),
|
tr.text("tr-menu-edit-remove-bands"),
|
||||||
egui::Modifiers::NONE,
|
egui::Modifiers::NONE,
|
||||||
|
|
@ -128,6 +142,11 @@ impl EditActions {
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
self.select_all.button(ctx, ui);
|
||||||
|
self.unselect_all.button(ctx, ui);
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
//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);
|
||||||
//});
|
//});
|
||||||
|
|
@ -174,7 +193,7 @@ impl RouteActions {
|
||||||
autoroute: Action::new(
|
autoroute: Action::new(
|
||||||
tr.text("tr-menu-route-autoroute"),
|
tr.text("tr-menu-route-autoroute"),
|
||||||
egui::Modifiers::CTRL,
|
egui::Modifiers::CTRL,
|
||||||
egui::Key::A,
|
egui::Key::R,
|
||||||
)
|
)
|
||||||
.into_trigger(),
|
.into_trigger(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,12 @@ impl MenuBar {
|
||||||
workspace.interactor.redo();
|
workspace.interactor.redo();
|
||||||
} else if actions.edit.abort.consume_key_triggered(ctx, ui) {
|
} else if actions.edit.abort.consume_key_triggered(ctx, ui) {
|
||||||
workspace.interactor.abort();
|
workspace.interactor.abort();
|
||||||
|
} else if actions.edit.unselect_all.consume_key_triggered(ctx, ui) {
|
||||||
|
// NOTE: we need to check `unselect` first because `Ctrl+A` would also match `Ctrl+Shift+A`
|
||||||
|
workspace.overlay.unselect_all();
|
||||||
|
} else if actions.edit.select_all.consume_key_triggered(ctx, ui) {
|
||||||
|
let board = workspace.interactor.invoker().autorouter().board();
|
||||||
|
workspace.overlay.select_all(board);
|
||||||
} else if actions.place.place_via.consume_key_enabled(
|
} else if actions.place.place_via.consume_key_enabled(
|
||||||
ctx,
|
ctx,
|
||||||
ui,
|
ui,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use geo::Point;
|
use geo::Point;
|
||||||
use rstar::AABB;
|
use rstar::{Point as _, AABB};
|
||||||
use spade::InsertionError;
|
use spade::InsertionError;
|
||||||
|
|
||||||
use topola::{
|
use topola::{
|
||||||
|
|
@ -18,21 +18,32 @@ use topola::{
|
||||||
layout::{
|
layout::{
|
||||||
poly::{MakePolyShape, PolyWeight},
|
poly::{MakePolyShape, PolyWeight},
|
||||||
via::ViaWeight,
|
via::ViaWeight,
|
||||||
CompoundWeight, NodeIndex,
|
CompoundWeight, Layout, NodeIndex,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum SelectionMode {
|
||||||
|
Addition,
|
||||||
|
Substitution,
|
||||||
|
Toggling,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Overlay {
|
pub struct Overlay {
|
||||||
ratsnest: Ratsnest,
|
ratsnest: Ratsnest,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
|
reselect_bbox: Option<(SelectionMode, Point)>,
|
||||||
active_layer: usize,
|
active_layer: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INF: f64 = f64::INFINITY;
|
||||||
|
|
||||||
impl Overlay {
|
impl Overlay {
|
||||||
pub fn new(board: &Board<impl AccessMesadata>) -> Result<Self, InsertionError> {
|
pub fn new(board: &Board<impl AccessMesadata>) -> Result<Self, InsertionError> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ratsnest: Ratsnest::new(board.layout())?,
|
ratsnest: Ratsnest::new(board.layout())?,
|
||||||
selection: Selection::new(),
|
selection: Selection::new(),
|
||||||
|
reselect_bbox: None,
|
||||||
active_layer: 0,
|
active_layer: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -41,11 +52,64 @@ impl Overlay {
|
||||||
core::mem::replace(&mut self.selection, Selection::new())
|
core::mem::replace(&mut self.selection, Selection::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_selection(&mut self) {
|
pub fn select_all(&mut self, board: &Board<impl AccessMesadata>) {
|
||||||
|
self.select_all_in_bbox(board, &AABB::from_corners([-INF, -INF], [INF, INF]));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unselect_all(&mut self) {
|
||||||
self.selection = Selection::new();
|
self.selection = Selection::new();
|
||||||
|
self.reselect_bbox = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drag_start(
|
||||||
|
&mut self,
|
||||||
|
board: &Board<impl AccessMesadata>,
|
||||||
|
at: Point,
|
||||||
|
modifiers: &egui::Modifiers,
|
||||||
|
) {
|
||||||
|
if self.reselect_bbox.is_none() {
|
||||||
|
// handle bounding box selection
|
||||||
|
let selmode = if modifiers.ctrl {
|
||||||
|
SelectionMode::Toggling
|
||||||
|
} else if modifiers.shift {
|
||||||
|
SelectionMode::Addition
|
||||||
|
} else {
|
||||||
|
SelectionMode::Substitution
|
||||||
|
};
|
||||||
|
self.reselect_bbox = Some((selmode, at));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drag_stop(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
|
||||||
|
if let Some((selmode, 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);
|
||||||
|
}
|
||||||
|
SelectionMode::Addition => {
|
||||||
|
self.select_all_in_bbox(board, &aabb);
|
||||||
|
}
|
||||||
|
SelectionMode::Toggling => {
|
||||||
|
let old_selection = self.take_selection();
|
||||||
|
self.select_all_in_bbox(board, &aabb);
|
||||||
|
self.selection ^= &old_selection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn click(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
|
pub fn click(&mut self, board: &Board<impl AccessMesadata>, at: Point) {
|
||||||
|
if self.reselect_bbox.is_some() {
|
||||||
|
// handle bounding box selection (takes precendence over other interactions)
|
||||||
|
// this is mostly in order to allow the user to recover from a missed/dropped drag_stop event
|
||||||
|
self.drag_stop(board, at);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let geoms: Vec<_> = board
|
let geoms: Vec<_> = board
|
||||||
.layout()
|
.layout()
|
||||||
.drawing()
|
.drawing()
|
||||||
|
|
@ -57,61 +121,22 @@ 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 select_all_in_bbox(
|
||||||
&self,
|
&mut self,
|
||||||
board: &Board<impl AccessMesadata>,
|
board: &Board<impl AccessMesadata>,
|
||||||
node: NodeIndex,
|
aabb: &AABB<[f64; 2]>,
|
||||||
p: Point,
|
) {
|
||||||
) -> bool {
|
self.selection
|
||||||
let shape: Shape = match node {
|
.select_all_in_bbox(board, aabb, self.active_layer);
|
||||||
NodeIndex::Primitive(primitive) => {
|
|
||||||
primitive.primitive(board.layout().drawing()).shape().into()
|
|
||||||
}
|
|
||||||
NodeIndex::Compound(compound) => {
|
|
||||||
match board.layout().drawing().compound_weight(compound) {
|
|
||||||
CompoundWeight::Poly(_) => board
|
|
||||||
.layout()
|
|
||||||
.poly(GenericIndex::<PolyWeight>::new(compound.petgraph_index()))
|
|
||||||
.shape()
|
|
||||||
.into(),
|
|
||||||
CompoundWeight::Via(_) => board
|
|
||||||
.layout()
|
|
||||||
.via(GenericIndex::<ViaWeight>::new(compound.petgraph_index()))
|
|
||||||
.shape()
|
|
||||||
.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
shape.contains_point(p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ratsnest(&self) -> &Ratsnest {
|
pub fn ratsnest(&self) -> &Ratsnest {
|
||||||
|
|
@ -121,4 +146,14 @@ impl Overlay {
|
||||||
pub fn selection(&self) -> &Selection {
|
pub fn selection(&self) -> &Selection {
|
||||||
&self.selection
|
&self.selection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the currently selected bounding box of a bounding-box reselect
|
||||||
|
pub fn get_bbox_reselect(&self, at: Point) -> Option<(SelectionMode, AABB<[f64; 2]>)> {
|
||||||
|
self.reselect_bbox.map(|(selmode, pt)| {
|
||||||
|
(
|
||||||
|
selmode,
|
||||||
|
AABB::from_corners([pt.x(), pt.y()], [at.x(), at.y()]),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ impl<'a> Painter<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint_bbox(&mut self, bbox: AABB<[f64; 2]>) {
|
pub fn paint_bbox(&mut self, bbox: AABB<[f64; 2]>) {
|
||||||
|
self.paint_bbox_with_color(bbox, egui::Color32::GRAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paint_bbox_with_color(&mut self, bbox: AABB<[f64; 2]>, color: egui::Color32) {
|
||||||
let rect = egui::epaint::Rect {
|
let rect = egui::epaint::Rect {
|
||||||
min: [bbox.lower()[0] as f32, -bbox.upper()[1] as f32].into(),
|
min: [bbox.lower()[0] as f32, -bbox.upper()[1] as f32].into(),
|
||||||
max: [bbox.upper()[0] as f32, -bbox.lower()[1] as f32].into(),
|
max: [bbox.upper()[0] as f32, -bbox.lower()[1] as f32].into(),
|
||||||
|
|
@ -66,7 +70,7 @@ impl<'a> Painter<'a> {
|
||||||
self.ui.painter().add(egui::Shape::rect_stroke(
|
self.ui.painter().add(egui::Shape::rect_stroke(
|
||||||
self.transform * rect,
|
self.transform * rect,
|
||||||
egui::Rounding::ZERO,
|
egui::Rounding::ZERO,
|
||||||
egui::Stroke::new(1.0, egui::Color32::GRAY),
|
egui::Stroke::new(1.0, color),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,232 +45,320 @@ impl Viewport {
|
||||||
menu_bar: &MenuBar,
|
menu_bar: &MenuBar,
|
||||||
maybe_workspace: Option<&mut Workspace>,
|
maybe_workspace: Option<&mut Workspace>,
|
||||||
) -> egui::Rect {
|
) -> egui::Rect {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default()
|
||||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.ctx().request_repaint();
|
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||||
|
ui.ctx().request_repaint();
|
||||||
|
|
||||||
let (_id, viewport_rect) = ui.allocate_space(ui.available_size());
|
let (id, viewport_rect) = ui.allocate_space(ui.available_size());
|
||||||
let latest_pos = self.transform.inverse() * (ctx.input(|i| i.pointer.latest_pos().unwrap_or_default()));
|
let response = ui.interact(viewport_rect, id, egui::Sense::click_and_drag());
|
||||||
|
// NOTE: we use `interact_pos` instead of `latest_pos` to handle "pointer gone"
|
||||||
|
// events more graceful
|
||||||
|
let latest_pos = self.transform.inverse()
|
||||||
|
* (response.interact_pointer_pos().unwrap_or_else(|| {
|
||||||
|
ctx.input(|i| i.pointer.interact_pos().unwrap_or_default())
|
||||||
|
}));
|
||||||
|
|
||||||
let old_scaling = self.transform.scaling;
|
let old_scaling = self.transform.scaling;
|
||||||
self.transform.scaling *= ctx.input(|i| i.zoom_delta());
|
self.transform.scaling *= ctx.input(|i| i.zoom_delta());
|
||||||
|
|
||||||
self.transform.translation += latest_pos.to_vec2() * (old_scaling - self.transform.scaling);
|
self.transform.translation +=
|
||||||
self.transform.translation += ctx.input(|i| i.smooth_scroll_delta);
|
latest_pos.to_vec2() * (old_scaling - self.transform.scaling);
|
||||||
|
self.transform.translation += ctx.input(|i| i.smooth_scroll_delta);
|
||||||
|
|
||||||
let mut painter = Painter::new(ui, self.transform, menu_bar.show_bboxes);
|
let mut painter = Painter::new(ui, self.transform, menu_bar.show_bboxes);
|
||||||
|
|
||||||
if let Some(workspace) = maybe_workspace {
|
if let Some(workspace) = maybe_workspace {
|
||||||
let layers = &mut workspace.appearance_panel;
|
let layers = &mut workspace.appearance_panel;
|
||||||
let overlay = &mut workspace.overlay;
|
let overlay = &mut workspace.overlay;
|
||||||
|
let latest_point = point! {x: latest_pos.x as f64, y: -latest_pos.y as f64};
|
||||||
|
let board = workspace.interactor.invoker().autorouter().board();
|
||||||
|
|
||||||
if ctx.input(|i| i.pointer.any_click()) {
|
if response.clicked_by(egui::PointerButton::Primary) {
|
||||||
if menu_bar.is_placing_via {
|
if menu_bar.is_placing_via {
|
||||||
workspace.interactor.execute(
|
workspace.interactor.execute(Command::PlaceVia(ViaWeight {
|
||||||
Command::PlaceVia(ViaWeight {
|
|
||||||
from_layer: 0,
|
from_layer: 0,
|
||||||
to_layer: 0,
|
to_layer: 0,
|
||||||
circle: Circle {
|
circle: Circle {
|
||||||
pos: point! {x: latest_pos.x as f64, y: -latest_pos.y as f64},
|
pos: latest_point,
|
||||||
r: menu_bar.autorouter_options.router_options.routed_band_width / 2.0,
|
r: menu_bar
|
||||||
|
.autorouter_options
|
||||||
|
.router_options
|
||||||
|
.routed_band_width
|
||||||
|
/ 2.0,
|
||||||
},
|
},
|
||||||
maybe_net: Some(1234),
|
maybe_net: Some(1234),
|
||||||
}),
|
}));
|
||||||
);
|
} else {
|
||||||
} else {
|
overlay.click(board, latest_point);
|
||||||
overlay.click(
|
}
|
||||||
workspace.interactor.invoker().autorouter().board(),
|
} else if response.drag_started_by(egui::PointerButton::Primary) {
|
||||||
point! {x: latest_pos.x as f64, y: -latest_pos.y as f64},
|
overlay.drag_start(
|
||||||
|
board,
|
||||||
|
latest_point,
|
||||||
|
&response.ctx.input(|i| i.modifiers),
|
||||||
);
|
);
|
||||||
|
} 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)
|
||||||
|
{
|
||||||
|
painter.paint_bbox_with_color(cur_bbox, egui::Color32::RED);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let board = workspace.interactor.invoker().autorouter().board();
|
let board = workspace.interactor.invoker().autorouter().board();
|
||||||
|
|
||||||
for i in (0..layers.visible.len()).rev() {
|
for i in (0..layers.visible.len()).rev() {
|
||||||
if layers.visible[i] {
|
if layers.visible[i] {
|
||||||
for primitive in board.layout().drawing().layer_primitive_nodes(i) {
|
for primitive in board.layout().drawing().layer_primitive_nodes(i) {
|
||||||
let shape = primitive.primitive(board.layout().drawing()).shape();
|
let shape =
|
||||||
|
primitive.primitive(board.layout().drawing()).shape();
|
||||||
|
|
||||||
let color = if overlay
|
let color = if overlay
|
||||||
.selection()
|
.selection()
|
||||||
.contains_node(board, GenericNode::Primitive(primitive))
|
.contains_node(board, GenericNode::Primitive(primitive))
|
||||||
{
|
{
|
||||||
config.colors(ctx).layers.color(board.layout().rules().layer_layername(i)).highlighted
|
config
|
||||||
} else if let Some(activity) = &mut workspace.interactor.maybe_activity() {
|
.colors(ctx)
|
||||||
if activity.obstacles().contains(&primitive) {
|
.layers
|
||||||
config.colors(ctx).layers.color(board.layout().rules().layer_layername(i)).highlighted
|
.color(board.layout().rules().layer_layername(i))
|
||||||
|
.highlighted
|
||||||
|
} else if let Some(activity) =
|
||||||
|
&mut workspace.interactor.maybe_activity()
|
||||||
|
{
|
||||||
|
if activity.obstacles().contains(&primitive) {
|
||||||
|
config
|
||||||
|
.colors(ctx)
|
||||||
|
.layers
|
||||||
|
.color(board.layout().rules().layer_layername(i))
|
||||||
|
.highlighted
|
||||||
|
} else {
|
||||||
|
config
|
||||||
|
.colors(ctx)
|
||||||
|
.layers
|
||||||
|
.color(board.layout().rules().layer_layername(i))
|
||||||
|
.normal
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
config.colors(ctx).layers.color(board.layout().rules().layer_layername(i)).normal
|
config
|
||||||
}
|
.colors(ctx)
|
||||||
} else {
|
.layers
|
||||||
config.colors(ctx).layers.color(board.layout().rules().layer_layername(i)).normal
|
.color(board.layout().rules().layer_layername(i))
|
||||||
};
|
.normal
|
||||||
|
|
||||||
painter.paint_primitive(&shape, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
for poly in board.layout().layer_poly_nodes(i) {
|
|
||||||
let color = if overlay
|
|
||||||
.selection()
|
|
||||||
.contains_node(board, GenericNode::Compound(poly.into()))
|
|
||||||
{
|
|
||||||
config.colors(ctx).layers.color(board.layout().rules().layer_layername(i)).highlighted
|
|
||||||
} else {
|
|
||||||
config.colors(ctx).layers.color(board.layout().rules().layer_layername(i)).normal
|
|
||||||
};
|
|
||||||
|
|
||||||
painter.paint_polygon(&board.layout().poly(poly).shape().polygon, color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if menu_bar.show_ratsnest {
|
|
||||||
let graph = overlay.ratsnest().graph();
|
|
||||||
for edge in graph.edge_references() {
|
|
||||||
let from = graph
|
|
||||||
.node_weight(edge.source())
|
|
||||||
.unwrap()
|
|
||||||
.pos;
|
|
||||||
let to = graph
|
|
||||||
.node_weight(edge.target())
|
|
||||||
.unwrap()
|
|
||||||
.pos;
|
|
||||||
|
|
||||||
painter.paint_edge(
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
egui::Stroke::new(1.0, egui::Color32::from_rgb(90, 90, 200)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if menu_bar.show_navmesh {
|
|
||||||
if let Some(activity) = workspace.interactor.maybe_activity() {
|
|
||||||
if let Some(navmesh) = activity.maybe_navmesh() {
|
|
||||||
for edge in navmesh.edge_references() {
|
|
||||||
let mut from = PrimitiveIndex::from(navmesh.node_weight(edge.source()).unwrap().node)
|
|
||||||
.primitive(board.layout().drawing())
|
|
||||||
.shape()
|
|
||||||
.center();
|
|
||||||
let mut to = PrimitiveIndex::from(navmesh.node_weight(edge.target()).unwrap().node)
|
|
||||||
.primitive(board.layout().drawing())
|
|
||||||
.shape()
|
|
||||||
.center();
|
|
||||||
|
|
||||||
if let Some(from_cw) = navmesh.node_weight(edge.source()).unwrap().maybe_cw {
|
|
||||||
if from_cw {
|
|
||||||
from -= [0.0, 150.0].into();
|
|
||||||
} else {
|
|
||||||
from += [0.0, 150.0].into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(to_cw) = navmesh.node_weight(edge.target()).unwrap().maybe_cw {
|
|
||||||
if to_cw {
|
|
||||||
to -= [0.0, 150.0].into();
|
|
||||||
} else {
|
|
||||||
to += [0.0, 150.0].into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let stroke = 'blk: {
|
|
||||||
if let (Some(source_pos), Some(target_pos)) = (
|
|
||||||
activity.maybe_navcord().map(|navcord|
|
|
||||||
navcord.path
|
|
||||||
.iter()
|
|
||||||
.position(|node| *node == edge.source())).flatten(),
|
|
||||||
activity.maybe_navcord().map(|navcord|
|
|
||||||
navcord.path
|
|
||||||
.iter()
|
|
||||||
.position(|node| *node == edge.target())).flatten(),
|
|
||||||
) {
|
|
||||||
if target_pos == source_pos + 1
|
|
||||||
|| source_pos == target_pos + 1
|
|
||||||
{
|
|
||||||
break 'blk egui::Stroke::new(
|
|
||||||
5.0,
|
|
||||||
egui::Color32::from_rgb(250, 250, 0),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
egui::Stroke::new(1.0, egui::Color32::from_rgb(125, 125, 125))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
painter.paint_edge(from, to, stroke);
|
painter.paint_primitive(&shape, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
for poly in board.layout().layer_poly_nodes(i) {
|
||||||
|
let color = if overlay
|
||||||
|
.selection()
|
||||||
|
.contains_node(board, GenericNode::Compound(poly.into()))
|
||||||
|
{
|
||||||
|
config
|
||||||
|
.colors(ctx)
|
||||||
|
.layers
|
||||||
|
.color(board.layout().rules().layer_layername(i))
|
||||||
|
.highlighted
|
||||||
|
} else {
|
||||||
|
config
|
||||||
|
.colors(ctx)
|
||||||
|
.layers
|
||||||
|
.color(board.layout().rules().layer_layername(i))
|
||||||
|
.normal
|
||||||
|
};
|
||||||
|
|
||||||
|
painter.paint_polygon(
|
||||||
|
&board.layout().poly(poly).shape().polygon,
|
||||||
|
color,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if menu_bar.show_bboxes {
|
if menu_bar.show_ratsnest {
|
||||||
let root_bbox3d = board.layout().drawing().rtree().root().envelope();
|
let graph = overlay.ratsnest().graph();
|
||||||
|
for edge in graph.edge_references() {
|
||||||
|
let from = graph.node_weight(edge.source()).unwrap().pos;
|
||||||
|
let to = graph.node_weight(edge.target()).unwrap().pos;
|
||||||
|
|
||||||
let root_bbox = AABB::<[f64; 2]>::from_corners([root_bbox3d.lower()[0], root_bbox3d.lower()[1]].into(), [root_bbox3d.upper()[0], root_bbox3d.upper()[1]].into());
|
painter.paint_edge(
|
||||||
painter.paint_bbox(root_bbox);
|
from,
|
||||||
}
|
to,
|
||||||
|
egui::Stroke::new(1.0, egui::Color32::from_rgb(90, 90, 200)),
|
||||||
if let Some(activity) = &mut workspace.interactor.maybe_activity() {
|
|
||||||
for ghost in activity.ghosts().iter() {
|
|
||||||
painter.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(navmesh) = activity.maybe_navmesh() {
|
|
||||||
if menu_bar.show_origin_destination {
|
|
||||||
let (origin, destination) = (navmesh.origin(), navmesh.destination());
|
|
||||||
painter.paint_dot(
|
|
||||||
Circle {
|
|
||||||
pos: board.layout().drawing().primitive(origin).shape().center(),
|
|
||||||
r: 150.0,
|
|
||||||
},
|
|
||||||
egui::Color32::from_rgb(255, 255, 100),
|
|
||||||
);
|
|
||||||
painter.paint_dot(
|
|
||||||
Circle {
|
|
||||||
pos: board.layout().drawing().primitive(destination).shape().center(),
|
|
||||||
r: 150.0,
|
|
||||||
},
|
|
||||||
egui::Color32::from_rgb(255, 255, 100),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if menu_bar.show_navmesh {
|
||||||
|
if let Some(activity) = workspace.interactor.maybe_activity() {
|
||||||
|
if let Some(navmesh) = activity.maybe_navmesh() {
|
||||||
|
for edge in navmesh.edge_references() {
|
||||||
|
let mut from = PrimitiveIndex::from(
|
||||||
|
navmesh.node_weight(edge.source()).unwrap().node,
|
||||||
|
)
|
||||||
|
.primitive(board.layout().drawing())
|
||||||
|
.shape()
|
||||||
|
.center();
|
||||||
|
let mut to = PrimitiveIndex::from(
|
||||||
|
navmesh.node_weight(edge.target()).unwrap().node,
|
||||||
|
)
|
||||||
|
.primitive(board.layout().drawing())
|
||||||
|
.shape()
|
||||||
|
.center();
|
||||||
|
|
||||||
|
if let Some(from_cw) =
|
||||||
|
navmesh.node_weight(edge.source()).unwrap().maybe_cw
|
||||||
|
{
|
||||||
|
if from_cw {
|
||||||
|
from -= [0.0, 150.0].into();
|
||||||
|
} else {
|
||||||
|
from += [0.0, 150.0].into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(to_cw) =
|
||||||
|
navmesh.node_weight(edge.target()).unwrap().maybe_cw
|
||||||
|
{
|
||||||
|
if to_cw {
|
||||||
|
to -= [0.0, 150.0].into();
|
||||||
|
} else {
|
||||||
|
to += [0.0, 150.0].into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stroke =
|
||||||
|
'blk: {
|
||||||
|
if let (Some(source_pos), Some(target_pos)) = (
|
||||||
|
activity
|
||||||
|
.maybe_navcord()
|
||||||
|
.map(|navcord| {
|
||||||
|
navcord.path.iter().position(|node| {
|
||||||
|
*node == edge.source()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
activity
|
||||||
|
.maybe_navcord()
|
||||||
|
.map(|navcord| {
|
||||||
|
navcord.path.iter().position(|node| {
|
||||||
|
*node == edge.target()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
) {
|
||||||
|
if target_pos == source_pos + 1
|
||||||
|
|| source_pos == target_pos + 1
|
||||||
|
{
|
||||||
|
break 'blk egui::Stroke::new(
|
||||||
|
5.0,
|
||||||
|
egui::Color32::from_rgb(250, 250, 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
egui::Stroke::new(
|
||||||
|
1.0,
|
||||||
|
egui::Color32::from_rgb(125, 125, 125),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
painter.paint_edge(from, to, stroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if menu_bar.show_bboxes {
|
||||||
|
let root_bbox3d = board.layout().drawing().rtree().root().envelope();
|
||||||
|
|
||||||
|
let root_bbox = AABB::<[f64; 2]>::from_corners(
|
||||||
|
[root_bbox3d.lower()[0], root_bbox3d.lower()[1]].into(),
|
||||||
|
[root_bbox3d.upper()[0], root_bbox3d.upper()[1]].into(),
|
||||||
|
);
|
||||||
|
painter.paint_bbox(root_bbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(activity) = workspace.interactor.maybe_activity() {
|
||||||
|
for ghost in activity.ghosts().iter() {
|
||||||
|
painter
|
||||||
|
.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(navmesh) = activity.maybe_navmesh() {
|
||||||
|
if menu_bar.show_origin_destination {
|
||||||
|
let (origin, destination) =
|
||||||
|
(navmesh.origin(), navmesh.destination());
|
||||||
|
painter.paint_dot(
|
||||||
|
Circle {
|
||||||
|
pos: board
|
||||||
|
.layout()
|
||||||
|
.drawing()
|
||||||
|
.primitive(origin)
|
||||||
|
.shape()
|
||||||
|
.center(),
|
||||||
|
r: 150.0,
|
||||||
|
},
|
||||||
|
egui::Color32::from_rgb(255, 255, 100),
|
||||||
|
);
|
||||||
|
painter.paint_dot(
|
||||||
|
Circle {
|
||||||
|
pos: board
|
||||||
|
.layout()
|
||||||
|
.drawing()
|
||||||
|
.primitive(destination)
|
||||||
|
.shape()
|
||||||
|
.center(),
|
||||||
|
r: 150.0,
|
||||||
|
},
|
||||||
|
egui::Color32::from_rgb(255, 255, 100),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.scheduled_zoom_to_fit {
|
||||||
|
let root_bbox = workspace
|
||||||
|
.interactor
|
||||||
|
.invoker()
|
||||||
|
.autorouter()
|
||||||
|
.board()
|
||||||
|
.layout()
|
||||||
|
.drawing()
|
||||||
|
.rtree()
|
||||||
|
.root()
|
||||||
|
.envelope();
|
||||||
|
|
||||||
|
let root_bbox_width = root_bbox.upper()[0] - root_bbox.lower()[0];
|
||||||
|
let root_bbox_height = root_bbox.upper()[1] - root_bbox.lower()[1];
|
||||||
|
|
||||||
|
self.transform.scaling = 0.8
|
||||||
|
* if root_bbox_width / root_bbox_height
|
||||||
|
>= (viewport_rect.width() as f64)
|
||||||
|
/ (viewport_rect.height() as f64)
|
||||||
|
{
|
||||||
|
viewport_rect.width() / root_bbox_width as f32
|
||||||
|
} else {
|
||||||
|
viewport_rect.height() / root_bbox_height as f32
|
||||||
|
};
|
||||||
|
|
||||||
|
self.transform.translation = egui::Vec2::new(
|
||||||
|
viewport_rect.center()[0] as f32,
|
||||||
|
viewport_rect.center()[1] as f32,
|
||||||
|
) - (self.transform.scaling
|
||||||
|
* egui::Pos2::new(
|
||||||
|
root_bbox.center()[0] as f32,
|
||||||
|
-root_bbox.center()[1] as f32,
|
||||||
|
))
|
||||||
|
.to_vec2();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scheduled_zoom_to_fit = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.scheduled_zoom_to_fit {
|
viewport_rect
|
||||||
let root_bbox = workspace.interactor.invoker()
|
})
|
||||||
.autorouter()
|
|
||||||
.board()
|
|
||||||
.layout()
|
|
||||||
.drawing()
|
|
||||||
.rtree()
|
|
||||||
.root()
|
|
||||||
.envelope();
|
|
||||||
|
|
||||||
let root_bbox_width = root_bbox.upper()[0] - root_bbox.lower()[0];
|
|
||||||
let root_bbox_height = root_bbox.upper()[1] - root_bbox.lower()[1];
|
|
||||||
|
|
||||||
self.transform.scaling = 0.8 * if root_bbox_width / root_bbox_height
|
|
||||||
>= (viewport_rect.width() as f64) / (viewport_rect.height() as f64)
|
|
||||||
{
|
|
||||||
viewport_rect.width() / root_bbox_width as f32
|
|
||||||
} else {
|
|
||||||
viewport_rect.height() / root_bbox_height as f32
|
|
||||||
};
|
|
||||||
|
|
||||||
self.transform.translation = egui::Vec2::new(
|
|
||||||
viewport_rect.center()[0] as f32,
|
|
||||||
viewport_rect.center()[1] as f32,
|
|
||||||
) - (self.transform.scaling
|
|
||||||
* egui::Pos2::new(root_bbox.center()[0] as f32, -root_bbox.center()[1] as f32))
|
|
||||||
.to_vec2();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scheduled_zoom_to_fit = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
viewport_rect
|
|
||||||
})
|
})
|
||||||
}).inner.inner
|
.inner
|
||||||
|
.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ tr-menu-edit = Edit
|
||||||
tr-menu-edit-undo = Undo
|
tr-menu-edit-undo = Undo
|
||||||
tr-menu-edit-redo = Redo
|
tr-menu-edit-redo = Redo
|
||||||
tr-menu-edit-abort = Abort
|
tr-menu-edit-abort = Abort
|
||||||
|
tr-menu-edit-select-all = Select All
|
||||||
|
tr-menu-edit-unselect-all = Unselect All
|
||||||
tr-menu-edit-remove-bands = Remove Bands
|
tr-menu-edit-remove-bands = Remove Bands
|
||||||
|
|
||||||
tr-menu-view = View
|
tr-menu-view = View
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use rstar::AABB;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -145,6 +146,37 @@ impl Selection {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select_all_in_bbox(
|
||||||
|
&mut self,
|
||||||
|
board: &Board<impl AccessMesadata>,
|
||||||
|
aabb: &AABB<[f64; 2]>,
|
||||||
|
active_layer: usize,
|
||||||
|
) {
|
||||||
|
use rstar::Envelope;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +198,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