#include "GLGizmoSimplify.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/NotificationManager.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/format.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/QuadricEdgeCollapse.hpp" #include #include namespace Slic3r::GUI { // Extend call after only when Simplify gizmo is still alive static void call_after_if_active(std::function fn, GUI_App* app = &wxGetApp()) { // check application GUI if (app == nullptr) return; app->CallAfter([fn, app]() { // app must exist because it call this // if (app == nullptr) return; const Plater *plater = app->plater(); if (plater == nullptr) return; const GLCanvas3D *canvas = plater->canvas3D(); if (canvas == nullptr) return; const GLGizmosManager &mng = canvas->get_gizmos_manager(); // check if simplify is still activ gizmo if (mng.get_current_type() != GLGizmosManager::Simplify) return; fn(); }); } static std::set get_volume_ids(const Selection &selection) { const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); const ModelObjectPtrs &model_objects = selection.get_model()->objects; std::set result; for (auto volume_id : volume_ids) { const GLVolume *selected_volume = selection.get_volume(volume_id); assert(selected_volume != nullptr); const GLVolume::CompositeID &cid = selected_volume->composite_id; assert(cid.object_id >= 0); assert(model_objects.size() > static_cast(cid.object_id)); const ModelObject *obj = model_objects[cid.object_id]; const ModelVolume *volume = obj->volumes[cid.volume_id]; const ObjectID & id = volume->id(); // prevent selection of volume without inidces if (volume->mesh().its.indices.empty()) continue; assert(result.find(id) == result.end()); result.insert(id); } return result; } // return ModelVolume from selection by object id static ModelVolume *get_volume(const ObjectID &id, const Selection &selection) { const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); const ModelObjectPtrs &model_objects = selection.get_model()->objects; for (auto volume_id : volume_ids) { const GLVolume *selected_volume = selection.get_volume(volume_id); const GLVolume::CompositeID &cid = selected_volume->composite_id; ModelObject *obj = model_objects[cid.object_id]; ModelVolume *volume = obj->volumes[cid.volume_id]; if (id == volume->id()) return volume; } return nullptr; } static std::string create_volumes_name(const std::set& ids, const Selection &selection){ assert(!ids.empty()); std::string name; bool is_first = true; for (const ObjectID &id : ids) { if (is_first) is_first = false; else name += " + "; const ModelVolume *volume = get_volume(id, selection); assert(volume != nullptr); name += volume->name; } return name; } GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D &parent) : GLGizmoBase(parent, M_ICON_FILENAME, -1) , m_show_wireframe(false) , m_move_to_center(false) , m_original_triangle_count(0) , m_triangle_count(0) // translation for GUI size , tr_mesh_name(_u8L("Mesh name")) , tr_triangles(_u8L("Triangles")) , tr_detail_level(_u8L("Detail level")) , tr_decimate_ratio(_u8L("Decimate ratio")) {} GLGizmoSimplify::~GLGizmoSimplify() { stop_worker_thread_request(); if (m_worker.joinable()) m_worker.join(); } bool GLGizmoSimplify::on_esc_key_down() { //close(); return stop_worker_thread_request(); } // while opening needs GLGizmoSimplify to set window position void GLGizmoSimplify::add_simplify_suggestion_notification( const std::vector &object_ids, const std::vector& objects, NotificationManager & manager) { std::vector big_ids; big_ids.reserve(object_ids.size()); auto is_big_object = [&objects](size_t object_id) { const uint32_t triangles_to_suggest_simplify = 1000000; if (object_id >= objects.size()) return false; // out of object index ModelVolumePtrs &volumes = objects[object_id]->volumes; if (volumes.size() != 1) return false; // not only one volume size_t triangle_count = volumes.front()->mesh().its.indices.size(); if (triangle_count < triangles_to_suggest_simplify) return false; // small volume return true; }; std::copy_if(object_ids.begin(), object_ids.end(), std::back_inserter(big_ids), is_big_object); if (big_ids.empty()) return; for (size_t object_id : big_ids) { std::string t = GUI::format(_L( "Processing model '%1%' with more than 1M triangles " "could be slow. It is highly recommend to reduce " "amount of triangles."), objects[object_id]->name); std::string hypertext = _u8L("Simplify model"); std::function open_simplify = [object_id](wxEvtHandler *) { auto plater = wxGetApp().plater(); if (object_id >= plater->model().objects.size()) return true; Selection &selection = plater->canvas3D()->get_selection(); selection.clear(); selection.add_object((unsigned int) object_id); auto &manager = plater->canvas3D()->get_gizmos_manager(); bool close_notification = true; if(!manager.open_gizmo(GLGizmosManager::Simplify)) return close_notification; GLGizmoSimplify* simplify = dynamic_cast(manager.get_current()); if (simplify == nullptr) return close_notification; simplify->set_center_position(); return close_notification; }; manager.push_simplify_suggestion_notification( t, objects[object_id]->id(), hypertext, open_simplify); } } std::string GLGizmoSimplify::on_get_name() const { return _u8L("Simplify"); } void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { create_gui_cfg(); const Selection &selection = m_parent.get_selection(); auto act_volume_ids = get_volume_ids(selection); if (act_volume_ids.empty()) { stop_worker_thread_request(); close(); if (! m_parent.get_selection().is_single_volume()) { MessageDialog msg((wxWindow*)wxGetApp().mainframe, _L("Simplification is currently only allowed when a single part is selected"), _L("Error")); msg.ShowModal(); } return; } bool is_cancelling = false; bool is_worker_running = false; bool is_result_ready = false; int progress = 0; { std::lock_guard lk(m_state_mutex); is_cancelling = m_state.status == State::cancelling; is_worker_running = m_state.status == State::running; is_result_ready = !m_state.result.empty(); progress = m_state.progress; } // Whether to trigger calculation after rendering is done. bool start_process = false; // Check selection of new volume (or change) // Do not reselect object when processing if (m_volume_ids != act_volume_ids) { bool change_window_position = m_volume_ids.empty(); // select different model // close suggestion notification auto notification_manager = wxGetApp().plater()->get_notification_manager(); for (const auto &id : act_volume_ids) notification_manager->remove_simplify_suggestion_with_id(id); m_volume_ids = std::move(act_volume_ids); init_model(); // triangle count is calculated in init model m_original_triangle_count = m_triangle_count; // Default value of configuration m_configuration.decimate_ratio = 50.; // default value m_configuration.fix_count_by_ratio(m_original_triangle_count); m_configuration.use_count = false; // Create volumes name to describe what will be simplified std::string name = create_volumes_name(m_volume_ids, selection); if (name.length() > m_gui_cfg->max_char_in_name) name = name.substr(0, m_gui_cfg->max_char_in_name - 3) + "..."; m_volumes_name = name; // Start processing. If we switched from another object, process will // stop the background thread and it will restart itself later. start_process = true; // set window position if (change_window_position) { ImVec2 pos; Size parent_size = m_parent.get_canvas_size(); if (m_move_to_center) { m_move_to_center = false; pos = ImVec2(parent_size.get_width() / 2 - m_gui_cfg->window_offset_x, parent_size.get_height() / 2 - m_gui_cfg->window_offset_y); } else { // keep window wisible on canvas and close to mouse click pos = ImGui::GetMousePos(); pos.x -= m_gui_cfg->window_offset_x; pos.y -= m_gui_cfg->window_offset_y; // minimal top left value ImVec2 tl(m_gui_cfg->window_padding, m_gui_cfg->window_padding + m_parent.get_main_toolbar_height()); if (pos.x < tl.x) pos.x = tl.x; if (pos.y < tl.y) pos.y = tl.y; // maximal bottom right value ImVec2 br(parent_size.get_width() - (2 * m_gui_cfg->window_offset_x + m_gui_cfg->window_padding), parent_size.get_height() -(2 * m_gui_cfg->window_offset_y + m_gui_cfg->window_padding)); if (pos.x > br.x) pos.x = br.x; if (pos.y > br.y) pos.y = br.y; } ImGui::SetNextWindowPos(pos, ImGuiCond_Always); } } bool is_multipart = (m_volume_ids.size() > 1); int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; m_imgui->begin(on_get_name(), flag); m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":"); ImGui::SameLine(m_gui_cfg->top_left_width); m_imgui->text(m_volumes_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(m_original_triangle_count)); ImGui::Separator(); if(ImGui::RadioButton("##use_error", !m_configuration.use_count) && !is_multipart) { m_configuration.use_count = !m_configuration.use_count; start_process = true; } ImGui::SameLine(); m_imgui->disabled_begin(m_configuration.use_count); ImGui::Text("%s", tr_detail_level.c_str()); std::vector reduce_captions = { static_cast(_u8L("Extra high")), static_cast(_u8L("High")), static_cast(_u8L("Medium")), static_cast(_u8L("Low")), static_cast(_u8L("Extra low")) }; ImGui::SameLine(m_gui_cfg->bottom_left_width); ImGui::SetNextItemWidth(m_gui_cfg->input_width); static int reduction = 2; if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) { if (reduction < 0) reduction = 0; if (reduction > 4) reduction = 4; switch (reduction) { case 0: m_configuration.max_error = 1e-3f; break; case 1: m_configuration.max_error = 1e-2f; break; case 2: m_configuration.max_error = 0.1f; break; case 3: m_configuration.max_error = 0.5f; break; case 4: m_configuration.max_error = 1.f; break; } start_process = true; } m_imgui->disabled_end(); // !use_count if (ImGui::RadioButton("##use_count", m_configuration.use_count) && !is_multipart) { m_configuration.use_count = !m_configuration.use_count; start_process = true; } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_multipart) ImGui::SetTooltip("%s", GUI::format(_L( "Multipart object can be simplified only by %1%. " "If you want specify %2% process it separately."), tr_detail_level, tr_decimate_ratio).c_str()); ImGui::SameLine(); // show preview result triangle count (percent) if (!m_configuration.use_count) { m_configuration.wanted_count = static_cast(m_triangle_count); m_configuration.decimate_ratio = (1.0f - (m_configuration.wanted_count / (float) m_original_triangle_count)) * 100.f; } m_imgui->disabled_begin(!m_configuration.use_count); ImGui::Text("%s", tr_decimate_ratio.c_str()); ImGui::SameLine(m_gui_cfg->bottom_left_width); ImGui::SetNextItemWidth(m_gui_cfg->input_width); const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%": ((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%"); if(m_imgui->slider_float("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)){ if (m_configuration.decimate_ratio < 0.f) 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(m_original_triangle_count); start_process = true; } ImGui::NewLine(); ImGui::SameLine(m_gui_cfg->bottom_left_width); ImGui::Text(_u8L("%d triangles").c_str(), m_configuration.wanted_count); m_imgui->disabled_end(); // use_count ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe); m_imgui->disabled_begin(is_cancelling); if (m_imgui->button(_L("Close"))) { 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(); m_imgui->disabled_begin(is_worker_running || ! is_result_ready); if (m_imgui->button(_L("Apply"))) { apply_simplify(); } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_worker_running) ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str()); m_imgui->disabled_end(); // state !settings // draw progress bar if (is_worker_running) { // apply or preview ImGui::SameLine(m_gui_cfg->bottom_left_width); // draw progress bar std::string progress_text = GUI::format(_L("Process %1% / 100"), std::to_string(progress)); ImVec2 progress_size(m_gui_cfg->input_width, 0.f); ImGui::ProgressBar(progress / 100., progress_size, progress_text.c_str()); } m_imgui->end(); if (start_process) process(); } void GLGizmoSimplify::close() { // close gizmo == open it again GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager(); gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify); } bool GLGizmoSimplify::stop_worker_thread_request() { std::lock_guard lk(m_state_mutex); if (m_state.status != State::running) return false; m_state.status = State::Status::cancelling; return true; } // Following is called from a UI thread when the worker terminates // worker calls it through a CallAfter. void GLGizmoSimplify::worker_finished() { { std::lock_guard lk(m_state_mutex); if (m_state.status == State::running) { // Someone started the worker again, before this callback // was called. Do nothing. return; } } if (m_worker.joinable()) m_worker.join(); if (GLGizmoBase::m_state == Off) return; const auto &result = m_state.result; if (!result.empty()) update_model(result); if (m_state.config != m_configuration || m_state.volume_ids != m_volume_ids) { // Settings were changed, restart the worker immediately. process(); } request_rerender(true); } void GLGizmoSimplify::process() { if (m_volume_ids.empty()) return; // m_volume->mesh().its.indices.empty() bool configs_match = false; bool result_valid = false; bool is_worker_running = false; { std::lock_guard lk(m_state_mutex); configs_match = (m_volume_ids == m_state.volume_ids && m_state.config == m_configuration); result_valid = !m_state.result.empty(); is_worker_running = m_state.status == State::running; } if ((result_valid || is_worker_running) && configs_match) { // Either finished or waiting for result already. Nothing to do. return; } if (is_worker_running && ! configs_match) { // Worker is running with outdated config. Stop it. It will // restart itself when cancellation is done. stop_worker_thread_request(); return; } if (m_worker.joinable()) { // This can happen when process() is called after previous worker terminated, // but before the worker_finished callback was called. In this case, just join the thread, // the callback will check this and do nothing. m_worker.join(); } // Copy configuration that will be used. m_state.config = m_configuration; m_state.volume_ids = m_volume_ids; m_state.status = State::running; // Create a copy of current meshes 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). const Selection& selection = m_parent.get_selection(); State::Data its; for (const auto &id : m_volume_ids) { const ModelVolume *volume = get_volume(id, selection); its[id] = std::make_unique(volume->mesh().its); // copy } m_worker = std::thread([this](State::Data its) { // Checks that the UI thread did not request cancellation, throws if so. std::function throw_on_cancel = [this]() { std::lock_guard lk(m_state_mutex); if (m_state.status == State::cancelling) throw SimplifyCanceledException(); }; // Called by worker thread, updates progress bar. // Using CallAfter so the rerequest function is run in UI thread. std::function statusfn = [this](int percent) { std::lock_guard lk(m_state_mutex); m_state.progress = percent; call_after_if_active([this]() { request_rerender(); }); }; // Initialize. uint32_t triangle_count = 0; float max_error = std::numeric_limits::max(); { std::lock_guard lk(m_state_mutex); if (m_state.config.use_count) triangle_count = m_state.config.wanted_count; if (! m_state.config.use_count) max_error = m_state.config.max_error; m_state.progress = 0; m_state.result.clear(); m_state.status = State::Status::running; } // Start the actual calculation. try { for (const auto& it : its) { float me = max_error; its_quadric_edge_collapse(*it.second, triangle_count, &me, throw_on_cancel, statusfn); } } catch (SimplifyCanceledException &) { std::lock_guard lk(m_state_mutex); m_state.status = State::idle; } 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. call_after_if_active([this]() { worker_finished(); }); }, std::move(its)); } void GLGizmoSimplify::apply_simplify() { // worker must be stopped assert(m_state.status == State::Status::idle); // check that there is NO change of volume assert(m_state.volume_ids == m_volume_ids); const Selection& selection = m_parent.get_selection(); auto plater = wxGetApp().plater(); plater->take_snapshot(_u8L("Simplify ") + create_volumes_name(m_volume_ids, selection)); plater->clear_before_change_mesh(selection.get_object_idx()); for (const auto &item: m_state.result) { const ObjectID &id = item.first; const indexed_triangle_set &its = *item.second; ModelVolume *volume = get_volume(id, selection); assert(volume != nullptr); ModelObject *obj = volume->get_object(); volume->set_mesh(std::move(its)); volume->calculate_convex_hull(); volume->set_new_unique_id(); obj->invalidate_bounding_box(); obj->ensure_on_bed(true); // allow negative z } m_state.result.clear(); // fix hollowing, sla support points, modifiers, ... int object_idx = selection.get_object_idx(); plater->changed_mesh(object_idx); // Fix warning icon in object list wxGetApp().obj_list()->update_item_error_icon(object_idx, -1); close(); } bool GLGizmoSimplify::on_is_activable() const { return !m_parent.get_selection().is_empty(); } void GLGizmoSimplify::on_set_state() { // Closing gizmo. e.g. selecting another one if (GLGizmoBase::m_state == GLGizmoBase::Off) { m_parent.toggle_model_objects_visibility(true); stop_worker_thread_request(); m_volume_ids.clear(); // invalidate selected model m_glmodels.clear(); // free gpu memory } else if (GLGizmoBase::m_state == GLGizmoBase::On) { // when open by hyperlink it needs to show up request_rerender(); } } void GLGizmoSimplify::create_gui_cfg() { if (m_gui_cfg.has_value()) return; int space_size = m_imgui->calc_text_size(std::string_view{":MM"}).x; GuiCfg cfg; cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x, m_imgui->calc_text_size(tr_triangles).x) + space_size; const float radio_size = ImGui::GetFrameHeight(); cfg.bottom_left_width = std::max(m_imgui->calc_text_size(tr_detail_level).x, m_imgui->calc_text_size(tr_decimate_ratio).x) + space_size + radio_size; cfg.input_width = cfg.bottom_left_width * 1.5; cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2; cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5; m_gui_cfg = cfg; } void GLGizmoSimplify::request_rerender(bool force) { int64_t now = m_parent.timestamp_now(); if (force || now > m_last_rerender_timestamp + 250) { // 250 ms set_dirty(); m_parent.schedule_extra_frame(0); m_last_rerender_timestamp = now; } } void GLGizmoSimplify::set_center_position() { m_move_to_center = true; } void GLGizmoSimplify::init_model() { // volume ids must be set before init model assert(!m_volume_ids.empty()); m_parent.toggle_model_objects_visibility(true); // selected volume may have changed const auto info = m_c->selection_info(); const Selection &selection = m_parent.get_selection(); Model & model = *selection.get_model(); const Selection::IndicesList &volume_ids = selection.get_volume_idxs(); const ModelObjectPtrs &model_objects = model.objects; m_glmodels.clear(); //m_glmodels.reserve(volume_ids.size()); m_triangle_count = 0; for (const ObjectID& id: m_volume_ids) { const GLVolume *selected_volume; const ModelVolume *volume = nullptr; for (auto volume_id : volume_ids) { selected_volume = selection.get_volume(volume_id); const GLVolume::CompositeID &cid = selected_volume->composite_id; ModelObject * obj = model_objects[cid.object_id]; ModelVolume * act_volume = obj->volumes[cid.volume_id]; if (id == act_volume->id()) { volume = act_volume; break; } } assert(volume != nullptr); #if ENABLE_LEGACY_OPENGL_REMOVAL // set actual triangle count m_triangle_count += volume->mesh().its.indices.size(); #else const indexed_triangle_set &its = volume->mesh().its; // set actual triangle count m_triangle_count += its.indices.size(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL assert(m_glmodels.find(id) == m_glmodels.end()); GLModel &glmodel = m_glmodels[id]; // create new glmodel #if ENABLE_LEGACY_OPENGL_REMOVAL glmodel.init_from(volume->mesh()); glmodel.set_color(selected_volume->color); #else glmodel.init_from(its); glmodel.set_color(-1,selected_volume->color); #endif // ENABLE_LEGACY_OPENGL_REMOVAL m_parent.toggle_model_objects_visibility(false, info->model_object(), info->get_active_instance(), volume); } } void GLGizmoSimplify::update_model(const State::Data &data) { // check that model exist if (m_glmodels.empty()) return; // check that result is for actual gl models size_t model_count = m_glmodels.size(); if (data.size() != model_count) return; m_triangle_count = 0; for (const auto &item : data) { const indexed_triangle_set &its = *item.second; auto it = m_glmodels.find(item.first); assert(it != m_glmodels.end()); GLModel &glmodel = it->second; auto color = glmodel.get_color(); // when not reset it keeps old shape glmodel.reset(); glmodel.init_from(its); #if ENABLE_LEGACY_OPENGL_REMOVAL glmodel.set_color(color); #else glmodel.set_color(-1, color); #endif // ENABLE_LEGACY_OPENGL_REMOVAL m_triangle_count += its.indices.size(); } } void GLGizmoSimplify::on_render() { if (m_glmodels.empty()) return; const Selection & selection = m_parent.get_selection(); // Check that the GLVolume still belongs to the ModelObject we work on. if (m_volume_ids != get_volume_ids(selection)) return; const ModelObjectPtrs &model_objects = selection.get_model()->objects; const Selection::IndicesList &volume_idxs = selection.get_volume_idxs(); // no need to render nothing if (volume_idxs.empty()) return; // Iteration over selection because of world transformation matrix of object for (auto volume_id : volume_idxs) { const GLVolume *selected_volume = selection.get_volume(volume_id); const GLVolume::CompositeID &cid = selected_volume->composite_id; ModelObject *obj = model_objects[cid.object_id]; ModelVolume *volume = obj->volumes[cid.volume_id]; auto it = m_glmodels.find(volume->id()); assert(it != m_glmodels.end()); GLModel &glmodel = it->second; const Transform3d trafo_matrix = selected_volume->world_matrix(); #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES auto* gouraud_shader = wxGetApp().get_shader("gouraud_light"); glsafe(::glPushAttrib(GL_DEPTH_TEST)); glsafe(::glEnable(GL_DEPTH_TEST)); gouraud_shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d view_model_matrix = camera.get_view_matrix() * trafo_matrix; gouraud_shader->set_uniform("view_model_matrix", view_model_matrix); gouraud_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); gouraud_shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES glmodel.render(); gouraud_shader->stop_using(); if (m_show_wireframe) { auto *contour_shader = wxGetApp().get_shader("mm_contour"); contour_shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES contour_shader->set_uniform("view_model_matrix", view_model_matrix); contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); const ColorRGBA color = glmodel.get_color(); glmodel.set_color(ColorRGBA::WHITE()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glLineWidth(1.0f)); glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); glmodel.render(); glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); #if ENABLE_GL_SHADERS_ATTRIBUTES glmodel.set_color(color); #endif // ENABLE_GL_SHADERS_ATTRIBUTES contour_shader->stop_using(); } glsafe(::glPopAttrib()); #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES } } CommonGizmosDataID GLGizmoSimplify::on_get_requirements() const { return CommonGizmosDataID( 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)); } // any existing icon filename to not influence GUI const std::string GLGizmoSimplify::M_ICON_FILENAME = "cut.svg"; } // namespace Slic3r::GUI