diff --git a/src/bin/topola-egui/app.rs b/src/bin/topola-egui/app.rs index 6ccc4a6..b01ea78 100644 --- a/src/bin/topola-egui/app.rs +++ b/src/bin/topola-egui/app.rs @@ -76,6 +76,9 @@ pub struct App { #[serde(skip)] maybe_layers: Option, + #[serde(skip)] + maybe_design: Option, + #[serde(skip)] update_counter: f32, } @@ -92,6 +95,7 @@ impl Default for App { top: Top::new(), bottom: Bottom::new(), maybe_layers: None, + maybe_design: None, update_counter: 0.0, } } @@ -124,6 +128,7 @@ impl App { let board = design.make_board(); self.maybe_overlay = Some(Overlay::new(&board).unwrap()); self.maybe_layers = Some(Layers::new(&board)); + self.maybe_design = Some(design); self.arc_mutex_maybe_invoker = Arc::new(Mutex::new(Some(Invoker::new( Autorouter::new(board).unwrap(), )))) @@ -165,6 +170,7 @@ impl eframe::App for App { self.arc_mutex_maybe_invoker.clone(), &mut self.maybe_execute, &mut self.maybe_overlay, + &self.maybe_design, ); if let Some(ref mut layers) = self.maybe_layers { diff --git a/src/bin/topola-egui/top.rs b/src/bin/topola-egui/top.rs index fc12d57..feb4c1e 100644 --- a/src/bin/topola-egui/top.rs +++ b/src/bin/topola-egui/top.rs @@ -1,13 +1,14 @@ use std::{ fs::File, sync::{mpsc::Sender, Arc, Mutex}, + path::Path, }; use topola::{ autorouter::invoker::{ Command, Execute, ExecuteWithStatus, Invoker, InvokerError, InvokerStatus, }, - specctra::mesadata::SpecctraMesadata, + specctra::{mesadata::SpecctraMesadata, design::SpecctraDesign}, }; use crate::{ @@ -40,9 +41,12 @@ impl Top { arc_mutex_maybe_invoker: Arc>>>, maybe_execute: &mut Option, maybe_overlay: &mut Option, + maybe_design: &Option, ) -> Result<(), InvokerError> { let mut open_design = Trigger::new(Action::new("Open", egui::Modifiers::CTRL, egui::Key::O)); + let mut export_session = + Trigger::new(Action::new("Export session file", egui::Modifiers::CTRL, egui::Key::S)); let mut import_history = Trigger::new(Action::new( "Import history", egui::Modifiers::CTRL, @@ -72,6 +76,7 @@ impl Top { egui::menu::bar(ui, |ui| { ui.menu_button("File", |ui| { open_design.button(ctx, ui); + export_session.button(ctx, ui); ui.separator(); @@ -119,6 +124,37 @@ impl Top { ctx.request_repaint(); } }); + } else if export_session.consume_key_triggered(ctx, ui) { + if let Some(design) = maybe_design { + if let Some(invoker) = + arc_mutex_maybe_invoker.clone().lock().unwrap().as_ref() + { + let ctx = ui.ctx().clone(); + let board = invoker.autorouter().board(); + + // FIXME: I don't know how to avoid buffering the entire exported file + let mut writebuf = vec![]; + + design.write_ses(board, &mut writebuf); + + let mut dialog = rfd::AsyncFileDialog::new(); + if let Some(filename) = Path::new(design.get_name()).file_stem() { + if let Some(filename) = filename.to_str() { + dialog = dialog.set_file_name(filename); + } + } + let task = dialog + .add_filter("Specctra session file", &["ses"]) + .save_file(); + + execute(async move { + if let Some(file_handle) = task.await { + file_handle.write(&writebuf).await; + ctx.request_repaint(); + } + }); + } + } } else if import_history.consume_key_triggered(ctx, ui) { let ctx = ctx.clone(); let task = rfd::AsyncFileDialog::new().pick_file(); diff --git a/src/specctra/design.rs b/src/specctra/design.rs index 887d9d6..a64db24 100644 --- a/src/specctra/design.rs +++ b/src/specctra/design.rs @@ -5,12 +5,23 @@ use thiserror::Error; use crate::{ board::{mesadata::AccessMesadata, Board}, - drawing::{dot::FixedDotWeight, seg::FixedSegWeight, Drawing}, + drawing::{ + dot::FixedDotWeight, + seg::FixedSegWeight, + Drawing, + graph::{GetMaybeNet, GetLayer, MakePrimitive}, + primitive::MakePrimitiveShape + }, + geometry::{ + primitive::{PrimitiveShape}, + GetWidth, + }, layout::{poly::SolidPolyWeight, Layout}, math::Circle, specctra::{ mesadata::SpecctraMesadata, read::{self, ListTokenizer}, + write::{self, ListWriter}, structure::{self, DsnFile, Layer, Pcb, Shape}, }, }; @@ -36,6 +47,80 @@ impl SpecctraDesign { Ok(Self { pcb: dsn.pcb }) } + pub fn get_name(&self) -> &str { + &self.pcb.name + } + + pub fn write_ses( + &self, + board: &Board, + writer: impl std::io::Write, + ) -> Result<(), std::io::Error> { + let mesadata = board.mesadata(); + let drawing = board.layout().drawing(); + //dbg!(&geometry); + + let mut net_outs = HashMap::::new(); + for index in drawing.primitive_nodes() { + let primitive = index.primitive(drawing); + match primitive.shape() { + PrimitiveShape::Seg(seg) => { + if let Some(net) = primitive.maybe_net() { + let net_name = mesadata.net_netname(net).unwrap().to_owned(); + + let wire = structure::Wire { + path: structure::Path { + layer: mesadata.layer_layername(primitive.layer()).unwrap().to_owned(), + width: primitive.width(), + coords: vec![ + structure::Point { x: seg.from.x(), y: seg.from.y() }, + structure::Point { x: seg.to.x(), y: seg.to.y() }, + ], + }, + net: net_name.clone(), + r#type: "route".to_owned(), + }; + + if let Some(net) = net_outs.get_mut(&net) { + net.wire.push(wire); + } else { + net_outs.insert( + net, + structure::NetOut { + name: net_name.clone(), + wire: vec![wire], + via: Vec::new(), + }, + ); + } + } + }, + _ => (), + } + } + + let ses = structure::SesFile { + session: structure::Session { + id: "ID".to_string(), + routes: structure::Routes { + resolution: structure::Resolution { + unit: "um".into(), + value: 1.0, + }, + library_out: structure::Library { + images: Vec::new(), + padstacks: Vec::new(), + }, + network_out: structure::NetworkOut { + net: net_outs.into_values().collect(), + }, + }, + }, + }; + + ListWriter::new(writer).write_value(&ses) + } + pub fn make_board(&self) -> Board { let mesadata = SpecctraMesadata::from_pcb(&self.pcb); let mut board = Board::new(Layout::new(Drawing::new(