Compare commits

...

9 Commits

Author SHA1 Message Date
Mikolaj Wielgus 623dc967ca Add methods to delete primitives and distribute some methods across new files 2026-05-27 03:28:48 +02:00
Mikolaj Wielgus 5a3a870a13 Make containment mode dependent on left-to-right, right-to-left mouse drag 2026-05-27 02:02:05 +02:00
Mikolaj Wielgus 15a7bc65f1 Implement selection of nets 2026-05-27 00:45:30 +02:00
Mikolaj Wielgus 51005bb97c Make drag selection interactor take selection modes as input 2026-05-27 00:21:13 +02:00
Mikolaj Wielgus 739bdafea2 Replace `RouteSelector` with `NetSelector`
I don't think identifying routes by pin name pair is going to be really
useful.
2026-05-26 12:22:16 +02:00
Mikolaj Wielgus 61c0134db4 Add drag selection interactors 2026-05-26 12:12:36 +02:00
Mikolaj Wielgus 1b4bb49a89 Add location methods for inside rect and intersecting rect
I did some renames while at it.
2026-05-26 03:02:21 +02:00
Mikolaj Wielgus d9a4c7a11f Add missing code for vias 2026-05-25 23:32:39 +02:00
Mikolaj Wielgus eef69694fd Move location methods to new files 2026-05-25 23:04:54 +02:00
30 changed files with 1637 additions and 520 deletions

View File

@ -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()
}

View File

@ -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.

View File

@ -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);

View File

@ -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),

View File

@ -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);
}
})

View File

@ -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);
}
}

View File

@ -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)
}
}

View File

@ -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())
}
}

View File

@ -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,
}

View File

@ -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)
}
}

View File

@ -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),
},
}
}

204
topola/src/board/locate.rs Normal file
View File

@ -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)),
)
}
}

View File

@ -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)
}

View File

@ -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);

View File

@ -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);
}
}
}
}

View File

@ -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};

View File

@ -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);
}
}
}
}

View File

@ -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(),
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}
}

126
topola/src/layout/delete.rs Normal file
View File

@ -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());
}
}

163
topola/src/layout/insert.rs Normal file
View File

@ -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
}
}

167
topola/src/layout/locate.rs Normal file
View File

@ -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()
}
}

View File

@ -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()]
}

134
topola/src/layout/modify.rs Normal file
View File

@ -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), ());
}
}

View File

@ -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;

View File

@ -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};

View File

@ -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 {

View File

@ -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

View File

@ -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,