mirror of https://codeberg.org/topola/topola.git
Merge branch 'develop' into fix-cli-build
This commit is contained in:
commit
df0d0fd918
|
|
@ -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
|
||||||
|
|
|
||||||
54
index.html
54
index.html
|
|
@ -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/ -->
|
||||||
|
|
|
||||||
|
|
@ -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 Origin–Destination
|
show-origin-destination = Show Origin–Destination
|
||||||
|
|
||||||
|
show-layer-manager = Show Layer Manager
|
||||||
|
|
||||||
specctra-session-file = Specctra session file
|
specctra-session-file = Specctra session 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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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,15 +178,21 @@ 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));
|
||||||
|
|
||||||
if let Some(ref mut layers) = self.maybe_layers {
|
self.bottom
|
||||||
if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_ref() {
|
.update(ctx, &self.translator, &self.viewport, &self.maybe_execute);
|
||||||
layers.update(ctx, invoker.autorouter().board());
|
|
||||||
|
if self.top.show_layer_manager {
|
||||||
|
if let Some(ref mut layers) = self.maybe_layers {
|
||||||
|
if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_ref() {
|
||||||
|
layers.update(ctx, invoker.autorouter().board());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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("");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,19 +60,22 @@ 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));
|
||||||
let rect = egui::epaint::Rect {
|
|
||||||
min: [bbox.lower()[0] as f32, -bbox.upper()[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.transform.transform_rect(rect),
|
|
||||||
egui::Rounding::ZERO,
|
|
||||||
egui::Stroke::new(1.0, egui::Color32::GRAY),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn paint_bbox(&mut self, bbox: AABB<[f64; 2]>) {
|
||||||
|
let rect = egui::epaint::Rect {
|
||||||
|
min: [bbox.lower()[0] as f32, -bbox.upper()[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.transform * rect,
|
||||||
|
egui::Rounding::ZERO,
|
||||||
|
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);
|
||||||
self.ui.painter().add(shape);
|
self.ui.painter().add(shape);
|
||||||
|
|
@ -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,
|
||||||
));
|
));
|
||||||
|
|
|
||||||
|
|
@ -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,39 +144,23 @@ impl Top {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.separator();
|
ui.menu_button(tr.text("menu-edit"), |ui| {
|
||||||
|
undo.button(ctx, ui);
|
||||||
|
redo.button(ctx, ui);
|
||||||
|
|
||||||
autoroute.button(ctx, ui);
|
ui.separator();
|
||||||
|
|
||||||
ui.menu_button(tr.text("menu-options"), |ui| {
|
remove_bands.button(ctx, ui);
|
||||||
ui.checkbox(
|
|
||||||
&mut self.autorouter_options.presort_by_pairwise_detours,
|
|
||||||
tr.text("presort-by-pairwise-detours"),
|
|
||||||
);
|
|
||||||
ui.checkbox(
|
|
||||||
&mut self.autorouter_options.router_options.squeeze_under_bands,
|
|
||||||
tr.text("squeeze-under-bands"),
|
|
||||||
);
|
|
||||||
ui.checkbox(
|
|
||||||
&mut self.autorouter_options.router_options.wrap_around_bands,
|
|
||||||
tr.text("wrap-around-bands"),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.separator();
|
ui.menu_button(tr.text("menu-view"), |ui| {
|
||||||
|
ui.toggle_value(
|
||||||
|
&mut viewport.scheduled_zoom_to_fit,
|
||||||
|
tr.text("zoom-to-fit"),
|
||||||
|
);
|
||||||
|
|
||||||
place_via.toggle_widget(ctx, ui, &mut self.is_placing_via);
|
ui.separator();
|
||||||
|
|
||||||
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_ratsnest, tr.text("show-ratsnest"));
|
||||||
ui.checkbox(&mut self.show_navmesh, tr.text("show-navmesh"));
|
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_bboxes, tr.text("show-bboxes"));
|
||||||
|
|
@ -182,6 +170,35 @@ impl Top {
|
||||||
);
|
);
|
||||||
|
|
||||||
ui.separator();
|
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);
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
ui.menu_button(tr.text("menu-options"), |ui| {
|
||||||
|
ui.checkbox(
|
||||||
|
&mut self.autorouter_options.presort_by_pairwise_detours,
|
||||||
|
tr.text("presort-by-pairwise-detours"),
|
||||||
|
);
|
||||||
|
ui.checkbox(
|
||||||
|
&mut self.autorouter_options.router_options.squeeze_under_bands,
|
||||||
|
tr.text("squeeze-under-bands"),
|
||||||
|
);
|
||||||
|
ui.checkbox(
|
||||||
|
&mut self.autorouter_options.router_options.wrap_around_bands,
|
||||||
|
tr.text("wrap-around-bands"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.menu_button(tr.text("menu-inspect"), |ui| {
|
||||||
compare_detours.button(ctx, ui);
|
compare_detours.button(ctx, ui);
|
||||||
measure_length.button(ctx, ui);
|
measure_length.button(ctx, ui);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue