First crude attempt at autoplacing using simulated annealing

This commit is contained in:
Mikolaj Wielgus 2026-06-05 17:56:57 +02:00
parent 65f96f1fc2
commit dabb82490e
12 changed files with 198 additions and 89 deletions

View File

@ -35,6 +35,7 @@ tr-menu-route-planar-autoroute = Planar Autoroute
tr-menu-route-topo-autoroute = Topological planar Autoroute tr-menu-route-topo-autoroute = Topological planar Autoroute
tr-menu-route-routed-band-width = Routed Band Width tr-menu-route-routed-band-width = Routed Band Width
tr-menu-route-fanout-clearance = Fanout Clearance tr-menu-route-fanout-clearance = Fanout Clearance
tr-menu-route-autoplace = Autoplace
tr-menu-debug = Debug tr-menu-debug = Debug
tr-menu-debug-highlight-obstacles = Highlight Obstacles tr-menu-debug-highlight-obstacles = Highlight Obstacles

View File

@ -12,6 +12,7 @@ use crate::{
pub struct Actions { pub struct Actions {
pub file: FileActions, pub file: FileActions,
pub run: RunActions,
pub debug: DebugActions, pub debug: DebugActions,
} }
@ -19,6 +20,7 @@ impl Actions {
pub fn new(tr: &Translator) -> Self { pub fn new(tr: &Translator) -> Self {
Self { Self {
file: FileActions::new(tr), file: FileActions::new(tr),
run: RunActions::new(tr),
debug: DebugActions::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 struct DebugActions {
pub fix_step_rate: Switch, pub fix_step_rate: Switch,
} }

View File

@ -125,8 +125,12 @@ impl eframe::App for App {
/// Called each time the UI has to be repainted. /// Called each time the UI has to be repainted.
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.menu_bar self.menu_bar.update(
.update(ctx, &mut self.translator, self.content_channel.0.clone()); ctx,
&mut self.translator,
self.content_channel.0.clone(),
self.controller.as_mut(),
);
self.update_state(); self.update_state();

View File

@ -11,18 +11,19 @@ use crate::{layers_panel::LayersPanel, translator::Translator};
pub struct Controller { pub struct Controller {
pub workspace: Workspace, pub workspace: Workspace,
pub appearance_panel: LayersPanel, pub appearance_panel: LayersPanel,
pub master_interactor: Option<MasterInteractor>, pub master_interactor: MasterInteractor,
pub dt_accum: f64, dt_accum: f64,
} }
impl Controller { impl Controller {
pub fn new(board: Board, tr: &Translator) -> Self { pub fn new(board: Board, tr: &Translator) -> Self {
let appearance_panel = LayersPanel::new(&board); let appearance_panel = LayersPanel::new(&board);
let workspace = Workspace::new_board(board);
Self { Self {
workspace: Workspace::new_board(board), master_interactor: MasterInteractor::new(workspace.selection().clone()),
workspace,
appearance_panel, appearance_panel,
master_interactor: None,
dt_accum: 0.0, dt_accum: 0.0,
} }
} }
@ -59,11 +60,8 @@ impl Controller {
true true
} }
pub fn step(&mut self, tr: &Translator) -> ControlFlow<()> { pub fn step(&mut self, _tr: &Translator) -> ControlFlow<()> {
self.master_interactor self.master_interactor.step(self.workspace.board_mut())
.as_mut()
.map(|master_interactor| master_interactor.step(self.workspace.board_mut()));
ControlFlow::Continue(())
} }
pub fn update_appearance_panel(&mut self, ctx: &egui::Context) { pub fn update_appearance_panel(&mut self, ctx: &egui::Context) {
@ -87,13 +85,22 @@ impl Controller {
ui: &mut egui::Ui, ui: &mut egui::Ui,
) { ) {
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
if let (Some(interactor), Workspace::Board(workspace)) = let board_master =
(&mut self.master_interactor, &mut self.workspace) if let MasterInteractor::Autoplacer(interactor) = &self.master_interactor {
{ Some(interactor.board_master().clone())
interactor.abort(&mut workspace.board); } else {
*self.workspace.selection_mut() = interactor.selection().clone(); 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)); 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); maybe_pointer_on_scene = Some(pointer_on_scene);
if primary_pressed && scene_hovered { if primary_pressed && scene_hovered {
self.master_interactor = self.master_interactor = MasterInteractor::new(self.workspace.selection().clone());
Some(MasterInteractor::new(self.workspace.selection().clone()));
} }
if let (Some(interactor), Workspace::Board(workspace)) = if let Workspace::Board(workspace) = &mut self.workspace {
(&mut self.master_interactor, &mut self.workspace)
{
if primary_down { if primary_down {
interactor.hold( self.master_interactor.hold(
&mut workspace.board, &mut workspace.board,
self.appearance_panel.active, self.appearance_panel.active,
pointer_on_scene, 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 origin = *select_interactor.origin();
let drag_rect_scene = egui::Rect::from_min_max( let drag_rect_scene = egui::Rect::from_min_max(
egui::pos2( egui::pos2(
@ -160,27 +166,25 @@ impl Controller {
} }
if primary_released { if primary_released {
if let Some(mut interactor) = self.master_interactor.take() { let active = self.appearance_panel.active;
let active = self.appearance_panel.active; let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| {
let pointer_for_scene = maybe_pointer_on_scene.unwrap_or_else(|| { self.master_interactor
interactor .select_interactor()
.select_interactor() .as_ref()
.as_ref() .map(|select_interactor| *select_interactor.origin())
.map(|select_interactor| *select_interactor.origin()) .unwrap_or(Vector2::new(0, 0))
.unwrap_or(Vector2::new(0, 0)) });
}); if let Workspace::Board(workspace) = &mut self.workspace {
if let Workspace::Board(workspace) = &mut self.workspace { self.master_interactor
interactor.release(&mut workspace.board, active, pointer_for_scene); .release(&mut workspace.board, active, pointer_for_scene);
*self.workspace.selection_mut() = interactor.selection().clone(); *self.workspace.selection_mut() = self.master_interactor.selection().clone();
}
} }
} }
if delete_pressed { if delete_pressed {
let mut interactor = MasterInteractor::new(self.workspace.selection().clone());
if let Workspace::Board(workspace) = &mut self.workspace { if let Workspace::Board(workspace) = &mut self.workspace {
interactor.delete(&mut workspace.board); self.master_interactor.delete(&mut workspace.board);
*self.workspace.selection_mut() = interactor.selection().clone(); *self.workspace.selection_mut() = self.master_interactor.selection().clone();
} }
} }
} }

View File

@ -14,10 +14,12 @@ use specctra::{
read::ListTokenizer, read::ListTokenizer,
structure::DsnFile, structure::DsnFile,
}; };
use topola::AutoplacerSchedule;
use crate::{ use crate::{
actions::Actions, actions::Actions,
app::{execute, handle_file}, app::{execute, handle_file},
controller::Controller,
translator::Translator, translator::Translator,
}; };
@ -40,17 +42,22 @@ impl MenuBar {
ctx: &egui::Context, ctx: &egui::Context,
tr: &mut Translator, tr: &mut Translator,
content_sender: Sender<Result<DsnFile, ParseErrorContext>>, content_sender: Sender<Result<DsnFile, ParseErrorContext>>,
controller: Option<&mut Controller>,
) { ) {
let mut actions = Actions::new(tr); let mut actions = Actions::new(tr);
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
egui::MenuBar::new().ui(ui, |ui| { egui::MenuBar::new().ui(ui, |ui| {
ui.menu_button("File", |ui| { ui.menu_button("File", |ui| {
actions.file.render_menu(ctx, ui, false); actions.file.render_menu(ctx, ui, controller.is_some());
}); });
ui.separator(); ui.separator();
actions.run.render_menu(ctx, ui, controller.is_some());
ui.separator();
MenuButton::new(tr.text("tr-menu-debug")) MenuButton::new(tr.text("tr-menu-debug"))
.config( .config(
MenuConfig::default() 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.99,
initial_std_dev: 1000.0,
std_dev_common_ratio: 0.99,
max_steps: 2000,
},
);
}
}
}); });
} }
} }

View File

@ -2,11 +2,15 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use std::ops::ControlFlow;
use derive_getters::Getters;
use crate::{ use crate::{
autoplacer::{Autoplacer, AutoplacerSchedule}, autoplacer::{Autoplacer, AutoplacerSchedule},
board::{ board::{
Board, Board,
interactors::{MasterInteractor as BoardMasterInteractor, SelectInteractor}, interactors::{BoardMasterInteractor, SelectInteractor},
selections::PersistableSelection, selections::PersistableSelection,
}, },
interactor::Interactor, interactor::Interactor,
@ -14,20 +18,23 @@ use crate::{
vector::Vector2, vector::Vector2,
}; };
pub struct MasterInteractor { #[derive(Getters)]
pub struct AutoplacerMasterInteractor {
board_master: BoardMasterInteractor, board_master: BoardMasterInteractor,
autoplacer: Autoplacer, autoplacer: Autoplacer,
} }
impl MasterInteractor { impl AutoplacerMasterInteractor {
pub fn new( pub fn new(
board: &mut Board, board: &mut Board,
selection: PersistableSelection, board_master: BoardMasterInteractor,
schedule: AutoplacerSchedule, schedule: AutoplacerSchedule,
) -> Self { ) -> Self {
let selection = board_master.selection().components.clone();
Self { Self {
board_master: BoardMasterInteractor::new(selection.clone()), board_master,
autoplacer: Autoplacer::new(board, selection.components.clone(), schedule), autoplacer: Autoplacer::new(board, selection, schedule),
} }
} }
@ -40,9 +47,9 @@ impl MasterInteractor {
} }
} }
impl Interactor for MasterInteractor { impl Interactor for AutoplacerMasterInteractor {
fn step(&mut self, board: &mut Board) { fn step(&mut self, board: &mut Board) -> ControlFlow<()> {
self.autoplacer.step(board); self.autoplacer.step(board)
} }
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) { fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {

View File

@ -4,4 +4,4 @@
mod master; mod master;
pub use master::MasterInteractor; pub use master::AutoplacerMasterInteractor;

View File

@ -4,6 +4,8 @@
pub mod interactors; pub mod interactors;
use std::ops::ControlFlow;
use rand::RngExt; use rand::RngExt;
use rand_distr::{Distribution, Normal}; use rand_distr::{Distribution, Normal};
use undoredo::{FlushDelta, ResetDelta}; use undoredo::{FlushDelta, ResetDelta};
@ -17,10 +19,11 @@ use crate::{
}; };
pub struct AutoplacerSchedule { pub struct AutoplacerSchedule {
initial_temperature: f64, pub initial_temperature: f64,
temperature_common_ratio: f64, pub temperature_common_ratio: f64,
initial_std_dev: f64, pub initial_std_dev: f64,
std_dev_common_ratio: f64, pub std_dev_common_ratio: f64,
pub max_steps: u64,
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -32,7 +35,7 @@ pub struct AutoplacerStepParams {
pub struct Autoplacer { pub struct Autoplacer {
components: Vec<ComponentId>, components: Vec<ComponentId>,
schedule: AutoplacerSchedule, schedule: AutoplacerSchedule,
step_counter: u32, step_counter: u64,
origin_delta: BoardDelta, //rng: ThreadRng, origin_delta: BoardDelta, //rng: ThreadRng,
} }
@ -50,22 +53,29 @@ impl Autoplacer {
} }
} }
pub fn step(&mut self, board: &mut Board) -> bool { pub fn step(&mut self, board: &mut Board) -> ControlFlow<()> {
self.step_with_params( if self.step_counter < self.schedule.max_steps {
board, let control_flow = self.step_with_params(
AutoplacerStepParams { board,
temperature: self.schedule.initial_temperature AutoplacerStepParams {
* self temperature: self.schedule.initial_temperature
.schedule * self
.temperature_common_ratio .schedule
.powf(self.step_counter as f64), .temperature_common_ratio
std_dev: self.schedule.initial_std_dev .powf(self.step_counter as f64),
* self std_dev: self.schedule.initial_std_dev
.schedule * self
.std_dev_common_ratio .schedule
.powf(self.step_counter as f64), .std_dev_common_ratio
}, .powf(self.step_counter as f64),
) },
);
self.step_counter += 1;
control_flow
} else {
ControlFlow::Break(())
}
} }
// TODO. // TODO.
@ -73,7 +83,11 @@ 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() { for &component in self.components.iter() {
//self.step_component_with_params(component, params); //self.step_component_with_params(component, params);
let last_cost = self.cost(board, params); let last_cost = self.cost(board, params);
@ -100,7 +114,7 @@ impl Autoplacer {
} }
} }
true ControlFlow::Continue(())
} }
fn cost(&self, board: &Board, params: AutoplacerStepParams) -> f64 { fn cost(&self, board: &Board, params: AutoplacerStepParams) -> f64 {

View File

@ -16,13 +16,13 @@ use crate::{
}; };
#[derive(Clone, Debug, Eq, Getters, PartialEq)] #[derive(Clone, Debug, Eq, Getters, PartialEq)]
pub struct MasterInteractor { pub struct BoardMasterInteractor {
select_interactor: Option<SelectInteractor>, select_interactor: Option<SelectInteractor>,
drag_move_interactor: Option<DragMoveInteractor>, drag_move_interactor: Option<DragMoveInteractor>,
selection: PersistableSelection, selection: PersistableSelection,
} }
impl MasterInteractor { impl BoardMasterInteractor {
pub fn new(selection: PersistableSelection) -> Self { pub fn new(selection: PersistableSelection) -> Self {
Self { Self {
select_interactor: None, select_interactor: None,
@ -32,7 +32,7 @@ impl MasterInteractor {
} }
} }
impl Interactor for MasterInteractor { impl Interactor for BoardMasterInteractor {
fn delete(&mut self, board: &mut Board) { fn delete(&mut self, board: &mut Board) {
board.delete_net_free_primitives(self.selection.nets.clone()); board.delete_net_free_primitives(self.selection.nets.clone());
} }

View File

@ -9,7 +9,7 @@ mod select;
pub use drag_move::DragMoveInteractor; pub use drag_move::DragMoveInteractor;
pub use drag_select::{DragSelectInteractor, DragSelectOptions}; pub use drag_select::{DragSelectInteractor, DragSelectOptions};
pub use master::MasterInteractor; pub use master::BoardMasterInteractor;
pub use select::SelectInteractor; pub use select::SelectInteractor;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -2,14 +2,23 @@
// //
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use std::ops::ControlFlow;
use crate::{ use crate::{
board::{Board, interactors::SelectInteractor, selections::PersistableSelection}, autoplacer::{AutoplacerSchedule, interactors::AutoplacerMasterInteractor},
board::{
Board,
interactors::{BoardMasterInteractor, SelectInteractor},
selections::PersistableSelection,
},
layout::LayerId, layout::LayerId,
vector::Vector2, vector::Vector2,
}; };
pub trait Interactor { 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 delete(&mut self, board: &mut Board) {}
fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {} fn hold(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {}
fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {} fn release(&mut self, board: &mut Board, layer: LayerId, pointer: Vector2<i64>) {}
@ -17,35 +26,57 @@ pub trait Interactor {
} }
pub enum MasterInteractor { pub enum MasterInteractor {
Board(crate::board::interactors::MasterInteractor), Board(BoardMasterInteractor),
Autoplacer(crate::autoplacer::interactors::MasterInteractor), Autoplacer(AutoplacerMasterInteractor),
} }
impl MasterInteractor { impl MasterInteractor {
pub fn new(selection: PersistableSelection) -> Self { 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 { pub fn selection(&self) -> &PersistableSelection {
match self { match self {
Self::Board(interactor) => interactor.selection(), Self::Board(board_master) => board_master.selection(),
Self::Autoplacer(interactor) => interactor.selection(), Self::Autoplacer(autoplacer_master) => autoplacer_master.selection(),
} }
} }
pub fn select_interactor(&self) -> &Option<SelectInteractor> { pub fn select_interactor(&self) -> &Option<SelectInteractor> {
match self { match self {
Self::Board(interactor) => interactor.select_interactor(), Self::Board(board_master) => board_master.select_interactor(),
Self::Autoplacer(interactor) => interactor.select_interactor(), Self::Autoplacer(autoplacer_master) => autoplacer_master.select_interactor(),
} }
} }
} }
impl Interactor for MasterInteractor { impl Interactor for MasterInteractor {
fn step(&mut self, board: &mut Board) { fn step(&mut self, board: &mut Board) -> ControlFlow<()> {
match self { match self {
Self::Board(interactor) => interactor.step(board), 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(())
}
} }
} }

View File

@ -20,6 +20,7 @@ mod specctra;
mod vector; mod vector;
mod workspace; mod workspace;
pub use crate::autoplacer::AutoplacerSchedule;
pub use crate::autorouter::Autorouter; pub use crate::autorouter::Autorouter;
pub use crate::board::selections; pub use crate::board::selections;
pub use crate::interactor::{Interactor, MasterInteractor}; pub use crate::interactor::{Interactor, MasterInteractor};