diff --git a/Cargo.toml b/Cargo.toml index 27f4e5c..d8f3063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,15 +16,10 @@ required-features = ["cli"] name = "topola-egui" required-features = ["egui"] -[[bin]] -name = "topola-sdl2-demo" -required-features = ["sdl2"] - [features] default = ["disable_contracts"] cli = ["dep:clap"] egui = ["dep:eframe", "dep:egui", "dep:rfd", "dep:futures"] -sdl2 = ["dep:sdl2", "dep:gl", "dep:pathfinder_canvas", "dep:pathfinder_geometry", "dep:pathfinder_gl", "dep:pathfinder_renderer", "dep:pathfinder_resources"] disable_contracts = ["contracts/disable_contracts"] [dependencies] @@ -75,12 +70,6 @@ version = "0.14.0" optional = true version = "0.3.30" -[dependencies.sdl2] -optional = true -version = "0.35.2" -default-features = false -features = ["bundled"] - [dependencies.gl] optional = true version = "0.14.0" diff --git a/INSTALL.md b/INSTALL.md index 44dbc62..7b38ed4 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -44,16 +44,6 @@ To build and open Topola in your browser, run trunk serve -### SDL2 demo - -Optionally, for shorter build times you may build the SDL2 demo instead -of the Egui port: - - cargo build --features sdl2 --bin topola-sdl2-demo - cargo run --features sdl2 --bin topola-sdl2-demo - -The downside is that the SDL2 demo's user interface is highly incomplete. - ### Automated tests Topola has automated tests to make sure its basic functionalities work. diff --git a/src/bin/topola-egui/top.rs b/src/bin/topola-egui/top.rs index feb4c1e..3ecf924 100644 --- a/src/bin/topola-egui/top.rs +++ b/src/bin/topola-egui/top.rs @@ -1,14 +1,14 @@ use std::{ fs::File, - sync::{mpsc::Sender, Arc, Mutex}, path::Path, + sync::{mpsc::Sender, Arc, Mutex}, }; use topola::{ autorouter::invoker::{ Command, Execute, ExecuteWithStatus, Invoker, InvokerError, InvokerStatus, }, - specctra::{mesadata::SpecctraMesadata, design::SpecctraDesign}, + specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata}, }; use crate::{ @@ -45,8 +45,11 @@ impl Top { ) -> 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 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, diff --git a/src/bin/topola-sdl2-demo/main.rs b/src/bin/topola-sdl2-demo/main.rs deleted file mode 100644 index 472579f..0000000 --- a/src/bin/topola-sdl2-demo/main.rs +++ /dev/null @@ -1,382 +0,0 @@ -extern crate sdl2; - -mod painter; - -macro_rules! dbg_dot { - ($graph:expr) => { - use petgraph::dot::Dot; - println!("{:?}", Dot::new(&$graph)); - }; -} - -use geo::point; -use painter::Painter; -use petgraph::graph::NodeIndex; -use petgraph::visit::{EdgeRef, IntoEdgeReferences}; -use topola::autorouter::selection::Selection; -use topola::autorouter::{Autorouter, AutorouterStatus}; -use topola::drawing::dot::FixedDotWeight; -use topola::drawing::graph::{MakePrimitive, PrimitiveIndex}; -use topola::drawing::primitive::MakePrimitiveShape; -use topola::drawing::rules::{AccessRules, Conditions}; -use topola::drawing::seg::FixedSegWeight; -use topola::drawing::{Infringement, LayoutException}; -use topola::geometry::primitive::{AccessPrimitiveShape, PrimitiveShape}; -use topola::geometry::shape::AccessShape; -use topola::layout::poly::MakePolyShape; -use topola::layout::Layout; -use topola::router::draw::DrawException; -use topola::router::navmesh::Navmesh; -use topola::router::tracer::Tracer; -use topola::specctra::design::SpecctraDesign; -use topola::specctra::mesadata::SpecctraMesadata; - -use sdl2::event::Event; -use sdl2::keyboard::Keycode; -use sdl2::video::{GLProfile, Window}; -use sdl2::EventPump; - -use pathfinder_canvas::{Canvas, CanvasFontContext, ColorU}; -use pathfinder_geometry::vector::{vec2f, vec2i, Vector2F}; -use pathfinder_gl::{GLDevice, GLVersion}; -use pathfinder_renderer::concurrent::rayon::RayonExecutor; -use pathfinder_renderer::concurrent::scene_proxy::SceneProxy; -use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererMode, RendererOptions}; -use pathfinder_renderer::gpu::renderer::Renderer; -use pathfinder_renderer::options::BuildOptions; -use pathfinder_resources::embedded::EmbeddedResourceLoader; - -use std::collections::HashMap; -use std::fs::File; -use std::io::BufReader; -use std::time::Duration; - -use topola::math::Circle; -use topola::router::Router; - -struct SimpleRules { - net_clearances: HashMap<(usize, usize), f64>, -} - -impl AccessRules for SimpleRules { - fn clearance(&self, conditions1: &Conditions, conditions2: &Conditions) -> f64 { - if let (Some(net1), Some(net2)) = (conditions1.maybe_net, conditions2.maybe_net) { - *self.net_clearances.get(&(net1, net2)).unwrap_or(&10.0) - } else { - 10.0 - } - } - - fn largest_clearance(&self, maybe_net: Option) -> f64 { - let mut highest_clearance = 0.0; - - if let Some(net) = maybe_net { - for ((net1, net2), clearance) in self.net_clearances.iter() { - if *net1 == net || *net2 == net { - highest_clearance = *clearance; - } - } - } - - highest_clearance - } -} - -// Clunky enum to work around borrow checker. -enum RouterOrLayout<'a, R: AccessRules> { - Router(&'a mut Router<'a, R>), - Layout(&'a Layout), -} - -fn main() -> Result<(), anyhow::Error> { - let sdl_context = sdl2::init().unwrap(); - let video_subsystem = sdl_context.video().unwrap(); - - let gl_attr = video_subsystem.gl_attr(); - gl_attr.set_context_profile(GLProfile::Core); - gl_attr.set_context_version(4, 0); - - let window = video_subsystem - .window("Topola demo", 800, 600) - .opengl() - .position_centered() - .build() - .unwrap(); - - let _context = window.gl_create_context().unwrap(); - gl::load_with(|name| video_subsystem.gl_get_proc_address(name) as *const _); - - // doing this later (after pathfinder assumes control of the context) would be a bad idea - // but for the early clear it's simpler than passing a blank canvas to pathfinder - unsafe { - gl::ClearColor(0.0, 0.0, 0.0, 1.0); - gl::Clear(gl::COLOR_BUFFER_BIT); - } - window.gl_swap_window(); - - // XXX: not sure if this automatically fallbacks if we get a GL3 context - // or if we have to detect it and/or retry - let device = GLDevice::new(GLVersion::GL4, 0); - - let mode = RendererMode::default_for_device(&device); - let options = RendererOptions { - dest: DestFramebuffer::full_window(vec2i(800, 600)), - background_color: Some(ColorU::black().to_f32()), - //show_debug_ui: true, - ..RendererOptions::default() - }; - let resource_loader = EmbeddedResourceLoader::new(); - let mut renderer = Renderer::new(device, &resource_loader, mode, options); - let font_context = CanvasFontContext::from_system_source(); - - // TODO: make a type like this wrapping the details of pathfinder - // so we don't pass so many arguments to render_times() - //let mut canvas = window.into_canvas().build().unwrap(); - - let mut event_pump = sdl_context.event_pump().unwrap(); - let _i = 0; - /*let mut router = Router::new(Layout::new(SimpleRules { - net_clearances: HashMap::from([ - ((1, 2), 8.0), - ((2, 1), 8.0), - ((2, 3), 3.0), - ((3, 2), 3.0), - ((3, 4), 15.0), - ((4, 3), 15.0), - ]), - }));*/ - - let design_file = File::open( - "tests/single_layer/data/de9_tht_female_to_tht_female/de9_tht_female_to_tht_female.dsn", - ) - .unwrap(); - let design_bufread = BufReader::new(design_file); - let design = SpecctraDesign::load(design_bufread)?; - let board = design.make_board(); - - let mut view = View { - pan: vec2f(-80000.0, -60000.0), - zoom: 0.005, - }; - - render_times( - &mut event_pump, - &window, - &mut renderer, - &font_context, - &mut view, - RouterOrLayout::Layout(board.layout()), - None, - None, - &[], - &[], - &[], - -1, - ); - - let mut autorouter = Autorouter::new(board).unwrap(); - if let Ok(mut autoroute) = autorouter.autoroute_walk(&Selection::new()) { - loop { - let status = match autoroute.step(&mut autorouter) { - Ok(status) => status, - Err(err) => break, - }; - - if let AutorouterStatus::Finished = status { - break; - } - } - } - - // these are both on net 1 in the test file - /*let _ = router.route_band( - dot_indices[1], - dot_indices[2], - 3.0, - )?;*/ - - render_times( - &mut event_pump, - &window, - &mut renderer, - &font_context, - &mut view, - RouterOrLayout::Layout(autorouter.board().layout()), - None, - None, - &[], - &[], - &[], - -1, - ); - - Ok(()) -} - -struct View { - pan: Vector2F, - zoom: f32, -} - -fn render_times( - event_pump: &mut EventPump, - window: &Window, - renderer: &mut Renderer, - font_context: &CanvasFontContext, - view: &mut View, - mut router_or_layout: RouterOrLayout, - _unused: Option<()>, - mut maybe_navmesh: Option, - path: &[NodeIndex], - ghosts: &[PrimitiveShape], - highlighteds: &[PrimitiveIndex], - times: i64, -) { - let mut i = 0; - - 'running: loop { - for event in event_pump.poll_iter() { - match event { - Event::Quit { .. } - | Event::KeyDown { - keycode: Some(Keycode::Escape), - .. - } => break 'running, - Event::MouseWheel { y, .. } => { - view.zoom *= f32::powf(1.4, y as f32); - } - Event::MouseMotion { - xrel, - yrel, - mousestate, - .. - } => { - if mousestate.left() { - view.pan += vec2f(xrel as f32, yrel as f32) / view.zoom; - } - } - _ => {} - } - } - - renderer.options_mut().background_color = Some(ColorU::new(0, 10, 35, 255).to_f32()); - - let window_size = window.size(); - let mut canvas = Canvas::new(vec2f(window_size.0 as f32, window_size.1 as f32)) - .get_context_2d(font_context.clone()); - - let center = vec2f(400.0, 300.0); - canvas.translate(center); - canvas.scale(vec2f(view.zoom, view.zoom)); - canvas.translate(-center + view.pan); - - let mut painter = Painter::new(&mut canvas); - - let layout = match router_or_layout { - RouterOrLayout::Router(ref mut router) => { - let state = event_pump.mouse_state(); - - /*if let Some(band) = maybe_band { - router - .reroute_band( - band, - point! {x: state.x() as f64, y: state.y() as f64}, - 3.0, - ) - .ok(); - maybe_navmesh = None; - }*/ - - router.layout() - } - RouterOrLayout::Layout(ref layout) => layout.clone(), - }; - - for node in layout.drawing().layer_primitive_nodes(1) { - let color = if highlighteds.contains(&node) { - ColorU::new(100, 100, 255, 255) - } else { - ColorU::new(52, 52, 200, 255) - }; - - let shape = node.primitive(layout.drawing()).shape(); - painter.paint_primitive(&shape, color, view.zoom); - } - - for poly in layout.layer_poly_nodes(1) { - painter.paint_polygon( - &layout.poly(poly).shape().polygon, - ColorU::new(52, 52, 200, 255), - view.zoom, - ); - } - - for node in layout.drawing().layer_primitive_nodes(0) { - let color = if highlighteds.contains(&node) { - ColorU::new(255, 100, 100, 255) - } else { - ColorU::new(200, 52, 52, 255) - }; - - let shape = node.primitive(layout.drawing()).shape(); - painter.paint_primitive(&shape, color, view.zoom); - } - - for poly in layout.layer_poly_nodes(0) { - painter.paint_polygon( - &layout.poly(poly).shape().polygon, - ColorU::new(200, 52, 52, 255), - view.zoom, - ); - } - - for ghost in ghosts { - painter.paint_primitive(&ghost, ColorU::new(75, 75, 150, 255), view.zoom); - } - - if let Some(ref navmesh) = maybe_navmesh { - for edge in navmesh.graph().edge_references() { - let from = - PrimitiveIndex::from(navmesh.graph().node_weight(edge.source()).unwrap().node) - .primitive(layout.drawing()) - .shape() - .center(); - let to = - PrimitiveIndex::from(navmesh.graph().node_weight(edge.target()).unwrap().node) - .primitive(layout.drawing()) - .shape() - .center(); - - let color = 'blk: { - if let (Some(source_pos), Some(target_pos)) = ( - path.iter().position(|node| *node == edge.source()), - path.iter().position(|node| *node == edge.target()), - ) { - if target_pos == source_pos + 1 { - break 'blk ColorU::new(250, 250, 0, 255); - } - } - - ColorU::new(125, 125, 125, 255) - }; - - painter.paint_edge(from, to, color, view.zoom); - } - } - - let mut scene = SceneProxy::from_scene( - canvas.into_canvas().into_scene(), - renderer.mode().level, - RayonExecutor, - ); - scene.build_and_render(renderer, BuildOptions::default()); - window.gl_swap_window(); - - i += 1; - if times != -1 && i >= times { - return; - } - - ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60)); - } -} diff --git a/src/bin/topola-sdl2-demo/painter.rs b/src/bin/topola-sdl2-demo/painter.rs deleted file mode 100644 index ae2f00f..0000000 --- a/src/bin/topola-sdl2-demo/painter.rs +++ /dev/null @@ -1,98 +0,0 @@ -use geo::{CoordsIter, Point, Polygon}; -use pathfinder_canvas::{ - vec2f, ArcDirection, Canvas, CanvasRenderingContext2D, ColorU, FillRule, Path2D, RectF, -}; -use topola::geometry::primitive::{AccessPrimitiveShape, PrimitiveShape}; - -pub struct Painter<'a> { - canvas: &'a mut CanvasRenderingContext2D, -} - -impl<'a> Painter<'a> { - pub fn new(canvas: &'a mut CanvasRenderingContext2D) -> Self { - Self { canvas } - } - - pub fn paint_primitive(&mut self, shape: &PrimitiveShape, color: ColorU, zoom: f32) { - self.canvas.set_stroke_style(color); - self.canvas.set_fill_style(color); - - match shape { - PrimitiveShape::Dot(dot) => { - let mut path = Path2D::new(); - path.ellipse( - vec2f(dot.circle.pos.x() as f32, -dot.circle.pos.y() as f32), - dot.circle.r as f32, - 0.0, - 0.0, - std::f32::consts::TAU, - ); - self.canvas.fill_path(path, FillRule::Winding); - } - PrimitiveShape::Seg(seg) => { - let mut path = Path2D::new(); - path.move_to(vec2f(seg.from.x() as f32, -seg.from.y() as f32)); - path.line_to(vec2f(seg.to.x() as f32, -seg.to.y() as f32)); - self.canvas.set_line_width(seg.width as f32); - self.canvas.stroke_path(path); - } - PrimitiveShape::Bend(bend) => { - /*let delta1 = bend.from - bend.c.pos; - let delta2 = bend.to - bend.c.pos; - - let angle1 = delta1.y().atan2(delta1.x()); - let angle2 = delta2.y().atan2(delta2.x()); - - let mut path = Path2D::new(); - path.arc( - vec2f(bend.c.pos.x() as f32, -bend.c.pos.y() as f32), - bend.circle().r as f32, - angle1 as f32, - angle2 as f32, - ArcDirection::CW, - ); - self.canvas.set_line_width(bend.width as f32); - self.canvas.stroke_path(path);*/ - } - } - - let envelope = AccessPrimitiveShape::envelope(shape, 0.0); - // XXX: points represented as arrays can't be conveniently converted to vector types - let topleft = vec2f(envelope.lower()[0] as f32, -envelope.upper()[1] as f32); - let bottomright = vec2f(envelope.upper()[0] as f32, -envelope.lower()[1] as f32); - self.canvas.set_line_width(2.0 / zoom); - self.canvas - .set_stroke_style(ColorU::new(100, 100, 100, 255)); - self.canvas - .stroke_rect(RectF::new(topleft, bottomright - topleft)); - } - - pub fn paint_polygon(&mut self, polygon: &Polygon, color: ColorU, zoom: f32) { - let mut path = Path2D::new(); - let mut it = polygon.exterior_coords_iter(); - - if let Some(initial_vertex) = it.next() { - path.move_to(vec2f(initial_vertex.x as f32, -initial_vertex.y as f32)); - } - - for vertex in it { - path.line_to(vec2f(vertex.x as f32, -vertex.y as f32)); - } - - path.close_path(); - - self.canvas.set_stroke_style(color); - self.canvas.set_fill_style(color); - self.canvas.set_line_width(0.0); - self.canvas.fill_path(path, FillRule::Winding); - } - - pub fn paint_edge(&mut self, from: Point, to: Point, color: ColorU, zoom: f32) { - let mut path = Path2D::new(); - path.move_to(vec2f(from.x() as f32, from.y() as f32)); - path.line_to(vec2f(to.x() as f32, to.y() as f32)); - self.canvas.set_stroke_style(color); - self.canvas.set_line_width(2.0 / zoom); - self.canvas.stroke_path(path); - } -} diff --git a/src/specctra/design.rs b/src/specctra/design.rs index 5bb2015..b347a6c 100644 --- a/src/specctra/design.rs +++ b/src/specctra/design.rs @@ -7,22 +7,19 @@ use crate::{ board::{mesadata::AccessMesadata, Board}, drawing::{ dot::FixedDotWeight, + graph::{GetLayer, GetMaybeNet, MakePrimitive}, + primitive::MakePrimitiveShape, seg::FixedSegWeight, Drawing, - graph::{GetMaybeNet, GetLayer, MakePrimitive}, - primitive::MakePrimitiveShape - }, - geometry::{ - primitive::{PrimitiveShape}, - GetWidth, }, + 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}, + write::{self, ListWriter}, }, }; @@ -58,7 +55,6 @@ impl SpecctraDesign { ) -> 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() { @@ -68,10 +64,16 @@ impl SpecctraDesign { let coords = match primitive.shape() { PrimitiveShape::Seg(seg) => { vec![ - structure::Point { x: seg.from.x(), y: seg.from.y() }, - structure::Point { x: seg.to.x(), y: seg.to.y() }, + structure::Point { + x: seg.from.x(), + y: seg.from.y(), + }, + structure::Point { + x: seg.to.x(), + y: seg.to.y(), + }, ] - }, + } PrimitiveShape::Bend(bend) => { // Since general circle arcs don't seem to be supported @@ -86,12 +88,14 @@ impl SpecctraDesign { let mut points = Vec::new(); for i in 0..=segment_count { - let x = circle.pos.x() + circle.r * (angle_from + i as f64 * angle_step).cos(); - let y = circle.pos.y() + circle.r * (angle_from + i as f64 * angle_step).sin(); + let x = circle.pos.x() + + circle.r * (angle_from + i as f64 * angle_step).cos(); + let y = circle.pos.y() + + circle.r * (angle_from + i as f64 * angle_step).sin(); points.push(structure::Point { x, y }); } points - }, + } // Intentionally skipped for now. // Topola stores trace segments and dots joining them @@ -103,7 +107,10 @@ impl SpecctraDesign { let wire = structure::WireOut { path: structure::Path { - layer: mesadata.layer_layername(primitive.layer()).unwrap().to_owned(), + layer: mesadata + .layer_layername(primitive.layer()) + .unwrap() + .to_owned(), width: primitive.width(), coords, },