mirror of https://codeberg.org/topola/topola.git
feat: Add interaction stepper for route building
This commit is contained in:
parent
e2aaa932fc
commit
a4b1b3893c
|
|
@ -54,6 +54,7 @@ allowed_scopes = [
|
||||||
"interactor/activity",
|
"interactor/activity",
|
||||||
"interactor/interaction",
|
"interactor/interaction",
|
||||||
"interactor/interactor",
|
"interactor/interactor",
|
||||||
|
"interactor/route_plan",
|
||||||
"layout/layout",
|
"layout/layout",
|
||||||
"layout/poly",
|
"layout/poly",
|
||||||
"layout/via",
|
"layout/via",
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,7 @@ impl ViewActions {
|
||||||
|
|
||||||
pub struct PlaceActions {
|
pub struct PlaceActions {
|
||||||
pub place_via: Switch,
|
pub place_via: Switch,
|
||||||
|
pub place_route_plan: Trigger,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceActions {
|
impl PlaceActions {
|
||||||
|
|
@ -246,6 +247,8 @@ impl PlaceActions {
|
||||||
egui::Key::P,
|
egui::Key::P,
|
||||||
)
|
)
|
||||||
.into_switch(),
|
.into_switch(),
|
||||||
|
place_route_plan: Action::new_keyless(tr.text("tr-menu-place-place-route-plan"))
|
||||||
|
.into_trigger(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,6 +261,7 @@ impl PlaceActions {
|
||||||
) -> egui::InnerResponse<()> {
|
) -> egui::InnerResponse<()> {
|
||||||
ui.add_enabled_ui(have_workspace, |ui| {
|
ui.add_enabled_ui(have_workspace, |ui| {
|
||||||
self.place_via.toggle_widget(ui, is_placing_via);
|
self.place_via.toggle_widget(ui, is_placing_via);
|
||||||
|
self.place_route_plan.button(ctx, ui);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,15 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use geo::point;
|
|
||||||
use std::{
|
use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
io,
|
io,
|
||||||
ops::ControlFlow,
|
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::mpsc::{channel, Receiver, Sender},
|
sync::mpsc::{channel, Receiver, Sender},
|
||||||
};
|
};
|
||||||
use unic_langid::{langid, LanguageIdentifier};
|
use unic_langid::{langid, LanguageIdentifier};
|
||||||
|
|
||||||
use topola::{
|
use topola::specctra::{design::SpecctraDesign, ParseErrorContext as SpecctraLoadingError};
|
||||||
interactor::activity::InteractiveInput,
|
|
||||||
specctra::{design::SpecctraDesign, ParseErrorContext as SpecctraLoadingError},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config, error_dialog::ErrorDialog, menu_bar::MenuBar, status_bar::StatusBar,
|
config::Config, error_dialog::ErrorDialog, menu_bar::MenuBar, status_bar::StatusBar,
|
||||||
|
|
@ -37,8 +32,6 @@ pub struct App {
|
||||||
error_dialog: ErrorDialog,
|
error_dialog: ErrorDialog,
|
||||||
|
|
||||||
maybe_workspace: Option<Workspace>,
|
maybe_workspace: Option<Workspace>,
|
||||||
|
|
||||||
update_counter: f32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for App {
|
impl Default for App {
|
||||||
|
|
@ -52,7 +45,6 @@ impl Default for App {
|
||||||
status_bar: StatusBar::new(),
|
status_bar: StatusBar::new(),
|
||||||
error_dialog: ErrorDialog::new(),
|
error_dialog: ErrorDialog::new(),
|
||||||
maybe_workspace: None,
|
maybe_workspace: None,
|
||||||
update_counter: 0.0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,23 +64,8 @@ impl App {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Advances the app's state by the delta time `dt`. May call
|
|
||||||
/// `.update_state()` more than once if the delta time is more than a multiple of
|
|
||||||
/// the timestep.
|
|
||||||
fn advance_state_by_dt(&mut self, interactive_input: &InteractiveInput) {
|
|
||||||
self.update_counter += interactive_input.dt;
|
|
||||||
|
|
||||||
while self.update_counter >= self.menu_bar.frame_timestep {
|
|
||||||
self.update_counter -= self.menu_bar.frame_timestep;
|
|
||||||
|
|
||||||
if let ControlFlow::Break(()) = self.update_state(interactive_input) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance the app's state by a single step.
|
/// Advance the app's state by a single step.
|
||||||
fn update_state(&mut self, interactive_input: &InteractiveInput) -> ControlFlow<()> {
|
fn update_state(&mut self) {
|
||||||
// If a new design has been loaded from a file, create a new workspace
|
// If a new design has been loaded from a file, create a new workspace
|
||||||
// with the design. Or handle the error if there was a failure to do so.
|
// with the design. Or handle the error if there was a failure to do so.
|
||||||
if let Ok(data) = self.content_channel.1.try_recv() {
|
if let Ok(data) = self.content_channel.1.try_recv() {
|
||||||
|
|
@ -128,16 +105,6 @@ impl App {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(workspace) = &mut self.maybe_workspace {
|
|
||||||
return workspace.update_state(
|
|
||||||
&self.translator,
|
|
||||||
&mut self.error_dialog,
|
|
||||||
interactive_input,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlFlow::Break(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the title displayed on the application window's frame to show the
|
/// Update the title displayed on the application window's frame to show the
|
||||||
|
|
@ -213,22 +180,7 @@ impl eframe::App for App {
|
||||||
self.maybe_workspace.as_mut(),
|
self.maybe_workspace.as_mut(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let pointer_pos = self.viewport.transform.inverse()
|
self.update_state();
|
||||||
* ctx.input(|i| i.pointer.latest_pos().unwrap_or_default());
|
|
||||||
|
|
||||||
self.advance_state_by_dt(&InteractiveInput {
|
|
||||||
pointer_pos: point! {x: pointer_pos.x as f64, y: pointer_pos.y as f64},
|
|
||||||
dt: ctx.input(|i| i.stable_dt),
|
|
||||||
});
|
|
||||||
|
|
||||||
self.status_bar.update(
|
|
||||||
ctx,
|
|
||||||
&self.translator,
|
|
||||||
&self.viewport,
|
|
||||||
self.maybe_workspace
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|w| w.interactor.maybe_activity().as_ref()),
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.menu_bar.show_appearance_panel {
|
if self.menu_bar.show_appearance_panel {
|
||||||
if let Some(workspace) = &mut self.maybe_workspace {
|
if let Some(workspace) = &mut self.maybe_workspace {
|
||||||
|
|
@ -241,10 +193,21 @@ impl eframe::App for App {
|
||||||
let _viewport_rect = self.viewport.update(
|
let _viewport_rect = self.viewport.update(
|
||||||
&self.config,
|
&self.config,
|
||||||
ctx,
|
ctx,
|
||||||
|
&self.translator,
|
||||||
&self.menu_bar,
|
&self.menu_bar,
|
||||||
|
&mut self.error_dialog,
|
||||||
self.maybe_workspace.as_mut(),
|
self.maybe_workspace.as_mut(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self.status_bar.update(
|
||||||
|
ctx,
|
||||||
|
&self.translator,
|
||||||
|
&self.viewport,
|
||||||
|
self.maybe_workspace
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|w| w.interactor.maybe_activity().as_ref()),
|
||||||
|
);
|
||||||
|
|
||||||
self.update_locale();
|
self.update_locale();
|
||||||
self.update_title(ctx);
|
self.update_title(ctx);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use topola::{
|
||||||
execution::Command, invoker::InvokerError, selection::Selection, AutorouterOptions,
|
execution::Command, invoker::InvokerError, selection::Selection, AutorouterOptions,
|
||||||
},
|
},
|
||||||
board::AccessMesadata,
|
board::AccessMesadata,
|
||||||
|
interactor::{interaction::InteractionStepper, route_plan::RoutePlan},
|
||||||
router::RouterOptions,
|
router::RouterOptions,
|
||||||
specctra::{design::SpecctraDesign, ParseError, ParseErrorContext as SpecctraLoadingError},
|
specctra::{design::SpecctraDesign, ParseError, ParseErrorContext as SpecctraLoadingError},
|
||||||
};
|
};
|
||||||
|
|
@ -303,16 +304,13 @@ impl MenuBar {
|
||||||
workspace
|
workspace
|
||||||
.interactor
|
.interactor
|
||||||
.schedule(op(selection, self.autorouter_options));
|
.schedule(op(selection, self.autorouter_options));
|
||||||
Ok::<(), InvokerError>(())
|
|
||||||
};
|
};
|
||||||
if actions.edit.remove_bands.consume_key_triggered(ctx, ui) {
|
if actions.edit.remove_bands.consume_key_triggered(ctx, ui) {
|
||||||
schedule(|selection, _| {
|
schedule(|selection, _| Command::RemoveBands(selection.band_selection));
|
||||||
Command::RemoveBands(selection.band_selection)
|
|
||||||
})?;
|
|
||||||
} else if actions.route.autoroute.consume_key_triggered(ctx, ui) {
|
} else if actions.route.autoroute.consume_key_triggered(ctx, ui) {
|
||||||
schedule(|selection, opts| {
|
schedule(|selection, opts| {
|
||||||
Command::Autoroute(selection.pin_selection, opts)
|
Command::Autoroute(selection.pin_selection, opts)
|
||||||
})?;
|
});
|
||||||
} else if actions
|
} else if actions
|
||||||
.inspect
|
.inspect
|
||||||
.compare_detours
|
.compare_detours
|
||||||
|
|
@ -320,7 +318,7 @@ impl MenuBar {
|
||||||
{
|
{
|
||||||
schedule(|selection, opts| {
|
schedule(|selection, opts| {
|
||||||
Command::CompareDetours(selection.pin_selection, opts)
|
Command::CompareDetours(selection.pin_selection, opts)
|
||||||
})?;
|
});
|
||||||
} else if actions
|
} else if actions
|
||||||
.inspect
|
.inspect
|
||||||
.measure_length
|
.measure_length
|
||||||
|
|
@ -328,7 +326,18 @@ impl MenuBar {
|
||||||
{
|
{
|
||||||
schedule(|selection, _| {
|
schedule(|selection, _| {
|
||||||
Command::MeasureLength(selection.band_selection)
|
Command::MeasureLength(selection.band_selection)
|
||||||
})?;
|
});
|
||||||
|
} else if actions
|
||||||
|
.place
|
||||||
|
.place_route_plan
|
||||||
|
.consume_key_triggered(ctx, ui)
|
||||||
|
{
|
||||||
|
if let Some(active_layer) = workspace.appearance_panel.active_layer {
|
||||||
|
self.is_placing_via = false;
|
||||||
|
workspace.interactor.interact(InteractionStepper::RoutePlan(
|
||||||
|
RoutePlan::new(active_layer),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,8 @@ impl Overlay {
|
||||||
.filter_map(|node| {
|
.filter_map(|node| {
|
||||||
board
|
board
|
||||||
.layout()
|
.layout()
|
||||||
.center_of_compoundless_node(node)
|
.apex_of_compoundless_node(node, active_layer)
|
||||||
.map(|pos| (node, pos))
|
.map(|(_, pos)| (node, pos))
|
||||||
})
|
})
|
||||||
.map(|(idx, pos)| TrianVertex {
|
.map(|(idx, pos)| TrianVertex {
|
||||||
idx,
|
idx,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use geo::{CoordsIter, Point, Polygon};
|
use geo::{CoordsIter, LineString, Point, Polygon};
|
||||||
use rstar::AABB;
|
use rstar::AABB;
|
||||||
use topola::{
|
use topola::{
|
||||||
geometry::{primitive::PrimitiveShape, shape::AccessShape},
|
geometry::{primitive::PrimitiveShape, shape::AccessShape},
|
||||||
|
|
@ -89,6 +89,23 @@ impl<'a> Painter<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn paint_linestring(&mut self, linestring: &LineString, color: egui::epaint::Color32) {
|
||||||
|
self.ui.painter().add(egui::Shape::line(
|
||||||
|
linestring
|
||||||
|
.exterior_coords_iter()
|
||||||
|
.map(|coords| {
|
||||||
|
self.transform
|
||||||
|
.mul_pos([coords.x as f32, -coords.y as f32].into())
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
egui::epaint::PathStroke {
|
||||||
|
width: 5.0,
|
||||||
|
color: egui::epaint::ColorMode::Solid(color),
|
||||||
|
kind: egui::epaint::StrokeKind::Inside,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn paint_polygon(&mut self, polygon: &Polygon, color: egui::epaint::Color32) {
|
pub fn paint_polygon(&mut self, polygon: &Polygon, color: egui::epaint::Color32) {
|
||||||
self.ui.painter().add(egui::Shape::convex_polygon(
|
self.ui.painter().add(egui::Shape::convex_polygon(
|
||||||
polygon
|
polygon
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use core::ops::ControlFlow;
|
||||||
use geo::point;
|
use geo::point;
|
||||||
use petgraph::{
|
use petgraph::{
|
||||||
data::DataMap,
|
data::DataMap,
|
||||||
|
|
@ -23,18 +24,27 @@ use topola::{
|
||||||
},
|
},
|
||||||
geometry::{shape::AccessShape, GenericNode},
|
geometry::{shape::AccessShape, GenericNode},
|
||||||
graph::MakeRef,
|
graph::MakeRef,
|
||||||
|
interactor::{
|
||||||
|
activity::{ActivityStepper, InteractiveEvent, InteractiveInput},
|
||||||
|
interaction::InteractionStepper,
|
||||||
|
},
|
||||||
layout::{poly::MakePolygon, via::ViaWeight},
|
layout::{poly::MakePolygon, via::ViaWeight},
|
||||||
math::{Circle, RotationSense},
|
math::{Circle, RotationSense},
|
||||||
router::navmesh::NavnodeIndex,
|
router::navmesh::NavnodeIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{config::Config, menu_bar::MenuBar, painter::Painter, workspace::Workspace};
|
use crate::{
|
||||||
|
config::Config, error_dialog::ErrorDialog, menu_bar::MenuBar, painter::Painter,
|
||||||
|
translator::Translator, workspace::Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Viewport {
|
pub struct Viewport {
|
||||||
pub transform: egui::emath::TSTransform,
|
pub transform: egui::emath::TSTransform,
|
||||||
/// how much should a single arrow key press scroll
|
/// how much should a single arrow key press scroll
|
||||||
pub kbd_scroll_delta_factor: f32,
|
pub kbd_scroll_delta_factor: f32,
|
||||||
pub scheduled_zoom_to_fit: bool,
|
pub scheduled_zoom_to_fit: bool,
|
||||||
|
|
||||||
|
update_counter: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Viewport {
|
impl Viewport {
|
||||||
|
|
@ -43,6 +53,7 @@ impl Viewport {
|
||||||
transform: egui::emath::TSTransform::new([0.0, 0.0].into(), 0.01),
|
transform: egui::emath::TSTransform::new([0.0, 0.0].into(), 0.01),
|
||||||
kbd_scroll_delta_factor: 5.0,
|
kbd_scroll_delta_factor: 5.0,
|
||||||
scheduled_zoom_to_fit: false,
|
scheduled_zoom_to_fit: false,
|
||||||
|
update_counter: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +61,9 @@ impl Viewport {
|
||||||
&mut self,
|
&mut self,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
|
tr: &Translator,
|
||||||
menu_bar: &MenuBar,
|
menu_bar: &MenuBar,
|
||||||
|
error_dialog: &mut ErrorDialog,
|
||||||
maybe_workspace: Option<&mut Workspace>,
|
maybe_workspace: Option<&mut Workspace>,
|
||||||
) -> egui::Rect {
|
) -> egui::Rect {
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
|
|
@ -123,11 +136,77 @@ impl Viewport {
|
||||||
let mut painter = Painter::new(ui, self.transform, menu_bar.show_bboxes);
|
let mut painter = Painter::new(ui, self.transform, menu_bar.show_bboxes);
|
||||||
|
|
||||||
if let Some(workspace) = maybe_workspace {
|
if let Some(workspace) = maybe_workspace {
|
||||||
|
let latest_point = point! {x: latest_pos.x as f64, y: -latest_pos.y as f64};
|
||||||
|
|
||||||
|
if !workspace.interactor.maybe_activity().as_ref().map_or(true, |activity| {
|
||||||
|
matches!(activity.maybe_status(), Some(ControlFlow::Break(..)))
|
||||||
|
}) {
|
||||||
|
// there is currently some activity
|
||||||
|
let interactive_event = if response.clicked_by(egui::PointerButton::Primary) {
|
||||||
|
Some(InteractiveEvent::PointerPrimaryButtonClicked)
|
||||||
|
} else if response.clicked_by(egui::PointerButton::Secondary) {
|
||||||
|
Some(InteractiveEvent::PointerSecondaryButtonClicked)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if let Some(event) = interactive_event {
|
||||||
|
log::debug!("got {:?}", event);
|
||||||
|
}
|
||||||
|
// Advances the app's state by the delta time `dt`. May call
|
||||||
|
// `.update_state()` more than once if the delta time is more than a multiple of
|
||||||
|
// the timestep.
|
||||||
|
let dt = ctx.input(|i| i.stable_dt);
|
||||||
|
let active_layer = workspace.appearance_panel.active_layer;
|
||||||
|
self.update_counter += if interactive_event.is_some() {
|
||||||
|
// make sure we run the loop below at least once on clicks
|
||||||
|
let mut dtx = menu_bar.frame_timestep;
|
||||||
|
if dt > dtx {
|
||||||
|
dtx = dt;
|
||||||
|
}
|
||||||
|
dtx
|
||||||
|
} else {
|
||||||
|
dt
|
||||||
|
};
|
||||||
|
while self.update_counter >= menu_bar.frame_timestep {
|
||||||
|
self.update_counter -= menu_bar.frame_timestep;
|
||||||
|
if let ControlFlow::Break(()) = workspace.update_state(
|
||||||
|
tr,
|
||||||
|
error_dialog,
|
||||||
|
&InteractiveInput {
|
||||||
|
active_layer,
|
||||||
|
pointer_pos: latest_point,
|
||||||
|
dt,
|
||||||
|
},
|
||||||
|
interactive_event,
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Advances the app's state by the delta time `dt`. May call
|
||||||
|
// `.update_state()` more than once if the delta time is more than a multiple of
|
||||||
|
// the timestep.
|
||||||
|
let dt = ctx.input(|i| i.stable_dt);
|
||||||
|
let active_layer = workspace.appearance_panel.active_layer;
|
||||||
|
self.update_counter += dt;
|
||||||
|
while self.update_counter >= menu_bar.frame_timestep {
|
||||||
|
self.update_counter -= menu_bar.frame_timestep;
|
||||||
|
if let ControlFlow::Break(()) = workspace.update_state(
|
||||||
|
tr,
|
||||||
|
error_dialog,
|
||||||
|
&InteractiveInput {
|
||||||
|
active_layer,
|
||||||
|
pointer_pos: point! {x: latest_pos.x as f64, y: latest_pos.y as f64},
|
||||||
|
dt,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
let layers = &mut workspace.appearance_panel;
|
let layers = &mut workspace.appearance_panel;
|
||||||
let overlay = &mut workspace.overlay;
|
let overlay = &mut workspace.overlay;
|
||||||
let latest_point = point! {x: latest_pos.x as f64, y: -latest_pos.y as f64};
|
|
||||||
let board = workspace.interactor.invoker().autorouter().board();
|
let board = workspace.interactor.invoker().autorouter().board();
|
||||||
|
|
||||||
if response.clicked_by(egui::PointerButton::Primary) {
|
if response.clicked_by(egui::PointerButton::Primary) {
|
||||||
if menu_bar.is_placing_via {
|
if menu_bar.is_placing_via {
|
||||||
workspace.interactor.execute(Command::PlaceVia(ViaWeight {
|
workspace.interactor.execute(Command::PlaceVia(ViaWeight {
|
||||||
|
|
@ -167,7 +246,10 @@ impl Viewport {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let layers = &mut workspace.appearance_panel;
|
||||||
|
let overlay = &mut workspace.overlay;
|
||||||
let board = workspace.interactor.invoker().autorouter().board();
|
let board = workspace.interactor.invoker().autorouter().board();
|
||||||
|
|
||||||
for i in (0..layers.visible.len()).rev() {
|
for i in (0..layers.visible.len()).rev() {
|
||||||
|
|
@ -466,6 +548,10 @@ impl Viewport {
|
||||||
.paint_primitive(ghost, egui::Color32::from_rgb(75, 75, 150));
|
.paint_primitive(ghost, egui::Color32::from_rgb(75, 75, 150));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let ActivityStepper::Interaction(InteractionStepper::RoutePlan(rp)) = activity.activity() {
|
||||||
|
painter.paint_linestring(&rp.lines, egui::Color32::from_rgb(245, 182, 66));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref navmesh) =
|
if let Some(ref navmesh) =
|
||||||
activity.maybe_thetastar().map(|astar| astar.graph())
|
activity.maybe_thetastar().map(|astar| astar.graph())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ use std::{
|
||||||
|
|
||||||
use topola::{
|
use topola::{
|
||||||
autorouter::history::History,
|
autorouter::history::History,
|
||||||
interactor::{activity::InteractiveInput, Interactor},
|
interactor::{
|
||||||
|
activity::{InteractiveEvent, InteractiveInput},
|
||||||
|
Interactor,
|
||||||
|
},
|
||||||
layout::LayoutEdit,
|
layout::LayoutEdit,
|
||||||
specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata},
|
specctra::{design::SpecctraDesign, mesadata::SpecctraMesadata},
|
||||||
};
|
};
|
||||||
|
|
@ -63,6 +66,7 @@ impl Workspace {
|
||||||
tr: &Translator,
|
tr: &Translator,
|
||||||
error_dialog: &mut ErrorDialog,
|
error_dialog: &mut ErrorDialog,
|
||||||
interactive_input: &InteractiveInput,
|
interactive_input: &InteractiveInput,
|
||||||
|
interactive_event: Option<InteractiveEvent>,
|
||||||
) -> ControlFlow<()> {
|
) -> ControlFlow<()> {
|
||||||
if let Ok(data) = self.history_channel.1.try_recv() {
|
if let Ok(data) = self.history_channel.1.try_recv() {
|
||||||
match data {
|
match data {
|
||||||
|
|
@ -88,7 +92,7 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.interactor.update(interactive_input) {
|
match self.interactor.update(interactive_input, interactive_event) {
|
||||||
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
ControlFlow::Continue(()) => ControlFlow::Continue(()),
|
||||||
ControlFlow::Break(Ok(())) => ControlFlow::Break(()),
|
ControlFlow::Break(Ok(())) => ControlFlow::Break(()),
|
||||||
ControlFlow::Break(Err(err)) => {
|
ControlFlow::Break(Err(err)) => {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ tr-menu-view-frame-timestep = Frame Timestep
|
||||||
|
|
||||||
tr-menu-place = Place
|
tr-menu-place = Place
|
||||||
tr-menu-place-place-via = Place Via
|
tr-menu-place-place-via = Place Via
|
||||||
|
tr-menu-place-place-route-plan = Place Route Plan
|
||||||
|
|
||||||
tr-menu-route = Route
|
tr-menu-route = Route
|
||||||
tr-menu-route-autoroute = Autoroute
|
tr-menu-route-autoroute = Autoroute
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::{
|
use crate::{
|
||||||
board::AccessMesadata,
|
board::AccessMesadata,
|
||||||
layout::{via::ViaWeight, LayoutEdit},
|
layout::{via::ViaWeight, LayoutEdit},
|
||||||
stepper::Step,
|
stepper::{Abort, Step},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
|
@ -110,3 +110,10 @@ impl<M: AccessMesadata> Step<Invoker<M>, String> for ExecutionStepper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<M: AccessMesadata> Abort<Invoker<M>> for ExecutionStepper {
|
||||||
|
fn abort(&mut self, context: &mut Invoker<M>) {
|
||||||
|
// TODO: fix this
|
||||||
|
self.finish(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ pub use specctra_core::mesadata::AccessMesadata;
|
||||||
|
|
||||||
use bimap::BiBTreeMap;
|
use bimap::BiBTreeMap;
|
||||||
use derive_getters::Getters;
|
use derive_getters::Getters;
|
||||||
|
use geo::Point;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -175,6 +176,17 @@ impl<M: AccessMesadata> Board<M> {
|
||||||
self.node_to_pinname.get(node)
|
self.node_to_pinname.get(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the apex belonging to a given pin, if any
|
||||||
|
///
|
||||||
|
/// Warning: this is very slow.
|
||||||
|
pub fn pin_apex(&self, pin: &str, layer: usize) -> Option<(FixedDotIndex, Point)> {
|
||||||
|
self.node_to_pinname
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, node_pin)| *node_pin == pin)
|
||||||
|
// this should only ever return one result
|
||||||
|
.find_map(|(node, _)| self.layout().apex_of_compoundless_node(*node, layer))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the band name associated with a given band.
|
/// Returns the band name associated with a given band.
|
||||||
pub fn band_bandname(&self, band: &BandUid) -> Option<&BandName> {
|
pub fn band_bandname(&self, band: &BandUid) -> Option<&BandName> {
|
||||||
self.band_bandname.get_by_left(band)
|
self.band_bandname.get_by_left(band)
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,23 @@ use crate::{
|
||||||
navmesh::{Navmesh, NavnodeIndex},
|
navmesh::{Navmesh, NavnodeIndex},
|
||||||
thetastar::ThetastarStepper,
|
thetastar::ThetastarStepper,
|
||||||
},
|
},
|
||||||
stepper::{Abort, Step},
|
stepper::{Abort, OnEvent, Step},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Stores the interactive input data from the user
|
/// Stores the interactive input data from the user
|
||||||
pub struct InteractiveInput {
|
pub struct InteractiveInput {
|
||||||
|
pub active_layer: Option<usize>,
|
||||||
pub pointer_pos: Point,
|
pub pointer_pos: Point,
|
||||||
pub dt: f32,
|
pub dt: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An event received from the user
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum InteractiveEvent {
|
||||||
|
PointerPrimaryButtonClicked,
|
||||||
|
PointerSecondaryButtonClicked,
|
||||||
|
}
|
||||||
|
|
||||||
/// This is the execution context passed to the stepper on each step
|
/// This is the execution context passed to the stepper on each step
|
||||||
pub struct ActivityContext<'a, M> {
|
pub struct ActivityContext<'a, M> {
|
||||||
pub interactive_input: &'a InteractiveInput,
|
pub interactive_input: &'a InteractiveInput,
|
||||||
|
|
@ -75,14 +83,27 @@ impl<M: AccessMesadata> Step<ActivityContext<'_, M>, String> for ActivityStepper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: AccessMesadata> Abort<ActivityContext<'_, M>> for ActivityStepper {
|
impl<M: AccessMesadata> Abort<Invoker<M>> for ActivityStepper {
|
||||||
fn abort(&mut self, context: &mut ActivityContext<M>) {
|
fn abort(&mut self, context: &mut Invoker<M>) {
|
||||||
match self {
|
match self {
|
||||||
ActivityStepper::Interaction(interaction) => interaction.abort(context),
|
ActivityStepper::Interaction(interaction) => interaction.abort(context),
|
||||||
ActivityStepper::Execution(execution) => {
|
ActivityStepper::Execution(execution) => execution.abort(context),
|
||||||
execution.finish(context.invoker);
|
}
|
||||||
} // TODO.
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
impl<M: AccessMesadata> OnEvent<ActivityContext<'_, M>, InteractiveEvent> for ActivityStepper {
|
||||||
|
type Output = Result<(), InteractionError>;
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
context: &mut ActivityContext<M>,
|
||||||
|
event: InteractiveEvent,
|
||||||
|
) -> Result<(), InteractionError> {
|
||||||
|
match self {
|
||||||
|
ActivityStepper::Interaction(interaction) => interaction.on_event(context, event),
|
||||||
|
ActivityStepper::Execution(_) => Ok(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +121,17 @@ impl ActivityStepperWithStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_interaction(interaction: InteractionStepper) -> ActivityStepperWithStatus {
|
||||||
|
Self {
|
||||||
|
activity: ActivityStepper::Interaction(interaction),
|
||||||
|
maybe_status: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activity(&self) -> &ActivityStepper {
|
||||||
|
&self.activity
|
||||||
|
}
|
||||||
|
|
||||||
pub fn maybe_status(&self) -> Option<ControlFlow<String>> {
|
pub fn maybe_status(&self) -> Option<ControlFlow<String>> {
|
||||||
self.maybe_status.clone()
|
self.maybe_status.clone()
|
||||||
}
|
}
|
||||||
|
|
@ -118,13 +150,27 @@ impl<M: AccessMesadata> Step<ActivityContext<'_, M>, String> for ActivityStepper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: AccessMesadata> Abort<ActivityContext<'_, M>> for ActivityStepperWithStatus {
|
impl<M: AccessMesadata> Abort<Invoker<M>> for ActivityStepperWithStatus {
|
||||||
fn abort(&mut self, context: &mut ActivityContext<M>) {
|
fn abort(&mut self, context: &mut Invoker<M>) {
|
||||||
self.maybe_status = Some(ControlFlow::Break(String::from("aborted")));
|
self.maybe_status = Some(ControlFlow::Break(String::from("aborted")));
|
||||||
self.activity.abort(context);
|
self.activity.abort(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<M: AccessMesadata> OnEvent<ActivityContext<'_, M>, InteractiveEvent>
|
||||||
|
for ActivityStepperWithStatus
|
||||||
|
{
|
||||||
|
type Output = Result<(), InteractionError>;
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
context: &mut ActivityContext<M>,
|
||||||
|
event: InteractiveEvent,
|
||||||
|
) -> Result<(), InteractionError> {
|
||||||
|
self.activity.on_event(context, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GetMaybeThetastarStepper for ActivityStepperWithStatus {
|
impl GetMaybeThetastarStepper for ActivityStepperWithStatus {
|
||||||
fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> {
|
fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> {
|
||||||
self.activity.maybe_thetastar()
|
self.activity.maybe_thetastar()
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,22 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use std::ops::ControlFlow;
|
use core::ops::ControlFlow;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
autorouter::invoker::{
|
autorouter::invoker::{
|
||||||
GetGhosts, GetMaybeNavcord, GetMaybeThetastarStepper, GetNavmeshDebugTexts, GetObstacles,
|
GetGhosts, GetMaybeNavcord, GetMaybeThetastarStepper, GetNavmeshDebugTexts, GetObstacles,
|
||||||
|
Invoker,
|
||||||
},
|
},
|
||||||
board::AccessMesadata,
|
board::AccessMesadata,
|
||||||
drawing::graph::PrimitiveIndex,
|
stepper::{Abort, OnEvent, Step},
|
||||||
geometry::primitive::PrimitiveShape,
|
|
||||||
router::{
|
|
||||||
navcord::Navcord,
|
|
||||||
navmesh::{Navmesh, NavnodeIndex},
|
|
||||||
thetastar::ThetastarStepper,
|
|
||||||
},
|
|
||||||
stepper::{Abort, Step},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::activity::ActivityContext;
|
use super::{
|
||||||
|
activity::{ActivityContext, InteractiveEvent},
|
||||||
|
route_plan::RoutePlan,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone)]
|
#[derive(Error, Debug, Clone)]
|
||||||
pub enum InteractionError {
|
pub enum InteractionError {
|
||||||
|
|
@ -30,10 +26,10 @@ pub enum InteractionError {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum InteractionStepper {
|
pub enum InteractionStepper {
|
||||||
// No interactions yet. This is only an empty skeleton for now.
|
|
||||||
// Examples of interactions:
|
// Examples of interactions:
|
||||||
// - interactively routing a track
|
// - interactively routing a track
|
||||||
// - interactively moving a footprint.
|
// - interactively moving a footprint.
|
||||||
|
RoutePlan(RoutePlan),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: AccessMesadata> Step<ActivityContext<'_, M>, String> for InteractionStepper {
|
impl<M: AccessMesadata> Step<ActivityContext<'_, M>, String> for InteractionStepper {
|
||||||
|
|
@ -41,48 +37,38 @@ impl<M: AccessMesadata> Step<ActivityContext<'_, M>, String> for InteractionStep
|
||||||
|
|
||||||
fn step(
|
fn step(
|
||||||
&mut self,
|
&mut self,
|
||||||
_context: &mut ActivityContext<M>,
|
context: &mut ActivityContext<M>,
|
||||||
) -> Result<ControlFlow<String>, InteractionError> {
|
) -> Result<ControlFlow<String>, InteractionError> {
|
||||||
Ok(ControlFlow::Break(String::from("")))
|
match self {
|
||||||
|
Self::RoutePlan(rp) => rp.step(context),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: AccessMesadata> Abort<ActivityContext<'_, M>> for InteractionStepper {
|
impl<M: AccessMesadata> Abort<Invoker<M>> for InteractionStepper {
|
||||||
fn abort(&mut self, _context: &mut ActivityContext<M>) {
|
fn abort(&mut self, context: &mut Invoker<M>) {
|
||||||
todo!();
|
match self {
|
||||||
|
Self::RoutePlan(rp) => rp.abort(context),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GetMaybeThetastarStepper for InteractionStepper {
|
impl<M: AccessMesadata> OnEvent<ActivityContext<'_, M>, InteractiveEvent> for InteractionStepper {
|
||||||
fn maybe_thetastar(&self) -> Option<&ThetastarStepper<Navmesh, f64>> {
|
type Output = Result<(), InteractionError>;
|
||||||
todo!()
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
context: &mut ActivityContext<M>,
|
||||||
|
event: InteractiveEvent,
|
||||||
|
) -> Result<(), InteractionError> {
|
||||||
|
match self {
|
||||||
|
Self::RoutePlan(rp) => rp.on_event(context, event),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GetMaybeNavcord for InteractionStepper {
|
impl GetGhosts for InteractionStepper {}
|
||||||
fn maybe_navcord(&self) -> Option<&Navcord> {
|
impl GetMaybeThetastarStepper for InteractionStepper {}
|
||||||
todo!()
|
impl GetMaybeNavcord for InteractionStepper {}
|
||||||
}
|
impl GetNavmeshDebugTexts for InteractionStepper {}
|
||||||
}
|
impl GetObstacles for InteractionStepper {}
|
||||||
|
|
||||||
impl GetGhosts for InteractionStepper {
|
|
||||||
fn ghosts(&self) -> &[PrimitiveShape] {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetObstacles for InteractionStepper {
|
|
||||||
fn obstacles(&self) -> &[PrimitiveIndex] {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetNavmeshDebugTexts for InteractionStepper {
|
|
||||||
fn navnode_debug_text(&self, _navnode: NavnodeIndex) -> Option<&str> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn navedge_debug_text(&self, _navedge: (NavnodeIndex, NavnodeIndex)) -> Option<&str> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,14 @@ use crate::{
|
||||||
Autorouter,
|
Autorouter,
|
||||||
},
|
},
|
||||||
board::{AccessMesadata, Board},
|
board::{AccessMesadata, Board},
|
||||||
interactor::activity::{
|
interactor::{
|
||||||
ActivityContext, ActivityError, ActivityStepperWithStatus, InteractiveInput,
|
activity::{
|
||||||
|
ActivityContext, ActivityError, ActivityStepperWithStatus, InteractiveEvent,
|
||||||
|
InteractiveInput,
|
||||||
},
|
},
|
||||||
stepper::{Abort, Step},
|
interaction::InteractionStepper,
|
||||||
|
},
|
||||||
|
stepper::{Abort, OnEvent, Step},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Structure that manages the invoker and activities
|
/// Structure that manages the invoker and activities
|
||||||
|
|
@ -48,6 +52,11 @@ impl<M: AccessMesadata> Interactor<M> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start an interaction activity
|
||||||
|
pub fn interact(&mut self, interaction: InteractionStepper) {
|
||||||
|
self.activity = Some(ActivityStepperWithStatus::new_interaction(interaction));
|
||||||
|
}
|
||||||
|
|
||||||
/// Undo last command
|
/// Undo last command
|
||||||
pub fn undo(&mut self) -> Result<(), InvokerError> {
|
pub fn undo(&mut self) -> Result<(), InvokerError> {
|
||||||
self.invoker.undo()
|
self.invoker.undo()
|
||||||
|
|
@ -61,13 +70,7 @@ impl<M: AccessMesadata> Interactor<M> {
|
||||||
/// Abort the currently running execution or activity
|
/// Abort the currently running execution or activity
|
||||||
pub fn abort(&mut self) {
|
pub fn abort(&mut self) {
|
||||||
if let Some(ref mut activity) = self.activity.take() {
|
if let Some(ref mut activity) = self.activity.take() {
|
||||||
activity.abort(&mut ActivityContext::<M> {
|
activity.abort(&mut self.invoker);
|
||||||
interactive_input: &InteractiveInput {
|
|
||||||
pointer_pos: [0.0, 0.0].into(),
|
|
||||||
dt: 0.0,
|
|
||||||
},
|
|
||||||
invoker: &mut self.invoker,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,9 +83,25 @@ impl<M: AccessMesadata> Interactor<M> {
|
||||||
pub fn update(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
interactive_input: &InteractiveInput,
|
interactive_input: &InteractiveInput,
|
||||||
|
interactive_event: Option<InteractiveEvent>,
|
||||||
) -> ControlFlow<Result<(), ActivityError>> {
|
) -> ControlFlow<Result<(), ActivityError>> {
|
||||||
if let Some(ref mut activity) = self.activity {
|
if let Some(ref mut activity) = self.activity {
|
||||||
return match activity.step(&mut ActivityContext {
|
if let Some(event) = interactive_event {
|
||||||
|
match activity.on_event(
|
||||||
|
&mut ActivityContext {
|
||||||
|
interactive_input,
|
||||||
|
invoker: &mut self.invoker,
|
||||||
|
},
|
||||||
|
event,
|
||||||
|
) {
|
||||||
|
Ok(()) => ControlFlow::Continue(()),
|
||||||
|
Err(err) => {
|
||||||
|
self.activity = None;
|
||||||
|
ControlFlow::Break(Err(err.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match activity.step(&mut ActivityContext {
|
||||||
interactive_input,
|
interactive_input,
|
||||||
invoker: &mut self.invoker,
|
invoker: &mut self.invoker,
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -95,10 +114,12 @@ impl<M: AccessMesadata> Interactor<M> {
|
||||||
self.activity = None;
|
self.activity = None;
|
||||||
ControlFlow::Break(Err(err))
|
ControlFlow::Break(Err(err))
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ControlFlow::Break(Ok(()))
|
ControlFlow::Break(Ok(()))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the invoker
|
/// Returns the invoker
|
||||||
pub fn invoker(&self) -> &Invoker<M> {
|
pub fn invoker(&self) -> &Invoker<M> {
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,6 @@
|
||||||
pub mod activity;
|
pub mod activity;
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
mod interactor;
|
mod interactor;
|
||||||
|
pub mod route_plan;
|
||||||
|
|
||||||
pub use interactor::*;
|
pub use interactor::*;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
// SPDX-FileCopyrightText: 2025 Topola contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use core::ops::ControlFlow;
|
||||||
|
use geo::{LineString, Point};
|
||||||
|
use rstar::AABB;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
autorouter::invoker::Invoker,
|
||||||
|
board::AccessMesadata,
|
||||||
|
drawing::dot::FixedDotIndex,
|
||||||
|
geometry::shape::AccessShape as _,
|
||||||
|
stepper::{Abort, OnEvent, Step},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
activity::{ActivityContext, InteractiveEvent},
|
||||||
|
interaction::InteractionError,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An interactive collector for a route
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RoutePlan {
|
||||||
|
pub active_layer: usize,
|
||||||
|
pub start_pin: Option<(FixedDotIndex, Point)>,
|
||||||
|
pub end_pin: Option<(FixedDotIndex, Point)>,
|
||||||
|
pub lines: LineString,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_select_pin<M: AccessMesadata>(
|
||||||
|
context: &ActivityContext<M>,
|
||||||
|
) -> Option<(usize, FixedDotIndex, Point)> {
|
||||||
|
let active_layer = context.interactive_input.active_layer?;
|
||||||
|
let pos = context.interactive_input.pointer_pos;
|
||||||
|
let board = context.invoker.autorouter().board();
|
||||||
|
let layout = board.layout();
|
||||||
|
let bbox = AABB::from_point([pos.x(), pos.y()]);
|
||||||
|
|
||||||
|
// gather relevant resolved selectors
|
||||||
|
let mut pin = None;
|
||||||
|
let mut apex_node = None;
|
||||||
|
for &geom in
|
||||||
|
layout
|
||||||
|
.drawing()
|
||||||
|
.rtree()
|
||||||
|
.locate_in_envelope_intersecting(&AABB::<[f64; 3]>::from_corners(
|
||||||
|
[pos.x(), pos.y(), active_layer as f64 - f64::EPSILON],
|
||||||
|
[pos.x(), pos.y(), active_layer as f64 + f64::EPSILON],
|
||||||
|
))
|
||||||
|
{
|
||||||
|
let node = geom.data;
|
||||||
|
if layout.node_shape(node).intersects_with_bbox(&bbox) {
|
||||||
|
if let Some(pin_name) = board.node_pinname(&node) {
|
||||||
|
// insert and check that we selected exactly one pin / node
|
||||||
|
log::debug!("node {:?} -> pin {:?}", node, pin_name);
|
||||||
|
if let Some(old_pin) = pin {
|
||||||
|
if pin_name != old_pin {
|
||||||
|
log::debug!(
|
||||||
|
"rejected pin due to ambiguity: [{:?}, {:?}]",
|
||||||
|
old_pin,
|
||||||
|
pin_name
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pin = Some(pin_name);
|
||||||
|
let (idx, pos) = board.pin_apex(pin_name, active_layer).unwrap();
|
||||||
|
if let Some((idx2, _)) = apex_node {
|
||||||
|
if idx != idx2 {
|
||||||
|
// node index is ambiguous
|
||||||
|
log::debug!("rejected pin due to ambiguity: [{:?}, {:?}]", idx2, idx);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
apex_node = Some((idx, pos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = apex_node.map(|(idx, pos)| (active_layer, idx, pos));
|
||||||
|
log::debug!("result {:?}", ret);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoutePlan {
|
||||||
|
pub fn new(active_layer: usize) -> Self {
|
||||||
|
log::debug!("initialized RoutePlan");
|
||||||
|
Self {
|
||||||
|
active_layer,
|
||||||
|
start_pin: None,
|
||||||
|
end_pin: None,
|
||||||
|
lines: LineString(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn last_pos(&self) -> Option<Point> {
|
||||||
|
self.lines.0.last().map(|i| Point(*i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: AccessMesadata> Step<ActivityContext<'_, M>, String> for RoutePlan {
|
||||||
|
type Error = InteractionError;
|
||||||
|
|
||||||
|
fn step(
|
||||||
|
&mut self,
|
||||||
|
_context: &mut ActivityContext<M>,
|
||||||
|
) -> Result<ControlFlow<String>, InteractionError> {
|
||||||
|
Ok(ControlFlow::Continue(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: AccessMesadata> Abort<Invoker<M>> for RoutePlan {
|
||||||
|
fn abort(&mut self, _context: &mut Invoker<M>) {
|
||||||
|
self.start_pin = None;
|
||||||
|
self.end_pin = None;
|
||||||
|
self.lines.0.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: AccessMesadata> OnEvent<ActivityContext<'_, M>, InteractiveEvent> for RoutePlan {
|
||||||
|
type Output = Result<(), InteractionError>;
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
&mut self,
|
||||||
|
context: &mut ActivityContext<M>,
|
||||||
|
event: InteractiveEvent,
|
||||||
|
) -> Result<(), InteractionError> {
|
||||||
|
match event {
|
||||||
|
InteractiveEvent::PointerPrimaryButtonClicked if self.end_pin.is_none() => {
|
||||||
|
if let Some((layer, idx, pos)) = try_select_pin(context) {
|
||||||
|
// make sure double-click or such doesn't corrupt state
|
||||||
|
if let Some(start_pin) = self.start_pin {
|
||||||
|
if self.active_layer == layer && idx != start_pin.0 {
|
||||||
|
log::debug!("clicked on end pin: {:?}", idx);
|
||||||
|
self.lines.0.push(pos.0);
|
||||||
|
self.end_pin = Some((idx, pos));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::debug!("clicked on start pin: {:?}", idx);
|
||||||
|
self.active_layer = layer;
|
||||||
|
self.start_pin = Some((idx, pos));
|
||||||
|
self.lines.0.push(pos.0);
|
||||||
|
}
|
||||||
|
} else if let Some(last_pos) = self.last_pos() {
|
||||||
|
let pos = context.interactive_input.pointer_pos;
|
||||||
|
if last_pos != pos {
|
||||||
|
self.lines.0.push(pos.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InteractiveEvent::PointerSecondaryButtonClicked => {
|
||||||
|
if self.end_pin.is_some() {
|
||||||
|
log::debug!("un-clicked end pin");
|
||||||
|
self.end_pin = None;
|
||||||
|
self.lines.0.pop();
|
||||||
|
}
|
||||||
|
if !self.lines.0.is_empty() {
|
||||||
|
self.lines.0.pop();
|
||||||
|
if self.lines.0.is_empty() {
|
||||||
|
log::debug!("un-clicked start pin");
|
||||||
|
self.start_pin = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,9 +17,9 @@ use crate::{
|
||||||
LooseDotWeight,
|
LooseDotWeight,
|
||||||
},
|
},
|
||||||
gear::GearIndex,
|
gear::GearIndex,
|
||||||
graph::{GetMaybeNet, IsInLayer, MakePrimitive, PrimitiveIndex},
|
graph::{GetMaybeNet, IsInLayer, MakePrimitive, PrimitiveIndex, PrimitiveWeight},
|
||||||
loose::LooseIndex,
|
loose::LooseIndex,
|
||||||
primitive::{GetWeight, MakePrimitiveShape, Primitive},
|
primitive::MakePrimitiveShape,
|
||||||
rules::AccessRules,
|
rules::AccessRules,
|
||||||
seg::{
|
seg::{
|
||||||
FixedSegIndex, FixedSegWeight, LoneLooseSegIndex, LoneLooseSegWeight, SegIndex,
|
FixedSegIndex, FixedSegWeight, LoneLooseSegIndex, LoneLooseSegWeight, SegIndex,
|
||||||
|
|
@ -296,26 +296,48 @@ impl<R: AccessRules> Layout<R> {
|
||||||
.compound_members(GenericIndex::new(poly.petgraph_index()))
|
.compound_members(GenericIndex::new(poly.petgraph_index()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn node_shape(&self, index: NodeIndex) -> Shape {
|
fn compound_shape(&self, compound: GenericIndex<CompoundWeight>) -> Shape {
|
||||||
match index {
|
match self.drawing.compound_weight(compound) {
|
||||||
NodeIndex::Primitive(primitive) => primitive.primitive(&self.drawing).shape().into(),
|
CompoundWeight::Poly(_) => GenericIndex::<PolyWeight>::new(compound.petgraph_index())
|
||||||
NodeIndex::Compound(compound) => match self.drawing.compound_weight(compound) {
|
|
||||||
CompoundWeight::Poly(_) => {
|
|
||||||
GenericIndex::<PolyWeight>::new(compound.petgraph_index())
|
|
||||||
.ref_(self)
|
.ref_(self)
|
||||||
.shape()
|
.shape()
|
||||||
.into()
|
|
||||||
}
|
|
||||||
CompoundWeight::Via(_) => self
|
|
||||||
.via(GenericIndex::<ViaWeight>::new(compound.petgraph_index()))
|
|
||||||
.shape()
|
|
||||||
.into(),
|
.into(),
|
||||||
},
|
CompoundWeight::Via(weight) => weight.shape().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node_shape(&self, index: NodeIndex) -> Shape {
|
||||||
|
match index {
|
||||||
|
NodeIndex::Primitive(primitive) => primitive.primitive(&self.drawing).shape().into(),
|
||||||
|
NodeIndex::Compound(compound) => self.compound_shape(compound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a node is not a primitive part of a compound, and if yes, returns its apex and center
|
||||||
|
pub fn apex_of_compoundless_node(
|
||||||
|
&self,
|
||||||
|
node: NodeIndex,
|
||||||
|
active_layer: usize,
|
||||||
|
) -> Option<(FixedDotIndex, Point)> {
|
||||||
|
fn handle_fixed_dot<R: AccessRules>(
|
||||||
|
drawing: &Drawing<CompoundWeight, CompoundEntryLabel, R>,
|
||||||
|
index: PrimitiveIndex,
|
||||||
|
) -> Option<(FixedDotIndex, &FixedDotWeight)> {
|
||||||
|
let PrimitiveIndex::FixedDot(dot) = index else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if let GenericNode::Primitive(PrimitiveWeight::FixedDot(weight)) = drawing
|
||||||
|
.geometry()
|
||||||
|
.graph()
|
||||||
|
.node_weight(dot.petgraph_index())
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
Some((dot, weight))
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a node is not a primitive part of a compound, and if yes, returns its center
|
|
||||||
pub fn center_of_compoundless_node(&self, node: NodeIndex) -> Option<Point> {
|
|
||||||
match node {
|
match node {
|
||||||
NodeIndex::Primitive(primitive) => {
|
NodeIndex::Primitive(primitive) => {
|
||||||
if self
|
if self
|
||||||
|
|
@ -327,13 +349,30 @@ impl<R: AccessRules> Layout<R> {
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
match primitive.primitive(self.drawing()) {
|
handle_fixed_dot(&self.drawing, primitive).map(|(dot, weight)| (dot, weight.pos()))
|
||||||
Primitive::FixedDot(dot) => Some(dot.weight().pos()),
|
}
|
||||||
// Primitive::LooseDot(dot) => Some(dot.weight().pos()),
|
NodeIndex::Compound(compound) => Some(match self.drawing.compound_weight(compound) {
|
||||||
_ => None,
|
CompoundWeight::Poly(_) => {
|
||||||
|
let poly =
|
||||||
|
GenericIndex::<PolyWeight>::new(compound.petgraph_index()).ref_(self);
|
||||||
|
(poly.apex(), poly.shape().center())
|
||||||
|
}
|
||||||
|
CompoundWeight::Via(weight) => {
|
||||||
|
let mut dots = self.drawing.geometry().compound_members(compound);
|
||||||
|
let apex = loop {
|
||||||
|
// this returns None if the via is not present on this layer
|
||||||
|
let (entry_label, dot) = dots.next()?;
|
||||||
|
if entry_label == CompoundEntryLabel::NotInConvexHull {
|
||||||
|
if let Some((dot, weight)) = handle_fixed_dot(&self.drawing, dot) {
|
||||||
|
if weight.layer() == active_layer {
|
||||||
|
break dot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeIndex::Compound(_) => Some(self.node_shape(node).center()),
|
}
|
||||||
|
};
|
||||||
|
(apex, weight.shape().center())
|
||||||
|
}
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,10 @@ use crate::{
|
||||||
band::BandTermsegIndex,
|
band::BandTermsegIndex,
|
||||||
dot::FixedDotIndex,
|
dot::FixedDotIndex,
|
||||||
graph::{MakePrimitive, PrimitiveIndex},
|
graph::{MakePrimitive, PrimitiveIndex},
|
||||||
head::Head,
|
|
||||||
primitive::MakePrimitiveShape,
|
primitive::MakePrimitiveShape,
|
||||||
rules::AccessRules,
|
rules::AccessRules,
|
||||||
},
|
},
|
||||||
geometry::{
|
geometry::{primitive::PrimitiveShape, shape::AccessShape},
|
||||||
primitive::PrimitiveShape,
|
|
||||||
shape::{AccessShape, MeasureLength},
|
|
||||||
},
|
|
||||||
graph::MakeRef,
|
|
||||||
layout::{Layout, LayoutEdit},
|
layout::{Layout, LayoutEdit},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use std::ops::ControlFlow;
|
use core::ops::ControlFlow;
|
||||||
|
|
||||||
/// This trait represents a linearly advanceable state whose advancement may
|
/// This trait represents a linearly advanceable state whose advancement may
|
||||||
/// break or fail with many different return values, and to which part of
|
/// break or fail with many different return values, and to which part of
|
||||||
|
|
@ -52,3 +52,10 @@ pub trait Abort<C> {
|
||||||
/// Abort the stepper.
|
/// Abort the stepper.
|
||||||
fn abort(&mut self, context: &mut C);
|
fn abort(&mut self, context: &mut C);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Steppers that may receive discrete events and act on them, implement this trait.
|
||||||
|
pub trait OnEvent<Ctx, Event> {
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
fn on_event(&mut self, context: &mut Ctx, event: Event) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue