use super::{
RETARGET_ASYNC, apply_ghost_preview_or_revert,
material::{resolve_target_selection, resolved_material_from_selection},
proposal::cancel_optimize_run,
proposal_view::{
RetargetRowView, RetargetView, push_retarget_view, push_target_error, push_target_readout,
row_view,
},
sync_embedded_comparison,
};
use crate::{
EditorModel, MainWindow, RetargetModel, ViewportModel,
bridge::render_thread::RenderContext,
gui::{
editor::{
auto_solve,
optimize_solve::{self, OptimizeSolveOutcome},
retarget::{self, CrownShift, RetargetMode, RetargetProposal},
stale::{self, ResultKind},
state::EditorState,
view,
},
solid_preview::preview_state::SolidPreviewState,
},
};
use indicatrix::{
geometry::meet_solver::Block,
optics::{LightingPreset, materials::GemMaterial},
};
use indicatrix_cut_core::{Design, ObjectiveWeights, OptimizeConfig, ResolvedMaterial};
use slint::ComponentHandle;
use std::{
collections::BTreeSet,
sync::{Arc, Mutex, atomic::Ordering as AtomicOrdering},
};
fn optimize_config_from_ui(ui: &MainWindow) -> OptimizeConfig {
let weights = view::parse_optimize_weights(
&ui.global::<EditorModel>().get_optimize_weight_windowing(),
&ui.global::<EditorModel>().get_optimize_weight_extinction(),
&ui.global::<EditorModel>()
.get_optimize_weight_tilt_brilliance(),
ui.global::<EditorModel>().get_optimize_weight_yield(),
)
.unwrap_or_else(|_| ObjectiveWeights::default());
OptimizeConfig {
weights,
lighting: LightingPreset::from_index(
ui.global::<ViewportModel>().get_selected_lighting_index(),
),
..OptimizeConfig::default()
}
}
fn push_anchored_refusal(ui: &MainWindow, st: &mut EditorState, anchored: &[(usize, String)]) {
let anchored_errors = anchored
.iter()
.map(|(index, name)| format!("#{index} \"{name}\""))
.collect();
push_retarget_view(
ui,
RetargetView {
rows: Vec::new(),
notes: Vec::new(),
anchored_errors,
solve_error: String::new(),
},
);
st.pending_retarget = None;
ui.global::<RetargetModel>().set_is_busy(false);
}
fn set_optimize_progress(ui: &MainWindow, evaluations: usize, max_evaluations: usize) {
ui.global::<RetargetModel>()
.set_optimize_evaluations(i32::try_from(evaluations).unwrap_or(i32::MAX));
ui.global::<RetargetModel>()
.set_optimize_max_evaluations(i32::try_from(max_evaluations).unwrap_or(i32::MAX));
if let (Some(activity), Some(id)) = (
auto_solve::activity(),
RETARGET_ASYNC.with(|cell| cell.borrow().activity_id),
) {
let fraction = if max_evaluations == 0 {
crate::gui::editor::activity::INDETERMINATE
} else {
evaluations as f32 / max_evaluations as f32
};
activity.progress(id, fraction);
}
}
pub(super) fn start_optimize_run(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &view::SolidLastSolved,
st: &mut EditorState,
crown: CrownShift,
) {
let custom = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.custom_materials
.as_ref()
.clone();
let combo_index = ui.global::<RetargetModel>().get_target_material_index();
let ri_text = ui.global::<RetargetModel>().get_target_ri_override_text();
let selection = match resolve_target_selection(&st.design, &custom, combo_index, &ri_text) {
Ok(selection) => selection,
Err(message) => {
push_target_error(ui, &message);
st.pending_retarget = None;
ui.global::<RetargetModel>().set_is_busy(false);
return;
}
};
let target = resolved_material_from_selection(&selection, &custom);
push_target_readout(ui, &selection, &target);
let (scope, blocks) = retarget::retarget_scope(&st.design);
let anchored = retarget::anchored_tiers_in(&st.design, &scope);
if !anchored.is_empty() {
push_anchored_refusal(ui, st, &anchored);
return;
}
let n_from = st.design.effective_refractive_index_with(&custom);
let n_to = target.n_d;
let seeded = retarget::seed_shift_design(&st.design, &scope, &blocks, n_from, n_to, crown);
let ctx = OptimizeRunContext {
original: st.design.clone(),
seeded: seeded.clone(),
scope,
blocks,
n_to,
target,
custom: custom.clone(),
render_ctx: Arc::clone(render_ctx),
preview_state: Arc::clone(preview_state),
};
let config = optimize_config_from_ui(ui);
let max_evaluations = config.max_evaluations;
ui.global::<RetargetModel>().set_is_busy(true);
let activity_id = auto_solve::activity().map(|a| {
let ui_weak = ui.as_weak();
a.start(
"retarget_optimize",
"Retarget Optimize",
Some(Box::new(move || {
let ui_weak = ui_weak.clone();
let timer = slint::Timer::default();
timer.start(
slint::TimerMode::SingleShot,
std::time::Duration::ZERO,
move || {
if let Some(ui) = ui_weak.upgrade() {
cancel_optimize_run(&ui);
}
},
);
})),
)
});
RETARGET_ASYNC.with(|cell| cell.borrow_mut().activity_id = activity_id);
set_optimize_progress(ui, 0, max_evaluations);
view::submit_preview_replan(
ui,
render_ctx,
preview_state,
solid_last_solved,
st,
BTreeSet::new(),
false,
);
let run_id = RETARGET_ASYNC.with(|cell| {
let mut run = cell.borrow_mut();
run.run_id = run.run_id.wrapping_add(1);
run.run_id
});
let started_generation = st.generation.load(AtomicOrdering::Relaxed);
let handle = optimize_solve::spawn_optimize_solve(
ui.as_weak(),
seeded,
selection,
custom,
config,
|ui: &MainWindow, progress: optimize_solve::OptimizeSolveProgress| {
set_optimize_progress(ui, progress.evaluations, progress.max_evaluations);
},
move |ui: &MainWindow, outcome: OptimizeSolveOutcome| {
finish_optimize_run(ui, outcome, run_id, started_generation, &ctx);
},
);
RETARGET_ASYNC.with(|cell| cell.borrow_mut().handle = Some(handle));
}
struct OptimizeRunContext {
original: Design,
seeded: Design,
scope: Vec<usize>,
blocks: Vec<Block>,
n_to: f64,
target: ResolvedMaterial,
custom: Vec<GemMaterial>,
render_ctx: Arc<Mutex<RenderContext>>,
preview_state: Arc<SolidPreviewState>,
}
fn finish_optimize_run(
ui: &MainWindow,
outcome: OptimizeSolveOutcome,
run_id: u64,
started_generation: u64,
ctx: &OptimizeRunContext,
) {
let still_current = RETARGET_ASYNC.with(|cell| cell.borrow().run_id == run_id);
if !still_current {
return;
}
RETARGET_ASYNC.with(|cell| cell.borrow_mut().handle = None);
if let (Some(activity), Some(id)) = (
auto_solve::activity(),
RETARGET_ASYNC.with(|cell| cell.borrow_mut().activity_id.take()),
) {
activity.finish(id);
}
let (rows, notes, solve_error) = match outcome {
OptimizeSolveOutcome::Completed { outcome }
| OptimizeSolveOutcome::Cancelled { outcome } => {
let rows = retarget::rows_from_outcome(
&ctx.original,
&ctx.seeded,
&ctx.scope,
&ctx.blocks,
ctx.n_to,
&outcome.changes,
);
let n_from = ctx.original.effective_refractive_index_with(&ctx.custom);
let notes = retarget::build_notes(
RetargetMode::Optimize(OptimizeConfig::default()),
n_from,
ctx.n_to,
);
(rows, notes, String::new())
}
OptimizeSolveOutcome::Failed { error } => (Vec::new(), Vec::new(), error.to_string()),
};
let row_views: Vec<RetargetRowView> = rows.iter().map(row_view).collect();
let proposal = (!rows.is_empty()).then(|| RetargetProposal {
rows,
target: ctx.target.clone(),
notes: notes.clone(),
});
let _ = apply_ghost_preview_or_revert(
ui,
&ctx.render_ctx,
&ctx.preview_state,
&ctx.original,
proposal.as_ref(),
);
let has_proposal = proposal.is_some();
RETARGET_ASYNC.with(|cell| {
cell.borrow_mut().pending = proposal.map(|p| (p, started_generation));
});
if has_proposal {
stale::stamp(ResultKind::Retarget, started_generation);
} else {
stale::clear(ResultKind::Retarget);
}
let view = RetargetView {
rows: row_views,
notes,
anchored_errors: Vec::new(),
solve_error,
};
push_retarget_view(ui, view);
ui.global::<RetargetModel>().set_is_busy(false);
sync_embedded_comparison(ui, has_proposal);
}