Merge branch 'develop' into fix-cli-build

This commit is contained in:
hakki 2024-09-24 16:54:57 +00:00
commit df0d0fd918
14 changed files with 301 additions and 127 deletions

View File

@ -67,13 +67,13 @@ version = "0.9.4"
[dependencies.eframe] [dependencies.eframe]
optional = true optional = true
version = "0.26.0" version = "0.28.1"
default-features = false default-features = false
features = ["accesskit", "default_fonts", "glow", "persistence"] features = ["accesskit", "default_fonts", "glow", "persistence"]
[dependencies.egui] [dependencies.egui]
optional = true optional = true
version = "0.26.0" version = "0.28.1"
[dependencies.rfd] [dependencies.rfd]
optional = true optional = true

View File

@ -3,19 +3,30 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!-- Disable zooming: --> <!-- Disable zooming: -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<head> <head>
<title>Topola</title> <title>Topola PCB router</title>
<link data-trunk rel="rust" data-bin="topola-egui" data-cargo-features="egui" data-wasm-opt="2" /> <link data-trunk rel="rust" data-bin="topola-egui" data-cargo-features="egui" data-wasm-opt="2" />
<!-- this is the base url relative to which other urls will be constructed. trunk will insert this from the public-url option -->
<base data-trunk-public-url /> <base data-trunk-public-url />
<link data-trunk rel="icon" href="assets/favicon.ico"> <link data-trunk rel="icon" href="assets/favicon.ico" />
<!-- TODO: More icons -->
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#404040"> <link data-trunk rel="copy-file" href="assets/sw.js"/>
<!--<link data-trunk rel="copy-file" href="assets/manifest.json" data-target-path="assets"/>-->
<!--<link data-trunk rel="copy-file" href="assets/icon-1024.png" data-target-path="assets"/>-->
<!--<link data-trunk rel="copy-file" href="assets/icon-256.png" data-target-path="assets"/>-->
<!--<link data-trunk rel="copy-file" href="assets/icon_ios_touch_192.png" data-target-path="assets"/>-->
<!--<link data-trunk rel="copy-file" href="assets/maskable_icon_x512.png" data-target-path="assets"/>-->
<!--<link rel="manifest" href="assets/manifest.json">-->
<!--<link rel="apple-touch-icon" href="assets/icon_ios_touch_192.png">-->
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#404040" />
<style> <style>
html { html {
@ -47,15 +58,16 @@
width: 100%; width: 100%;
} }
/* Position canvas in center-top: */ /* Make canvas fill entire document: */
canvas { canvas {
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
display: block; display: block;
position: absolute; position: absolute;
top: 0%; top: 0;
left: 50%; left: 0;
transform: translate(-50%, 0%); width: 100%;
height: 100%;
} }
.centered { .centered {
@ -101,22 +113,34 @@
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
</style> </style>
</head> </head>
<body> <body>
<!-- The WASM code will resize the canvas dynamically -->
<!-- the id is hardcoded in main.rs . so, make sure both match. -->
<canvas id="topola-egui"></canvas> <canvas id="topola-egui"></canvas>
<!--Register a service worker to cache the WASM and JS scripts for offline use. --> <!-- the loading spinner will be removed in main.rs -->
<script> <div class="centered" id="loading_text">
// Disable caching during development so that we don't end up seeing an outdated version. <p style="font-size:16px">
Loading…
</p>
<div class="lds-dual-ring"></div>
</div>
<!--Register Service Worker. this will cache the wasm / js scripts for offline use (for PWA functionality). -->
<!-- Force refresh (Ctrl + F5) to load the latest files instead of cached files -->
<!--<script>
// We disable caching during development so that we always view the latest version.
if ('serviceWorker' in navigator && window.location.hash !== "#dev") { if ('serviceWorker' in navigator && window.location.hash !== "#dev") {
window.addEventListener('load', function () { window.addEventListener('load', function () {
navigator.serviceWorker.register('sw.js'); navigator.serviceWorker.register('sw.js');
}); });
} }
</script> </script>-->
</body> </body>
</html> </html>
<!-- Powered by egui: https://github.com/emilk/egui/ -->

View File

@ -1,11 +1,17 @@
menu-file = File menu-file = File
menu-edit = Edit
menu-view = View
menu-place = Place
menu-route = Route
menu-inspect = Inspect
menu-options = Options menu-options = Options
menu-debug = Debug menu-debug = Debug
action-open-dsn = Open action-open-dsn = Open
action-export-ses = Export session file action-export-ses = Export Session File
action-import-cmd = Import history action-import-cmd = Import History
action-export-cmd = Export history action-export-cmd = Export History
action-quit = Quit action-quit = Quit
action-autoroute = Autoroute action-autoroute = Autoroute
@ -17,13 +23,17 @@ action-measure-length = Measure Length
action-undo = Undo action-undo = Undo
action-redo = Redo action-redo = Redo
presort-by-pairwise-detours = Presort by pairwise detours presort-by-pairwise-detours = Presort by Pairwise Detours
squeeze-under-bands = Squeeze under bands squeeze-under-bands = Squeeze under Bands
wrap-around-bands = Wrap around bands wrap-around-bands = Wrap around Bands
zoom-to-fit = Zoom to Fit
show-ratsnest = Show Ratsnest show-ratsnest = Show Ratsnest
show-navmesh = Show Navmesh show-navmesh = Show Navmesh
show-bboxes = Show BBoxes show-bboxes = Show BBoxes
show-origin-destination = Show OriginDestination show-origin-destination = Show OriginDestination
show-layer-manager = Show Layer Manager
specctra-session-file = Specctra session file specctra-session-file = Specctra session file

View File

@ -0,0 +1,19 @@
menu-file = Bestand
menu-debug = Debug
action-export-ses = Sessiebestand exporteren
action-export-cmd = Geschiedenis exporteren
action-quit = Stop
action-place-via = Plaats Via
action-remove-bands = Banden verwijderen
action-compare-detours = Vergelijk Omwegen
action-undo = Ongedaan maken
action-redo = Opnieuw doen
show-navmesh = Toon Navmesh
show-bboxes = Toon BBoxen
action-open-dsn = Open
action-autoroute = Automatische route
action-import-cmd = Geschiedenis importeren
show-ratsnest = Toon Rattennest
show-origin-destination = Toon Herkomst-Bestemming
specctra-session-file = Specctra-sessiebestand
action-measure-length = Lengte meten

View File

@ -0,0 +1,22 @@
action-place-via = Wstaw przelotkę
menu-debug = Debuguj
action-open-dsn = Otwórz
action-export-cmd = Eksportuj historię
action-autoroute = Autotrasuj
action-remove-bands = Usuń taśmy
action-measure-length = Zmierz długość
action-undo = Cofnij
show-ratsnest = Pokaż szczurze gniazdo
specctra-session-file = Plik sesji Specctra
menu-file = Plik
action-redo = Ponów
action-export-ses = Eksportuj plik sesji
action-import-cmd = Importuj historię
action-quit = Wyjdź
action-compare-detours = Porównaj objazdy
show-navmesh = Pokaż siatkę nawigacyjną
show-bboxes = Pokaż obwiednie
show-origin-destination = Pokaż pochodzenie i destynację
menu-options = Opcje
wrap-around-bands = Owiń wokół taśm
squeeze-under-bands = Ściśnij pod taśmami

View File

@ -142,7 +142,8 @@ impl App {
self.maybe_design = Some(design); self.maybe_design = Some(design);
self.arc_mutex_maybe_invoker = Arc::new(Mutex::new(Some(Invoker::new( self.arc_mutex_maybe_invoker = Arc::new(Mutex::new(Some(Invoker::new(
Autorouter::new(board).unwrap(), Autorouter::new(board).unwrap(),
)))) ))));
self.viewport.scheduled_zoom_to_fit = true;
} }
if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_mut() { if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_mut() {
@ -177,17 +178,23 @@ impl eframe::App for App {
self.history_channel.0.clone(), self.history_channel.0.clone(),
self.arc_mutex_maybe_invoker.clone(), self.arc_mutex_maybe_invoker.clone(),
&mut self.maybe_execute, &mut self.maybe_execute,
&mut self.viewport,
&mut self.maybe_overlay, &mut self.maybe_overlay,
&self.maybe_design, &self.maybe_design,
); );
self.update_state(ctx.input(|i| i.stable_dt)); self.update_state(ctx.input(|i| i.stable_dt));
self.bottom
.update(ctx, &self.translator, &self.viewport, &self.maybe_execute);
if self.top.show_layer_manager {
if let Some(ref mut layers) = self.maybe_layers { if let Some(ref mut layers) = self.maybe_layers {
if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_ref() { if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_ref() {
layers.update(ctx, invoker.autorouter().board()); layers.update(ctx, invoker.autorouter().board());
} }
} }
}
let viewport_rect = self.viewport.update( let viewport_rect = self.viewport.update(
ctx, ctx,
@ -198,14 +205,6 @@ impl eframe::App for App {
&self.maybe_layers, &self.maybe_layers,
); );
self.bottom.update(
ctx,
&self.translator,
&self.viewport,
viewport_rect,
&self.maybe_execute,
);
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) { if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
ctx.send_viewport_cmd(egui::ViewportCommand::Close); ctx.send_viewport_cmd(egui::ViewportCommand::Close);
} }

View File

@ -14,14 +14,11 @@ impl Bottom {
ctx: &egui::Context, ctx: &egui::Context,
tr: &Translator, tr: &Translator,
viewport: &Viewport, viewport: &Viewport,
viewport_rect: egui::Rect,
maybe_execute: &Option<ExecuteWithStatus>, maybe_execute: &Option<ExecuteWithStatus>,
) { ) {
egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| {
let transform = egui::emath::RectTransform::from_to(viewport.from_rect, viewport_rect); let latest_pos = viewport.transform.inverse()
let latest_pos = transform * ctx.input(|i| i.pointer.latest_pos().unwrap_or_default());
.inverse()
.transform_pos(ctx.input(|i| i.pointer.latest_pos().unwrap_or_default()));
let mut message = String::from(""); let mut message = String::from("");

View File

@ -19,6 +19,6 @@ impl<'a> FileReceiver<'a> {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn try_recv(&mut self) -> Result<Cursor<Vec<u8>>, TryRecvError> { pub fn try_recv(&mut self) -> Result<Cursor<Vec<u8>>, TryRecvError> {
Ok(Cursor::new(self.receiver.try_recv().unwrap().into())) Ok(Cursor::new(self.receiver.try_recv()?.into()))
} }
} }

View File

@ -21,6 +21,7 @@ use unic_langid::{langid, LanguageIdentifier};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn main() -> eframe::Result<()> { fn main() -> eframe::Result<()> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let langid = if let Some(langname) = get_locale() { let langid = if let Some(langname) = get_locale() {
langname.parse().unwrap_or(langid!("en-US")) langname.parse().unwrap_or(langid!("en-US"))
} else { } else {
@ -36,7 +37,7 @@ fn main() -> eframe::Result<()> {
eframe::run_native( eframe::run_native(
"topola-egui", "topola-egui",
native_options, native_options,
Box::new(|cc| Box::new(App::new(cc, langid))), Box::new(|cc| Ok(Box::new(App::new(cc, langid)))),
) )
} }
@ -47,16 +48,32 @@ fn main() {
eframe::WebLogger::init(log::LevelFilter::Debug).ok(); eframe::WebLogger::init(log::LevelFilter::Debug).ok();
let web_options = eframe::WebOptions::default(); let web_options = eframe::WebOptions::default();
let langid: LanguageIdentifier = "en-US".parse();
wasm_bindgen_futures::spawn_local(async { wasm_bindgen_futures::spawn_local(async {
eframe::WebRunner::new() let start_result = eframe::WebRunner::new()
.start( .start(
"topola-egui", "topola-egui",
web_options, web_options,
Box::new(|cc| Box::new(App::new(cc, langid))), Box::new(|cc| Ok(Box::new(App::new(cc, langid!("en-US"))))),
) )
.await .await;
.expect("failed to start eframe");
// Remove the loading text and spinner:
let loading_text = eframe::web_sys::window()
.and_then(|w| w.document())
.and_then(|d| d.get_element_by_id("loading_text"));
if let Some(loading_text) = loading_text {
match start_result {
Ok(_) => {
loading_text.remove();
}
Err(e) => {
loading_text.set_inner_html(
"<p> The app has crashed. See the developer console for details. </p>",
);
panic!("Failed to start eframe: {e:?}");
}
}
}
}); });
} }

View File

@ -1,4 +1,5 @@
use geo::{CoordsIter, Point, Polygon}; use geo::{CoordsIter, Point, Polygon};
use rstar::AABB;
use topola::{ use topola::{
geometry::primitive::{AccessPrimitiveShape, PrimitiveShape}, geometry::primitive::{AccessPrimitiveShape, PrimitiveShape},
math::{self, Circle}, math::{self, Circle},
@ -6,14 +7,14 @@ use topola::{
pub struct Painter<'a> { pub struct Painter<'a> {
ui: &'a mut egui::Ui, ui: &'a mut egui::Ui,
transform: egui::emath::RectTransform, transform: egui::emath::TSTransform,
paint_bboxes: bool, paint_bboxes: bool,
} }
impl<'a> Painter<'a> { impl<'a> Painter<'a> {
pub fn new( pub fn new(
ui: &'a mut egui::Ui, ui: &'a mut egui::Ui,
transform: egui::emath::RectTransform, transform: egui::emath::TSTransform,
paint_bboxes: bool, paint_bboxes: bool,
) -> Self { ) -> Self {
Self { Self {
@ -29,11 +30,11 @@ impl<'a> Painter<'a> {
PrimitiveShape::Seg(seg) => egui::Shape::line_segment( PrimitiveShape::Seg(seg) => egui::Shape::line_segment(
[ [
self.transform self.transform
.transform_pos([seg.from.x() as f32, -seg.from.y() as f32].into()), .mul_pos([seg.from.x() as f32, -seg.from.y() as f32].into()),
self.transform self.transform
.transform_pos([seg.to.x() as f32, -seg.to.y() as f32].into()), .mul_pos([seg.to.x() as f32, -seg.to.y() as f32].into()),
], ],
egui::Stroke::new(seg.width as f32 * self.transform.scale().x, color), egui::Stroke::new(seg.width as f32 * self.transform.scaling, color),
), ),
PrimitiveShape::Bend(bend) => { PrimitiveShape::Bend(bend) => {
let circle = bend.circle(); let circle = bend.circle();
@ -46,12 +47,12 @@ impl<'a> Painter<'a> {
for i in 0..=100 { for i in 0..=100 {
let x = circle.pos.x() + circle.r * (angle_from + i as f64 * angle_step).cos(); 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 y = circle.pos.y() + circle.r * (angle_from + i as f64 * angle_step).sin();
points.push(self.transform.transform_pos([x as f32, -y as f32].into())); points.push(self.transform.mul_pos([x as f32, -y as f32].into()));
} }
egui::Shape::line( egui::Shape::line(
points, points,
egui::Stroke::new(bend.width as f32 * self.transform.scale().x, color), egui::Stroke::new(bend.width as f32 * self.transform.scaling, color),
) )
} }
}; };
@ -59,18 +60,21 @@ impl<'a> Painter<'a> {
self.ui.painter().add(epaint_shape); self.ui.painter().add(epaint_shape);
if self.paint_bboxes { if self.paint_bboxes {
let bbox = AccessPrimitiveShape::bbox(shape, 0.0); self.paint_bbox(AccessPrimitiveShape::bbox(shape, 0.0));
}
}
pub fn paint_bbox(&mut self, bbox: AABB<[f64; 2]>) {
let rect = egui::epaint::Rect { let rect = egui::epaint::Rect {
min: [bbox.lower()[0] as f32, -bbox.upper()[1] as f32].into(), min: [bbox.lower()[0] as f32, -bbox.upper()[1] as f32].into(),
max: [bbox.upper()[0] as f32, -bbox.lower()[1] as f32].into(), max: [bbox.upper()[0] as f32, -bbox.lower()[1] as f32].into(),
}; };
self.ui.painter().add(egui::Shape::rect_stroke( self.ui.painter().add(egui::Shape::rect_stroke(
self.transform.transform_rect(rect), self.transform * rect,
egui::Rounding::ZERO, egui::Rounding::ZERO,
egui::Stroke::new(1.0, egui::Color32::GRAY), egui::Stroke::new(1.0, egui::Color32::GRAY),
)); ));
} }
}
pub fn paint_dot(&mut self, circle: Circle, color: egui::epaint::Color32) { pub fn paint_dot(&mut self, circle: Circle, color: egui::epaint::Color32) {
let shape = self.dot_shape(circle, color); let shape = self.dot_shape(circle, color);
@ -80,8 +84,8 @@ impl<'a> Painter<'a> {
fn dot_shape(&mut self, circle: Circle, color: egui::epaint::Color32) -> egui::Shape { fn dot_shape(&mut self, circle: Circle, color: egui::epaint::Color32) -> egui::Shape {
egui::Shape::circle_filled( egui::Shape::circle_filled(
self.transform self.transform
.transform_pos([circle.pos.x() as f32, -circle.pos.y() as f32].into()), .mul_pos([circle.pos.x() as f32, -circle.pos.y() as f32].into()),
circle.r as f32 * self.transform.scale().x, circle.r as f32 * self.transform.scaling,
color, color,
) )
} }
@ -92,7 +96,7 @@ impl<'a> Painter<'a> {
.exterior_coords_iter() .exterior_coords_iter()
.map(|coords| { .map(|coords| {
self.transform self.transform
.transform_pos([coords.x as f32, -coords.y as f32].into()) .mul_pos([coords.x as f32, -coords.y as f32].into())
}) })
.collect(), .collect(),
color, color,
@ -104,9 +108,9 @@ impl<'a> Painter<'a> {
self.ui.painter().add(egui::Shape::line_segment( self.ui.painter().add(egui::Shape::line_segment(
[ [
self.transform self.transform
.transform_pos([from.x() as f32, -from.y() as f32].into()), .mul_pos([from.x() as f32, -from.y() as f32].into()),
self.transform self.transform
.transform_pos([to.x() as f32, -to.y() as f32].into()), .mul_pos([to.x() as f32, -to.y() as f32].into()),
], ],
stroke, stroke,
)); ));

View File

@ -19,6 +19,7 @@ use crate::{
file_sender::FileSender, file_sender::FileSender,
overlay::Overlay, overlay::Overlay,
translator::Translator, translator::Translator,
viewport::Viewport,
}; };
pub struct Top { pub struct Top {
@ -28,6 +29,7 @@ pub struct Top {
pub show_navmesh: bool, pub show_navmesh: bool,
pub show_bboxes: bool, pub show_bboxes: bool,
pub show_origin_destination: bool, pub show_origin_destination: bool,
pub show_layer_manager: bool,
} }
impl Top { impl Top {
@ -45,6 +47,7 @@ impl Top {
show_navmesh: false, show_navmesh: false,
show_bboxes: false, show_bboxes: false,
show_origin_destination: false, show_origin_destination: false,
show_layer_manager: true,
} }
} }
@ -56,6 +59,7 @@ impl Top {
history_sender: Sender<String>, history_sender: Sender<String>,
arc_mutex_maybe_invoker: Arc<Mutex<Option<Invoker<SpecctraMesadata>>>>, arc_mutex_maybe_invoker: Arc<Mutex<Option<Invoker<SpecctraMesadata>>>>,
maybe_execute: &mut Option<ExecuteWithStatus>, maybe_execute: &mut Option<ExecuteWithStatus>,
viewport: &mut Viewport,
maybe_overlay: &mut Option<Overlay>, maybe_overlay: &mut Option<Overlay>,
maybe_design: &Option<SpecctraDesign>, maybe_design: &Option<SpecctraDesign>,
) -> Result<(), InvokerError> { ) -> Result<(), InvokerError> {
@ -140,9 +144,43 @@ impl Top {
} }
}); });
ui.menu_button(tr.text("menu-edit"), |ui| {
undo.button(ctx, ui);
redo.button(ctx, ui);
ui.separator(); ui.separator();
remove_bands.button(ctx, ui);
});
ui.menu_button(tr.text("menu-view"), |ui| {
ui.toggle_value(
&mut viewport.scheduled_zoom_to_fit,
tr.text("zoom-to-fit"),
);
ui.separator();
ui.checkbox(&mut self.show_ratsnest, tr.text("show-ratsnest"));
ui.checkbox(&mut self.show_navmesh, tr.text("show-navmesh"));
ui.checkbox(&mut self.show_bboxes, tr.text("show-bboxes"));
ui.checkbox(
&mut self.show_origin_destination,
tr.text("show-origin-destination"),
);
ui.separator();
ui.checkbox(&mut self.show_layer_manager, tr.text("show-layer-manager"));
});
ui.menu_button(tr.text("menu-place"), |ui| {
place_via.toggle_widget(ctx, ui, &mut self.is_placing_via);
});
ui.menu_button(tr.text("menu-route"), |ui| {
autoroute.button(ctx, ui); autoroute.button(ctx, ui);
ui.separator();
ui.menu_button(tr.text("menu-options"), |ui| { ui.menu_button(tr.text("menu-options"), |ui| {
ui.checkbox( ui.checkbox(
@ -158,30 +196,9 @@ impl Top {
tr.text("wrap-around-bands"), tr.text("wrap-around-bands"),
); );
}); });
});
ui.separator(); ui.menu_button(tr.text("menu-inspect"), |ui| {
place_via.toggle_widget(ctx, ui, &mut self.is_placing_via);
remove_bands.button(ctx, ui);
ui.separator();
undo.button(ctx, ui);
redo.button(ctx, ui);
ui.separator();
ui.menu_button(tr.text("menu-debug"), |ui| {
ui.checkbox(&mut self.show_ratsnest, tr.text("show-ratsnest"));
ui.checkbox(&mut self.show_navmesh, tr.text("show-navmesh"));
ui.checkbox(&mut self.show_bboxes, tr.text("show-bboxes"));
ui.checkbox(
&mut self.show_origin_destination,
tr.text("show-origin-destination"),
);
ui.separator();
compare_detours.button(ctx, ui); compare_detours.button(ctx, ui);
measure_length.button(ctx, ui); measure_length.button(ctx, ui);
}); });

View File

@ -3,6 +3,7 @@ use petgraph::{
data::DataMap, data::DataMap,
visit::{EdgeRef, IntoEdgeReferences}, visit::{EdgeRef, IntoEdgeReferences},
}; };
use rstar::{Envelope, AABB};
use topola::{ use topola::{
autorouter::invoker::{ autorouter::invoker::{
Command, ExecuteWithStatus, GetGhosts, GetMaybeNavmesh, GetMaybeTrace, GetObstacles, Command, ExecuteWithStatus, GetGhosts, GetMaybeNavmesh, GetMaybeTrace, GetObstacles,
@ -22,13 +23,15 @@ use topola::{
use crate::{app::execute, layers::Layers, overlay::Overlay, painter::Painter, top::Top}; use crate::{app::execute, layers::Layers, overlay::Overlay, painter::Painter, top::Top};
pub struct Viewport { pub struct Viewport {
pub from_rect: egui::emath::Rect, pub transform: egui::emath::TSTransform,
pub scheduled_zoom_to_fit: bool,
} }
impl Viewport { impl Viewport {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
from_rect: egui::Rect::from_x_y_ranges(0.0..=1000000.0, 0.0..=500000.0), transform: egui::emath::TSTransform::new([0.0, 0.0].into(), 0.01),
scheduled_zoom_to_fit: false,
} }
} }
@ -40,37 +43,46 @@ impl Viewport {
maybe_execute: &mut Option<ExecuteWithStatus>, maybe_execute: &mut Option<ExecuteWithStatus>,
maybe_overlay: &mut Option<Overlay>, maybe_overlay: &mut Option<Overlay>,
maybe_layers: &Option<Layers>, maybe_layers: &Option<Layers>,
) -> egui::Rect {
let viewport_rect = self.paint(
ctx,
top,
maybe_invoker,
maybe_execute,
maybe_overlay,
maybe_layers,
);
if self.scheduled_zoom_to_fit {
self.zoom_to_fit(maybe_invoker, &viewport_rect);
}
viewport_rect
}
pub fn paint(
&mut self,
ctx: &egui::Context,
top: &Top,
maybe_invoker: &mut Option<Invoker<SpecctraMesadata>>,
maybe_execute: &mut Option<ExecuteWithStatus>,
maybe_overlay: &mut Option<Overlay>,
maybe_layers: &Option<Layers>,
) -> egui::Rect { ) -> egui::Rect {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
egui::Frame::canvas(ui.style()).show(ui, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| {
ui.ctx().request_repaint(); ui.ctx().request_repaint();
let desired_size = ui.available_width() * egui::vec2(1.0, 0.5); let (_id, viewport_rect) = ui.allocate_space(ui.available_size());
let (_id, viewport_rect) = ui.allocate_space(desired_size); let latest_pos = self.transform.inverse() * (ctx.input(|i| i.pointer.latest_pos().unwrap_or_default()));
let old_transform = let old_scaling = self.transform.scaling;
egui::emath::RectTransform::from_to(self.from_rect, viewport_rect); self.transform.scaling *= ctx.input(|i| i.zoom_delta());
let latest_pos = old_transform
.inverse()
.transform_pos(ctx.input(|i| i.pointer.latest_pos().unwrap_or_default()));
let old_scale = old_transform.scale().x; self.transform.translation += latest_pos.to_vec2() * (old_scaling - self.transform.scaling);
self.from_rect = self.from_rect / ctx.input(|i| i.zoom_delta()); self.transform.translation += ctx.input(|i| i.smooth_scroll_delta);
let new_scale = egui::emath::RectTransform::from_to(self.from_rect, viewport_rect) let mut painter = Painter::new(ui, self.transform, top.show_bboxes);
.scale()
.x;
self.from_rect = self.from_rect.translate(
ctx.input(|i| latest_pos.to_vec2() * (new_scale - old_scale) / new_scale),
);
self.from_rect = self
.from_rect
.translate(ctx.input(|i| -i.raw_scroll_delta / new_scale));
let transform = egui::emath::RectTransform::from_to(self.from_rect, viewport_rect);
let mut painter = Painter::new(ui, transform, top.show_bboxes);
if let Some(ref mut invoker) = maybe_invoker { if let Some(ref mut invoker) = maybe_invoker {
if ctx.input(|i| i.pointer.any_click()) { if ctx.input(|i| i.pointer.any_click()) {
@ -224,6 +236,13 @@ impl Viewport {
} }
} }
if top.show_bboxes {
let root_bbox3d = board.layout().drawing().rtree().root().envelope();
let root_bbox = AABB::<[f64; 2]>::from_corners([root_bbox3d.lower()[0], root_bbox3d.lower()[1]].into(), [root_bbox3d.upper()[0], root_bbox3d.upper()[1]].into());
painter.paint_bbox(root_bbox);
}
if let Some(execute) = maybe_execute { if let Some(execute) = maybe_execute {
for ghost in execute.ghosts().iter() { for ghost in execute.ghosts().iter() {
painter.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150)); painter.paint_primitive(&ghost, egui::Color32::from_rgb(75, 75, 150));
@ -257,4 +276,43 @@ impl Viewport {
}) })
}).inner.inner }).inner.inner
} }
fn zoom_to_fit(
&mut self,
maybe_invoker: &mut Option<Invoker<SpecctraMesadata>>,
viewport_rect: &egui::Rect,
) {
if self.scheduled_zoom_to_fit {
if let Some(invoker) = maybe_invoker {
let root_bbox = invoker
.autorouter()
.board()
.layout()
.drawing()
.rtree()
.root()
.envelope();
let root_bbox_width = root_bbox.upper()[0] - root_bbox.lower()[0];
let root_bbox_height = root_bbox.upper()[1] - root_bbox.lower()[1];
if root_bbox_width / root_bbox_height
>= (viewport_rect.width() as f64) / (viewport_rect.height() as f64)
{
self.transform.scaling = 0.8 * viewport_rect.width() / root_bbox_width as f32;
} else {
self.transform.scaling = 0.8 * viewport_rect.height() / root_bbox_height as f32;
}
self.transform.translation = egui::Vec2::new(
viewport_rect.center()[0] as f32,
viewport_rect.center()[1] as f32,
) - (self.transform.scaling
* egui::Pos2::new(root_bbox.center()[0] as f32, -root_bbox.center()[1] as f32))
.to_vec2();
}
}
self.scheduled_zoom_to_fit = false;
}
} }

View File

@ -6,6 +6,8 @@ use topola::autorouter::invoker::Command;
use topola::autorouter::invoker::Invoker; use topola::autorouter::invoker::Invoker;
use topola::autorouter::selection::PinSelection; use topola::autorouter::selection::PinSelection;
use topola::autorouter::Autorouter; use topola::autorouter::Autorouter;
use topola::autorouter::AutorouterOptions;
use topola::router::RouterOptions;
use topola::specctra::design::SpecctraDesign; use topola::specctra::design::SpecctraDesign;
pub mod cli; pub mod cli;
@ -25,9 +27,16 @@ fn main() -> Result<(), std::io::Error> {
serde_json::from_reader(commands_bufread)? serde_json::from_reader(commands_bufread)?
} else { } else {
let mut history = History::new(); let mut history = History::new();
history.do_(Command::Autoroute(PinSelection::new_select_layer( history.do_(Command::Autoroute(
&board, 0, PinSelection::new_select_layer(&board, 0),
))); AutorouterOptions {
presort_by_pairwise_detours: false,
router_options: RouterOptions {
wrap_around_bands: true,
squeeze_under_bands: false,
},
},
));
history history
}; };

View File

@ -1,5 +1,3 @@
use std::convert::Infallible;
use geo::EuclideanDistance; use geo::EuclideanDistance;
use petgraph::{data::DataMap, visit::EdgeRef}; use petgraph::{data::DataMap, visit::EdgeRef};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -16,7 +14,7 @@ use crate::{
Collision, DrawingException, Infringement, Collision, DrawingException, Infringement,
}, },
geometry::{ geometry::{
primitive::{AccessPrimitiveShape, PrimitiveShape}, primitive::PrimitiveShape,
shape::{AccessShape, MeasureLength}, shape::{AccessShape, MeasureLength},
}, },
graph::{GetPetgraphIndex, MakeRef}, graph::{GetPetgraphIndex, MakeRef},