diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 6c4cce9ee..14bfcd020 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -16,12 +16,7 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent, const std::string &icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, -1) - , m_state({ State::settings, 0 }) - , m_is_valid_result(false) - , m_exist_preview(false) , m_volume(nullptr) - , m_obj_index(0) - , m_need_reload(false) , m_show_wireframe(false) , m_move_to_center(false) // translation for GUI size @@ -32,16 +27,15 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent, {} GLGizmoSimplify::~GLGizmoSimplify() { - m_state.status = State::canceling; + m_state.status = State::cancelling; if (m_worker.joinable()) m_worker.join(); m_glmodel.reset(); } bool GLGizmoSimplify::on_esc_key_down() { - if (m_state.status == State::settings || m_state.status == State::canceling) + if (m_state.status != State::running) return false; - - m_state.status = State::canceling; + m_state.status = State::cancelling; return true; } @@ -106,38 +100,27 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi { create_gui_cfg(); const Selection &selection = m_parent.get_selection(); - int obj_index = selection.get_object_idx(); ModelVolume *act_volume = get_volume(selection, wxGetApp().plater()->model()); if (act_volume == nullptr) { - switch (m_state.status) { - case State::settings: close(); break; - case State::canceling: break; - default: m_state.status = State::canceling; - } + stop_worker_thread(false); + close(); return; } // Check selection of new volume // Do not reselect object when processing - if (act_volume != m_volume && m_state.status == State::settings) { + if (act_volume != m_volume && m_state.status != State::Status::running) { bool change_window_position = (m_volume == nullptr); // select different model - if (m_volume != nullptr && m_original_its.has_value()) { - set_its(*m_original_its); - } // close suggestion notification auto notification_manager = wxGetApp().plater()->get_notification_manager(); notification_manager->remove_simplify_suggestion_with_id(act_volume->get_object()->id()); - m_obj_index = obj_index; // to remember correct object m_volume = act_volume; - m_original_its = {}; m_configuration.decimate_ratio = 50.; // default value m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size()); - m_is_valid_result = false; - m_exist_preview = false; - init_model(); + init_model(m_volume->mesh().its); process(); // set window position @@ -169,12 +152,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; m_imgui->begin(on_get_name(), flag); - - size_t triangle_count = m_volume->mesh().its.indices.size(); - // already reduced mesh - if (m_original_its.has_value()) - triangle_count = m_original_its->indices.size(); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":"); ImGui::SameLine(m_gui_cfg->top_left_width); std::string name = m_volume->name; @@ -183,7 +160,9 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_imgui->text(name); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":"); ImGui::SameLine(m_gui_cfg->top_left_width); - m_imgui->text(std::to_string(triangle_count)); + + size_t orig_triangle_count = m_volume->mesh().its.indices.size(); + m_imgui->text(std::to_string(orig_triangle_count)); ImGui::Separator(); @@ -226,10 +205,11 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::SameLine(); // show preview result triangle count (percent) - if (m_need_reload && !m_configuration.use_count) { - m_configuration.wanted_count = static_cast(m_volume->mesh().its.indices.size()); + // lm_FIXME + if (/*m_need_reload &&*/ !m_configuration.use_count) { + m_configuration.wanted_count = static_cast(m_triangle_count); m_configuration.decimate_ratio = - (1.0f - (m_configuration.wanted_count / (float) triangle_count)) * 100.f; + (1.0f - (m_configuration.wanted_count / (float) orig_triangle_count)) * 100.f; } m_imgui->disabled_begin(!m_configuration.use_count); @@ -244,7 +224,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_configuration.decimate_ratio = 0.01f; if (m_configuration.decimate_ratio > 100.f) m_configuration.decimate_ratio = 100.f; - m_configuration.fix_count_by_ratio(triangle_count); + m_configuration.fix_count_by_ratio(orig_triangle_count); process(); } @@ -255,37 +235,20 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe); - bool is_canceling = m_state.status == State::canceling; - m_imgui->disabled_begin(is_canceling); + bool is_cancelling = m_state.status == State::cancelling; + m_imgui->disabled_begin(is_cancelling); if (m_imgui->button(_L("Cancel"))) { - if (m_state.status == State::settings) { - if (m_original_its.has_value()) { - set_its(*m_original_its); - m_state.status = State::close_on_end; - } else { - close(); - } - } else { - m_state.status = State::canceling; - } - } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_canceling) - ImGui::SetTooltip("%s", _u8L("Operation already canceling. Please wait few seconds.").c_str()); - m_imgui->disabled_end(); // state canceling + close(); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_cancelling) + ImGui::SetTooltip("%s", _u8L("Operation already cancelling. Please wait few seconds.").c_str()); + m_imgui->disabled_end(); // state cancelling ImGui::SameLine(); - bool is_processing = m_state.status != State::settings; + bool is_processing = m_state.status == State::running; m_imgui->disabled_begin(is_processing); if (m_imgui->button(_L("Apply"))) { - if (!m_is_valid_result) { - m_state.status = State::close_on_end; - process(); - } else if (m_exist_preview) { - // use preview and close - after_apply(); - } else { // no changes made - close(); - } + apply_simplify(); } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_processing) ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str()); m_imgui->disabled_end(); // state !settings @@ -301,28 +264,20 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi m_imgui->end(); // refresh view when needed - if (m_need_reload) { + // lm_FIXME + /*if (m_need_reload) { m_need_reload = false; bool close_on_end = (m_state.status == State::close_on_end); request_rerender(); // set m_state.status must be before close() !!! m_state.status = State::settings; if (close_on_end) after_apply(); - else init_model(); + else init_model(m_volume->mesh().its); // Fix warning icon in object list wxGetApp().obj_list()->update_item_error_icon(m_obj_index, -1); - } + }*/ } -void GLGizmoSimplify::after_apply() { - // set flag to NOT revert changes when switch GLGizmoBase::m_state - m_exist_preview = false; - // fix hollowing, sla support points, modifiers, ... - auto plater = wxGetApp().plater(); - plater->take_snapshot(_u8L("Simplify ") + m_volume->name); - plater->changed_mesh(m_obj_index); - close(); -} void GLGizmoSimplify::close() { // close gizmo == open it again @@ -330,55 +285,58 @@ void GLGizmoSimplify::close() { gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); } +void GLGizmoSimplify::stop_worker_thread(bool wait) +{ + if (! m_is_worker_running) + return; + + std::lock_guard lk(m_state_mutex); + if (m_state.status == State::Status::running) { + assert(m_worker.joinable()); + m_state.status = State::Status::cancelling; + if (wait) + m_worker.join(); + } +} + + +// Following is called from a UI thread when the worker terminates +// worker calls it through a CallAfter. +void GLGizmoSimplify::worker_finished() +{ + assert(m_worker.joinable()); + m_worker.join(); + m_is_worker_running = false; + if (m_state.result) + init_model(*m_state.result); + request_rerender(); +} void GLGizmoSimplify::process() { - if (m_volume == nullptr) return; - if (m_volume->mesh().its.indices.empty()) return; - size_t count_triangles = m_volume->mesh().its.indices.size(); - // Is neccessary simplification - if ((m_configuration.use_count && m_configuration.wanted_count >= count_triangles) || - (!m_configuration.use_count && m_configuration.max_error <= 0.f)) { - - // Exist different original volume? - if (m_original_its.has_value() && - m_original_its->indices.size() != count_triangles) { - indexed_triangle_set its = *m_original_its; // copy - set_its(its); - } - m_is_valid_result = true; - - // re-render bargraph - set_dirty(); - m_parent.schedule_extra_frame(0); + if (m_is_worker_running || m_volume == nullptr || m_volume->mesh().its.indices.empty()) return; - } - // when not store original volume store it for cancelation - if (!m_original_its.has_value()) { - m_original_its = m_volume->mesh().its; // copy + assert(! m_worker.joinable()); - // store previous state - auto plater = wxGetApp().plater(); - // LUKAS: ??? - plater->clear_before_change_mesh(m_obj_index); - } + // Worker is not running now. No synchronization needed. + if (m_state.result && m_state.config == m_configuration) + return; // The result is still valid. - m_state.progress = 0; - if (m_worker.joinable()) - m_worker.join(); + // We are about to actually start simplification. + m_state.config = m_configuration; // Create a copy of current mesh to pass to the worker thread. // Using unique_ptr instead of pass-by-value to avoid an extra // copy (which would happen when passing to std::thread). - auto its = std::make_unique(*m_original_its); + auto its = std::make_unique(m_volume->mesh().its); m_worker = std::thread([this](std::unique_ptr its) { // Checks that the UI thread did not request cancellation, throw if so. std::function throw_on_cancel = [this]() { std::lock_guard lk(m_state_mutex); - if (m_state.status == State::canceling) + if (m_state.status == State::cancelling) throw SimplifyCanceledException(); }; @@ -388,30 +346,61 @@ void GLGizmoSimplify::process() m_state.progress = percent; }; - uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0; - float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits::max(); + // Initialize. + uint32_t triangle_count = 0; + float max_error = std::numeric_limits::max(); + { + std::lock_guard lk(m_state_mutex); + if (m_configuration.use_count) + triangle_count = m_configuration.wanted_count; + if (! m_configuration.use_count) + max_error = m_configuration.max_error; + m_state.progress = 0; + m_state.result.reset(); + m_state.status = State::Status::running; + } + // Start the actual calculation. try { its_quadric_edge_collapse(*its, triangle_count, &max_error, throw_on_cancel, statusfn); - set_its(*its); - m_is_valid_result = true; - m_exist_preview = true; } catch (SimplifyCanceledException &) { - // set state out of main thread - m_state.status = State::settings; + std::lock_guard lk(m_state_mutex); + m_state.status = State::idle; } - // need to render last status fn to change bar graph to buttons - request_rerender(); + + std::lock_guard lk(m_state_mutex); + if (m_state.status == State::Status::running) { + // We were not cancelled, the result is valid. + m_state.status = State::Status::idle; + m_state.result = std::move(its); + } + + // Update UI. Use CallAfter so the function is run on UI thread. + wxGetApp().CallAfter([this]() { worker_finished(); }); }, std::move(its)); + + m_is_worker_running = true; } -void GLGizmoSimplify::set_its(const indexed_triangle_set &its) { - if (m_volume == nullptr) return; // could appear after process - m_volume->set_mesh(its); +void GLGizmoSimplify::apply_simplify() { + assert(! m_is_worker_running); + + const Selection& selection = m_parent.get_selection(); + int object_idx = selection.get_object_idx(); + + auto plater = wxGetApp().plater(); + plater->take_snapshot(_u8L("Simplify ") + m_volume->name); + plater->clear_before_change_mesh(object_idx); + + m_volume->set_mesh(*m_state.result); m_volume->calculate_convex_hull(); m_volume->set_new_unique_id(); m_volume->get_object()->invalidate_bounding_box(); - m_need_reload = true; + + // fix hollowing, sla support points, modifiers, ... + + plater->changed_mesh(object_idx); + close(); } bool GLGizmoSimplify::on_is_activable() const @@ -425,40 +414,14 @@ void GLGizmoSimplify::on_set_state() if (GLGizmoBase::m_state == GLGizmoBase::Off) { m_parent.toggle_model_objects_visibility(true); - // can appear when delete objects - bool empty_selection = m_parent.get_selection().is_empty(); - - // cancel processing - if (empty_selection && - m_state.status != State::settings && - m_state.status != State::canceling) - m_state.status = State::canceling; - - // refuse outgoing during simlification - // object is not selected when it is deleted(cancel and close gizmo) - if (m_state.status != State::settings && !empty_selection) { - GLGizmoBase::m_state = GLGizmoBase::On; - auto notification_manager = wxGetApp().plater()->get_notification_manager(); - notification_manager->push_notification( - NotificationType::CustomNotification, - NotificationManager::NotificationLevel::PrintInfoNotificationLevel, - _u8L("ERROR: Wait until Simplification ends or Cancel process.")); - return; - } - - // revert preview - if (m_exist_preview) { - m_exist_preview = false; - if (exist_volume(m_volume)) { - set_its(*m_original_its); - request_rerender(); - m_need_reload = false; - } - } + // Stop worker but do not wait to join it. + stop_worker_thread(false); // invalidate selected model m_volume = nullptr; } else if (GLGizmoBase::m_state == GLGizmoBase::On) { + // Make sure the worker is not running and join it. + stop_worker_thread(true); // when open by hyperlink it needs to show up request_rerender(); } @@ -537,10 +500,10 @@ const ModelVolume *GLGizmoSimplify::get_volume(const GLVolume::CompositeID &cid, return obj->volumes[cid.volume_id]; } -void GLGizmoSimplify::init_model() +void GLGizmoSimplify::init_model(const indexed_triangle_set& its) { - const indexed_triangle_set &its = m_volume->mesh().its; - if (its.indices.empty()) return; + if (its.indices.empty()) + return; m_glmodel.reset(); m_glmodel.init_from(its); @@ -549,6 +512,7 @@ void GLGizmoSimplify::init_model() if (const Selection&sel = m_parent.get_selection(); sel.get_volume_idxs().size() == 1) m_glmodel.set_color(-1, sel.get_volume(*sel.get_volume_idxs().begin())->color); + m_triangle_count = its.indices.size(); } void GLGizmoSimplify::on_render() @@ -601,4 +565,16 @@ CommonGizmosDataID GLGizmoSimplify::on_get_requirements() const int(CommonGizmosDataID::SelectionInfo)); } + +void GLGizmoSimplify::Configuration::fix_count_by_ratio(size_t triangle_count) +{ + if (decimate_ratio <= 0.f) + wanted_count = static_cast(triangle_count); + else if (decimate_ratio >= 100.f) + wanted_count = 0; + else + wanted_count = static_cast(std::round( + triangle_count * (100.f - decimate_ratio) / 100.f)); +} + } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 3e0ea6706..573b1ea51 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -49,12 +49,16 @@ protected: virtual CommonGizmosDataID on_get_requirements() const; private: - void after_apply(); + void apply_simplify(); void close(); + void process(); - void set_its(const indexed_triangle_set &its); + void stop_worker_thread(bool wait); + void worker_finished(); + void create_gui_cfg(); void request_rerender(); + void init_model(const indexed_triangle_set& its); void set_center_position(); // move to global functions @@ -64,59 +68,54 @@ private: // return false when volume was deleted static bool exist_volume(const ModelVolume *volume); - std::atomic_bool m_is_valid_result; // differ what to do in apply - std::atomic_bool m_exist_preview; // set when process end - - bool m_move_to_center; // opening gizmo - - - ModelVolume *m_volume; // keep pointer to actual working volume - size_t m_obj_index; - - std::optional m_original_its; - bool m_show_wireframe; - GLModel m_glmodel; - - std::atomic m_need_reload; // after simplify, glReload must be on main thread - - struct State { - enum Status { - settings, - preview, // simplify to show preview - close_on_end, // simplify with close on end - canceling // after button click, before canceled - }; - - Status status; - int progress; // percent of done work - indexed_triangle_set result; - }; - - std::thread m_worker; - std::mutex m_state_mutex; // guards m_state - State m_state; struct Configuration { bool use_count = false; - // minimal triangle count float decimate_ratio = 50.f; // in percent - uint32_t wanted_count = 0; // initialize by percents + uint32_t wanted_count = 0; // initialize by percents + float max_error = 1.; // maximal quadric error - // maximal quadric error - float max_error = 1.; - - void fix_count_by_ratio(size_t triangle_count) - { - if (decimate_ratio <= 0.f) - wanted_count = static_cast(triangle_count); - else if (decimate_ratio >= 100.f) - wanted_count = 0; - else - wanted_count = static_cast(std::round( - triangle_count * (100.f - decimate_ratio) / 100.f)); + void fix_count_by_ratio(size_t triangle_count); + bool operator==(const Configuration& rhs) { + return (use_count == rhs.use_count && decimate_ratio == rhs.decimate_ratio + && wanted_count == rhs.wanted_count && max_error == rhs.max_error); } - } m_configuration; + }; + + Configuration m_configuration; + + bool m_move_to_center; // opening gizmo + + ModelVolume *m_volume; // keep pointer to actual working volume + + bool m_show_wireframe; + GLModel m_glmodel; + size_t m_triangle_count; // triangle count of the model currently shown + + // Following struct is accessed by both UI and worker thread. + // Accesses protected by a mutex. + struct State { + enum Status { + idle, + running, + cancelling + }; + + Status status = idle; + int progress = 0; // percent of done work + Configuration config; // Configuration we started with. + std::unique_ptr result; + }; + + std::thread m_worker; + std::mutex m_state_mutex; // guards m_state + State m_state; // accessed by both threads + + // Following variable is accessed by UI only. + bool m_is_worker_running = false; + + // This configs holds GUI layout size given by translated texts. // etc. When language changes, GUI is recreated and this class constructed again, @@ -145,8 +144,6 @@ private: const std::string tr_detail_level; const std::string tr_decimate_ratio; - void init_model(); - // cancel exception class SimplifyCanceledException: public std::exception {