Simplify does not touch ModelVolume all the time (runs, but needs polishing)

This commit is contained in:
Lukas Matena 2021-10-29 17:58:05 +02:00
parent ab260d005e
commit 7bcab6f795
2 changed files with 173 additions and 200 deletions

View file

@ -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<uint32_t>(m_volume->mesh().its.indices.size());
// lm_FIXME
if (/*m_need_reload &&*/ !m_configuration.use_count) {
m_configuration.wanted_count = static_cast<uint32_t>(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<indexed_triangle_set>(*m_original_its);
auto its = std::make_unique<indexed_triangle_set>(m_volume->mesh().its);
m_worker = std::thread([this](std::unique_ptr<indexed_triangle_set> its) {
// Checks that the UI thread did not request cancellation, throw if so.
std::function<void(void)> 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<float>::max();
// Initialize.
uint32_t triangle_count = 0;
float max_error = std::numeric_limits<float>::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<uint32_t>(triangle_count);
else if (decimate_ratio >= 100.f)
wanted_count = 0;
else
wanted_count = static_cast<uint32_t>(std::round(
triangle_count * (100.f - decimate_ratio) / 100.f));
}
} // namespace Slic3r::GUI

View file

@ -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<indexed_triangle_set> m_original_its;
bool m_show_wireframe;
GLModel m_glmodel;
std::atomic<bool> 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<uint32_t>(triangle_count);
else if (decimate_ratio >= 100.f)
wanted_count = 0;
else
wanted_count = static_cast<uint32_t>(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<indexed_triangle_set> 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
{