mod replan;
use super::{
SolidLastSolved, panel::refresh_editor_panel_from_solve, panel_stale::push_stale_content,
state::EditorState,
};
use crate::{
EditorModel, MainWindow, SolidPreviewModel, TiltModel,
bridge::render_thread::{PlanesOwner, RenderContext},
gui::{
editor::auto_solve,
render::camera_lighting::contained_request_size,
solid_preview::{
cut_slider,
preview_state::{CameraPose, SolidPreviewState},
},
tutorial_events::raise,
},
};
use indicatrix::geometry::meet_solver::SolvedTier;
use indicatrix_cut_core::Design;
use indicatrix_editor::{
guide::viewing_events as events,
solve_policy::{SolveCostEstimate, should_solve_synchronously_for},
};
use indicatrix_solid::preview::StoneGeometryBuf;
use slint::ComponentHandle;
use std::{
cell::RefCell,
collections::BTreeSet,
rc::Rc,
sync::{Arc, Mutex, PoisonError, atomic::Ordering as AtomicOrdering},
time::{Duration, Instant},
};
pub(in crate::gui::editor) use replan::{
ReplanSource, push_late_findings, push_solved_preview, submit_preview_replan,
submit_preview_replan_chained, submit_preview_replan_for,
};
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 cut_steps = cut_slider::sync_model(ui, &state.design);
let StoneGeometryBuf {
planes: planes_for_claim,
tools: tools_for_claim,
placements: placements_for_claim,
} = cut_slider::cut_geometry_no_solve(&state.design, solved, cut_steps);
let mut ctx = render_ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
ctx.claim_active_geometry(
std::sync::Arc::new(planes_for_claim),
std::sync::Arc::new(tools_for_claim),
placements_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;
crate::gui::editor::push_viewport_owner(ui, ctx.planes_owner);
let stone = StoneGeometryBuf {
planes: ctx.active_planes.as_ref().clone(),
tools: ctx.active_tools.as_ref().clone(),
placements: ctx.active_placements.clone(),
};
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_geometry(
stone,
CameraPose {
yaw: ctx.yaw,
pitch: ctx.pitch,
distance: ctx.distance,
},
size,
view_mode,
design_gear,
);
drop(ctx);
if let Some(solved) = solved {
*solid_last_solved
.lock()
.unwrap_or_else(PoisonError::into_inner) = Some((
state.generation.load(AtomicOrdering::Relaxed),
solved.to_vec(),
));
}
ui.global::<TiltModel>()
.set_cached_curve_material("".into());
if ui.global::<TiltModel>().get_dialog_open() {
ui.global::<TiltModel>().invoke_request_tilt_profile_axes();
}
push_trace_staleness(ui, render_ctx, state);
}
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,
) {
if wholesale {
cut_slider::reset_to_finished(ui, &state.borrow().design);
}
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 {
if !design.concave_tiers.is_empty() {
return false;
}
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));
cut_slider::reset_to_finished(ui, &state.design);
crate::gui::render::design_lighting::design_opened(
ui,
state.design_uuid(),
state.has_design,
);
}
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);
if wholesale {
raise(ui, events::DESIGN_REPLACED);
}
crate::gui::editor::guide::check_progress(ui, state);
}