use super::RunProvenance;
use crate::{
BatchModel, EditorModel, MainWindow,
bridge::render_thread::RenderContext,
gui::{
batch::tilt,
editor::{
auto_solve,
material_lookup::{EditorMaterialLookup, resolved_gem_material},
state::EditorState,
},
show_toast,
},
};
use indicatrix::{
color::metrics::{PROFILE_AZIMUTHS_DEG, SweepProgress},
optics::materials::GemMaterial,
};
use indicatrix_cut_core::Design;
use indicatrix_vault::{db::sqlite::Database, model::tilt_curves::TiltPerformanceCurves};
use slint::ComponentHandle;
use std::{
cell::RefCell,
rc::Rc,
sync::{
Arc, Mutex,
atomic::{AtomicBool, AtomicU64, Ordering as AtomicOrdering},
},
thread,
};
pub(in crate::gui::editor) fn setup_batch_tilt_for_open_design_callback(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
db: &Arc<Mutex<Database>>,
) {
let state = Rc::clone(state);
let render_ctx = Arc::clone(render_ctx);
let db = Arc::clone(db);
let ui_weak = ui.as_weak();
let run_epoch = Arc::new(AtomicU64::new(0));
let active_cancel: Rc<RefCell<Option<Arc<AtomicBool>>>> = Rc::new(RefCell::new(None));
let cancel_from_dialog = Rc::clone(&active_cancel);
ui.global::<BatchModel>().on_tilt_single_cancel(move || {
if let Some(cancel) = cancel_from_dialog.borrow().as_ref() {
cancel.store(true, AtomicOrdering::Relaxed);
}
});
ui.global::<EditorModel>()
.on_compute_tilt_curves_for_open_design(move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
begin_batch_tilt_for_open_design(
&ui,
&state,
&render_ctx,
&db,
&run_epoch,
&active_cancel,
);
});
}
struct BatchTiltRun {
design: Design,
custom_materials: Vec<GemMaterial>,
source_entry_id: Option<i64>,
provenance: RunProvenance,
run_epoch_done: Arc<AtomicU64>,
this_run: u64,
db: Arc<Mutex<Database>>,
ui_weak: slint::Weak<MainWindow>,
cancel: Arc<AtomicBool>,
in_dialog: bool,
}
impl BatchTiltRun {
fn is_current(&self) -> bool {
self.run_epoch_done.load(AtomicOrdering::Relaxed) == self.this_run
}
}
fn begin_batch_tilt_for_open_design(
ui: &MainWindow,
state: &Rc<RefCell<EditorState>>,
render_ctx: &Arc<Mutex<RenderContext>>,
db: &Arc<Mutex<Database>>,
run_epoch: &Arc<AtomicU64>,
active_cancel: &Rc<RefCell<Option<Arc<AtomicBool>>>>,
) {
let st = state.borrow();
let design = st.design.clone();
let source_entry_id = st.source_entry_id;
let provenance = RunProvenance::capture(&st);
drop(st);
let custom_materials = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.custom_materials
.as_ref()
.clone();
if let Some(previous) = active_cancel.borrow_mut().take() {
previous.store(true, AtomicOrdering::Relaxed);
}
let cancel = Arc::new(AtomicBool::new(false));
*active_cancel.borrow_mut() = Some(Arc::clone(&cancel));
let this_run = run_epoch.fetch_add(1, AtomicOrdering::Relaxed) + 1;
let run = BatchTiltRun {
design,
custom_materials,
source_entry_id,
provenance,
run_epoch_done: Arc::clone(run_epoch),
this_run,
db: Arc::clone(db),
ui_weak: ui.as_weak(),
cancel,
in_dialog: !ui.global::<BatchModel>().get_tilt_visible(),
};
if run.in_dialog {
open_single_design_dialog(ui);
} else {
show_toast(
ui,
"Computing tilt-performance curves for this design...",
"info",
);
}
thread::spawn(move || run_batch_tilt_for_open_design(run));
}
fn open_single_design_dialog(ui: &MainWindow) {
let batch = ui.global::<BatchModel>();
batch.set_tilt_single(true);
batch.set_tilt_confirming(false);
batch.set_tilt_done(false);
batch.set_tilt_local_lane_total(0);
batch.set_tilt_remote_active(false);
batch.set_tilt_single_fraction(0.0);
batch.set_tilt_single_stage("Solving the design...".into());
batch.set_tilt_summary(String::new().into());
batch.set_tilt_visible(true);
}
const PROGRESS_STRIDE: usize = 8;
fn push_sweep_progress(run: &BatchTiltRun, progress: SweepProgress) {
if !run.in_dialog || !progress.done.is_multiple_of(PROGRESS_STRIDE) {
return;
}
let run_epoch = Arc::clone(&run.run_epoch_done);
let this_run = run.this_run;
let azimuth = PROFILE_AZIMUTHS_DEG
.get(progress.axis)
.copied()
.unwrap_or(0.0);
let stage = format!(
"Axis {} of {} ({azimuth:.0}\u{b0}) \u{b7} {} / {} steps",
progress.axis + 1,
PROFILE_AZIMUTHS_DEG.len(),
progress.done,
progress.total
);
let fraction = match (u16::try_from(progress.done), u16::try_from(progress.total)) {
(Ok(done), Ok(total)) if total > 0 => f32::from(done) / f32::from(total),
_ => 0.0,
};
let _ = run.ui_weak.upgrade_in_event_loop(move |ui| {
let batch = ui.global::<BatchModel>();
if run_epoch.load(AtomicOrdering::Relaxed) == this_run && batch.get_tilt_single() {
batch.set_tilt_single_fraction(fraction);
batch.set_tilt_single_stage(stage.into());
}
});
}
fn report(ui: &MainWindow, in_dialog: bool, message: &str, kind: &str) {
let batch = ui.global::<BatchModel>();
if in_dialog && batch.get_tilt_single() && batch.get_tilt_visible() {
batch.set_tilt_summary(message.into());
batch.set_tilt_done(true);
} else {
show_toast(ui, message, kind);
}
}
fn run_batch_tilt_for_open_design(run: BatchTiltRun) {
let solved = match run.design.solve() {
Ok(solved) => solved,
Err(e) => {
let ui_weak = run.ui_weak.clone();
let run_epoch_done = Arc::clone(&run.run_epoch_done);
let this_run = run.this_run;
let in_dialog = run.in_dialog;
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
if run_epoch_done.load(AtomicOrdering::Relaxed) == this_run {
let message = format!("Cannot compute tilt curves: {e}");
report(&ui, in_dialog, &message, "error");
}
});
return;
}
};
let planes = auto_solve::design_to_gpu_planes_from_solved(&run.design, &solved);
let lookup = EditorMaterialLookup::new(&run.custom_materials);
let material = resolved_gem_material(&run.design.material, &lookup);
let curves = tilt::tilt_curves_for_planes(&planes, &material, None, &run.cancel, &mut |p| {
push_sweep_progress(&run, p);
});
let ui_weak = run.ui_weak.clone();
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
apply_batch_tilt_result(&ui, curves, &run);
});
}
fn apply_batch_tilt_result(
ui: &MainWindow,
curves: Option<TiltPerformanceCurves>,
run: &BatchTiltRun,
) {
if !run.is_current() {
return;
}
let Some(curves) = curves else {
if run.cancel.load(AtomicOrdering::Relaxed) {
report(ui, run.in_dialog, "Cancelled -- nothing was saved.", "info");
} else {
report(
ui,
run.in_dialog,
"Tilt-curve computation failed for this design.",
"error",
);
}
return;
};
if run.provenance.is_stale() {
report(
ui,
run.in_dialog,
"Result discarded: the design changed while computing tilt curves. Re-run to \
save an up-to-date result.",
"warning",
);
return;
}
match run.source_entry_id {
Some(entry_id) => {
if tilt::save_tilt_curves_for_entry(&run.db, entry_id, &curves) {
report(
ui,
run.in_dialog,
"Saved tilt-performance curves for this design.",
"success",
);
} else {
report(
ui,
run.in_dialog,
"Could not save tilt-performance curves for this design.",
"error",
);
}
}
None => report(
ui,
run.in_dialog,
"Computed tilt-performance curves -- save this design to the \
catalogue to keep them.",
"info",
),
}
}