mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-07-12 07:01:47 +08:00
Merge branch 'lm_simplify_fixes'
This commit is contained in:
commit
366a3bca07
@ -24,6 +24,7 @@
|
|||||||
#include "slic3r/GUI/Plater.hpp"
|
#include "slic3r/GUI/Plater.hpp"
|
||||||
#include "slic3r/GUI/MainFrame.hpp"
|
#include "slic3r/GUI/MainFrame.hpp"
|
||||||
#include "slic3r/Utils/UndoRedo.hpp"
|
#include "slic3r/Utils/UndoRedo.hpp"
|
||||||
|
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
|
||||||
|
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
@ -1143,7 +1144,7 @@ void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObje
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx)
|
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv)
|
||||||
{
|
{
|
||||||
for (GLVolume* vol : m_volumes.volumes) {
|
for (GLVolume* vol : m_volumes.volumes) {
|
||||||
if (vol->composite_id.object_id == 1000) { // wipe tower
|
if (vol->composite_id.object_id == 1000) { // wipe tower
|
||||||
@ -1151,7 +1152,8 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
|
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
|
||||||
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)) {
|
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
|
||||||
|
&& (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) {
|
||||||
vol->is_active = visible;
|
vol->is_active = visible;
|
||||||
|
|
||||||
if (instance_idx == -1) {
|
if (instance_idx == -1) {
|
||||||
@ -5239,10 +5241,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
|
|||||||
{
|
{
|
||||||
const GLGizmosManager& gm = get_gizmos_manager();
|
const GLGizmosManager& gm = get_gizmos_manager();
|
||||||
GLGizmosManager::EType type = gm.get_current_type();
|
GLGizmosManager::EType type = gm.get_current_type();
|
||||||
if (type == GLGizmosManager::FdmSupports
|
if (dynamic_cast<GLGizmoPainterBase*>(gm.get_current())) {
|
||||||
|| type == GLGizmosManager::Seam
|
|
||||||
|| type == GLGizmosManager::MmuSegmentation
|
|
||||||
|| type == GLGizmosManager::Simplify ) {
|
|
||||||
shader->stop_using();
|
shader->stop_using();
|
||||||
gm.render_painter_gizmo();
|
gm.render_painter_gizmo();
|
||||||
shader->start_using();
|
shader->start_using();
|
||||||
|
@ -635,7 +635,7 @@ public:
|
|||||||
void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); }
|
void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); }
|
||||||
|
|
||||||
void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
|
void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
|
||||||
void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
|
void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1, const ModelVolume* mv = nullptr);
|
||||||
void update_instance_printable_state_for_object(size_t obj_idx);
|
void update_instance_printable_state_for_object(size_t obj_idx);
|
||||||
void update_instance_printable_state_for_objects(const std::vector<size_t>& object_idxs);
|
void update_instance_printable_state_for_objects(const std::vector<size_t>& object_idxs);
|
||||||
|
|
||||||
|
@ -99,20 +99,11 @@ protected:
|
|||||||
GLPaintContour m_paint_contour;
|
GLPaintContour m_paint_contour;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GLGizmoTransparentRender
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// Following function renders the triangles and cursor. Having this separated
|
|
||||||
// from usual on_render method allows to render them before transparent
|
|
||||||
// objects, so they can be seen inside them. The usual on_render is called
|
|
||||||
// after all volumes (including transparent ones) are rendered.
|
|
||||||
virtual void render_painter_gizmo() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Following class is a base class for a gizmo with ability to paint on mesh
|
// Following class is a base class for a gizmo with ability to paint on mesh
|
||||||
// using circular blush (such as FDM supports gizmo and seam painting gizmo).
|
// using circular blush (such as FDM supports gizmo and seam painting gizmo).
|
||||||
// The purpose is not to duplicate code related to mesh painting.
|
// The purpose is not to duplicate code related to mesh painting.
|
||||||
class GLGizmoPainterBase : public GLGizmoTransparentRender, public GLGizmoBase
|
class GLGizmoPainterBase : public GLGizmoBase
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
ObjectID m_old_mo_id;
|
ObjectID m_old_mo_id;
|
||||||
@ -125,6 +116,12 @@ public:
|
|||||||
virtual void set_painter_gizmo_data(const Selection& selection);
|
virtual void set_painter_gizmo_data(const Selection& selection);
|
||||||
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
virtual bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||||
|
|
||||||
|
// Following function renders the triangles and cursor. Having this separated
|
||||||
|
// from usual on_render method allows to render them before transparent
|
||||||
|
// objects, so they can be seen inside them. The usual on_render is called
|
||||||
|
// after all volumes (including transparent ones) are rendered.
|
||||||
|
virtual void render_painter_gizmo() const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void render_triangles(const Selection& selection) const;
|
virtual void render_triangles(const Selection& selection) const;
|
||||||
void render_cursor() const;
|
void render_cursor() const;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#include "GLGizmoSimplify.hpp"
|
#include "GLGizmoSimplify.hpp"
|
||||||
#include "slic3r/GUI/3DScene.hpp"
|
|
||||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||||
@ -10,51 +9,85 @@
|
|||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/QuadricEdgeCollapse.hpp"
|
#include "libslic3r/QuadricEdgeCollapse.hpp"
|
||||||
|
|
||||||
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
namespace Slic3r::GUI {
|
namespace Slic3r::GUI {
|
||||||
|
|
||||||
|
// Extend call after only when Simplify gizmo is still alive
|
||||||
|
static void call_after_if_active(std::function<void()> 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 ModelVolume* get_model_volume(const Selection& selection, Model& model)
|
||||||
|
{
|
||||||
|
const Selection::IndicesList& idxs = selection.get_volume_idxs();
|
||||||
|
// only one selected volume
|
||||||
|
if (idxs.size() != 1)
|
||||||
|
return nullptr;
|
||||||
|
const GLVolume* selected_volume = selection.get_volume(*idxs.begin());
|
||||||
|
if (selected_volume == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const GLVolume::CompositeID& cid = selected_volume->composite_id;
|
||||||
|
const ModelObjectPtrs& objs = model.objects;
|
||||||
|
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
|
||||||
|
return nullptr;
|
||||||
|
const ModelObject* obj = objs[cid.object_id];
|
||||||
|
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
|
||||||
|
return nullptr;
|
||||||
|
return obj->volumes[cid.volume_id];
|
||||||
|
}
|
||||||
|
|
||||||
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
|
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
|
||||||
const std::string &icon_filename,
|
const std::string &icon_filename,
|
||||||
unsigned int sprite_id)
|
unsigned int sprite_id)
|
||||||
: GLGizmoBase(parent, icon_filename, -1)
|
: GLGizmoBase(parent, icon_filename, -1)
|
||||||
, m_state(State::settings)
|
|
||||||
, m_is_valid_result(false)
|
|
||||||
, m_exist_preview(false)
|
|
||||||
, m_progress(0)
|
|
||||||
, m_volume(nullptr)
|
, m_volume(nullptr)
|
||||||
, m_obj_index(0)
|
|
||||||
, m_need_reload(false)
|
|
||||||
, m_show_wireframe(false)
|
, m_show_wireframe(false)
|
||||||
, m_move_to_center(false)
|
, m_move_to_center(false)
|
||||||
// translation for GUI size
|
// translation for GUI size
|
||||||
, tr_mesh_name(_u8L("Mesh name"))
|
, tr_mesh_name(_u8L("Mesh name"))
|
||||||
, tr_triangles(_u8L("Triangles"))
|
, tr_triangles(_u8L("Triangles"))
|
||||||
, tr_preview(_u8L("Preview"))
|
|
||||||
, tr_detail_level(_u8L("Detail level"))
|
, tr_detail_level(_u8L("Detail level"))
|
||||||
, tr_decimate_ratio(_u8L("Decimate ratio"))
|
, tr_decimate_ratio(_u8L("Decimate ratio"))
|
||||||
// for wireframe
|
|
||||||
, m_wireframe_VBO_id(0)
|
|
||||||
, m_wireframe_IBO_id(0)
|
|
||||||
, m_wireframe_IBO_size(0)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
GLGizmoSimplify::~GLGizmoSimplify() {
|
GLGizmoSimplify::~GLGizmoSimplify()
|
||||||
m_state = State::canceling;
|
{
|
||||||
if (m_worker.joinable()) m_worker.join();
|
stop_worker_thread_request();
|
||||||
free_gpu();
|
if (m_worker.joinable())
|
||||||
|
m_worker.join();
|
||||||
|
m_glmodel.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GLGizmoSimplify::on_esc_key_down() {
|
bool GLGizmoSimplify::on_esc_key_down() {
|
||||||
if (m_state == State::settings || m_state == State::canceling)
|
return false;
|
||||||
|
/*if (!m_is_worker_running)
|
||||||
return false;
|
return false;
|
||||||
|
stop_worker_thread_request();
|
||||||
m_state = State::canceling;
|
return true;*/
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// while opening needs GLGizmoSimplify to set window position
|
// while opening needs GLGizmoSimplify to set window position
|
||||||
void GLGizmoSimplify::add_simplify_suggestion_notification(
|
void GLGizmoSimplify::add_simplify_suggestion_notification(
|
||||||
const std::vector<size_t> &object_ids,
|
const std::vector<size_t> &object_ids,
|
||||||
const ModelObjectPtrs & objects,
|
const std::vector<ModelObject*>& objects,
|
||||||
NotificationManager & manager)
|
NotificationManager & manager)
|
||||||
{
|
{
|
||||||
std::vector<size_t> big_ids;
|
std::vector<size_t> big_ids;
|
||||||
@ -74,14 +107,10 @@ void GLGizmoSimplify::add_simplify_suggestion_notification(
|
|||||||
if (big_ids.empty()) return;
|
if (big_ids.empty()) return;
|
||||||
|
|
||||||
for (size_t object_id : big_ids) {
|
for (size_t object_id : big_ids) {
|
||||||
std::string t = _u8L(
|
std::string t = GUI::format(_u8L(
|
||||||
"Processing model '@object_name' with more than 1M triangles "
|
"Processing model '%1%' with more than 1M triangles "
|
||||||
"could be slow. It is highly recommend to reduce "
|
"could be slow. It is highly recommend to reduce "
|
||||||
"amount of triangles.");
|
"amount of triangles."), objects[object_id]->name);
|
||||||
t.replace(t.find("@object_name"), sizeof("@object_name") - 1,
|
|
||||||
objects[object_id]->name);
|
|
||||||
// std::stringstream text;
|
|
||||||
// text << t << "\n";
|
|
||||||
std::string hypertext = _u8L("Simplify model");
|
std::string hypertext = _u8L("Simplify model");
|
||||||
|
|
||||||
std::function<bool(wxEvtHandler *)> open_simplify =
|
std::function<bool(wxEvtHandler *)> open_simplify =
|
||||||
@ -116,39 +145,47 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
|||||||
{
|
{
|
||||||
create_gui_cfg();
|
create_gui_cfg();
|
||||||
const Selection &selection = m_parent.get_selection();
|
const Selection &selection = m_parent.get_selection();
|
||||||
int obj_index = selection.get_object_idx();
|
const ModelVolume *act_volume = get_model_volume(selection, wxGetApp().plater()->model());
|
||||||
ModelVolume *act_volume = get_volume(selection, wxGetApp().plater()->model());
|
|
||||||
if (act_volume == nullptr) {
|
if (act_volume == nullptr) {
|
||||||
switch (m_state) {
|
stop_worker_thread_request();
|
||||||
case State::settings: close(); break;
|
close();
|
||||||
case State::canceling: break;
|
|
||||||
default: m_state = State::canceling;
|
|
||||||
}
|
|
||||||
return;
|
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 = bool(m_state.result);
|
||||||
|
progress = m_state.progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether to trigger calculation after rendering is done.
|
||||||
|
bool start_process = false;
|
||||||
|
|
||||||
|
|
||||||
// Check selection of new volume
|
// Check selection of new volume
|
||||||
// Do not reselect object when processing
|
// Do not reselect object when processing
|
||||||
if (act_volume != m_volume && m_state == State::settings) {
|
if (act_volume != m_volume) {
|
||||||
bool change_window_position = (m_volume == nullptr);
|
bool change_window_position = (m_volume == nullptr);
|
||||||
// select different model
|
// select different model
|
||||||
if (m_volume != nullptr && m_original_its.has_value()) {
|
|
||||||
set_its(*m_original_its);
|
|
||||||
}
|
|
||||||
|
|
||||||
// close suggestion notification
|
// close suggestion notification
|
||||||
auto notification_manager = wxGetApp().plater()->get_notification_manager();
|
auto notification_manager = wxGetApp().plater()->get_notification_manager();
|
||||||
notification_manager->remove_simplify_suggestion_with_id(act_volume->get_object()->id());
|
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_volume = act_volume;
|
||||||
m_original_its = {};
|
|
||||||
m_configuration.decimate_ratio = 50.; // default value
|
m_configuration.decimate_ratio = 50.; // default value
|
||||||
m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
|
m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
|
||||||
m_is_valid_result = false;
|
init_model(m_volume->mesh().its);
|
||||||
m_exist_preview = false;
|
|
||||||
init_wireframe();
|
// Start processing. If we switched from another object, process will
|
||||||
live_preview();
|
// stop the background thread and it will restart itself later.
|
||||||
|
start_process = true;
|
||||||
|
|
||||||
// set window position
|
// set window position
|
||||||
if (m_move_to_center && change_window_position) {
|
if (m_move_to_center && change_window_position) {
|
||||||
@ -179,12 +216,6 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
|||||||
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
|
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
|
||||||
ImGuiWindowFlags_NoCollapse;
|
ImGuiWindowFlags_NoCollapse;
|
||||||
m_imgui->begin(on_get_name(), flag);
|
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 + ":");
|
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":");
|
||||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||||
std::string name = m_volume->name;
|
std::string name = m_volume->name;
|
||||||
@ -193,21 +224,16 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
|||||||
m_imgui->text(name);
|
m_imgui->text(name);
|
||||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":");
|
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":");
|
||||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
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_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_preview + ":");
|
m_imgui->text(std::to_string(orig_triangle_count));
|
||||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
|
||||||
if (m_exist_preview) {
|
|
||||||
m_imgui->text(std::to_string(m_volume->mesh().its.indices.size()));
|
|
||||||
} else {
|
|
||||||
m_imgui->text("---");
|
|
||||||
}*/
|
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
|
if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
|
||||||
m_configuration.use_count = !m_configuration.use_count;
|
m_configuration.use_count = !m_configuration.use_count;
|
||||||
live_preview();
|
start_process = true;
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
m_imgui->disabled_begin(m_configuration.use_count);
|
m_imgui->disabled_begin(m_configuration.use_count);
|
||||||
@ -232,21 +258,21 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
|||||||
case 3: m_configuration.max_error = 0.5f; break;
|
case 3: m_configuration.max_error = 0.5f; break;
|
||||||
case 4: m_configuration.max_error = 1.f; break;
|
case 4: m_configuration.max_error = 1.f; break;
|
||||||
}
|
}
|
||||||
live_preview();
|
start_process = true;
|
||||||
}
|
}
|
||||||
m_imgui->disabled_end(); // !use_count
|
m_imgui->disabled_end(); // !use_count
|
||||||
|
|
||||||
if (ImGui::RadioButton("##use_count", m_configuration.use_count)) {
|
if (ImGui::RadioButton("##use_count", m_configuration.use_count)) {
|
||||||
m_configuration.use_count = !m_configuration.use_count;
|
m_configuration.use_count = !m_configuration.use_count;
|
||||||
live_preview();
|
start_process = true;
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
// show preview result triangle count (percent)
|
// show preview result triangle count (percent)
|
||||||
if (m_need_reload && !m_configuration.use_count) {
|
if (!m_configuration.use_count) {
|
||||||
m_configuration.wanted_count = static_cast<uint32_t>(m_volume->mesh().its.indices.size());
|
m_configuration.wanted_count = static_cast<uint32_t>(m_triangle_count);
|
||||||
m_configuration.decimate_ratio =
|
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);
|
m_imgui->disabled_begin(!m_configuration.use_count);
|
||||||
@ -261,8 +287,8 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
|||||||
m_configuration.decimate_ratio = 0.01f;
|
m_configuration.decimate_ratio = 0.01f;
|
||||||
if (m_configuration.decimate_ratio > 100.f)
|
if (m_configuration.decimate_ratio > 100.f)
|
||||||
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);
|
||||||
live_preview();
|
start_process = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::NewLine();
|
ImGui::NewLine();
|
||||||
@ -270,79 +296,37 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
|
|||||||
ImGui::Text(_u8L("%d triangles").c_str(), m_configuration.wanted_count);
|
ImGui::Text(_u8L("%d triangles").c_str(), m_configuration.wanted_count);
|
||||||
m_imgui->disabled_end(); // use_count
|
m_imgui->disabled_end(); // use_count
|
||||||
|
|
||||||
if (ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe)) {
|
ImGui::Checkbox(_u8L("Show wireframe").c_str(), &m_show_wireframe);
|
||||||
if (m_show_wireframe) init_wireframe();
|
|
||||||
else free_gpu();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_canceling = m_state == State::canceling;
|
m_imgui->disabled_begin(is_cancelling);
|
||||||
m_imgui->disabled_begin(is_canceling);
|
if (m_imgui->button(_L("Close"))) {
|
||||||
if (m_imgui->button(_u8L("Cancel"))) {
|
close();
|
||||||
if (m_state == State::settings) {
|
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_cancelling)
|
||||||
if (m_original_its.has_value()) {
|
ImGui::SetTooltip("%s", _u8L("Operation already cancelling. Please wait few seconds.").c_str());
|
||||||
set_its(*m_original_its);
|
m_imgui->disabled_end(); // state cancelling
|
||||||
m_state = State::close_on_end;
|
|
||||||
} else {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m_state = 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
|
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|
||||||
bool is_processing = m_state != State::settings;
|
m_imgui->disabled_begin(is_worker_running || ! is_result_ready);
|
||||||
m_imgui->disabled_begin(is_processing);
|
if (m_imgui->button(_L("Apply"))) {
|
||||||
if (m_imgui->button(_u8L("Apply"))) {
|
apply_simplify();
|
||||||
if (!m_is_valid_result) {
|
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_worker_running)
|
||||||
m_state = State::close_on_end;
|
|
||||||
process();
|
|
||||||
} else if (m_exist_preview) {
|
|
||||||
// use preview and close
|
|
||||||
after_apply();
|
|
||||||
} else { // no changes made
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
} else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && is_processing)
|
|
||||||
ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str());
|
ImGui::SetTooltip("%s", _u8L("Can't apply when proccess preview.").c_str());
|
||||||
m_imgui->disabled_end(); // state !settings
|
m_imgui->disabled_end(); // state !settings
|
||||||
|
|
||||||
// draw progress bar
|
// draw progress bar
|
||||||
if (is_processing) { // apply or preview
|
if (is_worker_running) { // apply or preview
|
||||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||||
// draw progress bar
|
// draw progress bar
|
||||||
char buf[32];
|
std::string progress_text = GUI::format(_L("Process %1% / 100"), std::to_string(progress));
|
||||||
sprintf(buf, L("Process %d / 100"), m_progress);
|
ImVec2 progress_size(m_gui_cfg->input_width, 0.f);
|
||||||
ImGui::ProgressBar(m_progress / 100., ImVec2(m_gui_cfg->input_width, 0.f), buf);
|
ImGui::ProgressBar(progress / 100., progress_size, progress_text.c_str());
|
||||||
}
|
}
|
||||||
m_imgui->end();
|
m_imgui->end();
|
||||||
|
if (start_process)
|
||||||
// refresh view when needed
|
process();
|
||||||
if (m_need_reload) {
|
|
||||||
m_need_reload = false;
|
|
||||||
bool close_on_end = (m_state == State::close_on_end);
|
|
||||||
// Reload visualization of mesh - change VBO, FBO on GPU
|
|
||||||
m_parent.reload_scene(true);
|
|
||||||
// set m_state must be before close() !!!
|
|
||||||
m_state = State::settings;
|
|
||||||
if (close_on_end) after_apply();
|
|
||||||
else init_wireframe();
|
|
||||||
// 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->changed_mesh(m_obj_index);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLGizmoSimplify::close() {
|
void GLGizmoSimplify::close() {
|
||||||
// close gizmo == open it again
|
// close gizmo == open it again
|
||||||
@ -350,117 +334,157 @@ void GLGizmoSimplify::close() {
|
|||||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSimplify::live_preview() {
|
void GLGizmoSimplify::stop_worker_thread_request()
|
||||||
m_is_valid_result = false;
|
{
|
||||||
if (m_state != State::settings) {
|
std::lock_guard lk(m_state_mutex);
|
||||||
// already canceling process
|
if (m_state.status == State::running)
|
||||||
if (m_state == State::canceling) return;
|
m_state.status = State::Status::cancelling;
|
||||||
|
}
|
||||||
|
|
||||||
// wait until cancel
|
|
||||||
if (m_worker.joinable()) {
|
// Following is called from a UI thread when the worker terminates
|
||||||
m_state = State::canceling;
|
// worker calls it through a CallAfter.
|
||||||
m_dealy_process_cv.notify_one();
|
void GLGizmoSimplify::worker_finished()
|
||||||
m_worker.join();
|
{
|
||||||
|
{
|
||||||
|
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_state = State::preview;
|
m_worker.join();
|
||||||
process();
|
if (GLGizmoBase::m_state == Off)
|
||||||
|
return;
|
||||||
|
if (m_state.result)
|
||||||
|
init_model(*m_state.result);
|
||||||
|
if (m_state.config != m_configuration || m_state.mv != m_volume) {
|
||||||
|
// Settings were changed, restart the worker immediately.
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
request_rerender(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSimplify::process()
|
void GLGizmoSimplify::process()
|
||||||
{
|
{
|
||||||
if (m_volume == nullptr) return;
|
if (m_volume == nullptr || m_volume->mesh().its.indices.empty())
|
||||||
if (m_volume->mesh().its.indices.empty()) return;
|
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
|
bool configs_match = false;
|
||||||
set_dirty();
|
bool result_valid = false;
|
||||||
m_parent.schedule_extra_frame(0);
|
bool is_worker_running = false;
|
||||||
|
{
|
||||||
|
std::lock_guard lk(m_state_mutex);
|
||||||
|
configs_match = (m_volume == m_state.mv && m_state.config == m_configuration);
|
||||||
|
result_valid = bool(m_state.result);
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when not store original volume store it for cancelation
|
if (is_worker_running && ! configs_match) {
|
||||||
if (!m_original_its.has_value()) {
|
// Worker is running with outdated config. Stop it. It will
|
||||||
m_original_its = m_volume->mesh().its; // copy
|
// restart itself when cancellation is done.
|
||||||
|
stop_worker_thread_request();
|
||||||
// store previous state
|
return;
|
||||||
auto plater = wxGetApp().plater();
|
|
||||||
plater->take_snapshot(_u8L("Simplify ") + m_volume->name);
|
|
||||||
plater->clear_before_change_mesh(m_obj_index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_progress = 0;
|
|
||||||
if (m_worker.joinable()) m_worker.join();
|
|
||||||
|
|
||||||
m_worker = std::thread([this]() {
|
if (m_worker.joinable()) {
|
||||||
{// delay before process
|
// This can happen when process() is called after previous worker terminated,
|
||||||
std::unique_lock<std::mutex> lk(m_state_mutex);
|
// but before the worker_finished callback was called. In this case, just join the thread,
|
||||||
auto is_modify = [this]() { return m_state == State::canceling; };
|
// the callback will check this and do nothing.
|
||||||
if (m_dealy_process_cv.wait_for(lk, m_gui_cfg->prcess_delay, is_modify)) {
|
m_worker.join();
|
||||||
// exist modification
|
}
|
||||||
m_state = State::settings;
|
|
||||||
request_rerender();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// store original triangles
|
// Copy configuration that will be used.
|
||||||
uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0;
|
m_state.config = m_configuration;
|
||||||
float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits<float>::max();
|
m_state.mv = m_volume;
|
||||||
|
m_state.status = State::running;
|
||||||
|
|
||||||
std::function<void(void)> throw_on_cancel = [&]() {
|
// Create a copy of current mesh to pass to the worker thread.
|
||||||
if (m_state == State::canceling) {
|
// 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_volume->mesh().its);
|
||||||
|
|
||||||
|
m_worker = std::thread([this](std::unique_ptr<indexed_triangle_set> its) {
|
||||||
|
|
||||||
|
// Checks that the UI thread did not request cancellation, throws if so.
|
||||||
|
std::function<void(void)> throw_on_cancel = [this]() {
|
||||||
|
std::lock_guard lk(m_state_mutex);
|
||||||
|
if (m_state.status == State::cancelling)
|
||||||
throw SimplifyCanceledException();
|
throw SimplifyCanceledException();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int64_t last = 0;
|
// Called by worker thread, updates progress bar.
|
||||||
std::function<void(int)> statusfn = [this, &last](int percent) {
|
// Using CallAfter so the rerequest function is run in UI thread.
|
||||||
m_progress = percent;
|
std::function<void(int)> statusfn = [this](int percent) {
|
||||||
|
std::lock_guard lk(m_state_mutex);
|
||||||
// check max 4fps
|
m_state.progress = percent;
|
||||||
int64_t now = m_parent.timestamp_now();
|
call_after_if_active([this]() { request_rerender(); });
|
||||||
if ((now - last) < 250) return;
|
|
||||||
last = now;
|
|
||||||
|
|
||||||
request_rerender();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
indexed_triangle_set collapsed = *m_original_its; // copy
|
// Initialize.
|
||||||
|
uint32_t triangle_count = 0;
|
||||||
try {
|
float max_error = std::numeric_limits<float>::max();
|
||||||
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn);
|
{
|
||||||
set_its(collapsed);
|
std::lock_guard lk(m_state_mutex);
|
||||||
m_is_valid_result = true;
|
if (m_state.config.use_count)
|
||||||
m_exist_preview = true;
|
triangle_count = m_state.config.wanted_count;
|
||||||
} catch (SimplifyCanceledException &) {
|
if (! m_state.config.use_count)
|
||||||
// set state out of main thread
|
max_error = m_state.config.max_error;
|
||||||
m_state = State::settings;
|
m_state.progress = 0;
|
||||||
|
m_state.result.reset();
|
||||||
|
m_state.status = State::Status::running;
|
||||||
}
|
}
|
||||||
// need to render last status fn to change bar graph to buttons
|
|
||||||
request_rerender();
|
// Start the actual calculation.
|
||||||
});
|
try {
|
||||||
|
its_quadric_edge_collapse(*its, triangle_count, &max_error, 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::set_its(indexed_triangle_set &its) {
|
void GLGizmoSimplify::apply_simplify() {
|
||||||
if (m_volume == nullptr) return; // could appear after process
|
|
||||||
m_volume->set_mesh(its);
|
const Selection& selection = m_parent.get_selection();
|
||||||
m_volume->calculate_convex_hull();
|
int object_idx = selection.get_object_idx();
|
||||||
m_volume->set_new_unique_id();
|
|
||||||
m_volume->get_object()->invalidate_bounding_box();
|
auto plater = wxGetApp().plater();
|
||||||
m_need_reload = true;
|
plater->take_snapshot(_u8L("Simplify ") + m_volume->name);
|
||||||
|
plater->clear_before_change_mesh(object_idx);
|
||||||
|
|
||||||
|
ModelVolume* mv = get_model_volume(selection, wxGetApp().model());
|
||||||
|
assert(mv == m_volume);
|
||||||
|
|
||||||
|
mv->set_mesh(std::move(*m_state.result));
|
||||||
|
m_state.result.reset();
|
||||||
|
mv->calculate_convex_hull();
|
||||||
|
mv->set_new_unique_id();
|
||||||
|
mv->get_object()->invalidate_bounding_box();
|
||||||
|
|
||||||
|
// fix hollowing, sla support points, modifiers, ...
|
||||||
|
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
|
bool GLGizmoSimplify::on_is_activable() const
|
||||||
@ -472,39 +496,11 @@ void GLGizmoSimplify::on_set_state()
|
|||||||
{
|
{
|
||||||
// Closing gizmo. e.g. selecting another one
|
// Closing gizmo. e.g. selecting another one
|
||||||
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
|
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
|
||||||
// can appear when delete objects
|
m_parent.toggle_model_objects_visibility(true);
|
||||||
bool empty_selection = m_parent.get_selection().is_empty();
|
|
||||||
|
|
||||||
// cancel processing
|
stop_worker_thread_request();
|
||||||
if (empty_selection &&
|
m_volume = nullptr; // invalidate selected model
|
||||||
m_state != State::settings &&
|
m_glmodel.reset();
|
||||||
m_state != State::canceling)
|
|
||||||
m_state = State::canceling;
|
|
||||||
|
|
||||||
// refuse outgoing during simlification
|
|
||||||
// object is not selected when it is deleted(cancel and close gizmo)
|
|
||||||
if (m_state != 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);
|
|
||||||
m_parent.reload_scene(false);
|
|
||||||
m_need_reload = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// invalidate selected model
|
|
||||||
m_volume = nullptr;
|
|
||||||
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
|
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
|
||||||
// when open by hyperlink it needs to show up
|
// when open by hyperlink it needs to show up
|
||||||
request_rerender();
|
request_rerender();
|
||||||
@ -532,142 +528,95 @@ void GLGizmoSimplify::create_gui_cfg() {
|
|||||||
m_gui_cfg = cfg;
|
m_gui_cfg = cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSimplify::request_rerender() {
|
void GLGizmoSimplify::request_rerender(bool force) {
|
||||||
wxGetApp().plater()->CallAfter([this]() {
|
int64_t now = m_parent.timestamp_now();
|
||||||
|
if (force || now > m_last_rerender_timestamp + 250) { // 250 ms
|
||||||
set_dirty();
|
set_dirty();
|
||||||
m_parent.schedule_extra_frame(0);
|
m_parent.schedule_extra_frame(0);
|
||||||
});
|
m_last_rerender_timestamp = now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSimplify::set_center_position() {
|
void GLGizmoSimplify::set_center_position() {
|
||||||
m_move_to_center = true;
|
m_move_to_center = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GLGizmoSimplify::exist_volume(ModelVolume *volume) {
|
|
||||||
auto objs = wxGetApp().plater()->model().objects;
|
|
||||||
for (const auto &obj : objs) {
|
|
||||||
const auto &vlms = obj->volumes;
|
|
||||||
auto item = std::find(vlms.begin(), vlms.end(), volume);
|
|
||||||
if (item != vlms.end()) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModelVolume * GLGizmoSimplify::get_volume(const Selection &selection, Model &model)
|
void GLGizmoSimplify::init_model(const indexed_triangle_set& its)
|
||||||
{
|
{
|
||||||
const Selection::IndicesList& idxs = selection.get_volume_idxs();
|
if (its.indices.empty())
|
||||||
if (idxs.empty()) return nullptr;
|
return;
|
||||||
// only one selected volume
|
|
||||||
if (idxs.size() != 1) return nullptr;
|
|
||||||
const GLVolume *selected_volume = selection.get_volume(*idxs.begin());
|
|
||||||
if (selected_volume == nullptr) return nullptr;
|
|
||||||
|
|
||||||
const GLVolume::CompositeID &cid = selected_volume->composite_id;
|
m_glmodel.reset();
|
||||||
const ModelObjectPtrs& objs = model.objects;
|
m_glmodel.init_from(its);
|
||||||
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
|
m_parent.toggle_model_objects_visibility(true); // selected volume may have changed
|
||||||
return nullptr;
|
m_parent.toggle_model_objects_visibility(false, m_c->selection_info()->model_object(),
|
||||||
const ModelObject* obj = objs[cid.object_id];
|
m_c->selection_info()->get_active_instance(), m_volume);
|
||||||
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
|
|
||||||
return nullptr;
|
|
||||||
return obj->volumes[cid.volume_id];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ModelVolume *GLGizmoSimplify::get_volume(const GLVolume::CompositeID &cid, const Model &model)
|
|
||||||
{
|
|
||||||
const ModelObjectPtrs &objs = model.objects;
|
|
||||||
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
|
|
||||||
return nullptr;
|
|
||||||
const ModelObject *obj = objs[cid.object_id];
|
|
||||||
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
|
|
||||||
return nullptr;
|
|
||||||
return obj->volumes[cid.volume_id];
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLGizmoSimplify::init_wireframe()
|
|
||||||
{
|
|
||||||
if (!m_show_wireframe) return;
|
|
||||||
const indexed_triangle_set &its = m_volume->mesh().its;
|
|
||||||
free_gpu();
|
|
||||||
if (its.indices.empty()) return;
|
|
||||||
|
|
||||||
// vertices
|
|
||||||
glsafe(::glGenBuffers(1, &m_wireframe_VBO_id));
|
|
||||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_VBO_id));
|
|
||||||
glsafe(::glBufferData(GL_ARRAY_BUFFER,
|
|
||||||
its.vertices.size() * 3 * sizeof(float),
|
|
||||||
its.vertices.data(), GL_STATIC_DRAW));
|
|
||||||
|
|
||||||
// indices
|
if (const Selection&sel = m_parent.get_selection(); sel.get_volume_idxs().size() == 1)
|
||||||
std::vector<Vec2i> contour_indices;
|
m_glmodel.set_color(-1, sel.get_volume(*sel.get_volume_idxs().begin())->color);
|
||||||
contour_indices.reserve((its.indices.size() * 3) / 2);
|
m_triangle_count = its.indices.size();
|
||||||
for (const auto &triangle : its.indices) {
|
|
||||||
for (size_t ti1 = 0; ti1 < 3; ++ti1) {
|
|
||||||
size_t ti2 = (ti1 == 2) ? 0 : (ti1 + 1);
|
|
||||||
if (triangle[ti1] > triangle[ti2]) continue;
|
|
||||||
contour_indices.emplace_back(triangle[ti1], triangle[ti2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
glsafe(::glGenBuffers(1, &m_wireframe_IBO_id));
|
|
||||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_IBO_id));
|
|
||||||
glsafe(::glBufferData(GL_ARRAY_BUFFER,
|
|
||||||
2*contour_indices.size() * sizeof(coord_t),
|
|
||||||
contour_indices.data(), GL_STATIC_DRAW));
|
|
||||||
m_wireframe_IBO_size = contour_indices.size() * 2;
|
|
||||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSimplify::render_wireframe() const
|
void GLGizmoSimplify::on_render()
|
||||||
{
|
{
|
||||||
// is initialized?
|
if (! m_glmodel.is_initialized())
|
||||||
if (m_wireframe_VBO_id == 0 || m_wireframe_IBO_id == 0) return;
|
return;
|
||||||
if (!m_show_wireframe) return;
|
|
||||||
|
|
||||||
const auto& selection = m_parent.get_selection();
|
const auto& selection = m_parent.get_selection();
|
||||||
const auto& volume_idxs = selection.get_volume_idxs();
|
const auto& volume_idxs = selection.get_volume_idxs();
|
||||||
if (volume_idxs.empty() || volume_idxs.size() != 1) return;
|
if (volume_idxs.empty() || volume_idxs.size() != 1) return;
|
||||||
const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin());
|
const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin());
|
||||||
|
|
||||||
// check that selected model is wireframe initialized
|
// Check that the GLVolume still belongs to the ModelObject we work on.
|
||||||
if (m_volume != get_volume(selected_volume->composite_id, *m_parent.get_model()))
|
if (m_volume != get_model_volume(selection, wxGetApp().model()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const Transform3d trafo_matrix = selected_volume->world_matrix();
|
const Transform3d trafo_matrix = selected_volume->world_matrix();
|
||||||
glsafe(::glPushMatrix());
|
glsafe(::glPushMatrix());
|
||||||
glsafe(::glMultMatrixd(trafo_matrix.data()));
|
glsafe(::glMultMatrixd(trafo_matrix.data()));
|
||||||
|
|
||||||
auto *contour_shader = wxGetApp().get_shader("mm_contour");
|
auto *gouraud_shader = wxGetApp().get_shader("gouraud_light");
|
||||||
contour_shader->start_using();
|
glsafe(::glPushAttrib(GL_DEPTH_TEST));
|
||||||
glsafe(::glDepthFunc(GL_LEQUAL));
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||||
glsafe(::glLineWidth(1.0f));
|
gouraud_shader->start_using();
|
||||||
|
m_glmodel.render();
|
||||||
|
gouraud_shader->stop_using();
|
||||||
|
|
||||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_VBO_id));
|
if (m_show_wireframe) {
|
||||||
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
|
auto* contour_shader = wxGetApp().get_shader("mm_contour");
|
||||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
contour_shader->start_using();
|
||||||
|
glsafe(::glLineWidth(1.0f));
|
||||||
|
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE));
|
||||||
|
//ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); });
|
||||||
|
//glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
||||||
|
//glsafe(::glPolygonOffset(5.0, 5.0));
|
||||||
|
m_glmodel.render();
|
||||||
|
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
|
||||||
|
contour_shader->stop_using();
|
||||||
|
}
|
||||||
|
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_wireframe_IBO_id));
|
glsafe(::glPopAttrib());
|
||||||
glsafe(::glDrawElements(GL_LINES, m_wireframe_IBO_size, GL_UNSIGNED_INT, nullptr));
|
glsafe(::glPopMatrix());
|
||||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
|
||||||
|
|
||||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
|
||||||
|
|
||||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
|
||||||
glsafe(::glDepthFunc(GL_LESS));
|
|
||||||
|
|
||||||
glsafe(::glPopMatrix()); // pop trafo
|
|
||||||
contour_shader->stop_using();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSimplify::free_gpu()
|
|
||||||
{
|
|
||||||
if (m_wireframe_VBO_id != 0) {
|
|
||||||
glsafe(::glDeleteBuffers(1, &m_wireframe_VBO_id));
|
|
||||||
m_wireframe_VBO_id = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_wireframe_IBO_id != 0) {
|
CommonGizmosDataID GLGizmoSimplify::on_get_requirements() const
|
||||||
glsafe(::glDeleteBuffers(1, &m_wireframe_IBO_id));
|
{
|
||||||
m_wireframe_IBO_id = 0;
|
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<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
|
} // namespace Slic3r::GUI
|
||||||
|
@ -4,27 +4,18 @@
|
|||||||
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
|
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
|
||||||
// which overrides our localization "L" macro.
|
// which overrides our localization "L" macro.
|
||||||
#include "GLGizmoBase.hpp"
|
#include "GLGizmoBase.hpp"
|
||||||
#include "GLGizmoPainterBase.hpp" // for render wireframe
|
#include "slic3r/GUI/3DScene.hpp"
|
||||||
#include "admesh/stl.h" // indexed_triangle_set
|
#include "admesh/stl.h" // indexed_triangle_set
|
||||||
#include <thread>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <condition_variable>
|
|
||||||
#include <chrono>
|
|
||||||
#include <optional>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
#include <GL/glew.h> // GLUint
|
|
||||||
|
|
||||||
// for simplify suggestion
|
|
||||||
class ModelObjectPtrs; // std::vector<ModelObject*>
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
class ModelVolume;
|
class ModelVolume;
|
||||||
|
class Model;
|
||||||
|
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
class NotificationManager; // for simplify suggestion
|
class NotificationManager; // for simplify suggestion
|
||||||
|
|
||||||
class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GLGizmoBase
|
class GLGizmoSimplify: public GLGizmoBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||||
@ -32,8 +23,9 @@ public:
|
|||||||
bool on_esc_key_down();
|
bool on_esc_key_down();
|
||||||
static void add_simplify_suggestion_notification(
|
static void add_simplify_suggestion_notification(
|
||||||
const std::vector<size_t> &object_ids,
|
const std::vector<size_t> &object_ids,
|
||||||
const ModelObjectPtrs & objects,
|
const std::vector<ModelObject*> & objects,
|
||||||
NotificationManager & manager);
|
NotificationManager & manager);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual std::string on_get_name() const override;
|
virtual std::string on_get_name() const override;
|
||||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||||
@ -43,76 +35,75 @@ protected:
|
|||||||
|
|
||||||
// must implement
|
// must implement
|
||||||
virtual bool on_init() override { return true;};
|
virtual bool on_init() override { return true;};
|
||||||
virtual void on_render() override{};
|
virtual void on_render() override;
|
||||||
virtual void on_render_for_picking() override{};
|
virtual void on_render_for_picking() override{};
|
||||||
|
|
||||||
// GLGizmoPainterBase
|
virtual CommonGizmosDataID on_get_requirements() const;
|
||||||
virtual void render_painter_gizmo() const override{ render_wireframe(); }
|
|
||||||
private:
|
private:
|
||||||
void after_apply();
|
void apply_simplify();
|
||||||
void close();
|
void close();
|
||||||
void live_preview();
|
|
||||||
void process();
|
void process();
|
||||||
void set_its(indexed_triangle_set &its);
|
void stop_worker_thread_request();
|
||||||
|
void worker_finished();
|
||||||
|
|
||||||
void create_gui_cfg();
|
void create_gui_cfg();
|
||||||
void request_rerender();
|
void request_rerender(bool force = false);
|
||||||
|
void init_model(const indexed_triangle_set& its);
|
||||||
|
|
||||||
void set_center_position();
|
void set_center_position();
|
||||||
// move to global functions
|
|
||||||
static ModelVolume *get_volume(const Selection &selection, Model &model);
|
|
||||||
static const ModelVolume *get_volume(const GLVolume::CompositeID &cid, const Model &model);
|
|
||||||
|
|
||||||
// return false when volume was deleted
|
|
||||||
static bool exist_volume(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
|
|
||||||
|
|
||||||
volatile int m_progress; // percent of done work
|
|
||||||
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;
|
|
||||||
|
|
||||||
volatile bool m_need_reload; // after simplify, glReload must be on main thread
|
|
||||||
|
|
||||||
std::thread m_worker;
|
|
||||||
// wait before process
|
|
||||||
std::mutex m_state_mutex;
|
|
||||||
std::condition_variable m_dealy_process_cv;
|
|
||||||
|
|
||||||
enum class State {
|
|
||||||
settings,
|
|
||||||
preview, // simplify to show preview
|
|
||||||
close_on_end, // simplify with close on end
|
|
||||||
canceling // after button click, before canceled
|
|
||||||
};
|
|
||||||
volatile State m_state;
|
|
||||||
|
|
||||||
struct Configuration
|
struct Configuration
|
||||||
{
|
{
|
||||||
bool use_count = false;
|
bool use_count = false;
|
||||||
// minimal triangle count
|
|
||||||
float decimate_ratio = 50.f; // in percent
|
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
|
void fix_count_by_ratio(size_t triangle_count);
|
||||||
float max_error = 1.;
|
bool operator==(const Configuration& rhs) {
|
||||||
|
return (use_count == rhs.use_count && decimate_ratio == rhs.decimate_ratio
|
||||||
void fix_count_by_ratio(size_t triangle_count)
|
&& wanted_count == rhs.wanted_count && max_error == rhs.max_error);
|
||||||
{
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
} m_configuration;
|
bool operator!=(const Configuration& rhs) {
|
||||||
|
return ! (*this == rhs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Configuration m_configuration;
|
||||||
|
|
||||||
|
bool m_move_to_center; // opening gizmo
|
||||||
|
|
||||||
|
const 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
|
||||||
|
|
||||||
|
// Timestamp of the last rerender request. Only accessed from UI thread.
|
||||||
|
int64_t m_last_rerender_timestamp = std::numeric_limits<int64_t>::min();
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
const ModelVolume* mv = nullptr;
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
// This configs holds GUI layout size given by translated texts.
|
// This configs holds GUI layout size given by translated texts.
|
||||||
// etc. When language changes, GUI is recreated and this class constructed again,
|
// etc. When language changes, GUI is recreated and this class constructed again,
|
||||||
@ -138,17 +129,9 @@ private:
|
|||||||
// translations used for calc window size
|
// translations used for calc window size
|
||||||
const std::string tr_mesh_name;
|
const std::string tr_mesh_name;
|
||||||
const std::string tr_triangles;
|
const std::string tr_triangles;
|
||||||
const std::string tr_preview;
|
|
||||||
const std::string tr_detail_level;
|
const std::string tr_detail_level;
|
||||||
const std::string tr_decimate_ratio;
|
const std::string tr_decimate_ratio;
|
||||||
|
|
||||||
// rendering wireframe
|
|
||||||
void render_wireframe() const;
|
|
||||||
void init_wireframe();
|
|
||||||
void free_gpu();
|
|
||||||
GLuint m_wireframe_VBO_id, m_wireframe_IBO_id;
|
|
||||||
size_t m_wireframe_IBO_size;
|
|
||||||
|
|
||||||
// cancel exception
|
// cancel exception
|
||||||
class SimplifyCanceledException: public std::exception
|
class SimplifyCanceledException: public std::exception
|
||||||
{
|
{
|
||||||
|
@ -483,7 +483,7 @@ void GLGizmosManager::render_painter_gizmo() const
|
|||||||
if (!m_enabled || m_current == Undefined)
|
if (!m_enabled || m_current == Undefined)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto *gizmo = dynamic_cast<GLGizmoTransparentRender*>(get_current());
|
auto *gizmo = dynamic_cast<GLGizmoPainterBase*>(get_current());
|
||||||
assert(gizmo); // check the precondition
|
assert(gizmo); // check the precondition
|
||||||
gizmo->render_painter_gizmo();
|
gizmo->render_painter_gizmo();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user