mirror of https://codeberg.org/topola/topola.git
Compare commits
9 Commits
31107559c8
...
623dc967ca
| Author | SHA1 | Date |
|---|---|---|
|
|
623dc967ca | |
|
|
5a3a870a13 | |
|
|
15a7bc65f1 | |
|
|
51005bb97c | |
|
|
739bdafea2 | |
|
|
61c0134db4 | |
|
|
1b4bb49a89 | |
|
|
d9a4c7a11f | |
|
|
eef69694fd |
|
|
@ -25,8 +25,9 @@ use topola::{
|
|||
|
||||
pub fn load_design(filename: &str) -> Autorouter<SpecctraMesadata> {
|
||||
let design_file = File::open(filename).unwrap();
|
||||
let design_bufread = BufReader::new(design_file);
|
||||
let design = SpecctraDesign::load(design_bufread).unwrap();
|
||||
let design_bufreader = BufReader::new(design_file);
|
||||
let design = SpecctraDesign::load(design_bufreader).unwrap();
|
||||
|
||||
Autorouter::new(design.make_board(&mut BoardEdit::new())).unwrap()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
|
|||
|
||||
[dependencies]
|
||||
derive-getters.workspace = true
|
||||
egui = "0.33"
|
||||
egui = { version = "0.33", features = ["serde"] }
|
||||
eframe = { version = "0.33", default-features = false, features = [
|
||||
#"accesskit", # Make egui compatible with screen readers. NOTE: adds a lot of dependencies.
|
||||
"default_fonts", # Embed the default egui fonts.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use crate::{viewport::Viewport, workspace::Workspace};
|
||||
use topola::LayerId;
|
||||
use topola::primitives::{Joint, Polygon, Segment};
|
||||
use topola::primitives::{Joint, Polygon, Segment, Via};
|
||||
|
||||
pub struct Display {}
|
||||
|
||||
|
|
@ -46,6 +46,8 @@ impl Display {
|
|||
|
||||
for joint_id in layout.layer_joints(layer) {
|
||||
let joint = layout.joint(joint_id);
|
||||
let pin_selected = board.pins_contain_joint(&workspace.selection.pins, joint_id);
|
||||
let net_selected = board.nets_contain_joint(&workspace.selection.nets, joint_id);
|
||||
self.paint_joint(
|
||||
ctx,
|
||||
ui,
|
||||
|
|
@ -54,13 +56,17 @@ impl Display {
|
|||
workspace.appearance_panel.layer_color(
|
||||
ctx,
|
||||
board.layer_desc(joint.spec.layer),
|
||||
board.pin_selection_contains_joint(&workspace.selection.pins, joint_id),
|
||||
pin_selected || (joint.spec.pin.is_none() && net_selected),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
for segment_id in layout.layer_segments(layer) {
|
||||
let segment = layout.segment(segment_id);
|
||||
let pin_selected =
|
||||
board.pins_contain_segment(&workspace.selection.pins, segment_id);
|
||||
let net_selected =
|
||||
board.nets_contain_segment(&workspace.selection.nets, segment_id);
|
||||
self.paint_segment(
|
||||
ctx,
|
||||
ui,
|
||||
|
|
@ -69,15 +75,34 @@ impl Display {
|
|||
workspace.appearance_panel.layer_color(
|
||||
ctx,
|
||||
board.layer_desc(segment.layer),
|
||||
board.pin_selection_contains_segment(&workspace.selection.pins, segment_id),
|
||||
pin_selected || (segment.spec.pin.is_none() && net_selected),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Vias.
|
||||
for via_id in layout.layer_vias(layer) {
|
||||
let via = layout.via(via_id);
|
||||
let pin_selected = board.pins_contain_via(&workspace.selection.pins, via_id);
|
||||
let net_selected = board.nets_contain_via(&workspace.selection.nets, via_id);
|
||||
self.paint_via(
|
||||
ctx,
|
||||
ui,
|
||||
viewport,
|
||||
via,
|
||||
workspace.appearance_panel.layer_color(
|
||||
ctx,
|
||||
board.layer_desc(layer),
|
||||
pin_selected || (via.spec.pin.is_none() && net_selected),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
for polygon_id in layout.layer_polygons(layer) {
|
||||
let polygon = layout.polygon(polygon_id);
|
||||
let pin_selected =
|
||||
board.pins_contain_polygon(&workspace.selection.pins, polygon_id);
|
||||
let net_selected =
|
||||
board.nets_contain_polygon(&workspace.selection.nets, polygon_id);
|
||||
self.paint_polygon(
|
||||
ctx,
|
||||
ui,
|
||||
|
|
@ -86,7 +111,7 @@ impl Display {
|
|||
workspace.appearance_panel.layer_color(
|
||||
ctx,
|
||||
board.layer_desc(polygon.layer),
|
||||
board.pin_selection_contains_polygon(&workspace.selection.pins, polygon_id),
|
||||
pin_selected || (polygon.pin.is_none() && net_selected),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -137,6 +162,21 @@ impl Display {
|
|||
);
|
||||
}
|
||||
|
||||
fn paint_via(
|
||||
&mut self,
|
||||
ctx: &egui::Context,
|
||||
ui: &egui::Ui,
|
||||
viewport: &Viewport,
|
||||
via: &Via,
|
||||
color: egui::Color32,
|
||||
) {
|
||||
ui.painter().circle_filled(
|
||||
egui::pos2(via.position.x as f32, via.position.y as f32),
|
||||
via.spec.radius as f32,
|
||||
color,
|
||||
);
|
||||
}
|
||||
|
||||
fn paint_polygon(
|
||||
&mut self,
|
||||
ctx: &egui::Context,
|
||||
|
|
@ -206,7 +246,20 @@ impl Display {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: vias.
|
||||
for via_id in layout.layer_vias(layer) {
|
||||
let via = layout.via(via_id);
|
||||
let bbox = via.bbox();
|
||||
|
||||
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),
|
||||
},
|
||||
egui::CornerRadius::ZERO,
|
||||
egui::Stroke::new(5.0, egui::Color32::GRAY),
|
||||
egui::StrokeKind::Middle,
|
||||
);
|
||||
}
|
||||
|
||||
for polygon_id in layout.layer_polygons(layer) {
|
||||
let polygon = layout.polygon(polygon_id);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::collections::BTreeMap;
|
|||
|
||||
use egui::{Context, Grid, ScrollArea, SidePanel, widget_text::WidgetText};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use topola::{Board, LayerDesc, LayerId, LayerTier, LayerType};
|
||||
use topola::{Board, LayerDesc, LayerId, LayerSide, LayerType};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Colors {
|
||||
|
|
@ -59,8 +59,8 @@ impl LayersPanel {
|
|||
};
|
||||
|
||||
let color = match layer_desc.typ {
|
||||
LayerType::Copper => match layer_desc.tier {
|
||||
LayerTier::Top => Some((
|
||||
LayerType::Copper => match layer_desc.side {
|
||||
LayerSide::Top => Some((
|
||||
LayerColors {
|
||||
normal: egui::Color32::from_rgb(255, 52, 52),
|
||||
highlighted: egui::Color32::from_rgb(255, 100, 100),
|
||||
|
|
@ -70,7 +70,7 @@ impl LayersPanel {
|
|||
highlighted: egui::Color32::from_rgb(255, 52, 52),
|
||||
},
|
||||
)),
|
||||
LayerTier::Bottom => Some((
|
||||
LayerSide::Bottom => Some((
|
||||
LayerColors {
|
||||
normal: egui::Color32::from_rgb(52, 52, 255),
|
||||
highlighted: egui::Color32::from_rgb(100, 100, 255),
|
||||
|
|
@ -80,7 +80,7 @@ impl LayersPanel {
|
|||
highlighted: egui::Color32::from_rgb(52, 52, 255),
|
||||
},
|
||||
)),
|
||||
LayerTier::Inner => (layer_desc.index % 2 == 0)
|
||||
LayerSide::Inner => (layer_desc.index % 2 == 0)
|
||||
.then_some((
|
||||
LayerColors {
|
||||
normal: egui::Color32::from_rgb(127, 200, 127),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use egui::Pos2;
|
||||
use topola::Vector2;
|
||||
use topola::{InteractiveInput, SelectionCombineMode, SelectionInteractor, Vector2};
|
||||
|
||||
use crate::{display::Display, workspace::Workspace};
|
||||
|
||||
|
|
@ -11,6 +11,7 @@ pub struct Viewport {
|
|||
pub scene_rect: egui::Rect,
|
||||
pub ref_scene_rect: egui::Rect,
|
||||
pub scheduled_zoom_to_fit: bool,
|
||||
selection_interactor: Option<SelectionInteractor>,
|
||||
}
|
||||
|
||||
impl Viewport {
|
||||
|
|
@ -19,6 +20,7 @@ impl Viewport {
|
|||
scene_rect: egui::Rect::from_min_max(egui::pos2(-1.0, -1.0), egui::pos2(1.0, 1.0)),
|
||||
ref_scene_rect: egui::Rect::from_min_max(egui::pos2(-1.0, -1.0), egui::pos2(1.0, 1.0)),
|
||||
scheduled_zoom_to_fit: false,
|
||||
selection_interactor: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -34,7 +36,7 @@ impl Viewport {
|
|||
|
||||
let response = egui::Scene::new()
|
||||
.zoom_range(zoom_range.clone())
|
||||
//.sense(egui::Sense::hover())
|
||||
.drag_pan_buttons(egui::DragPanButtons::MIDDLE)
|
||||
.show(ui, &mut scene_rect, |ui| {
|
||||
if let Some(ref workspace) = workspace {
|
||||
let mut display = Display::new();
|
||||
|
|
@ -49,28 +51,60 @@ impl Viewport {
|
|||
Self::fit_to_rect_in_scene(viewport_rect, scene_rect, zoom_range.into());
|
||||
|
||||
if let Some(workspace) = workspace {
|
||||
if let Some(pointer_viewport_pos) = ctx.input(|i| i.pointer.interact_pos()) {
|
||||
let pointer_scene_pos = scene_to_viewport.inverse() * pointer_viewport_pos;
|
||||
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
self.selection_interactor = None;
|
||||
}
|
||||
|
||||
if response.clicked() {
|
||||
if let Some(pin_selector) = workspace
|
||||
.autorouter
|
||||
.router()
|
||||
.navmesher_board()
|
||||
.board()
|
||||
.point_pin_selector(
|
||||
let primary_pressed =
|
||||
ctx.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary));
|
||||
let primary_down =
|
||||
ctx.input(|i| i.pointer.button_down(egui::PointerButton::Primary));
|
||||
let primary_released =
|
||||
ctx.input(|i| i.pointer.button_released(egui::PointerButton::Primary));
|
||||
let mut maybe_pointer_on_scene: Option<Vector2<i64>> = None;
|
||||
|
||||
if let Some(pointer_viewport_pos) = ctx.input(|i| i.pointer.interact_pos()) {
|
||||
let pointer_on_scene_pos =
|
||||
scene_to_viewport.inverse() * pointer_viewport_pos;
|
||||
let pointer_on_scene = Vector2::new(
|
||||
pointer_on_scene_pos.x as i64,
|
||||
pointer_on_scene_pos.y as i64,
|
||||
);
|
||||
maybe_pointer_on_scene = Some(pointer_on_scene);
|
||||
|
||||
if primary_pressed && response.hovered() {
|
||||
self.selection_interactor = Some(SelectionInteractor::new(
|
||||
pointer_on_scene,
|
||||
workspace.selection.clone(),
|
||||
SelectionCombineMode::Replace,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(interactor) = self.selection_interactor.as_mut() {
|
||||
if primary_down {
|
||||
let _ = interactor.update(
|
||||
workspace.autorouter.router().navmesher_board().board(),
|
||||
workspace.appearance_panel.active,
|
||||
Vector2::new(
|
||||
pointer_scene_pos.x as i64,
|
||||
pointer_scene_pos.y as i64,
|
||||
),
|
||||
)
|
||||
{
|
||||
workspace.selection.pins.toggle(pin_selector);
|
||||
InteractiveInput::new(pointer_on_scene, false, false),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if primary_released {
|
||||
if let Some(mut interactor) = self.selection_interactor.take() {
|
||||
let pointer_for_scene =
|
||||
maybe_pointer_on_scene.unwrap_or(*interactor.origin());
|
||||
let _ = interactor.update(
|
||||
workspace.autorouter.router().navmesher_board().board(),
|
||||
workspace.appearance_panel.active,
|
||||
InteractiveInput::new(pointer_for_scene, true, false),
|
||||
);
|
||||
|
||||
workspace.selection = interactor.selection().clone();
|
||||
}
|
||||
}
|
||||
|
||||
self.zoom_to_fit_if_scheduled(workspace);
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use crate::{
|
||||
board::Board,
|
||||
primitives::{JointId, PolygonId, SegmentId, ViaId},
|
||||
};
|
||||
|
||||
impl Board {
|
||||
pub fn delete_joint(&mut self, joint_id: JointId) {
|
||||
self.layout.delete_joint(joint_id);
|
||||
}
|
||||
|
||||
pub fn delete_segment(&mut self, segment_id: SegmentId) {
|
||||
self.layout.delete_segment(segment_id);
|
||||
}
|
||||
|
||||
pub fn delete_via(&mut self, via_id: ViaId) {
|
||||
self.layout.delete_via(via_id);
|
||||
}
|
||||
|
||||
pub fn delete_polygon(&mut self, polygon_id: PolygonId) {
|
||||
self.layout.delete_polygon(polygon_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use crate::{
|
||||
board::Board,
|
||||
layout::compounds::ComponentId,
|
||||
primitives::{
|
||||
JointId, JointSpec, Polygon, PolygonId, Segment, SegmentId, SegmentSpec, Via, ViaId,
|
||||
ViaSpec,
|
||||
},
|
||||
};
|
||||
|
||||
impl Board {
|
||||
pub fn insert_component(&mut self) -> ComponentId {
|
||||
self.layout.insert_component()
|
||||
}
|
||||
|
||||
pub fn insert_joint(&mut self, spec: JointSpec) -> JointId {
|
||||
self.layout.insert_joint(spec)
|
||||
}
|
||||
|
||||
pub fn insert_segment(&mut self, spec: SegmentSpec) -> SegmentId {
|
||||
self.layout.insert_segment(spec)
|
||||
}
|
||||
|
||||
pub fn insert_segment_raw(&mut self, segment: Segment) -> SegmentId {
|
||||
self.layout.insert_segment_raw(segment)
|
||||
}
|
||||
|
||||
pub fn insert_via(&mut self, spec: ViaSpec) -> ViaId {
|
||||
self.layout.insert_via(spec)
|
||||
}
|
||||
|
||||
pub fn insert_via_raw(&mut self, via: Via) -> ViaId {
|
||||
self.layout.insert_via_raw(via)
|
||||
}
|
||||
|
||||
pub fn insert_polygon(&mut self, polygon: Polygon) -> PolygonId {
|
||||
self.layout.insert_polygon(polygon)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use derive_getters::Getters;
|
||||
use derive_more::Constructor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
Rect3, Vector2, Vector3,
|
||||
board::{
|
||||
Board,
|
||||
interactors::{InteractiveInput, SelectionCombineMode, SelectionContainMode},
|
||||
selections::PersistableSelection,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct DragSelectionOptions {
|
||||
combine: SelectionCombineMode,
|
||||
contain: SelectionContainMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Getters, Ord, PartialEq, PartialOrd)]
|
||||
pub struct DragSelectionInteractor {
|
||||
origin: Vector2<i64>,
|
||||
original_selection: PersistableSelection,
|
||||
selection: PersistableSelection,
|
||||
options: DragSelectionOptions,
|
||||
}
|
||||
|
||||
impl DragSelectionInteractor {
|
||||
pub fn new(
|
||||
origin: Vector2<i64>,
|
||||
original_selection: PersistableSelection,
|
||||
options: DragSelectionOptions,
|
||||
) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
original_selection,
|
||||
selection: PersistableSelection::new(),
|
||||
options,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
board: &Board,
|
||||
input: InteractiveInput,
|
||||
) -> Option<PersistableSelection> {
|
||||
if input.cancel {
|
||||
self.selection = self.original_selection.clone();
|
||||
return Some(self.selection.clone());
|
||||
}
|
||||
|
||||
self.selection = PersistableSelection::new();
|
||||
|
||||
for layer_index in 0..*board.layout().layer_count() {
|
||||
let rect = Rect3::new(
|
||||
Vector3::new(self.origin.x, self.origin.y, layer_index as i64),
|
||||
Vector3::new(input.pointer.x, input.pointer.y, layer_index as i64),
|
||||
);
|
||||
|
||||
match self.options.contain {
|
||||
SelectionContainMode::Crossing => {
|
||||
self.selection
|
||||
.components
|
||||
.add(board.locate_components_intersecting_rect(rect));
|
||||
}
|
||||
SelectionContainMode::Window => {
|
||||
self.selection
|
||||
.components
|
||||
.add(board.locate_components_inside_rect(rect));
|
||||
}
|
||||
}
|
||||
|
||||
match self.options.contain {
|
||||
SelectionContainMode::Crossing => {
|
||||
self.selection
|
||||
.nets
|
||||
.add(board.locate_nets_intersecting_rect(rect));
|
||||
}
|
||||
SelectionContainMode::Window => {
|
||||
self.selection.nets.add(board.locate_nets_inside_rect(rect));
|
||||
}
|
||||
}
|
||||
|
||||
match self.options.contain {
|
||||
SelectionContainMode::Crossing => {
|
||||
self.selection
|
||||
.pins
|
||||
.add(board.locate_pins_intersecting_rect(rect));
|
||||
}
|
||||
SelectionContainMode::Window => {
|
||||
self.selection.pins.add(board.locate_pins_inside_rect(rect));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: There's no need to clone on each update.
|
||||
let mut combined_selection = self.original_selection.clone();
|
||||
match self.options.combine {
|
||||
SelectionCombineMode::Replace => {
|
||||
combined_selection.components = self.selection.components.clone();
|
||||
combined_selection.nets = self.selection.nets.clone();
|
||||
combined_selection.pins = self.selection.pins.clone();
|
||||
}
|
||||
SelectionCombineMode::Additive => {
|
||||
combined_selection
|
||||
.pins
|
||||
.add(self.selection.pins.0.iter().cloned());
|
||||
combined_selection
|
||||
.nets
|
||||
.add(self.selection.nets.0.iter().cloned());
|
||||
combined_selection
|
||||
.components
|
||||
.add(self.selection.components.0.iter().cloned());
|
||||
}
|
||||
SelectionCombineMode::Subtractive => {
|
||||
combined_selection
|
||||
.pins
|
||||
.sub(self.selection.pins.0.iter().cloned());
|
||||
combined_selection
|
||||
.nets
|
||||
.sub(self.selection.nets.0.iter().cloned());
|
||||
combined_selection
|
||||
.components
|
||||
.sub(self.selection.components.0.iter().cloned());
|
||||
}
|
||||
SelectionCombineMode::Toggle => {
|
||||
combined_selection
|
||||
.components
|
||||
.xor(self.selection.components.0.iter().cloned());
|
||||
combined_selection
|
||||
.nets
|
||||
.xor(self.selection.nets.0.iter().cloned());
|
||||
combined_selection
|
||||
.pins
|
||||
.xor(self.selection.pins.0.iter().cloned());
|
||||
}
|
||||
}
|
||||
|
||||
self.selection = combined_selection;
|
||||
Some(self.selection.clone())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
mod drag_selection;
|
||||
mod selection;
|
||||
|
||||
pub use drag_selection::{DragSelectionInteractor, DragSelectionOptions};
|
||||
pub use selection::SelectionInteractor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Vector2;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct InteractiveInput {
|
||||
pointer: Vector2<i64>,
|
||||
released: bool,
|
||||
cancel: bool,
|
||||
}
|
||||
|
||||
impl InteractiveInput {
|
||||
pub fn new(pointer: Vector2<i64>, released: bool, cancel: bool) -> Self {
|
||||
Self {
|
||||
pointer,
|
||||
released,
|
||||
cancel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum SelectionCombineMode {
|
||||
Replace,
|
||||
Additive,
|
||||
Subtractive,
|
||||
Toggle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum SelectionContainMode {
|
||||
Crossing,
|
||||
Window,
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use derive_getters::Getters;
|
||||
|
||||
use crate::{
|
||||
Vector2, Vector3,
|
||||
board::{
|
||||
Board,
|
||||
interactors::{
|
||||
DragSelectionInteractor, DragSelectionOptions, InteractiveInput, SelectionCombineMode,
|
||||
SelectionContainMode,
|
||||
},
|
||||
selections::PersistableSelection,
|
||||
},
|
||||
layout::LayerId,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Eq, Getters, Ord, PartialEq, PartialOrd)]
|
||||
pub struct SelectionInteractor {
|
||||
origin: Vector2<i64>,
|
||||
original_selection: PersistableSelection,
|
||||
selection: PersistableSelection,
|
||||
combine: SelectionCombineMode,
|
||||
}
|
||||
|
||||
impl SelectionInteractor {
|
||||
pub fn new(
|
||||
origin: Vector2<i64>,
|
||||
original_selection: PersistableSelection,
|
||||
combine: SelectionCombineMode,
|
||||
) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
original_selection,
|
||||
selection: PersistableSelection::new(),
|
||||
combine,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
board: &Board,
|
||||
layer: LayerId,
|
||||
input: InteractiveInput,
|
||||
) -> Option<PersistableSelection> {
|
||||
if input.cancel {
|
||||
self.selection = self.original_selection.clone();
|
||||
return Some(self.selection.clone());
|
||||
}
|
||||
|
||||
if input.released && input.pointer == self.origin {
|
||||
let mut selection = self.original_selection.clone();
|
||||
|
||||
// Pins have intentional precedence over nets and components.
|
||||
if let Some(pin_selector) = board.locate_pin_at_point(Vector3::new(
|
||||
input.pointer.x,
|
||||
input.pointer.y,
|
||||
layer.index() as i64,
|
||||
)) {
|
||||
selection.pins.xor(std::iter::once(pin_selector));
|
||||
} else if let Some(net_selector) = board.locate_net_at_point(Vector3::new(
|
||||
input.pointer.x,
|
||||
input.pointer.y,
|
||||
layer.index() as i64,
|
||||
)) {
|
||||
selection.nets.xor(std::iter::once(net_selector));
|
||||
} else if let Some(component_selector) = board.locate_component_at_point(Vector3::new(
|
||||
input.pointer.x,
|
||||
input.pointer.y,
|
||||
layer.index() as i64,
|
||||
)) {
|
||||
selection
|
||||
.components
|
||||
.xor(std::iter::once(component_selector));
|
||||
}
|
||||
|
||||
self.selection = selection.clone();
|
||||
return Some(selection);
|
||||
}
|
||||
|
||||
let contain = if input.pointer.x >= self.origin.x {
|
||||
SelectionContainMode::Window
|
||||
} else {
|
||||
SelectionContainMode::Crossing
|
||||
};
|
||||
|
||||
let options = DragSelectionOptions::new(self.combine.clone(), contain);
|
||||
let mut drag_selection_interactor =
|
||||
DragSelectionInteractor::new(self.origin, self.original_selection.clone(), options);
|
||||
let selection = drag_selection_interactor.update(board, input)?;
|
||||
|
||||
self.selection = selection.clone();
|
||||
Some(selection)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ pub enum LayerType {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub enum LayerTier {
|
||||
pub enum LayerSide {
|
||||
Top,
|
||||
Inner,
|
||||
Bottom,
|
||||
|
|
@ -23,22 +23,22 @@ pub enum LayerTier {
|
|||
#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct LayerDesc {
|
||||
pub typ: LayerType,
|
||||
pub tier: LayerTier,
|
||||
pub side: LayerSide,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl Display for LayerDesc {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self.typ {
|
||||
LayerType::Copper => match self.tier {
|
||||
LayerTier::Top => write!(f, "F.Cu"),
|
||||
LayerTier::Bottom => write!(f, "B.Cu"),
|
||||
LayerTier::Inner => write!(f, "In{}.Cu", self.index.saturating_sub(1)),
|
||||
LayerType::Copper => match self.side {
|
||||
LayerSide::Top => write!(f, "F.Cu"),
|
||||
LayerSide::Bottom => write!(f, "B.Cu"),
|
||||
LayerSide::Inner => write!(f, "In{}.Cu", self.index.saturating_sub(1)),
|
||||
},
|
||||
LayerType::Outline => match self.tier {
|
||||
LayerTier::Top => write!(f, "outlines.top"),
|
||||
LayerTier::Bottom => write!(f, "outlines.bottom"),
|
||||
LayerTier::Inner => write!(f, "outlines.{}", self.index),
|
||||
LayerType::Outline => match self.side {
|
||||
LayerSide::Top => write!(f, "F.Outl"),
|
||||
LayerSide::Bottom => write!(f, "B.Outl"),
|
||||
LayerSide::Inner => write!(f, "In{}.Outl", self.index),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,204 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::{
|
||||
Rect3, Vector3,
|
||||
board::Board,
|
||||
selections::{ComponentSelector, NetSelector, PinSelector},
|
||||
};
|
||||
|
||||
impl Board {
|
||||
pub fn locate_component_at_point(&self, point: Vector3<i64>) -> Option<ComponentSelector> {
|
||||
if let Some(joint_id) = self.layout.locate_joints_at_point(point).next() {
|
||||
return self.joint_component_selector(joint_id);
|
||||
}
|
||||
|
||||
if let Some(segment_id) = self.layout.locate_segments_at_point(point).next() {
|
||||
return self.segment_component_selector(segment_id);
|
||||
}
|
||||
|
||||
if let Some(via_id) = self.layout.locate_vias_at_point(point).next() {
|
||||
return self.via_component_selector(via_id);
|
||||
}
|
||||
|
||||
if let Some(polygon_id) = self.layout.locate_polygons_at_point(point).next() {
|
||||
return self.polygon_component_selector(polygon_id);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn locate_components_intersecting_rect(
|
||||
&self,
|
||||
rect: Rect3<i64>,
|
||||
) -> impl Iterator<Item = ComponentSelector> + '_ {
|
||||
self.layout
|
||||
.locate_joints_intersecting_rect(rect)
|
||||
.filter_map(|joint_id| self.joint_component_selector(joint_id))
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_segments_intersecting_rect(rect)
|
||||
.filter_map(|segment_id| self.segment_component_selector(segment_id)),
|
||||
)
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_vias_intersecting_rect(rect)
|
||||
.filter_map(|via_id| self.via_component_selector(via_id)),
|
||||
)
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_polygons_intersecting_rect(rect)
|
||||
.filter_map(|polygon_id| self.polygon_component_selector(polygon_id)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn locate_components_inside_rect(
|
||||
&self,
|
||||
rect: Rect3<i64>,
|
||||
) -> impl Iterator<Item = ComponentSelector> + '_ {
|
||||
self.layout
|
||||
.locate_joints_inside_rect(rect)
|
||||
.filter_map(|joint_id| self.joint_component_selector(joint_id))
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_segments_inside_rect(rect)
|
||||
.filter_map(|segment_id| self.segment_component_selector(segment_id)),
|
||||
)
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_vias_inside_rect(rect)
|
||||
.filter_map(|via_id| self.via_component_selector(via_id)),
|
||||
)
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_polygons_inside_rect(rect)
|
||||
.filter_map(|polygon_id| self.polygon_component_selector(polygon_id)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn locate_net_at_point(&self, point: Vector3<i64>) -> Option<NetSelector> {
|
||||
if let Some(joint_id) = self.layout.locate_joints_at_point(point).next() {
|
||||
return self.joint_net_selector(joint_id);
|
||||
}
|
||||
|
||||
if let Some(segment_id) = self.layout.locate_segments_at_point(point).next() {
|
||||
return self.segment_net_selector(segment_id);
|
||||
}
|
||||
|
||||
if let Some(via_id) = self.layout.locate_vias_at_point(point).next() {
|
||||
return self.via_net_selector(via_id);
|
||||
}
|
||||
|
||||
if let Some(polygon_id) = self.layout.locate_polygons_at_point(point).next() {
|
||||
return self.polygon_net_selector(polygon_id);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn locate_nets_intersecting_rect(
|
||||
&self,
|
||||
rect: Rect3<i64>,
|
||||
) -> impl Iterator<Item = NetSelector> + '_ {
|
||||
let mut selectors = BTreeSet::new();
|
||||
|
||||
for net_id in self.layout.locate_nets_intersecting_rect(rect) {
|
||||
let Some(net_name) = self.net_name(net_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
selectors.insert(NetSelector::new(net_name.to_string()));
|
||||
}
|
||||
|
||||
selectors.into_iter()
|
||||
}
|
||||
|
||||
pub fn locate_nets_inside_rect(
|
||||
&self,
|
||||
rect: Rect3<i64>,
|
||||
) -> impl Iterator<Item = NetSelector> + '_ {
|
||||
let mut selectors = BTreeSet::new();
|
||||
|
||||
for net_id in self.layout.locate_nets_inside_rect(rect) {
|
||||
let Some(net_name) = self.net_name(net_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
selectors.insert(NetSelector::new(net_name.to_string()));
|
||||
}
|
||||
|
||||
selectors.into_iter()
|
||||
}
|
||||
|
||||
pub fn locate_pin_at_point(&self, point: Vector3<i64>) -> Option<PinSelector> {
|
||||
// Polygons have intentional precedence for pins.
|
||||
if let Some(polygon_id) = self.layout.locate_polygons_at_point(point).next() {
|
||||
return self.polygon_pin_selector(polygon_id);
|
||||
}
|
||||
|
||||
if let Some(joint_id) = self.layout.locate_joints_at_point(point).next() {
|
||||
return self.joint_pin_selector(joint_id);
|
||||
}
|
||||
|
||||
if let Some(segment_id) = self.layout.locate_segments_at_point(point).next() {
|
||||
return self.segment_pin_selector(segment_id);
|
||||
}
|
||||
|
||||
if let Some(via_id) = self.layout.locate_vias_at_point(point).next() {
|
||||
return self.via_pin_selector(via_id);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn locate_pins_intersecting_rect(
|
||||
&self,
|
||||
rect: Rect3<i64>,
|
||||
) -> impl Iterator<Item = PinSelector> + '_ {
|
||||
self.layout
|
||||
.locate_joints_intersecting_rect(rect)
|
||||
.filter_map(|joint_id| self.joint_pin_selector(joint_id))
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_segments_intersecting_rect(rect)
|
||||
.filter_map(|segment_id| self.segment_pin_selector(segment_id)),
|
||||
)
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_vias_intersecting_rect(rect)
|
||||
.filter_map(|via_id| self.via_pin_selector(via_id)),
|
||||
)
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_polygons_intersecting_rect(rect)
|
||||
.filter_map(|polygon_id| self.polygon_pin_selector(polygon_id)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn locate_pins_inside_rect(
|
||||
&self,
|
||||
rect: Rect3<i64>,
|
||||
) -> impl Iterator<Item = PinSelector> + '_ {
|
||||
self.layout
|
||||
.locate_joints_inside_rect(rect)
|
||||
.filter_map(|joint_id| self.joint_pin_selector(joint_id))
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_segments_inside_rect(rect)
|
||||
.filter_map(|segment_id| self.segment_pin_selector(segment_id)),
|
||||
)
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_vias_inside_rect(rect)
|
||||
.filter_map(|via_id| self.via_pin_selector(via_id)),
|
||||
)
|
||||
.chain(
|
||||
self.layout
|
||||
.locate_polygons_inside_rect(rect)
|
||||
.filter_map(|polygon_id| self.polygon_pin_selector(polygon_id)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,17 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
mod delete;
|
||||
mod insert;
|
||||
pub mod interactors;
|
||||
mod layer;
|
||||
mod locate;
|
||||
mod resolve;
|
||||
mod select;
|
||||
pub mod selections;
|
||||
mod transforms;
|
||||
|
||||
pub use crate::board::layer::{LayerDesc, LayerTier, LayerType};
|
||||
pub use crate::board::layer::{LayerDesc, LayerSide, LayerType};
|
||||
|
||||
use bidimap::BiBTreeMap;
|
||||
use derive_getters::Getters;
|
||||
|
|
@ -18,10 +22,6 @@ use crate::{
|
|||
layout::{
|
||||
LayerId, Layout, LayoutHalfDelta,
|
||||
compounds::{ComponentId, NetId, PinId},
|
||||
primitives::{
|
||||
JointId, JointSpec, Polygon, PolygonId, Segment, SegmentId, SegmentSpec, Via, ViaId,
|
||||
ViaSpec,
|
||||
},
|
||||
},
|
||||
math::Vector2,
|
||||
};
|
||||
|
|
@ -72,7 +72,7 @@ impl Board {
|
|||
return *component;
|
||||
};
|
||||
|
||||
let component_id = self.layout.add_component();
|
||||
let component_id = self.layout.insert_component();
|
||||
self.component_names.insert(component_id, component_name);
|
||||
|
||||
component_id
|
||||
|
|
@ -83,40 +83,12 @@ impl Board {
|
|||
return *pin;
|
||||
};
|
||||
|
||||
let pin_id = self.layout.add_pin();
|
||||
let pin_id = self.layout.insert_pin();
|
||||
self.pin_names.insert(pin_id, pin_name);
|
||||
|
||||
pin_id
|
||||
}
|
||||
|
||||
pub fn add_component(&mut self) -> ComponentId {
|
||||
self.layout.add_component()
|
||||
}
|
||||
|
||||
pub fn add_joint(&mut self, spec: JointSpec) -> JointId {
|
||||
self.layout.add_joint(spec)
|
||||
}
|
||||
|
||||
pub fn add_segment(&mut self, spec: SegmentSpec) -> SegmentId {
|
||||
self.layout.add_segment(spec)
|
||||
}
|
||||
|
||||
pub fn add_segment_raw(&mut self, segment: Segment) -> SegmentId {
|
||||
self.layout.add_segment_raw(segment)
|
||||
}
|
||||
|
||||
pub fn add_via(&mut self, spec: ViaSpec) -> ViaId {
|
||||
self.layout.add_via(spec)
|
||||
}
|
||||
|
||||
pub fn add_via_raw(&mut self, via: Via) -> ViaId {
|
||||
self.layout.add_via_raw(via)
|
||||
}
|
||||
|
||||
pub fn add_polygon(&mut self, polygon: Polygon) -> PolygonId {
|
||||
self.layout.add_polygon(polygon)
|
||||
}
|
||||
|
||||
pub fn component_name(&self, id: ComponentId) -> Option<&str> {
|
||||
self.component_names.get_by_left(&id).map(String::as_str)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,18 +5,16 @@
|
|||
use crate::{
|
||||
board::{
|
||||
Board,
|
||||
selections::{ComponentSelection, ComponentSelector, PinSelection, PinSelector},
|
||||
selections::{
|
||||
ComponentSelection, ComponentSelector, NetSelection, NetSelector, PinSelection,
|
||||
PinSelector,
|
||||
},
|
||||
},
|
||||
layout::LayerId,
|
||||
math::Vector2,
|
||||
primitives::{JointId, PolygonId, SegmentId},
|
||||
primitives::{JointId, PolygonId, SegmentId, ViaId},
|
||||
};
|
||||
|
||||
impl Board {
|
||||
pub fn pin_selection_to_component_selection(
|
||||
&mut self,
|
||||
pin_selection: PinSelection,
|
||||
) -> ComponentSelection {
|
||||
pub fn pins_to_components(&mut self, pin_selection: PinSelection) -> ComponentSelection {
|
||||
let mut component_selection = ComponentSelection::new();
|
||||
|
||||
for selector in pin_selection.0 {
|
||||
|
|
@ -40,7 +38,17 @@ impl Board {
|
|||
component_selection.0.insert(component_selector);
|
||||
}
|
||||
|
||||
// TODO: Vias.
|
||||
for via_id in self.layout.layer_vias(layer_id) {
|
||||
if self.layout.via(via_id).spec.pin != Some(pin_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(component_selector) = self.via_component_selector(via_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
component_selection.0.insert(component_selector);
|
||||
}
|
||||
|
||||
for segment_id in self.layout.layer_segments(layer_id) {
|
||||
if self.layout.segment(segment_id).spec.pin != Some(pin_id) {
|
||||
|
|
@ -70,11 +78,7 @@ impl Board {
|
|||
component_selection
|
||||
}
|
||||
|
||||
pub fn component_selection_contains_joint(
|
||||
&self,
|
||||
selection: &ComponentSelection,
|
||||
id: JointId,
|
||||
) -> bool {
|
||||
pub fn components_contain_joint(&self, selection: &ComponentSelection, id: JointId) -> bool {
|
||||
let Some(selector) = self.joint_component_selector(id) else {
|
||||
return false;
|
||||
};
|
||||
|
|
@ -82,7 +86,7 @@ impl Board {
|
|||
selection.0.contains(&selector)
|
||||
}
|
||||
|
||||
pub fn component_selection_contains_segment(
|
||||
pub fn components_contain_segment(
|
||||
&self,
|
||||
selection: &ComponentSelection,
|
||||
id: SegmentId,
|
||||
|
|
@ -94,9 +98,15 @@ impl Board {
|
|||
selection.0.contains(&selector)
|
||||
}
|
||||
|
||||
// TODO: Vias.
|
||||
pub fn components_contain_via(&self, selection: &ComponentSelection, id: ViaId) -> bool {
|
||||
let Some(selector) = self.via_component_selector(id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
pub fn component_selection_contains_polygon(
|
||||
selection.0.contains(&selector)
|
||||
}
|
||||
|
||||
pub fn components_contain_polygon(
|
||||
&self,
|
||||
selection: &ComponentSelection,
|
||||
id: PolygonId,
|
||||
|
|
@ -108,28 +118,6 @@ impl Board {
|
|||
selection.0.contains(&selector)
|
||||
}
|
||||
|
||||
pub fn point_component_selector(
|
||||
&self,
|
||||
layer: LayerId,
|
||||
point: Vector2<i64>,
|
||||
) -> Option<ComponentSelector> {
|
||||
if let Some(joint_id) = self.layout.locate_joints_at_point(layer, point).next() {
|
||||
return self.joint_component_selector(joint_id);
|
||||
}
|
||||
|
||||
if let Some(segment_id) = self.layout.locate_segments_at_point(layer, point).next() {
|
||||
return self.segment_component_selector(segment_id);
|
||||
}
|
||||
|
||||
// TODO: Vias.
|
||||
|
||||
if let Some(polygon_id) = self.layout.locate_polygons_at_point(layer, point).next() {
|
||||
return self.polygon_component_selector(polygon_id);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn joint_component_selector(&self, id: JointId) -> Option<ComponentSelector> {
|
||||
let joint = self.layout.joint(id);
|
||||
|
||||
|
|
@ -146,7 +134,13 @@ impl Board {
|
|||
})
|
||||
}
|
||||
|
||||
// TODO: Vias.
|
||||
pub fn via_component_selector(&self, id: ViaId) -> Option<ComponentSelector> {
|
||||
let via = self.layout.via(id);
|
||||
|
||||
Some(ComponentSelector {
|
||||
component: self.component_name(via.spec.component?)?.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn polygon_component_selector(&self, id: PolygonId) -> Option<ComponentSelector> {
|
||||
let polygon = self.layout.polygon(id);
|
||||
|
|
@ -156,7 +150,83 @@ impl Board {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn pin_selection_contains_joint(&self, selection: &PinSelection, id: JointId) -> bool {
|
||||
pub fn nets_contain_joint(&self, selection: &NetSelection, id: JointId) -> bool {
|
||||
let joint = self.layout.joint(id);
|
||||
let Some(net_name) = self.net_name(joint.spec.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) = self.net_name(segment.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) = self.net_name(via.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) = self.net_name(polygon.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);
|
||||
|
||||
Some(NetSelector {
|
||||
net: self.net_name(joint.spec.net)?.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn segment_net_selector(&self, id: SegmentId) -> Option<NetSelector> {
|
||||
let segment = self.layout.segment(id);
|
||||
|
||||
Some(NetSelector {
|
||||
net: self.net_name(segment.net)?.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn via_net_selector(&self, id: ViaId) -> Option<NetSelector> {
|
||||
let via = self.layout.via(id);
|
||||
|
||||
Some(NetSelector {
|
||||
net: self.net_name(via.net)?.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn polygon_net_selector(&self, id: PolygonId) -> Option<NetSelector> {
|
||||
let polygon = self.layout.polygon(id);
|
||||
|
||||
Some(NetSelector {
|
||||
net: self.net_name(polygon.net)?.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pins_contain_joint(&self, selection: &PinSelection, id: JointId) -> bool {
|
||||
let Some(selector) = self.joint_pin_selector(id) else {
|
||||
return false;
|
||||
};
|
||||
|
|
@ -164,7 +234,7 @@ impl Board {
|
|||
selection.0.contains(&selector)
|
||||
}
|
||||
|
||||
pub fn pin_selection_contains_segment(&self, selection: &PinSelection, id: SegmentId) -> bool {
|
||||
pub fn pins_contain_segment(&self, selection: &PinSelection, id: SegmentId) -> bool {
|
||||
let Some(selector) = self.segment_pin_selector(id) else {
|
||||
return false;
|
||||
};
|
||||
|
|
@ -172,32 +242,20 @@ impl Board {
|
|||
selection.0.contains(&selector)
|
||||
}
|
||||
|
||||
// TODO: Vias.
|
||||
|
||||
pub fn pin_selection_contains_polygon(&self, selection: &PinSelection, id: PolygonId) -> bool {
|
||||
let Some(selector) = self.polygon_pin_selector(id) else {
|
||||
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 point_pin_selector(&self, layer: LayerId, point: Vector2<i64>) -> Option<PinSelector> {
|
||||
if let Some(joint_id) = self.layout.locate_joints_at_point(layer, point).next() {
|
||||
return self.joint_pin_selector(joint_id);
|
||||
}
|
||||
pub fn pins_contain_polygon(&self, selection: &PinSelection, id: PolygonId) -> bool {
|
||||
let Some(selector) = self.polygon_pin_selector(id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if let Some(segment_id) = self.layout.locate_segments_at_point(layer, point).next() {
|
||||
return self.segment_pin_selector(segment_id);
|
||||
}
|
||||
|
||||
// TODO: Vias.
|
||||
|
||||
if let Some(polygon_id) = self.layout.locate_polygons_at_point(layer, point).next() {
|
||||
return self.polygon_pin_selector(polygon_id);
|
||||
}
|
||||
|
||||
None
|
||||
selection.0.contains(&selector)
|
||||
}
|
||||
|
||||
pub fn joint_pin_selector(&self, id: JointId) -> Option<PinSelector> {
|
||||
|
|
@ -218,7 +276,14 @@ impl Board {
|
|||
})
|
||||
}
|
||||
|
||||
// TODO: Vias.
|
||||
pub fn via_pin_selector(&self, id: ViaId) -> Option<PinSelector> {
|
||||
let via = self.layout.via(id);
|
||||
|
||||
Some(PinSelector {
|
||||
pin: self.pin_name(via.spec.pin?)?.to_string(),
|
||||
layer: self.layer_name(via.min_layer)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn polygon_pin_selector(&self, id: PolygonId) -> Option<PinSelector> {
|
||||
let polygon = self.layout.polygon(id);
|
||||
|
|
|
|||
|
|
@ -20,11 +20,23 @@ impl ComponentSelection {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self, selector: ComponentSelector) {
|
||||
if self.0.contains(&selector) {
|
||||
pub fn add(&mut self, selectors: impl IntoIterator<Item = ComponentSelector>) {
|
||||
self.0.extend(selectors);
|
||||
}
|
||||
|
||||
pub fn sub(&mut self, selectors: impl IntoIterator<Item = ComponentSelector>) {
|
||||
for selector in selectors {
|
||||
self.0.remove(&selector);
|
||||
} else {
|
||||
self.0.insert(selector);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xor(&mut self, selectors: impl IntoIterator<Item = ComponentSelector>) {
|
||||
for selector in selectors {
|
||||
if self.0.contains(&selector) {
|
||||
self.0.remove(&selector);
|
||||
} else {
|
||||
self.0.insert(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
mod component;
|
||||
mod net;
|
||||
mod persistable;
|
||||
mod pin;
|
||||
mod route;
|
||||
|
||||
pub use component::{ComponentSelection, ComponentSelector};
|
||||
pub use net::{NetSelection, NetSelector};
|
||||
pub use persistable::PersistableSelection;
|
||||
pub use pin::{PinSelection, PinSelector};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct NetSelector {
|
||||
pub net: String,
|
||||
}
|
||||
|
||||
impl NetSelector {
|
||||
pub fn new(net: String) -> Self {
|
||||
Self { net }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct NetSelection(pub BTreeSet<NetSelector>);
|
||||
|
||||
impl NetSelection {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn add(&mut self, selectors: impl IntoIterator<Item = NetSelector>) {
|
||||
self.0.extend(selectors);
|
||||
}
|
||||
|
||||
pub fn sub(&mut self, selectors: impl IntoIterator<Item = NetSelector>) {
|
||||
for selector in selectors {
|
||||
self.0.remove(&selector);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xor(&mut self, selectors: impl IntoIterator<Item = NetSelector>) {
|
||||
for selector in selectors {
|
||||
if self.0.contains(&selector) {
|
||||
self.0.remove(&selector);
|
||||
} else {
|
||||
self.0.insert(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,15 +4,12 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
board::selections::{ComponentSelection, PinSelection},
|
||||
selections::route::RouteSelection,
|
||||
};
|
||||
use crate::board::selections::{ComponentSelection, NetSelection, PinSelection};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct PersistableSelection {
|
||||
pub components: ComponentSelection,
|
||||
pub routes: RouteSelection,
|
||||
pub nets: NetSelection,
|
||||
pub pins: PinSelection,
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +17,7 @@ impl PersistableSelection {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
components: ComponentSelection::new(),
|
||||
routes: RouteSelection::new(),
|
||||
nets: NetSelection::new(),
|
||||
pins: PinSelection::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,23 @@ impl PinSelection {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self, selector: PinSelector) {
|
||||
if self.0.contains(&selector) {
|
||||
pub fn add(&mut self, selectors: impl IntoIterator<Item = PinSelector>) {
|
||||
self.0.extend(selectors);
|
||||
}
|
||||
|
||||
pub fn sub(&mut self, selectors: impl IntoIterator<Item = PinSelector>) {
|
||||
for selector in selectors {
|
||||
self.0.remove(&selector);
|
||||
} else {
|
||||
self.0.insert(selector);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xor(&mut self, selectors: impl IntoIterator<Item = PinSelector>) {
|
||||
for selector in selectors {
|
||||
if self.0.contains(&selector) {
|
||||
self.0.remove(&selector);
|
||||
} else {
|
||||
self.0.insert(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::selections::PinSelector;
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct RouteSelector {
|
||||
lesser_pin: PinSelector,
|
||||
greater_pin: PinSelector,
|
||||
}
|
||||
|
||||
impl RouteSelector {
|
||||
pub fn new(pin1: PinSelector, pin2: PinSelector) -> Self {
|
||||
Self {
|
||||
lesser_pin: std::cmp::min(pin1.clone(), pin2.clone()),
|
||||
greater_pin: std::cmp::max(pin1, pin2),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct RouteSelection(pub BTreeSet<RouteSelector>);
|
||||
|
||||
impl RouteSelection {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self, selector: RouteSelector) {
|
||||
if self.0.contains(&selector) {
|
||||
self.0.remove(&selector);
|
||||
} else {
|
||||
self.0.insert(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use rstar::primitives::GeomWithData;
|
||||
|
||||
use crate::{
|
||||
layout::Layout,
|
||||
primitives::{JointId, PolygonId, SegmentId, ViaId},
|
||||
};
|
||||
|
||||
impl Layout {
|
||||
pub fn delete_joint(&mut self, joint_id: JointId) {
|
||||
let joint = self.joint(joint_id);
|
||||
let bbox = joint.bbox();
|
||||
let component = joint.spec.component;
|
||||
let pin = joint.spec.pin;
|
||||
|
||||
if let Some(component_id) = component {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
if let Some(index) = component.joints.iter().position(|&curr| curr == joint_id) {
|
||||
component.joints.remove(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin {
|
||||
self.pins.modify(pin_id.index(), |pin| {
|
||||
if let Some(index) = pin.joints.iter().position(|&curr| curr == joint_id) {
|
||||
pin.joints.remove(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.joints_rtree.remove(&GeomWithData::new(bbox, joint_id));
|
||||
self.joints.remove(&joint_id.index());
|
||||
}
|
||||
|
||||
pub fn delete_segment(&mut self, segment_id: SegmentId) {
|
||||
let segment = self.segment(segment_id);
|
||||
let bbox = segment.bbox();
|
||||
let component = segment.spec.component;
|
||||
let pin = segment.spec.pin;
|
||||
|
||||
if let Some(component_id) = component {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
if let Some(index) = component
|
||||
.segments
|
||||
.iter()
|
||||
.position(|&curr| curr == segment_id)
|
||||
{
|
||||
component.segments.remove(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin {
|
||||
self.pins.modify(pin_id.index(), |pin| {
|
||||
if let Some(index) = pin.segments.iter().position(|&curr| curr == segment_id) {
|
||||
pin.segments.remove(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.segments_rtree
|
||||
.remove(&GeomWithData::new(bbox, segment_id));
|
||||
self.segments.remove(&segment_id.index());
|
||||
}
|
||||
|
||||
pub fn delete_via(&mut self, via_id: ViaId) {
|
||||
let via = self.via(via_id);
|
||||
let bbox = via.bbox();
|
||||
let component = via.spec.component;
|
||||
let pin = via.spec.pin;
|
||||
|
||||
if let Some(component_id) = component {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
if let Some(index) = component.vias.iter().position(|&curr| curr == via_id) {
|
||||
component.vias.remove(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin {
|
||||
self.pins.modify(pin_id.index(), |pin| {
|
||||
if let Some(index) = pin.vias.iter().position(|&curr| curr == via_id) {
|
||||
pin.vias.remove(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.vias_rtree.remove(&GeomWithData::new(bbox, via_id));
|
||||
self.vias.remove(&via_id.index());
|
||||
}
|
||||
|
||||
pub fn delete_polygon(&mut self, polygon_id: PolygonId) {
|
||||
let polygon = self.polygon(polygon_id);
|
||||
let bbox = polygon.bbox();
|
||||
let component = polygon.component;
|
||||
let pin = polygon.pin;
|
||||
|
||||
if let Some(component_id) = component {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
if let Some(index) = component
|
||||
.polygons
|
||||
.iter()
|
||||
.position(|&curr| curr == polygon_id)
|
||||
{
|
||||
component.polygons.remove(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin {
|
||||
self.pins.modify(pin_id.index(), |pin| {
|
||||
if let Some(index) = pin.polygons.iter().position(|&curr| curr == polygon_id) {
|
||||
pin.polygons.remove(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.polygons_rtree
|
||||
.remove(&GeomWithData::new(bbox, polygon_id));
|
||||
self.polygons.remove(&polygon_id.index());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use rstar::primitives::GeomWithData;
|
||||
|
||||
use crate::{
|
||||
Pin, PinId,
|
||||
layout::{
|
||||
Layout,
|
||||
compounds::{Component, ComponentId},
|
||||
},
|
||||
primitives::{
|
||||
Joint, JointId, JointSpec, Polygon, PolygonId, Segment, SegmentId, SegmentSpec, Via, ViaId,
|
||||
ViaSpec,
|
||||
},
|
||||
};
|
||||
|
||||
impl Layout {
|
||||
pub fn insert_component(&mut self) -> ComponentId {
|
||||
ComponentId::new(self.components.push(Component::new()))
|
||||
}
|
||||
|
||||
pub fn insert_pin(&mut self) -> PinId {
|
||||
PinId::new(self.pins.push(Pin::new()))
|
||||
}
|
||||
|
||||
pub fn insert_joint(&mut self, spec: JointSpec) -> JointId {
|
||||
let joint = Joint {
|
||||
spec,
|
||||
segments: Vec::new(),
|
||||
vias: Vec::new(),
|
||||
};
|
||||
let bbox = joint.bbox();
|
||||
let component_id = joint.spec.component;
|
||||
let pin_id = joint.spec.pin;
|
||||
let joint_id = JointId::new(self.joints.push(joint));
|
||||
|
||||
self.joints_rtree
|
||||
.insert(GeomWithData::new(bbox, joint_id), ());
|
||||
|
||||
if let Some(component_id) = component_id {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
component.joints.push(joint_id)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin_id {
|
||||
self.pins
|
||||
.modify(pin_id.index(), |pin| pin.joints.push(joint_id));
|
||||
}
|
||||
|
||||
joint_id
|
||||
}
|
||||
|
||||
pub fn insert_segment(&mut self, spec: SegmentSpec) -> SegmentId {
|
||||
self.insert_segment_raw(Segment {
|
||||
spec,
|
||||
endpoints: [
|
||||
self.joint(spec.endjoints[0]).spec.position,
|
||||
self.joint(spec.endjoints[1]).spec.position,
|
||||
],
|
||||
layer: self.joint(spec.endjoints[0]).spec.layer,
|
||||
net: self.joint(spec.endjoints[0]).spec.net,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_segment_raw(&mut self, segment: Segment) -> SegmentId {
|
||||
let component_id = segment.spec.component;
|
||||
let pin_id = segment.spec.pin;
|
||||
let bbox = segment.bbox();
|
||||
let segment_id = SegmentId::new(self.segments.push(segment));
|
||||
|
||||
self.joints
|
||||
.modify(segment.spec.endjoints[0].index(), |joint| {
|
||||
joint.segments.push(segment_id)
|
||||
});
|
||||
self.joints
|
||||
.modify(segment.spec.endjoints[1].index(), |joint| {
|
||||
joint.segments.push(segment_id)
|
||||
});
|
||||
|
||||
self.segments_rtree
|
||||
.insert(GeomWithData::new(bbox, segment_id), ());
|
||||
|
||||
if let Some(component_id) = component_id {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
component.segments.push(segment_id)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin_id {
|
||||
self.pins
|
||||
.modify(pin_id.index(), |pin| pin.segments.push(segment_id));
|
||||
}
|
||||
|
||||
segment_id
|
||||
}
|
||||
|
||||
pub fn insert_via(&mut self, spec: ViaSpec) -> ViaId {
|
||||
let joints = [self.joint(spec.endjoints[0]), self.joint(spec.endjoints[1])];
|
||||
|
||||
self.insert_via_raw(Via {
|
||||
spec,
|
||||
position: joints[0].spec.position,
|
||||
min_layer: std::cmp::min(joints[0].spec.layer, joints[1].spec.layer),
|
||||
max_layer: std::cmp::max(joints[0].spec.layer, joints[1].spec.layer),
|
||||
net: joints[0].spec.net,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_via_raw(&mut self, via: Via) -> ViaId {
|
||||
let bbox = via.bbox();
|
||||
let component_id = via.spec.component;
|
||||
let pin_id = via.spec.pin;
|
||||
let via_id = ViaId::new(self.vias.push(via));
|
||||
|
||||
self.joints.modify(via.spec.endjoints[0].index(), |joint| {
|
||||
joint.vias.push(via_id)
|
||||
});
|
||||
self.joints.modify(via.spec.endjoints[1].index(), |joint| {
|
||||
joint.vias.push(via_id)
|
||||
});
|
||||
|
||||
self.vias_rtree.insert(GeomWithData::new(bbox, via_id), ());
|
||||
|
||||
if let Some(component_id) = component_id {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
component.vias.push(via_id)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin_id {
|
||||
self.pins
|
||||
.modify(pin_id.index(), |pin| pin.vias.push(via_id));
|
||||
}
|
||||
|
||||
via_id
|
||||
}
|
||||
|
||||
pub fn insert_polygon(&mut self, polygon: Polygon) -> PolygonId {
|
||||
let bbox = polygon.bbox();
|
||||
let component_id = polygon.component;
|
||||
let pin_id = polygon.pin;
|
||||
let polygon_id = PolygonId::new(self.polygons.push(polygon));
|
||||
|
||||
self.polygons_rtree
|
||||
.insert(GeomWithData::new(bbox, polygon_id), ());
|
||||
|
||||
if let Some(component_id) = component_id {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
component.polygons.push(polygon_id)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin_id {
|
||||
self.pins
|
||||
.modify(pin_id.index(), |pin| pin.polygons.push(polygon_id));
|
||||
}
|
||||
|
||||
polygon_id
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::{
|
||||
Rect3, Vector3,
|
||||
layout::{LayerId, Layout, compounds::NetId},
|
||||
primitives::{JointId, PolygonId, SegmentId, ViaId},
|
||||
};
|
||||
|
||||
impl Layout {
|
||||
pub fn locate_joints_at_point(&self, point: Vector3<i64>) -> impl Iterator<Item = JointId> {
|
||||
let point2 = point.xy();
|
||||
self.joints_rtree
|
||||
.as_ref()
|
||||
.locate_all_at_point(&[point.x, point.y, point.z])
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
.filter(move |&joint_id| self.joints[joint_id.index()].contains_point(point2))
|
||||
}
|
||||
|
||||
pub fn locate_joints_intersecting_rect(
|
||||
&self,
|
||||
rect: Rect3<i64>,
|
||||
) -> impl Iterator<Item = JointId> {
|
||||
let rect_aabb = rect.aabb3();
|
||||
self.joints_rtree
|
||||
.as_ref()
|
||||
.locate_in_envelope_intersecting(&rect_aabb)
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
}
|
||||
|
||||
pub fn locate_joints_inside_rect(&self, rect: Rect3<i64>) -> impl Iterator<Item = JointId> {
|
||||
let rect_aabb = rect.aabb3();
|
||||
self.joints_rtree
|
||||
.as_ref()
|
||||
.locate_in_envelope(&rect_aabb)
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
}
|
||||
|
||||
pub fn locate_segments_at_point(&self, point: Vector3<i64>) -> impl Iterator<Item = SegmentId> {
|
||||
let point2 = point.xy();
|
||||
self.segments_rtree
|
||||
.as_ref()
|
||||
.locate_all_at_point(&[point.x, point.y, point.z])
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
.filter(move |&segment_id| self.segment(segment_id).contains_point(point2))
|
||||
}
|
||||
|
||||
pub fn locate_segments_intersecting_rect(
|
||||
&self,
|
||||
rect: Rect3<i64>,
|
||||
) -> impl Iterator<Item = SegmentId> {
|
||||
let rect_aabb = rect.aabb3();
|
||||
self.segments_rtree
|
||||
.as_ref()
|
||||
.locate_in_envelope_intersecting(&rect_aabb)
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
}
|
||||
|
||||
pub fn locate_segments_inside_rect(&self, rect: Rect3<i64>) -> impl Iterator<Item = SegmentId> {
|
||||
let rect_aabb = rect.aabb3();
|
||||
self.segments_rtree
|
||||
.as_ref()
|
||||
.locate_in_envelope(&rect_aabb)
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
}
|
||||
|
||||
pub fn locate_vias_at_point(&self, point: Vector3<i64>) -> impl Iterator<Item = ViaId> {
|
||||
let layer = LayerId::new(point.z as usize);
|
||||
let point2 = point.xy();
|
||||
self.vias_rtree
|
||||
.as_ref()
|
||||
.locate_all_at_point(&[point.x, point.y, point.z])
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
.filter(move |&via_id| self.vias[via_id.index()].contains_point(layer, point2))
|
||||
}
|
||||
|
||||
pub fn locate_vias_intersecting_rect(&self, rect: Rect3<i64>) -> impl Iterator<Item = ViaId> {
|
||||
let rect_aabb = rect.aabb3();
|
||||
self.vias_rtree
|
||||
.as_ref()
|
||||
.locate_in_envelope_intersecting(&rect_aabb)
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
}
|
||||
|
||||
pub fn locate_vias_inside_rect(&self, rect: Rect3<i64>) -> impl Iterator<Item = ViaId> {
|
||||
let rect_aabb = rect.aabb3();
|
||||
self.vias_rtree
|
||||
.as_ref()
|
||||
.locate_in_envelope(&rect_aabb)
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
}
|
||||
|
||||
pub fn locate_polygons_at_point(&self, point: Vector3<i64>) -> impl Iterator<Item = PolygonId> {
|
||||
let point2 = point.xy();
|
||||
self.polygons_rtree
|
||||
.as_ref()
|
||||
.locate_all_at_point(&[point.x, point.y, point.z])
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
.filter(move |&polygon_id| self.polygons[polygon_id.index()].contains_point(point2))
|
||||
}
|
||||
|
||||
pub fn locate_polygons_intersecting_rect(
|
||||
&self,
|
||||
rect: Rect3<i64>,
|
||||
) -> impl Iterator<Item = PolygonId> {
|
||||
let rect_aabb = rect.aabb3();
|
||||
self.polygons_rtree
|
||||
.as_ref()
|
||||
.locate_in_envelope_intersecting(&rect_aabb)
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
}
|
||||
|
||||
pub fn locate_polygons_inside_rect(&self, rect: Rect3<i64>) -> impl Iterator<Item = PolygonId> {
|
||||
let rect_aabb = rect.aabb3();
|
||||
self.polygons_rtree
|
||||
.as_ref()
|
||||
.locate_in_envelope(&rect_aabb)
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
}
|
||||
|
||||
pub fn locate_nets_intersecting_rect(&self, rect: Rect3<i64>) -> impl Iterator<Item = NetId> {
|
||||
let mut nets = BTreeSet::new();
|
||||
|
||||
for joint_id in self.locate_joints_intersecting_rect(rect) {
|
||||
nets.insert(self.joint(joint_id).spec.net);
|
||||
}
|
||||
|
||||
for segment_id in self.locate_segments_intersecting_rect(rect) {
|
||||
nets.insert(self.segment(segment_id).net);
|
||||
}
|
||||
|
||||
for via_id in self.locate_vias_intersecting_rect(rect) {
|
||||
nets.insert(self.via(via_id).net);
|
||||
}
|
||||
|
||||
for polygon_id in self.locate_polygons_intersecting_rect(rect) {
|
||||
nets.insert(self.polygon(polygon_id).net);
|
||||
}
|
||||
|
||||
nets.into_iter()
|
||||
}
|
||||
|
||||
pub fn locate_nets_inside_rect(&self, rect: Rect3<i64>) -> impl Iterator<Item = NetId> {
|
||||
let mut nets = BTreeSet::new();
|
||||
|
||||
for joint_id in self.locate_joints_inside_rect(rect) {
|
||||
nets.insert(self.joint(joint_id).spec.net);
|
||||
}
|
||||
|
||||
for segment_id in self.locate_segments_inside_rect(rect) {
|
||||
nets.insert(self.segment(segment_id).net);
|
||||
}
|
||||
|
||||
for via_id in self.locate_vias_inside_rect(rect) {
|
||||
nets.insert(self.via(via_id).net);
|
||||
}
|
||||
|
||||
for polygon_id in self.locate_polygons_inside_rect(rect) {
|
||||
nets.insert(self.polygon(polygon_id).net);
|
||||
}
|
||||
|
||||
nets.into_iter()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,10 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
pub mod compounds;
|
||||
mod delete;
|
||||
mod insert;
|
||||
mod locate;
|
||||
mod modify;
|
||||
pub mod primitives;
|
||||
mod transforms;
|
||||
|
||||
|
|
@ -18,12 +22,8 @@ use undoredo::aliases::RTreeHalfDelta;
|
|||
use undoredo::{Delta, Recorder};
|
||||
|
||||
use crate::{
|
||||
layout::compounds::{Component, ComponentId, Pin, PinId},
|
||||
layout::primitives::{
|
||||
Joint, JointId, JointSpec, Polygon, PolygonId, Segment, SegmentId, SegmentSpec, Via, ViaId,
|
||||
ViaSpec,
|
||||
},
|
||||
math::Vector2,
|
||||
layout::compounds::{Component, Pin, PinId},
|
||||
layout::primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId},
|
||||
};
|
||||
|
||||
#[derive(
|
||||
|
|
@ -106,308 +106,6 @@ impl Layout {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_component(&mut self) -> ComponentId {
|
||||
ComponentId::new(self.components.push(Component::new()))
|
||||
}
|
||||
|
||||
pub fn add_pin(&mut self) -> PinId {
|
||||
PinId::new(self.pins.push(Pin::new()))
|
||||
}
|
||||
|
||||
pub fn add_joint(&mut self, spec: JointSpec) -> JointId {
|
||||
let joint = Joint {
|
||||
spec,
|
||||
segments: Vec::new(),
|
||||
vias: Vec::new(),
|
||||
};
|
||||
let bbox = joint.bbox();
|
||||
let component_id = joint.spec.component;
|
||||
let pin_id = joint.spec.pin;
|
||||
let joint_id = JointId::new(self.joints.push(joint));
|
||||
|
||||
self.joints_rtree
|
||||
.insert(GeomWithData::new(bbox, joint_id), ());
|
||||
|
||||
if let Some(component_id) = component_id {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
component.joints.push(joint_id)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin_id {
|
||||
self.pins
|
||||
.modify(pin_id.index(), |pin| pin.joints.push(joint_id));
|
||||
}
|
||||
|
||||
joint_id
|
||||
}
|
||||
|
||||
pub fn modify_joint<F>(&mut self, id: JointId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut JointSpec),
|
||||
{
|
||||
self.modify_joint_raw(id, |joint| f(&mut joint.spec));
|
||||
let new_joint = self.joints[id.index()].clone();
|
||||
|
||||
for &segment_id in &new_joint.segments {
|
||||
self.update_segment(segment_id);
|
||||
}
|
||||
|
||||
for &via_id in &new_joint.vias {
|
||||
self.update_via(via_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_joint_raw<F>(&mut self, id: JointId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Joint),
|
||||
{
|
||||
let old_joint = &self.joints[id.index()];
|
||||
self.joints_rtree
|
||||
.remove(&GeomWithData::new(old_joint.bbox(), 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), ());
|
||||
}
|
||||
|
||||
pub fn add_segment(&mut self, spec: SegmentSpec) -> SegmentId {
|
||||
self.add_segment_raw(Segment {
|
||||
spec,
|
||||
endpoints: [
|
||||
self.joint(spec.endjoints[0]).spec.position,
|
||||
self.joint(spec.endjoints[1]).spec.position,
|
||||
],
|
||||
layer: self.joint(spec.endjoints[0]).spec.layer,
|
||||
net: self.joint(spec.endjoints[0]).spec.net,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_segment_raw(&mut self, segment: Segment) -> SegmentId {
|
||||
let component_id = segment.spec.component;
|
||||
let pin_id = segment.spec.pin;
|
||||
let bbox = segment.bbox();
|
||||
let segment_id = SegmentId::new(self.segments.push(segment));
|
||||
|
||||
self.joints
|
||||
.modify(segment.spec.endjoints[0].index(), |joint| {
|
||||
joint.segments.push(segment_id)
|
||||
});
|
||||
self.joints
|
||||
.modify(segment.spec.endjoints[1].index(), |joint| {
|
||||
joint.segments.push(segment_id)
|
||||
});
|
||||
|
||||
self.segments_rtree
|
||||
.insert(GeomWithData::new(bbox, segment_id), ());
|
||||
|
||||
if let Some(component_id) = component_id {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
component.segments.push(segment_id)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin_id {
|
||||
self.pins
|
||||
.modify(pin_id.index(), |pin| pin.segments.push(segment_id));
|
||||
}
|
||||
|
||||
segment_id
|
||||
}
|
||||
|
||||
pub fn modify_segment<F>(&mut self, id: SegmentId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut SegmentSpec),
|
||||
{
|
||||
let old_segment = &self.segments[id.index()];
|
||||
self.segments_rtree
|
||||
.remove(&GeomWithData::new(old_segment.bbox(), 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), ());
|
||||
}
|
||||
|
||||
fn update_segment(&mut self, id: SegmentId) {
|
||||
let old_segment = &self.segments[id.index()];
|
||||
self.segments_rtree
|
||||
.remove(&GeomWithData::new(old_segment.bbox(), id));
|
||||
|
||||
let endjoint_ids = old_segment.spec.endjoints;
|
||||
let endjoint_specs = [
|
||||
self.joints[endjoint_ids[0].index()].spec,
|
||||
self.joints[endjoint_ids[1].index()].spec,
|
||||
];
|
||||
self.segments.modify(id.index(), |segment| {
|
||||
segment.endpoints = [endjoint_specs[0].position, endjoint_specs[1].position];
|
||||
segment.layer = endjoint_specs[0].layer;
|
||||
segment.net = endjoint_specs[0].net;
|
||||
});
|
||||
|
||||
let new_segment = &self.segments[id.index()];
|
||||
self.segments_rtree
|
||||
.insert(GeomWithData::new(new_segment.bbox(), id), ());
|
||||
}
|
||||
|
||||
pub fn add_via(&mut self, spec: ViaSpec) -> ViaId {
|
||||
let joints = [self.joint(spec.endjoints[0]), self.joint(spec.endjoints[1])];
|
||||
|
||||
self.add_via_raw(Via {
|
||||
spec,
|
||||
position: joints[0].spec.position,
|
||||
min_layer: std::cmp::min(joints[0].spec.layer, joints[1].spec.layer),
|
||||
max_layer: std::cmp::max(joints[0].spec.layer, joints[1].spec.layer),
|
||||
net: joints[0].spec.net,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_via_raw(&mut self, via: Via) -> ViaId {
|
||||
let bbox = via.bbox();
|
||||
let component_id = via.spec.component;
|
||||
let pin_id = via.spec.pin;
|
||||
let via_id = ViaId::new(self.vias.push(via));
|
||||
|
||||
self.joints.modify(via.spec.endjoints[0].index(), |joint| {
|
||||
joint.vias.push(via_id)
|
||||
});
|
||||
self.joints.modify(via.spec.endjoints[1].index(), |joint| {
|
||||
joint.vias.push(via_id)
|
||||
});
|
||||
|
||||
self.vias_rtree.insert(GeomWithData::new(bbox, via_id), ());
|
||||
|
||||
if let Some(component_id) = component_id {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
component.vias.push(via_id)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin_id {
|
||||
self.pins
|
||||
.modify(pin_id.index(), |pin| pin.vias.push(via_id));
|
||||
}
|
||||
|
||||
via_id
|
||||
}
|
||||
|
||||
pub fn modify_via<F>(&mut self, id: ViaId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut ViaSpec),
|
||||
{
|
||||
let old_via = &self.vias[id.index()];
|
||||
self.vias_rtree
|
||||
.remove(&GeomWithData::new(old_via.bbox(), 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), ());
|
||||
}
|
||||
|
||||
fn update_via(&mut self, id: ViaId) {
|
||||
let old_via = &self.vias[id.index()];
|
||||
self.vias_rtree
|
||||
.remove(&GeomWithData::new(old_via.bbox(), id));
|
||||
|
||||
let endjoint_ids = old_via.spec.endjoints;
|
||||
let endjoint_specs = [
|
||||
self.joints[endjoint_ids[0].index()].spec,
|
||||
self.joints[endjoint_ids[1].index()].spec,
|
||||
];
|
||||
self.vias.modify(id.index(), |via| {
|
||||
via.position = endjoint_specs[0].position;
|
||||
via.min_layer = std::cmp::min(endjoint_specs[0].layer, endjoint_specs[1].layer);
|
||||
via.max_layer = std::cmp::max(endjoint_specs[0].layer, endjoint_specs[1].layer);
|
||||
via.net = endjoint_specs[0].net;
|
||||
});
|
||||
|
||||
let new_via = &self.vias[id.index()];
|
||||
self.vias_rtree
|
||||
.insert(GeomWithData::new(new_via.bbox(), id), ());
|
||||
}
|
||||
|
||||
pub fn add_polygon(&mut self, polygon: Polygon) -> PolygonId {
|
||||
let bbox = polygon.bbox();
|
||||
let component_id = polygon.component;
|
||||
let pin_id = polygon.pin;
|
||||
let polygon_id = PolygonId::new(self.polygons.push(polygon));
|
||||
|
||||
self.polygons_rtree
|
||||
.insert(GeomWithData::new(bbox, polygon_id), ());
|
||||
|
||||
if let Some(component_id) = component_id {
|
||||
self.components.modify(component_id.index(), |component| {
|
||||
component.polygons.push(polygon_id)
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(pin_id) = pin_id {
|
||||
self.pins
|
||||
.modify(pin_id.index(), |pin| pin.polygons.push(polygon_id));
|
||||
}
|
||||
|
||||
polygon_id
|
||||
}
|
||||
|
||||
pub fn modify_polygon<F>(&mut self, id: PolygonId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Polygon),
|
||||
{
|
||||
let old_polygon = &self.polygons[id.index()];
|
||||
self.polygons_rtree
|
||||
.remove(&GeomWithData::new(old_polygon.bbox(), 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), ());
|
||||
}
|
||||
|
||||
pub fn locate_joints_at_point(
|
||||
&self,
|
||||
layer: LayerId,
|
||||
point: Vector2<i64>,
|
||||
) -> impl Iterator<Item = JointId> {
|
||||
self.joints_rtree
|
||||
.as_ref()
|
||||
.locate_all_at_point(&[point.x, point.y, layer.index() as i64])
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
.filter(move |&joint_id| self.joints[joint_id.index()].contains_point(point))
|
||||
}
|
||||
|
||||
pub fn locate_segments_at_point(
|
||||
&self,
|
||||
layer: LayerId,
|
||||
point: Vector2<i64>,
|
||||
) -> impl Iterator<Item = SegmentId> {
|
||||
self.segments_rtree
|
||||
.as_ref()
|
||||
.locate_all_at_point(&[point.x, point.y, layer.index() as i64])
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
.filter(move |&segment_id| self.segment(segment_id).contains_point(point))
|
||||
}
|
||||
|
||||
// TODO: vias.
|
||||
|
||||
pub fn locate_polygons_at_point(
|
||||
&self,
|
||||
layer: LayerId,
|
||||
point: Vector2<i64>,
|
||||
) -> impl Iterator<Item = PolygonId> {
|
||||
self.polygons_rtree
|
||||
.as_ref()
|
||||
.locate_all_at_point(&[point.x, point.y, layer.index() as i64])
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
.filter(move |&polygon_id| self.polygons[polygon_id.index()].contains_point(point))
|
||||
}
|
||||
|
||||
pub fn layer_joints(&self, layer: LayerId) -> impl Iterator<Item = JointId> + '_ {
|
||||
let envelope = Self::whole_layer_aabb(layer);
|
||||
self.joints_rtree
|
||||
|
|
@ -426,6 +124,18 @@ impl Layout {
|
|||
.filter(move |&id| self.segment(id).layer == layer)
|
||||
}
|
||||
|
||||
pub fn layer_vias(&self, layer: LayerId) -> impl Iterator<Item = ViaId> + '_ {
|
||||
let envelope = Self::whole_layer_aabb(layer);
|
||||
self.vias_rtree
|
||||
.as_ref()
|
||||
.locate_in_envelope_intersecting(&envelope)
|
||||
.map(|geom_with_data| geom_with_data.data)
|
||||
.filter(move |&id| {
|
||||
let via = self.via(id);
|
||||
via.min_layer <= layer && layer <= via.max_layer
|
||||
})
|
||||
}
|
||||
|
||||
pub fn layer_polygons(&self, layer: LayerId) -> impl Iterator<Item = PolygonId> + '_ {
|
||||
let envelope = Self::whole_layer_aabb(layer);
|
||||
self.polygons_rtree
|
||||
|
|
@ -450,6 +160,10 @@ impl Layout {
|
|||
&self.segments[segment_id.index()]
|
||||
}
|
||||
|
||||
pub fn via(&self, via_id: ViaId) -> &Via {
|
||||
&self.vias[via_id.index()]
|
||||
}
|
||||
|
||||
pub fn polygon(&self, polygon_id: PolygonId) -> &Polygon {
|
||||
&self.polygons[polygon_id.index()]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use rstar::primitives::GeomWithData;
|
||||
|
||||
use crate::{
|
||||
layout::Layout,
|
||||
primitives::{
|
||||
Joint, JointId, JointSpec, Polygon, PolygonId, SegmentId, SegmentSpec, ViaId, ViaSpec,
|
||||
},
|
||||
};
|
||||
|
||||
impl Layout {
|
||||
pub fn modify_joint<F>(&mut self, id: JointId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut JointSpec),
|
||||
{
|
||||
self.modify_joint_raw(id, |joint| f(&mut joint.spec));
|
||||
let new_joint = self.joints[id.index()].clone();
|
||||
|
||||
for &segment_id in &new_joint.segments {
|
||||
self.update_segment(segment_id);
|
||||
}
|
||||
|
||||
for &via_id in &new_joint.vias {
|
||||
self.update_via(via_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn modify_joint_raw<F>(&mut self, id: JointId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Joint),
|
||||
{
|
||||
let old_joint = &self.joints[id.index()];
|
||||
self.joints_rtree
|
||||
.remove(&GeomWithData::new(old_joint.bbox(), 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), ());
|
||||
}
|
||||
|
||||
pub fn modify_segment<F>(&mut self, id: SegmentId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut SegmentSpec),
|
||||
{
|
||||
let old_segment = &self.segments[id.index()];
|
||||
self.segments_rtree
|
||||
.remove(&GeomWithData::new(old_segment.bbox(), 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), ());
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
let endjoint_ids = old_segment.spec.endjoints;
|
||||
let endjoint_specs = [
|
||||
self.joints[endjoint_ids[0].index()].spec,
|
||||
self.joints[endjoint_ids[1].index()].spec,
|
||||
];
|
||||
self.segments.modify(id.index(), |segment| {
|
||||
segment.endpoints = [endjoint_specs[0].position, endjoint_specs[1].position];
|
||||
segment.layer = endjoint_specs[0].layer;
|
||||
segment.net = endjoint_specs[0].net;
|
||||
});
|
||||
|
||||
let new_segment = &self.segments[id.index()];
|
||||
self.segments_rtree
|
||||
.insert(GeomWithData::new(new_segment.bbox(), id), ());
|
||||
}
|
||||
|
||||
pub fn modify_via<F>(&mut self, id: ViaId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut ViaSpec),
|
||||
{
|
||||
let old_via = &self.vias[id.index()];
|
||||
self.vias_rtree
|
||||
.remove(&GeomWithData::new(old_via.bbox(), 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), ());
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
let endjoint_ids = old_via.spec.endjoints;
|
||||
let endjoint_specs = [
|
||||
self.joints[endjoint_ids[0].index()].spec,
|
||||
self.joints[endjoint_ids[1].index()].spec,
|
||||
];
|
||||
self.vias.modify(id.index(), |via| {
|
||||
via.position = endjoint_specs[0].position;
|
||||
via.min_layer = std::cmp::min(endjoint_specs[0].layer, endjoint_specs[1].layer);
|
||||
via.max_layer = std::cmp::max(endjoint_specs[0].layer, endjoint_specs[1].layer);
|
||||
via.net = endjoint_specs[0].net;
|
||||
});
|
||||
|
||||
let new_via = &self.vias[id.index()];
|
||||
self.vias_rtree
|
||||
.insert(GeomWithData::new(new_via.bbox(), id), ());
|
||||
}
|
||||
|
||||
pub fn modify_polygon<F>(&mut self, id: PolygonId, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Polygon),
|
||||
{
|
||||
let old_polygon = &self.polygons[id.index()];
|
||||
self.polygons_rtree
|
||||
.remove(&GeomWithData::new(old_polygon.bbox(), 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), ());
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,15 @@ pub struct Via {
|
|||
}
|
||||
|
||||
impl Via {
|
||||
pub fn contains_point(&self, layer: LayerId, point: Vector2<i64>) -> bool {
|
||||
if layer < self.min_layer || layer > self.max_layer {
|
||||
return false;
|
||||
}
|
||||
|
||||
(point.x - self.position.x).pow(2) as u64 + (point.y - self.position.y).pow(2) as u64
|
||||
<= self.spec.radius.pow(2)
|
||||
}
|
||||
|
||||
pub fn bbox(&self) -> Rectangle<[i64; 3]> {
|
||||
let radius = self.spec.radius as i64;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,16 @@ mod specctra;
|
|||
pub use crate::autorouter::Autorouter;
|
||||
pub use crate::board::Board;
|
||||
pub use crate::board::LayerDesc;
|
||||
pub use crate::board::LayerTier;
|
||||
pub use crate::board::LayerSide;
|
||||
pub use crate::board::LayerType;
|
||||
pub use crate::board::interactors::{
|
||||
DragSelectionInteractor, DragSelectionOptions, InteractiveInput, SelectionCombineMode,
|
||||
SelectionContainMode, SelectionInteractor,
|
||||
};
|
||||
pub use crate::board::selections;
|
||||
pub use crate::layout::LayerId;
|
||||
pub use crate::layout::Layout;
|
||||
pub use crate::layout::compounds::{Pin, PinId};
|
||||
pub use crate::layout::primitives;
|
||||
pub use crate::math::Vector2;
|
||||
pub use crate::math::{Rect2, Rect3, Vector2, Vector3};
|
||||
pub use crate::ratsnest::{Ratline, Ratsnest};
|
||||
|
|
|
|||
|
|
@ -2,12 +2,67 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use derive_getters::Getters;
|
||||
use derive_more::{
|
||||
Add, AddAssign, Constructor, Div, DivAssign, From, Into, Mul, MulAssign, Sub, SubAssign,
|
||||
};
|
||||
use polygon_unionfind::UnionFind;
|
||||
use rstar::AABB;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct Rect2<T> {
|
||||
min: Vector2<T>,
|
||||
max: Vector2<T>,
|
||||
}
|
||||
|
||||
impl<T: Ord + Copy> Rect2<T> {
|
||||
pub fn new(from: Vector2<T>, to: Vector2<T>) -> Self {
|
||||
Self {
|
||||
min: Vector2::new(std::cmp::min(from.x, to.x), std::cmp::min(from.y, to.y)),
|
||||
max: Vector2::new(std::cmp::max(from.x, to.x), std::cmp::max(from.y, to.y)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rect2<i64> {
|
||||
pub fn aabb3(self, z: i64) -> AABB<[i64; 3]> {
|
||||
AABB::from_corners([self.min.x, self.min.y, z], [self.max.x, self.max.y, z])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
pub struct Rect3<T> {
|
||||
min: Vector3<T>,
|
||||
max: Vector3<T>,
|
||||
}
|
||||
|
||||
impl<T: Ord + Copy> Rect3<T> {
|
||||
pub fn new(from: Vector3<T>, to: Vector3<T>) -> Self {
|
||||
Self {
|
||||
min: Vector3::new(
|
||||
std::cmp::min(from.x, to.x),
|
||||
std::cmp::min(from.y, to.y),
|
||||
std::cmp::min(from.z, to.z),
|
||||
),
|
||||
max: Vector3::new(
|
||||
std::cmp::max(from.x, to.x),
|
||||
std::cmp::max(from.y, to.y),
|
||||
std::cmp::max(from.z, to.z),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Rect3<i64> {
|
||||
pub fn aabb3(self) -> AABB<[i64; 3]> {
|
||||
AABB::from_corners(
|
||||
[self.min.x, self.min.y, self.min.z],
|
||||
[self.max.x, self.max.y, self.max.z],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Add,
|
||||
AddAssign,
|
||||
|
|
@ -35,6 +90,40 @@ pub struct Vector2<T> {
|
|||
pub y: T,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Add,
|
||||
AddAssign,
|
||||
Clone,
|
||||
Constructor,
|
||||
Copy,
|
||||
Debug,
|
||||
Deserialize,
|
||||
Div,
|
||||
DivAssign,
|
||||
Eq,
|
||||
From,
|
||||
Into,
|
||||
Mul,
|
||||
MulAssign,
|
||||
Ord,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Serialize,
|
||||
Sub,
|
||||
SubAssign,
|
||||
)]
|
||||
pub struct Vector3<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
}
|
||||
|
||||
impl<T: Copy> Vector3<T> {
|
||||
pub fn xy(self) -> Vector2<T> {
|
||||
Vector2::new(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> From<[T; 2]> for Vector2<T> {
|
||||
fn from(from: [T; 2]) -> Self {
|
||||
Self {
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ impl NavmesherBoard {
|
|||
segments: Vec::new(),
|
||||
vias: Vec::new(),
|
||||
});
|
||||
let joint_id = self.board.add_joint(spec);
|
||||
let joint_id = self.board.insert_joint(spec);
|
||||
self.joint_multiobstacles.insert(
|
||||
joint_id.index(),
|
||||
self.navmesher.insert_multiobstacle(layer, obstacle),
|
||||
|
|
@ -281,7 +281,7 @@ impl NavmesherBoard {
|
|||
pub fn insert_segment_with_cache(&mut self, segment: Segment) -> SegmentId {
|
||||
let layer = segment.layer;
|
||||
let obstacle = segment.bounding_rectangle();
|
||||
let segment_id = self.board.add_segment_raw(segment);
|
||||
let segment_id = self.board.insert_segment_raw(segment);
|
||||
self.segment_multiobstacles.insert(
|
||||
segment_id.index(),
|
||||
self.navmesher.insert_multiobstacle(layer, obstacle),
|
||||
|
|
@ -291,7 +291,7 @@ impl NavmesherBoard {
|
|||
}
|
||||
|
||||
pub fn insert_polygon(&mut self, polygon: Polygon) -> PolygonId {
|
||||
let polygon_id = self.board.add_polygon(polygon.clone());
|
||||
let polygon_id = self.board.insert_polygon(polygon.clone());
|
||||
self.polygon_multiobstacles.insert(
|
||||
polygon_id.index(),
|
||||
self.navmesher
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use specctra::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
board::{Board, LayerDesc, LayerTier, LayerType},
|
||||
board::{Board, LayerDesc, LayerSide, LayerType},
|
||||
layout::LayerId,
|
||||
layout::compounds::{ComponentId, NetId, PinId},
|
||||
math::Vector2,
|
||||
|
|
@ -31,11 +31,11 @@ impl Board {
|
|||
BiBTreeMap::from_iter(dsn.pcb.structure.layers.iter().enumerate().map(
|
||||
|(index, _layer)| {
|
||||
let tier = if index == 0 {
|
||||
LayerTier::Top
|
||||
LayerSide::Top
|
||||
} else if index + 1 == dsn.pcb.structure.layers.len() {
|
||||
LayerTier::Bottom
|
||||
LayerSide::Bottom
|
||||
} else {
|
||||
LayerTier::Inner
|
||||
LayerSide::Inner
|
||||
};
|
||||
(
|
||||
LayerId::new(index + pcb_layer_offset),
|
||||
|
|
@ -48,7 +48,7 @@ impl Board {
|
|||
top_outline_layer_id,
|
||||
LayerDesc::new(
|
||||
LayerType::Outline,
|
||||
LayerTier::Top,
|
||||
LayerSide::Top,
|
||||
top_outline_layer_id.index(),
|
||||
),
|
||||
);
|
||||
|
|
@ -56,7 +56,7 @@ impl Board {
|
|||
bottom_outline_layer_id,
|
||||
LayerDesc::new(
|
||||
LayerType::Outline,
|
||||
LayerTier::Bottom,
|
||||
LayerSide::Bottom,
|
||||
bottom_outline_layer_id.index(),
|
||||
),
|
||||
);
|
||||
|
|
@ -356,7 +356,7 @@ impl Board {
|
|||
flip: bool,
|
||||
coordinate_scale: f64,
|
||||
) {
|
||||
board.add_joint(JointSpec {
|
||||
board.insert_joint(JointSpec {
|
||||
position: Self::pos(place, pin_pos, 0.0, 0.0, flip, coordinate_scale),
|
||||
layer,
|
||||
net,
|
||||
|
|
@ -381,7 +381,7 @@ impl Board {
|
|||
flip: bool,
|
||||
coordinate_scale: f64,
|
||||
) {
|
||||
board.add_polygon(Polygon {
|
||||
board.insert_polygon(Polygon {
|
||||
vertices: vec![
|
||||
Self::pos(place, pin_pos, x1, y1, flip, coordinate_scale),
|
||||
Self::pos(place, pin_pos, x2, y1, flip, coordinate_scale),
|
||||
|
|
@ -416,7 +416,7 @@ impl Board {
|
|||
flip,
|
||||
coordinate_scale,
|
||||
);
|
||||
let mut prev_joint = board.add_joint(JointSpec {
|
||||
let mut prev_joint = board.insert_joint(JointSpec {
|
||||
position: prev_pos,
|
||||
layer,
|
||||
radius: Self::scale_size(width / 2.0, coordinate_scale),
|
||||
|
|
@ -432,7 +432,7 @@ impl Board {
|
|||
continue;
|
||||
}
|
||||
|
||||
let joint = board.add_joint(JointSpec {
|
||||
let joint = board.insert_joint(JointSpec {
|
||||
position: pos,
|
||||
layer,
|
||||
radius: Self::scale_size(width / 2.0, coordinate_scale),
|
||||
|
|
@ -441,7 +441,7 @@ impl Board {
|
|||
pin,
|
||||
});
|
||||
|
||||
let _ = board.add_segment_raw(Segment {
|
||||
let _ = board.insert_segment_raw(Segment {
|
||||
spec: SegmentSpec {
|
||||
endjoints: [prev_joint, joint],
|
||||
half_width: Self::scale_size(width / 2.0, coordinate_scale),
|
||||
|
|
@ -475,7 +475,7 @@ impl Board {
|
|||
.iter()
|
||||
.map(|coord| Self::pos(place, pin_pos, coord.x, coord.y, flip, coordinate_scale))
|
||||
.collect();
|
||||
board.add_polygon(Polygon {
|
||||
board.insert_polygon(Polygon {
|
||||
vertices,
|
||||
layer,
|
||||
net,
|
||||
|
|
|
|||
Loading…
Reference in New Issue