use super::{
SolidLastSolved,
panel::refresh_editor_panel_from_solve,
panel_stale::push_stale_content,
state::{
EditorState, apply_multi_selection, design_to_gpu_planes,
manufacturability_warnings_tagged, push_multi_selected_count, push_tiers,
status_text_and_is_problem_from_solved, tier_items_from_solved,
yield_report_texts_from_solved,
},
};
use crate::{
EditorModel, MainWindow, SolidPreviewModel, TiltModel,
bridge::render_thread::{PlanesOwner, RenderContext},
gui::{
editor::{
auto_solve,
manipulate::{frame_updates_mast_cache, note_committed_replan_submitted},
},
render::camera_lighting::contained_request_size,
solid_preview::preview_state::{CameraPose, ReplanRequest, SolidPreviewState},
},
};
use indicatrix::geometry::meet_solver::SolvedTier;
use indicatrix_cut_core::Design;
use indicatrix_editor::solve_policy::{SolveCostEstimate, should_solve_synchronously_for};
use slint::{ComponentHandle, SharedString};
use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex, PoisonError, atomic::Ordering as AtomicOrdering},
time::{Duration, Instant},
};
pub(in crate::gui::editor) fn scaled_viewport_size(ui: &MainWindow) -> (u32, u32) {
let scale = ui.window().scale_factor();
(
(ui.global::<SolidPreviewModel>().get_viewport_width() * scale) as u32,
(ui.global::<SolidPreviewModel>().get_viewport_height() * scale) as u32,
)
}
pub(super) fn push_trace_staleness(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
state: &EditorState,
) {
let generation = state.generation.load(std::sync::atomic::Ordering::Relaxed);
let stale = {
let ctx = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
ctx.traced_planes_are_stale(generation)
};
ui.global::<crate::ViewportModel>().set_trace_stale(stale);
}
fn refresh_viewport(
ui: &MainWindow,
render_ctx: &Arc<Mutex<crate::bridge::render_thread::RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
state: &EditorState,
solved: Option<&[SolvedTier]>,
) {
super::optimize_apply::cancel_ghost_preview();
let mut ctx = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let planes_for_claim = solved.map_or_else(
|| design_to_gpu_planes(&state.design),
|solved| auto_solve::design_to_gpu_planes_from_solved(&state.design, solved),
);
ctx.claim_active_planes(
std::sync::Arc::new(planes_for_claim),
Some((
state.design.meta.gear_teeth_abs(),
state.design.meta.gear_reference_angle as f32,
)),
PlanesOwner::Editor {
generation: state.generation.load(std::sync::atomic::Ordering::Relaxed),
},
);
ctx.dirty = true;
let design_gear = ctx.design_gear;
let planes: Vec<(glam::Vec3, f32)> = ctx
.active_planes
.iter()
.map(|p| (glam::Vec3::from(p.normal), -p.d))
.collect();
let view_mode = ui.global::<SolidPreviewModel>().get_view_mode() as u8;
let size = contained_request_size(view_mode, scaled_viewport_size(ui), (ctx.width, ctx.height));
preview_state.request_redraw_with_gear(
planes,
CameraPose {
yaw: ctx.yaw,
pitch: ctx.pitch,
distance: ctx.distance,
},
size,
view_mode,
design_gear,
);
drop(ctx);
ui.global::<TiltModel>()
.set_cached_curve_material("".into());
if ui.global::<TiltModel>().get_dialog_open() {
ui.global::<TiltModel>().invoke_request_tilt_profile_axes();
}
if let Some(solved) = solved {
*solid_last_solved
.lock()
.unwrap_or_else(PoisonError::into_inner) = Some((
state.generation.load(AtomicOrdering::Relaxed),
solved.to_vec(),
));
}
push_trace_staleness(ui, render_ctx, state);
}
pub(in crate::gui::editor) fn submit_preview_replan(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
state: &EditorState,
dirty: BTreeSet<usize>,
force_full_solve: bool,
) {
submit_preview_replan_for(
ui,
render_ctx,
preview_state,
solid_last_solved,
ReplanSource {
design: &state.design,
generation: state.generation.load(AtomicOrdering::Relaxed),
multi_selected: &state.multi_selected,
},
dirty,
force_full_solve,
);
}
#[derive(Clone, Copy)]
pub(in crate::gui::editor) struct ReplanSource<'a> {
pub(in crate::gui::editor) design: &'a Design,
pub(in crate::gui::editor) generation: u64,
pub(in crate::gui::editor) multi_selected: &'a BTreeSet<usize>,
}
pub(in crate::gui::editor) fn submit_preview_replan_for(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
source: ReplanSource<'_>,
dirty: BTreeSet<usize>,
force_full_solve: bool,
) {
let generation = source.generation;
let last_solved = if force_full_solve {
None
} else {
solid_last_solved
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
.and_then(|(cached_generation, masts)| {
(*cached_generation == generation || *cached_generation + 1 == generation)
.then(|| masts.clone())
})
};
submit_preview_replan_chained(ui, render_ctx, preview_state, source, dirty, last_solved);
}
pub(in crate::gui::editor) fn submit_preview_replan_chained(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
source: ReplanSource<'_>,
dirty: BTreeSet<usize>,
last_solved: Option<Vec<SolvedTier>>,
) {
super::optimize_apply::cancel_ghost_preview();
let ReplanSource {
design,
generation,
multi_selected,
} = source;
let (camera, render_size, n_d) = {
let ctx = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
(
CameraPose {
yaw: ctx.yaw,
pitch: ctx.pitch,
distance: ctx.distance,
},
(ctx.width, ctx.height),
design.effective_refractive_index_with(&ctx.custom_materials),
)
};
let selected_tier = usize::try_from(ui.global::<EditorModel>().get_selected_tier_index()).ok();
let view_mode = ui.global::<SolidPreviewModel>().get_view_mode() as u8;
let size = contained_request_size(view_mode, scaled_viewport_size(ui), render_size);
let show_preform = ui.global::<SolidPreviewModel>().get_show_preform_planes();
let enlarged_panel = ui
.global::<SolidPreviewModel>()
.get_diagram_enlarged_panel();
let design_snapshot = Arc::new(design.clone());
if frame_updates_mast_cache(generation) {
note_committed_replan_submitted();
auto_solve::stash_current_design(
generation,
Arc::clone(&design_snapshot),
multi_selected.clone(),
);
}
let cutoff = ui.global::<SolidPreviewModel>().get_tier_cutoff();
preview_state.set_tier_cutoff(usize::try_from(cutoff).ok());
preview_state.request_replan(ReplanRequest {
design: Arc::clone(&design_snapshot),
dirty,
last_solved,
camera,
size,
selected_tier,
n_d,
view_mode,
generation,
show_preform,
enlarged_panel,
});
}
pub(in crate::gui::editor) fn push_solved_preview(
ui: &MainWindow,
design: &Design,
solved: &[SolvedTier],
multi_selected: &BTreeSet<usize>,
custom_materials: &[indicatrix::optics::materials::GemMaterial],
custom_sg: &[(String, f64)],
) {
let n_d = design.effective_refractive_index_with(custom_materials);
let mut tiers = tier_items_from_solved(design, solved, n_d);
apply_multi_selection(&mut tiers, multi_selected);
push_tiers(ui, tiers);
push_multi_selected_count(ui, multi_selected.len());
let (status_text, is_problem) = status_text_and_is_problem_from_solved(design, solved);
ui.global::<EditorModel>()
.set_status_text(status_text.into());
ui.global::<EditorModel>().set_status_is_problem(is_problem);
ui.global::<EditorModel>()
.set_solve_state(if is_problem { "failed" } else { "solved" }.into());
crate::gui::editor::guide::check_design_progress(ui, design);
let tagged_warnings = manufacturability_warnings_tagged(design, Some(solved));
let warning_tiers: Vec<i32> = tagged_warnings
.iter()
.map(|(index, _)| i32::try_from(*index).unwrap_or(i32::MAX))
.collect();
let warnings: Vec<SharedString> = tagged_warnings
.into_iter()
.map(|(_, text)| SharedString::from(text))
.collect();
super::state::push_rows(
&ui.global::<EditorModel>().get_manufacturability_warnings(),
warnings,
|model| {
ui.global::<EditorModel>()
.set_manufacturability_warnings(model);
},
);
super::state::push_rows(
&ui.global::<EditorModel>()
.get_manufacturability_warning_tiers(),
warning_tiers,
|model| {
ui.global::<EditorModel>()
.set_manufacturability_warning_tiers(model);
},
);
let (vol_yield_text, carat_text, sg_used_text, fit_text) =
yield_report_texts_from_solved(design, solved, custom_sg);
ui.global::<EditorModel>()
.set_volumetric_yield_text(vol_yield_text.into());
ui.global::<EditorModel>()
.set_carat_weight_text(carat_text.into());
ui.global::<EditorModel>()
.set_specific_gravity_used_text(sg_used_text.into());
ui.global::<EditorModel>()
.set_preform_fit_warning(fit_text.into());
super::panel::push_proportion_verdicts_from_solved(ui, design, Some(solved), n_d);
}
pub(in crate::gui::editor) fn refresh_all_now(
ui: &MainWindow,
render_ctx: &Arc<Mutex<crate::bridge::render_thread::RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
state: &Rc<RefCell<EditorState>>,
wholesale: bool,
) {
let inline = solves_inline(&state.borrow().design, wholesale);
if !inline {
let st = state.borrow();
refresh_all(
ui,
render_ctx,
preview_state,
solid_last_solved,
&st,
wholesale,
);
return;
}
ui.global::<EditorModel>().set_solve_running(true);
ui.global::<EditorModel>().set_solve_state("solving".into());
let ui_weak = ui.as_weak();
let render_ctx = Arc::clone(render_ctx);
let preview_state = Arc::clone(preview_state);
let solid_last_solved = Arc::clone(solid_last_solved);
let state = Rc::clone(state);
slint::Timer::single_shot(Duration::ZERO, move || {
let Some(ui) = ui_weak.upgrade() else {
return;
};
let st = state.borrow();
refresh_all(
&ui,
&render_ctx,
&preview_state,
&solid_last_solved,
&st,
wholesale,
);
});
}
pub(in crate::gui::editor) fn reclaim_viewport_for_editor(
ui: &MainWindow,
render_ctx: &Arc<Mutex<crate::bridge::render_thread::RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
state: &EditorState,
) {
let catalogue_owns = {
let mut ctx = render_ctx.lock().unwrap_or_else(PoisonError::into_inner);
let owns = matches!(ctx.planes_owner, PlanesOwner::Catalogue { .. });
if owns && state.has_design {
ctx.restore_editor_material();
}
owns
};
if catalogue_owns && state.has_design {
refresh_all(
ui,
render_ctx,
preview_state,
solid_last_solved,
state,
false,
);
let name = render_ctx
.lock()
.unwrap_or_else(PoisonError::into_inner)
.material_name
.clone();
let options = ui.global::<crate::ViewportModel>().get_material_options();
if let Some(index) = crate::gui::startup_settings::find_option_index(&options, &name) {
ui.global::<crate::ViewportModel>()
.set_selected_material_index(index);
}
}
}
pub(in crate::gui::editor) fn push_has_design(ui: &MainWindow, state: &EditorState) {
ui.global::<EditorModel>().set_has_design(state.has_design);
}
fn solves_inline(design: &Design, wholesale: bool) -> bool {
let last_solve = if wholesale {
None
} else {
auto_solve::last_solve()
};
should_solve_synchronously_for(SolveCostEstimate::of(design), last_solve)
}
pub(in crate::gui::editor) fn refresh_all(
ui: &MainWindow,
render_ctx: &Arc<Mutex<crate::bridge::render_thread::RenderContext>>,
preview_state: &Arc<SolidPreviewState>,
solid_last_solved: &SolidLastSolved,
state: &EditorState,
wholesale: bool,
) {
if wholesale {
auto_solve::reset_for_new_design(state.generation.load(AtomicOrdering::Relaxed));
}
if solves_inline(&state.design, wholesale) {
ui.global::<EditorModel>().set_solve_running(true);
ui.global::<EditorModel>().set_solve_state("solving".into());
let start = Instant::now();
let solved_result = state.design.solve();
let elapsed = start.elapsed();
auto_solve::record_solve_duration(elapsed);
ui.global::<EditorModel>()
.set_last_solve_duration_ms(i32::try_from(elapsed.as_millis()).unwrap_or(i32::MAX));
let solved = refresh_editor_panel_from_solve(ui, render_ctx, state, solved_result);
refresh_viewport(
ui,
render_ctx,
preview_state,
solid_last_solved,
state,
solved.as_deref(),
);
ui.global::<EditorModel>().set_solve_running(false);
} else {
let all_dirty: BTreeSet<usize> = (0..state.design.tiers.len()).collect();
push_stale_content(ui, render_ctx, state, &all_dirty);
auto_solve::dispatch_background_solve(
ui,
render_ctx,
state.design.clone(),
&state.generation,
state.multi_selected.clone(),
state.generation.load(AtomicOrdering::Relaxed),
);
}
push_has_design(ui, state);
crate::gui::editor::guide::check_progress(ui, state);
}