diff --git a/locales/en-US/main.ftl b/locales/en-US/main.ftl index b7ef7d3..538d684 100644 --- a/locales/en-US/main.ftl +++ b/locales/en-US/main.ftl @@ -35,6 +35,7 @@ tr-menu-route-planar-autoroute = Planar Autoroute tr-menu-route-topo-autoroute = Topological planar Autoroute tr-menu-route-routed-band-width = Routed Band Width tr-menu-route-fanout-clearance = Fanout Clearance +tr-menu-route-autoplace = Autoplace tr-menu-debug = Debug tr-menu-debug-highlight-obstacles = Highlight Obstacles diff --git a/topola-egui/src/actions.rs b/topola-egui/src/actions.rs index 702e054..d0b177c 100644 --- a/topola-egui/src/actions.rs +++ b/topola-egui/src/actions.rs @@ -12,6 +12,7 @@ use crate::{ pub struct Actions { pub file: FileActions, + pub run: RunActions, pub debug: DebugActions, } @@ -19,6 +20,7 @@ impl Actions { pub fn new(tr: &Translator) -> Self { Self { file: FileActions::new(tr), + run: RunActions::new(tr), debug: DebugActions::new(tr), } } @@ -74,6 +76,29 @@ impl FileActions { } } +pub struct RunActions { + pub autoplace: Trigger, +} + +impl RunActions { + pub fn new(tr: &Translator) -> Self { + Self { + autoplace: Action::new( + tr.text("tr-menu-route-autoplace"), + egui::Modifiers::NONE, + egui::Key::Space, + ) + .into_trigger(), + } + } + + pub fn render_menu(&mut self, ctx: &egui::Context, ui: &mut egui::Ui, have_workspace: bool) { + ui.add_enabled_ui(have_workspace, |ui| { + self.autoplace.button(ctx, ui); + }); + } +} + pub struct DebugActions { pub fix_step_rate: Switch, } diff --git a/topola-egui/src/app.rs b/topola-egui/src/app.rs index 079f5f6..03c9999 100644 --- a/topola-egui/src/app.rs +++ b/topola-egui/src/app.rs @@ -125,8 +125,12 @@ impl eframe::App for App { /// Called each time the UI has to be repainted. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - self.menu_bar - .update(ctx, &mut self.translator, self.content_channel.0.clone()); + self.menu_bar.update( + ctx, + &mut self.translator, + self.content_channel.0.clone(), + self.controller.as_mut(), + ); self.update_state(); diff --git a/topola-egui/src/controller.rs b/topola-egui/src/controller.rs index 9fa2206..4ad1510 100644 --- a/topola-egui/src/controller.rs +++ b/topola-egui/src/controller.rs @@ -11,18 +11,19 @@ use crate::{layers_panel::LayersPanel, translator::Translator}; pub struct Controller { pub workspace: Workspace, pub appearance_panel: LayersPanel, - pub master_interactor: Option, - pub dt_accum: f64, + pub master_interactor: MasterInteractor, + dt_accum: f64, } impl Controller { pub fn new(board: Board, tr: &Translator) -> Self { let appearance_panel = LayersPanel::new(&board); + let workspace = Workspace::new_board(board); Self { - workspace: Workspace::new_board(board), + master_interactor: MasterInteractor::new(workspace.selection().clone()), + workspace, appearance_panel, - master_interactor: None, dt_accum: 0.0, } } @@ -59,11 +60,8 @@ impl Controller { true } - pub fn step(&mut self, tr: &Translator) -> ControlFlow<()> { - self.master_interactor - .as_mut() - .map(|master_interactor| master_interactor.step(self.workspace.board_mut())); - ControlFlow::Continue(()) + pub fn step(&mut self, _tr: &Translator) -> ControlFlow<()> { + self.master_interactor.step(self.workspace.board_mut()) } pub fn update_appearance_panel(&mut self, ctx: &egui::Context) { @@ -87,13 +85,22 @@ impl Controller { ui: &mut egui::Ui, ) { if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { - if let (Some(interactor), Workspace::Board(workspace)) = - (&mut self.master_interactor, &mut self.workspace) - { - interactor.abort(&mut workspace.board); - *self.workspace.selection_mut() = interactor.selection().clone(); + let board_master = + if let MasterInteractor::Autoplacer(interactor) = &self.master_interactor { + Some(interactor.board_master().clone()) + } else { + None + }; + + if let Workspace::Board(workspace) = &mut self.workspace { + self.master_interactor.abort(&mut workspace.board); + + if let Some(board_master) = board_master { + self.master_interactor = MasterInteractor::Board(board_master); + } + + *self.workspace.selection_mut() = self.master_interactor.selection().clone(); } - self.master_interactor = None; } let primary_pressed = ctx.input(|i| i.pointer.button_pressed(egui::PointerButton::Primary)); @@ -110,21 +117,20 @@ impl Controller { maybe_pointer_on_scene = Some(pointer_on_scene); if primary_pressed && scene_hovered { - self.master_interactor = - Some(MasterInteractor::new(self.workspace.selection().clone())); + self.master_interactor = MasterInteractor::new(self.workspace.selection().clone()); } - if let (Some(interactor), Workspace::Board(workspace)) = - (&mut self.master_interactor, &mut self.workspace) - { + if let Workspace::Board(workspace) = &mut self.workspace { if primary_down { - interactor.hold( + self.master_interactor.hold( &mut workspace.board, self.appearance_panel.active, pointer_on_scene, ); - if let Some(select_interactor) = interactor.select_interactor().as_ref() { + if let Some(select_interactor) = + self.master_interactor.select_interactor().as_ref() + { let origin = *select_interactor.origin(); let drag_rect_scene = egui::Rect::from_min_max( egui::pos2( @@ -160,27 +166,25 @@ impl Controller { } if primary_released { - if let Some(mut interactor) = self.master_interactor.take() { - let active = self.appearance_panel.active; - let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| { - interactor - .select_interactor() - .as_ref() - .map(|select_interactor| *select_interactor.origin()) - .unwrap_or(Vector2::new(0, 0)) - }); - if let Workspace::Board(workspace) = &mut self.workspace { - interactor.release(&mut workspace.board, active, pointer_for_scene); - *self.workspace.selection_mut() = interactor.selection().clone(); - } + let active = self.appearance_panel.active; + let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| { + self.master_interactor + .select_interactor() + .as_ref() + .map(|select_interactor| *select_interactor.origin()) + .unwrap_or(Vector2::new(0, 0)) + }); + if let Workspace::Board(workspace) = &mut self.workspace { + self.master_interactor + .release(&mut workspace.board, active, pointer_for_scene); + *self.workspace.selection_mut() = self.master_interactor.selection().clone(); } } if delete_pressed { - let mut interactor = MasterInteractor::new(self.workspace.selection().clone()); if let Workspace::Board(workspace) = &mut self.workspace { - interactor.delete(&mut workspace.board); - *self.workspace.selection_mut() = interactor.selection().clone(); + self.master_interactor.delete(&mut workspace.board); + *self.workspace.selection_mut() = self.master_interactor.selection().clone(); } } } diff --git a/topola-egui/src/display.rs b/topola-egui/src/display.rs index 2d3e57b..379ff6a 100644 --- a/topola-egui/src/display.rs +++ b/topola-egui/src/display.rs @@ -26,6 +26,7 @@ impl Display { self.display_layout(ctx, ui, /*menu_bar,*/ viewport, workspace); self.display_repulsions(ui, viewport, workspace); self.display_attractions(ui, viewport, workspace); + self.display_retentions(ui, viewport, workspace); self.display_bboxes(ctx, ui, viewport, workspace); self.display_navmeshes(ctx, ui, viewport, workspace); self.display_ratsnest(ctx, ui, viewport, workspace); @@ -152,8 +153,8 @@ impl Display { .boundary() .iter() .map(|p| egui::Pos2 { - x: p[0] as f32, - y: p[1] as f32, + x: p.x as f32, + y: p.y as f32, }) .collect::>(), egui::Stroke::new(5.0 / viewport.scale_factor(), egui::Color32::WHITE), @@ -201,6 +202,47 @@ impl Display { } } + fn display_retentions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { + let board = workspace.workspace.board(); + let layout = board.layout(); + let stroke = egui::Stroke::new( + 150.0 / viewport.scale_factor(), + egui::Color32::from_rgb(192, 64, 255), + ); + + for selector in &workspace.workspace.selection().components.0 { + let Some(component_id) = board.component_id(&selector.component) else { + continue; + }; + + let Some(bbox) = layout.component_bbox2(component_id) else { + continue; + }; + + let origin = Vector2::new((bbox.min.x + bbox.max.x) / 2, (bbox.min.y + bbox.max.y) / 2); + + Self::paint_arrows( + ui, + origin, + layout.component_retentions(component_id), + stroke, + ); + } + + for selector in &workspace.workspace.selection().pins.0 { + let Some(pin_id) = board.pin_id(&selector.pin) else { + continue; + }; + + Self::paint_arrows( + ui, + layout.pin_centroid(pin_id), + layout.pin_retentions(pin_id), + stroke, + ); + } + } + fn display_attractions(&mut self, ui: &egui::Ui, viewport: &Viewport, workspace: &Controller) { let board = workspace.workspace.board(); let layout = board.layout(); diff --git a/topola-egui/src/menu_bar.rs b/topola-egui/src/menu_bar.rs index 28d9c8f..49fa01b 100644 --- a/topola-egui/src/menu_bar.rs +++ b/topola-egui/src/menu_bar.rs @@ -14,10 +14,12 @@ use specctra::{ read::ListTokenizer, structure::DsnFile, }; +use topola::AutoplacerSchedule; use crate::{ actions::Actions, app::{execute, handle_file}, + controller::Controller, translator::Translator, }; @@ -40,17 +42,22 @@ impl MenuBar { ctx: &egui::Context, tr: &mut Translator, content_sender: Sender>, + controller: Option<&mut Controller>, ) { let mut actions = Actions::new(tr); egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::MenuBar::new().ui(ui, |ui| { ui.menu_button("File", |ui| { - actions.file.render_menu(ctx, ui, false); + actions.file.render_menu(ctx, ui, controller.is_some()); }); ui.separator(); + actions.run.render_menu(ctx, ui, controller.is_some()); + + ui.separator(); + MenuButton::new(tr.text("tr-menu-debug")) .config( MenuConfig::default() @@ -95,6 +102,21 @@ impl MenuBar { } }); } + + if actions.run.autoplace.consume_key_triggered(ctx, ui) { + if let Some(controller) = controller { + controller.master_interactor.autoplace( + controller.workspace.board_mut(), + AutoplacerSchedule { + initial_temperature: 1000.0, + temperature_common_ratio: 0.95, + initial_std_dev: 1000.0, + std_dev_common_ratio: 0.995, + max_steps: 200, + }, + ); + } + } }); } } diff --git a/topola-egui/src/viewport.rs b/topola-egui/src/viewport.rs index c509a91..369be97 100644 --- a/topola-egui/src/viewport.rs +++ b/topola-egui/src/viewport.rs @@ -124,23 +124,23 @@ impl Viewport { fn boundary_bounding_box(controller: &Controller) -> egui::Rect { let first = controller.workspace.board().layout().boundary()[0]; - let mut min_x = first[0]; - let mut max_x = first[0]; - let mut min_y = first[1]; - let mut max_y = first[1]; + let mut min_x = first.x; + let mut max_x = first.x; + let mut min_y = first.y; + let mut max_y = first.y; for point in controller.workspace.board().layout().boundary()[1..].iter() { - if point[0] < min_x { - min_x = point[0]; + if point.x < min_x { + min_x = point.x; } - if point[0] > max_x { - max_x = point[0]; + if point.x > max_x { + max_x = point.x; } - if point[1] < min_y { - min_y = point[1]; + if point.y < min_y { + min_y = point.y; } - if point[1] > max_y { - max_y = point[1]; + if point.y > max_y { + max_y = point.y; } } diff --git a/topola/src/autoplacer/interactors/master.rs b/topola/src/autoplacer/interactors/master.rs index 5154153..9974dfa 100644 --- a/topola/src/autoplacer/interactors/master.rs +++ b/topola/src/autoplacer/interactors/master.rs @@ -2,11 +2,15 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use std::ops::ControlFlow; + +use derive_getters::Getters; + use crate::{ autoplacer::{Autoplacer, AutoplacerSchedule}, board::{ Board, - interactors::{MasterInteractor as BoardMasterInteractor, SelectInteractor}, + interactors::{BoardMasterInteractor, SelectInteractor}, selections::PersistableSelection, }, interactor::Interactor, @@ -14,20 +18,23 @@ use crate::{ vector::Vector2, }; -pub struct MasterInteractor { +#[derive(Getters)] +pub struct AutoplacerMasterInteractor { board_master: BoardMasterInteractor, autoplacer: Autoplacer, } -impl MasterInteractor { +impl AutoplacerMasterInteractor { pub fn new( board: &mut Board, - selection: PersistableSelection, + board_master: BoardMasterInteractor, schedule: AutoplacerSchedule, ) -> Self { + let selection = board_master.selection().components.clone(); + Self { - board_master: BoardMasterInteractor::new(selection.clone()), - autoplacer: Autoplacer::new(board, selection.components.clone(), schedule), + board_master, + autoplacer: Autoplacer::new(board, selection, schedule), } } @@ -40,9 +47,9 @@ impl MasterInteractor { } } -impl Interactor for MasterInteractor { - fn step(&mut self, board: &mut Board) { - self.autoplacer.step(board); +impl Interactor for AutoplacerMasterInteractor { + fn step(&mut self, board: &mut Board) -> ControlFlow<()> { + self.autoplacer.step(board) } fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) { diff --git a/topola/src/autoplacer/interactors/mod.rs b/topola/src/autoplacer/interactors/mod.rs index c29522b..1b26d5e 100644 --- a/topola/src/autoplacer/interactors/mod.rs +++ b/topola/src/autoplacer/interactors/mod.rs @@ -4,4 +4,4 @@ mod master; -pub use master::MasterInteractor; +pub use master::AutoplacerMasterInteractor; diff --git a/topola/src/autoplacer/mod.rs b/topola/src/autoplacer/mod.rs index 89abcf0..a3608be 100644 --- a/topola/src/autoplacer/mod.rs +++ b/topola/src/autoplacer/mod.rs @@ -4,6 +4,8 @@ pub mod interactors; +use std::ops::ControlFlow; + use rand::RngExt; use rand_distr::{Distribution, Normal}; use undoredo::{FlushDelta, ResetDelta}; @@ -17,10 +19,11 @@ use crate::{ }; pub struct AutoplacerSchedule { - initial_temperature: f64, - temperature_common_ratio: f64, - initial_std_dev: f64, - std_dev_common_ratio: f64, + pub initial_temperature: f64, + pub temperature_common_ratio: f64, + pub initial_std_dev: f64, + pub std_dev_common_ratio: f64, + pub max_steps: u64, } #[derive(Clone, Copy)] @@ -32,7 +35,7 @@ pub struct AutoplacerStepParams { pub struct Autoplacer { components: Vec, schedule: AutoplacerSchedule, - step_counter: u32, + step_counter: u64, origin_delta: BoardDelta, //rng: ThreadRng, } @@ -50,22 +53,29 @@ impl Autoplacer { } } - pub fn step(&mut self, board: &mut Board) -> bool { - self.step_with_params( - board, - AutoplacerStepParams { - temperature: self.schedule.initial_temperature - * self - .schedule - .temperature_common_ratio - .powf(self.step_counter as f64), - std_dev: self.schedule.initial_std_dev - * self - .schedule - .std_dev_common_ratio - .powf(self.step_counter as f64), - }, - ) + pub fn step(&mut self, board: &mut Board) -> ControlFlow<()> { + if self.step_counter < self.schedule.max_steps { + let control_flow = self.step_with_params( + board, + AutoplacerStepParams { + temperature: self.schedule.initial_temperature + * self + .schedule + .temperature_common_ratio + .powf(self.step_counter as f64), + std_dev: self.schedule.initial_std_dev + * self + .schedule + .std_dev_common_ratio + .powf(self.step_counter as f64), + }, + ); + + self.step_counter += 1; + control_flow + } else { + ControlFlow::Break(()) + } } // TODO. @@ -73,10 +83,15 @@ impl Autoplacer { }*/ - fn step_with_params(&mut self, board: &mut Board, params: AutoplacerStepParams) -> bool { + fn step_with_params( + &mut self, + board: &mut Board, + params: AutoplacerStepParams, + ) -> ControlFlow<()> { for &component in self.components.iter() { //self.step_component_with_params(component, params); - let last_cost = self.cost(board, params); + //let last_cost = self.cost(board, params); + let last_cost = self.component_cost(board, component, params); let dx_gaussian = Normal::new(0.0, params.std_dev).unwrap(); let dy_gaussian = Normal::new(0.0, params.std_dev).unwrap(); @@ -86,13 +101,14 @@ impl Autoplacer { let dx = dx_gaussian.sample(&mut rand::rng()); let dy = dy_gaussian.sample(&mut rand::rng()); - board.move_resolved_components_by(&self.components, Vector2::new(dx as i64, dy as i64)); + board.move_resolved_components_by(&[component], Vector2::new(dx as i64, dy as i64)); - let new_cost = self.cost(board, params); + //let new_cost = self.cost(board, params); + let new_cost = self.component_cost(board, component, params); let delta_cost = new_cost - last_cost; - if delta_cost <= 0.0 - || f64::exp(-delta_cost / params.temperature) <= rand::rng().random() + if delta_cost < 0.0 + || rand::rng().random::() < f64::exp(-delta_cost / params.temperature) { self.origin_delta = self.origin_delta.clone().merge_delta(board.flush_delta()); } else { @@ -100,21 +116,21 @@ impl Autoplacer { } } - true + ControlFlow::Continue(()) } - fn cost(&self, board: &Board, params: AutoplacerStepParams) -> f64 { + /*fn cost(&self, board: &Board, params: AutoplacerStepParams) -> f64 { self.components .iter() .map(|&component| self.component_cost(board, component, params)) .sum() - } + }*/ fn component_cost( &self, board: &Board, component: ComponentId, - _params: AutoplacerStepParams, + params: AutoplacerStepParams, ) -> f64 { let layout = board.layout(); @@ -126,8 +142,12 @@ impl Autoplacer { .component_attractions(component) .map(|vector| 1.0 / (1.0 + (vector.x.abs() + vector.y.abs()) as f64)) .sum(); + let retention_cost: i64 = layout + .component_retentions(component) + .map(|vector| 100 * (vector.x.abs() + vector.y.abs())) + .sum(); - repulsion_cost as f64 + attraction_cost + repulsion_cost as f64 + attraction_cost + retention_cost as f64 } /*fn step_component_with_params( diff --git a/topola/src/board/interactors/master.rs b/topola/src/board/interactors/master.rs index d83928f..f509ecf 100644 --- a/topola/src/board/interactors/master.rs +++ b/topola/src/board/interactors/master.rs @@ -16,13 +16,13 @@ use crate::{ }; #[derive(Clone, Debug, Eq, Getters, PartialEq)] -pub struct MasterInteractor { +pub struct BoardMasterInteractor { select_interactor: Option, drag_move_interactor: Option, selection: PersistableSelection, } -impl MasterInteractor { +impl BoardMasterInteractor { pub fn new(selection: PersistableSelection) -> Self { Self { select_interactor: None, @@ -32,7 +32,7 @@ impl MasterInteractor { } } -impl Interactor for MasterInteractor { +impl Interactor for BoardMasterInteractor { fn delete(&mut self, board: &mut Board) { board.delete_net_free_primitives(self.selection.nets.clone()); } diff --git a/topola/src/board/interactors/mod.rs b/topola/src/board/interactors/mod.rs index 0b8705b..3d6cfd3 100644 --- a/topola/src/board/interactors/mod.rs +++ b/topola/src/board/interactors/mod.rs @@ -9,7 +9,7 @@ mod select; pub use drag_move::DragMoveInteractor; pub use drag_select::{DragSelectInteractor, DragSelectOptions}; -pub use master::MasterInteractor; +pub use master::BoardMasterInteractor; pub use select::SelectInteractor; use serde::{Deserialize, Serialize}; diff --git a/topola/src/board/mod.rs b/topola/src/board/mod.rs index 44fed60..49d2366 100644 --- a/topola/src/board/mod.rs +++ b/topola/src/board/mod.rs @@ -61,11 +61,7 @@ impl Board { net_names: BiBTreeMap, ) -> Self { Self { - layout: Layout::new( - boundary.into_iter().map(Into::into).collect(), - layer_descs.len(), - net_names.len(), - ), + layout: Layout::new(boundary, layer_descs.len(), net_names.len()), component_names: Recorder::new(BiBTreeMap::new()), pin_names: Recorder::new(BiBTreeMap::new()), layer_descs: Recorder::new(layer_descs), diff --git a/topola/src/interactor.rs b/topola/src/interactor.rs index c10acab..178f55e 100644 --- a/topola/src/interactor.rs +++ b/topola/src/interactor.rs @@ -2,14 +2,23 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +use std::ops::ControlFlow; + use crate::{ - board::{Board, interactors::SelectInteractor, selections::PersistableSelection}, + autoplacer::{AutoplacerSchedule, interactors::AutoplacerMasterInteractor}, + board::{ + Board, + interactors::{BoardMasterInteractor, SelectInteractor}, + selections::PersistableSelection, + }, layout::LayerId, vector::Vector2, }; pub trait Interactor { - fn step(&mut self, board: &mut Board) {} + fn step(&mut self, _board: &mut Board) -> ControlFlow<()> { + ControlFlow::Continue(()) + } fn delete(&mut self, board: &mut Board) {} fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) {} fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2) {} @@ -17,35 +26,57 @@ pub trait Interactor { } pub enum MasterInteractor { - Board(crate::board::interactors::MasterInteractor), - Autoplacer(crate::autoplacer::interactors::MasterInteractor), + Board(BoardMasterInteractor), + Autoplacer(AutoplacerMasterInteractor), } impl MasterInteractor { pub fn new(selection: PersistableSelection) -> Self { - Self::Board(crate::board::interactors::MasterInteractor::new(selection)) + Self::Board(crate::board::interactors::BoardMasterInteractor::new( + selection, + )) + } + + pub fn autoplace(&mut self, board: &mut Board, schedule: AutoplacerSchedule) { + match self { + Self::Board(board_master) => { + *self = Self::Autoplacer(AutoplacerMasterInteractor::new( + board, + board_master.clone(), + schedule, + )); + } + _ => (), + //_ => panic!("autoplacement can be only started from board at rest"), + } } pub fn selection(&self) -> &PersistableSelection { match self { - Self::Board(interactor) => interactor.selection(), - Self::Autoplacer(interactor) => interactor.selection(), + Self::Board(board_master) => board_master.selection(), + Self::Autoplacer(autoplacer_master) => autoplacer_master.selection(), } } pub fn select_interactor(&self) -> &Option { match self { - Self::Board(interactor) => interactor.select_interactor(), - Self::Autoplacer(interactor) => interactor.select_interactor(), + Self::Board(board_master) => board_master.select_interactor(), + Self::Autoplacer(autoplacer_master) => autoplacer_master.select_interactor(), } } } impl Interactor for MasterInteractor { - fn step(&mut self, board: &mut Board) { + fn step(&mut self, board: &mut Board) -> ControlFlow<()> { match self { Self::Board(interactor) => interactor.step(board), - Self::Autoplacer(interactor) => interactor.step(board), + Self::Autoplacer(interactor) => { + if interactor.step(board).is_break() { + *self = Self::Board(interactor.board_master().clone()); + } + + ControlFlow::Continue(()) + } } } diff --git a/topola/src/layout/mod.rs b/topola/src/layout/mod.rs index 2c03189..fa2640c 100644 --- a/topola/src/layout/mod.rs +++ b/topola/src/layout/mod.rs @@ -13,6 +13,7 @@ mod modify; mod overlap; pub mod primitives; mod repulsion; +mod retention; mod transforms; use derive_getters::Getters; @@ -26,9 +27,12 @@ use stable_vec::StableVec; use undoredo::aliases::RTreeHalfDelta; use undoredo::{Delta, Recorder}; -use crate::layout::{ - compounds::{Component, ComponentId, Net, Pin, PinId}, - primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId}, +use crate::{ + layout::{ + compounds::{Component, ComponentId, Net, Pin, PinId}, + primitives::{Joint, JointId, Polygon, PolygonId, Segment, SegmentId, Via, ViaId}, + }, + vector::Vector2, }; #[derive( @@ -57,9 +61,9 @@ impl LayerId { #[derive(Clone, Debug, Delta, Getters)] pub struct Layout { #[undoredo(skip)] - boundary: Vec<[i64; 2]>, + boundary: Vec>, #[undoredo(skip)] - place_boundary: Vec<[i64; 2]>, + place_boundary: Vec>, #[undoredo(skip)] layer_count: usize, @@ -91,7 +95,7 @@ pub struct Layout { } impl Layout { - pub fn new(boundary: Vec<[i64; 2]>, layer_count: usize, net_count: usize) -> Self { + pub fn new(boundary: Vec>, layer_count: usize, net_count: usize) -> Self { let mut nets = StableVec::new(); for _ in 0..net_count { nets.push(Net::default()); diff --git a/topola/src/layout/primitives/mod.rs b/topola/src/layout/primitives/mod.rs index 6964bd3..ad1e0c6 100644 --- a/topola/src/layout/primitives/mod.rs +++ b/topola/src/layout/primitives/mod.rs @@ -15,7 +15,10 @@ pub use polygon::*; pub use segment::*; pub use via::*; -use crate::layout::{Layout, compounds::PinId}; +use crate::{ + Rect2, + layout::{Layout, compounds::PinId}, +}; #[derive(Clone, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize)] pub enum PrimitiveId { @@ -34,4 +37,13 @@ impl Layout { PrimitiveId::Polygon(polygon_id) => self.polygon(polygon_id).pin, } } + + pub fn primitive_bbox2(&self, primitive: PrimitiveId) -> Rect2 { + match primitive { + PrimitiveId::Joint(joint_id) => self.joint(joint_id).bbox().xy(), + PrimitiveId::Segment(segment_id) => self.segment(segment_id).bbox().xy(), + PrimitiveId::Via(via_id) => self.via(via_id).bbox().xy(), + PrimitiveId::Polygon(polygon_id) => self.polygon(polygon_id).bbox().xy(), + } + } } diff --git a/topola/src/layout/repulsion.rs b/topola/src/layout/repulsion.rs index 5b7119e..45e6865 100644 --- a/topola/src/layout/repulsion.rs +++ b/topola/src/layout/repulsion.rs @@ -19,10 +19,10 @@ impl Layout { &self, infringer: ComponentId, orientation: Orientation, - ) -> impl Iterator> { + ) -> impl Iterator> + '_ { self.locate_component_infringements(infringer) - .map(move |infringement| { - self.component_component_repulsion( + .flat_map(move |infringement| { + self.component_component_repulsions( infringement.infringer(), infringement.infringee(), orientation, @@ -30,28 +30,25 @@ impl Layout { }) } - pub fn component_component_repulsion( + pub fn component_component_repulsions( &self, infringer: ComponentId, infringee: ComponentId, orientation: Orientation, - ) -> Vector2 { - let mut max_repulsion = Vector2::new(0, 0); - let mut max_repulsion_magnitude = 0; - - for &infringer_pin in self.component(infringer).pins.iter() { - for &infringee_pin in self.component(infringee).pins.iter() { - let repulsion = self.pin_pin_repulsion(infringer_pin, infringee_pin, orientation); - let repulsion_magnitude = repulsion.x.abs() + repulsion.y.abs(); - - if repulsion_magnitude > max_repulsion_magnitude { - max_repulsion = repulsion; - max_repulsion_magnitude = repulsion_magnitude; - } - } - } - - max_repulsion + ) -> impl Iterator> + '_ { + self.component(infringer) + .pins + .iter() + .copied() + .flat_map(move |infringer_pin| { + self.component(infringee) + .pins + .iter() + .copied() + .flat_map(move |infringee_pin| { + self.pin_pin_repulsions(infringer_pin, infringee_pin, orientation) + }) + }) } pub fn locate_pin_repulsions( @@ -60,8 +57,8 @@ impl Layout { orientation: Orientation, ) -> impl Iterator> + '_ { self.locate_pin_infringements(infringer) - .map(move |infringement| { - self.pin_pin_repulsion( + .flat_map(move |infringement| { + self.pin_pin_repulsions( infringement.infringer(), infringement.infringee(), orientation, @@ -69,32 +66,25 @@ impl Layout { }) } - pub fn pin_pin_repulsion( + pub fn pin_pin_repulsions( &self, infringer: PinId, infringee: PinId, orientation: Orientation, - ) -> Vector2 { - let mut max_repulsion = Vector2::new(0, 0); - let mut max_repulsion_magnitude = 0; - - for infringer_primitive in self.pin(infringer).primitives() { - for infringee_primitive in self.pin(infringee).primitives() { - let repulsion = self.primitive_primitive_repulsion( - infringer_primitive, - infringee_primitive, - orientation, - ); - let repulsion_magnitude = repulsion.x.abs() + repulsion.y.abs(); - - if repulsion_magnitude > max_repulsion_magnitude { - max_repulsion = repulsion; - max_repulsion_magnitude = repulsion_magnitude; - } - } - } - - max_repulsion + ) -> impl Iterator> + '_ { + self.pin(infringer) + .primitives() + .flat_map(move |infringer_primitive| { + self.pin(infringee) + .primitives() + .map(move |infringee_primitive| { + self.primitive_primitive_repulsion( + infringer_primitive, + infringee_primitive, + orientation, + ) + }) + }) } pub fn primitive_primitive_repulsion( diff --git a/topola/src/layout/retention.rs b/topola/src/layout/retention.rs new file mode 100644 index 0000000..431da1b --- /dev/null +++ b/topola/src/layout/retention.rs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{ + Rect2, + layout::{ + Layout, + compounds::{ComponentId, PinId}, + primitives::PrimitiveId, + }, + vector::Vector2, +}; + +impl Layout { + pub fn component_retentions( + &self, + violator: ComponentId, + ) -> impl Iterator> + '_ { + self.component(violator) + .pins + .iter() + .copied() + .flat_map(move |pin_id| self.pin_retentions(pin_id)) + } + + pub fn pin_retentions(&self, violator: PinId) -> impl Iterator> + '_ { + self.pin(violator) + .primitives() + .flat_map(move |primitive| self.primitive_retentions(primitive)) + } + + pub fn primitive_retentions( + &self, + violator: PrimitiveId, + ) -> impl Iterator> + '_ { + let boundary = self.place_boundary(); + std::array::IntoIter::new(self.primitive_bbox2(violator).corners()) + .map(move |corner| Self::point_retention(corner, boundary)) + } + + fn point_retention(violator: Vector2, boundary: &[Vector2]) -> Vector2 { + if boundary.len() < 3 || violator.inside_polygon(boundary) { + return Vector2::new(0, 0); + } + + violator.closest_point_on_polygon_boundary(boundary) - violator + } +} diff --git a/topola/src/lib.rs b/topola/src/lib.rs index 4182b2a..050e948 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -20,6 +20,7 @@ mod specctra; mod vector; mod workspace; +pub use crate::autoplacer::AutoplacerSchedule; pub use crate::autorouter::Autorouter; pub use crate::board::selections; pub use crate::interactor::{Interactor, MasterInteractor}; diff --git a/topola/src/navmesher.rs b/topola/src/navmesher.rs index bbcf86c..d77a384 100644 --- a/topola/src/navmesher.rs +++ b/topola/src/navmesher.rs @@ -206,11 +206,7 @@ impl NavmesherBoard { pub fn new(board: Board) -> Self { let mut this = Self { navmesher: Navmesher::new( - board - .layout() - .boundary() - .iter() - .map(|p| Vector2::new(p[0], p[1])), + board.layout().boundary().iter().copied(), *board.layout().layer_count(), ), board, diff --git a/topola/src/vector.rs b/topola/src/vector.rs index 801d2e5..f59789b 100644 --- a/topola/src/vector.rs +++ b/topola/src/vector.rs @@ -144,6 +144,72 @@ impl_vector2_inside_polygon!(u128); impl_vector2_inside_polygon!(f32); impl_vector2_inside_polygon!(f64); +macro_rules! impl_vector2_closest_point_on_boundary { + ($type:ty) => { + impl Vector2<$type> { + pub fn closest_point_on_segment( + self, + segment_start: Vector2<$type>, + segment_end: Vector2<$type>, + ) -> Vector2<$type> { + let abx = segment_end.x - segment_start.x; + let aby = segment_end.y - segment_start.y; + let apx = self.x - segment_start.x; + let apy = self.y - segment_start.y; + let ab_len_sq = abx * abx + aby * aby; + + if ab_len_sq == 0 as $type { + return segment_start; + } + + let t = (apx * abx + apy * aby).clamp(0 as $type, ab_len_sq); + + Vector2::new( + segment_start.x + abx * t / ab_len_sq, + segment_start.y + aby * t / ab_len_sq, + ) + } + + pub fn closest_point_on_polygon_boundary( + self, + polygon: &[Vector2<$type>], + ) -> Vector2<$type> { + let mut closest_point = polygon[0]; + let mut best_distance_sq = <$type as Bounded>::max_value(); + + for i in 0..polygon.len() { + let segment_start = polygon[i]; + let segment_end = polygon[(i + 1) % polygon.len()]; + let candidate = self.closest_point_on_segment(segment_start, segment_end); + let dx = self.x - candidate.x; + let dy = self.y - candidate.y; + let distance_sq = dx * dx + dy * dy; + + if distance_sq < best_distance_sq { + best_distance_sq = distance_sq; + closest_point = candidate; + } + } + + closest_point + } + } + }; +} + +impl_vector2_closest_point_on_boundary!(i8); +impl_vector2_closest_point_on_boundary!(i16); +impl_vector2_closest_point_on_boundary!(i32); +impl_vector2_closest_point_on_boundary!(i64); +impl_vector2_closest_point_on_boundary!(i128); +impl_vector2_closest_point_on_boundary!(u8); +impl_vector2_closest_point_on_boundary!(u16); +impl_vector2_closest_point_on_boundary!(u32); +impl_vector2_closest_point_on_boundary!(u64); +impl_vector2_closest_point_on_boundary!(u128); +impl_vector2_closest_point_on_boundary!(f32); +impl_vector2_closest_point_on_boundary!(f64); + macro_rules! impl_vector2_rotate_around_point { ($type:ty) => { impl Vector2<$type> { @@ -197,10 +263,12 @@ impl_polygon_centroid!(i8); impl_polygon_centroid!(i16); impl_polygon_centroid!(i32); impl_polygon_centroid!(i64); +impl_polygon_centroid!(i128); impl_polygon_centroid!(u8); impl_polygon_centroid!(u16); impl_polygon_centroid!(u32); impl_polygon_centroid!(u64); +impl_polygon_centroid!(u128); impl_polygon_centroid!(f32); impl_polygon_centroid!(f64);