Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_sinking_objects_collision

This commit is contained in:
enricoturri1966 2021-09-30 15:08:35 +02:00
commit 1f82eb5624
25 changed files with 477 additions and 500 deletions

View file

@ -5201,7 +5201,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
GLGizmosManager::EType type = gm.get_current_type();
if (type == GLGizmosManager::FdmSupports
|| type == GLGizmosManager::Seam
|| type == GLGizmosManager::MmuSegmentation) {
|| type == GLGizmosManager::MmuSegmentation
|| type == GLGizmosManager::Simplify ) {
shader->stop_using();
gm.render_painter_gizmo();
shader->start_using();

View file

@ -136,6 +136,7 @@ public:
bool is_selectable() const { return on_is_selectable(); }
CommonGizmosDataID get_requirements() const { return on_get_requirements(); }
virtual bool wants_enter_leave_snapshots() const { return false; }
virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); }
void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; }
unsigned int get_sprite_id() const { return m_sprite_id; }

View file

@ -8,6 +8,7 @@
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include <GL/glew.h>
@ -165,7 +166,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"),
UndoRedo::SnapshotType::GizmoAction);
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
@ -298,8 +300,6 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
}
}
activate_internal_undo_redo_stack(true);
Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
: _L("Add supports by angle"));
update_model_object();

View file

@ -21,6 +21,8 @@ protected:
std::string get_gizmo_entering_text() const override { return _u8L("Entering Paint-on supports"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Paint-on supports"); }
std::string get_action_snapshot_name() override { return _u8L("Paint-on supports editing"); }
private:
bool on_init() override;

View file

@ -11,6 +11,7 @@
#include "slic3r/GUI/NotificationManager.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Model.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include <GL/glew.h>
@ -503,7 +504,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"),
UndoRedo::SnapshotType::GizmoAction);
ModelObject * mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume *mv : mo->volumes)

View file

@ -130,6 +130,7 @@ protected:
std::string get_gizmo_entering_text() const override { return _u8L("Entering Multimaterial painting"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Multimaterial painting"); }
std::string get_action_snapshot_name() override { return _u8L("Multimaterial painting editing"); }
size_t m_first_selected_extruder_idx = 0;
size_t m_second_selected_extruder_idx = 1;

View file

@ -26,35 +26,6 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic
m_vbo_sphere.finalize_geometry(true);
}
// port of 948bc382655993721d93d3b9fce9b0186fcfb211
void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate)
{
Plater* plater = wxGetApp().plater();
// Following is needed to prevent taking an extra snapshot when the activation of
// the internal stack happens when the gizmo is already active (such as open gizmo,
// close gizmo, undo, start painting). The internal stack does not activate on the
// undo, because that would obliterate all future of the main stack (user would
// have to close the gizmo himself, he has no access to main undo/redo after the
// internal stack opens). We don't want the "entering" snapshot taken in this case,
// because there already is one.
std::string last_snapshot_name;
plater->undo_redo_topmost_string_getter(plater->can_undo(), last_snapshot_name);
if (activate && !m_internal_stack_active) {
if (std::string str = this->get_gizmo_entering_text(); last_snapshot_name != str)
Plater::TakeSnapshot(plater, str, UndoRedo::SnapshotType::EnteringGizmo);
plater->enter_gizmos_stack();
m_internal_stack_active = true;
}
if (!activate && m_internal_stack_active) {
plater->leave_gizmos_stack();
if (std::string str = this->get_gizmo_leaving_text(); last_snapshot_name != str)
Plater::TakeSnapshot(plater, str, UndoRedo::SnapshotType::LeavingGizmoWithAction);
m_internal_stack_active = false;
}
}
void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection)
{
if (m_state != On)
@ -462,8 +433,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
&& m_button_down != Button::None) {
// Take snapshot and update ModelVolume data.
wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down);
activate_internal_undo_redo_stack(true);
Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name);
Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name, UndoRedo::SnapshotType::GizmoAction);
update_model_object();
m_button_down = Button::None;
@ -560,16 +530,10 @@ void GLGizmoPainterBase::on_set_state()
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
on_opening();
if (! m_parent.get_gizmos_manager().is_serializing()) {
wxGetApp().CallAfter([this]() {
activate_internal_undo_redo_stack(true);
});
}
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
// we are actually shutting down
on_shutdown();
activate_internal_undo_redo_stack(false);
m_old_mo_id = -1;
//m_iva.release_geometry();
m_triangle_selectors.clear();

View file

@ -57,30 +57,32 @@ private:
std::array<GLIndexedVertexArray, 3> m_varrays;
};
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
// using circular blush (such as FDM supports gizmo and seam painting gizmo).
// The purpose is not to duplicate code related to mesh painting.
class GLGizmoPainterBase : public GLGizmoBase
class GLGizmoPainterBase : public GLGizmoTransparentRender, public GLGizmoBase
{
private:
ObjectID m_old_mo_id;
size_t m_old_volumes_size = 0;
void on_render() override {}
void on_render_for_picking() override {}
public:
GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
~GLGizmoPainterBase() override = default;
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);
// 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:
void render_triangles(const Selection& selection, const bool use_polygon_offset_fill = true) const;
void render_cursor() const;
@ -88,7 +90,6 @@ protected:
void render_cursor_sphere(const Transform3d& trafo) const;
virtual void update_model_object() const = 0;
virtual void update_from_model_object() = 0;
void activate_internal_undo_redo_stack(bool activate);
virtual std::array<float, 4> get_cursor_sphere_left_button_color() const { return {0.f, 0.f, 1.f, 0.25f}; }
virtual std::array<float, 4> get_cursor_sphere_right_button_color() const { return {1.f, 0.f, 0.f, 0.25f}; }
@ -170,6 +171,7 @@ protected:
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override {}
CommonGizmosDataID on_get_requirements() const override;
bool wants_enter_leave_snapshots() const override { return true; }
virtual wxString handle_snapshot_action_name(bool shift_down, Button button_down) const = 0;

View file

@ -8,6 +8,7 @@
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/Utils/UndoRedo.hpp"
#include <GL/glew.h>
@ -121,7 +122,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
m_imgui->text("");
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"),
UndoRedo::SnapshotType::GizmoAction);
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {

View file

@ -22,6 +22,7 @@ protected:
std::string get_gizmo_entering_text() const override { return _u8L("Entering Seam painting"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Seam painting"); }
std::string get_action_snapshot_name() override { return _u8L("Paint-on seam editing"); }
private:
bool on_init() override;

View file

@ -1,4 +1,5 @@
#include "GLGizmoSimplify.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
@ -21,18 +22,23 @@ GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
, m_progress(0)
, m_volume(nullptr)
, m_obj_index(0)
, m_need_reload(false)
, m_need_reload(false)
, m_show_wireframe(false)
, tr_mesh_name(_u8L("Mesh name"))
, tr_triangles(_u8L("Triangles"))
, tr_preview(_u8L("Preview"))
, tr_detail_level(_u8L("Detail level"))
, tr_decimate_ratio(_u8L("Decimate ratio"))
, m_wireframe_VBO_id(0)
, m_wireframe_IBO_id(0)
{}
GLGizmoSimplify::~GLGizmoSimplify() {
m_state = State::canceling;
if (m_worker.joinable()) m_worker.join();
free_gpu();
}
bool GLGizmoSimplify::on_init()
@ -47,7 +53,8 @@ std::string GLGizmoSimplify::on_get_name() const
return _u8L("Simplify");
}
void GLGizmoSimplify::on_render() {}
void GLGizmoSimplify::on_render() { }
void GLGizmoSimplify::on_render_for_picking() {}
void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit)
@ -55,8 +62,12 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
create_gui_cfg();
int obj_index;
ModelVolume *act_volume = get_selected_volume(&obj_index);
if (act_volume == nullptr) {
close();
if (act_volume == nullptr) {
switch (m_state) {
case State::settings: close(); break;
case State::canceling: break;
default: m_state = State::canceling;
}
return;
}
@ -76,6 +87,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
m_is_valid_result = false;
m_exist_preview = false;
init_wireframe();
if (change_window_position) {
ImVec2 pos = ImGui::GetMousePos();
@ -98,7 +110,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse;
m_imgui->begin(get_name(), flag);
m_imgui->begin(on_get_name(), flag);
size_t triangle_count = m_volume->mesh().its.indices.size();
// already reduced mesh
@ -189,6 +201,11 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count);
m_imgui->disabled_end(); // use_count
if (ImGui::Checkbox(_L("Show wireframe").c_str(), &m_show_wireframe)) {
if (m_show_wireframe) init_wireframe();
else free_gpu();
}
if (m_state == State::settings) {
if (m_imgui->button(_L("Cancel"))) {
if (m_original_its.has_value()) {
@ -237,7 +254,7 @@ void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limi
// 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);
}
@ -330,10 +347,18 @@ void GLGizmoSimplify::on_set_state()
{
// Closing gizmo. e.g. selecting another one
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
bool exist_selected_object = is_selected_object();
// can appear when delete objects
bool empty_selection = m_parent.get_selection().is_empty();
// cancel processing
if (empty_selection &&
m_state != State::settings &&
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 && exist_selected_object) {
if (m_state != State::settings && !empty_selection) {
GLGizmoBase::m_state = GLGizmoBase::On;
auto notification_manager = wxGetApp().plater()->get_notification_manager();
notification_manager->push_notification(
@ -344,10 +369,13 @@ void GLGizmoSimplify::on_set_state()
}
// revert preview
if (m_exist_preview && exist_selected_object) {
set_its(*m_original_its);
m_parent.reload_scene(true);
m_need_reload = false;
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
@ -385,33 +413,110 @@ void GLGizmoSimplify::request_rerender() {
});
}
bool GLGizmoSimplify::is_selected_object(int *object_idx)
{
int index = (object_idx != nullptr) ? *object_idx :
m_parent.get_selection().get_object_idx();
// no selected object --> can appear after delete model
if (index < 0) {
switch (m_state) {
case State::settings: close(); break;
case State::canceling: break;
default: m_state = State::canceling;
}
return false;
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 true;
return false;
}
ModelVolume *GLGizmoSimplify::get_selected_volume(int *object_idx_ptr)
ModelVolume *GLGizmoSimplify::get_selected_volume(int *object_idx_ptr) const
{
const Selection &selection = m_parent.get_selection();
int object_idx = selection.get_object_idx();
if (object_idx_ptr != nullptr) *object_idx_ptr = object_idx;
if (!is_selected_object(&object_idx)) return nullptr;
if (object_idx < 0) return nullptr;
ModelObjectPtrs &objs = wxGetApp().plater()->model().objects;
if (objs.size() <= object_idx) return nullptr;
if (static_cast<int>(objs.size()) <= object_idx) return nullptr;
ModelObject *obj = objs[object_idx];
if (obj->volumes.empty()) return nullptr;
return obj->volumes.front();
return obj->volumes.front();
}
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
std::vector<Vec2i> contour_indices;
contour_indices.reserve((its.indices.size() * 3) / 2);
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
{
// is initialized?
if (m_wireframe_VBO_id == 0 || m_wireframe_IBO_id == 0) return;
if (!m_show_wireframe) return;
ModelVolume *act_volume = get_selected_volume();
if (act_volume == nullptr) return;
const Transform3d trafo_matrix =
act_volume->get_object()->instances[m_parent.get_selection().get_instance_idx()]
->get_transformation().get_matrix() *
act_volume->get_matrix();
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
auto *contour_shader = wxGetApp().get_shader("mm_contour");
contour_shader->start_using();
glsafe(::glDepthFunc(GL_LEQUAL));
glsafe(::glLineWidth(1.0f));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_wireframe_VBO_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_wireframe_IBO_id));
glsafe(::glDrawElements(GL_LINES, m_wireframe_IBO_size, GL_UNSIGNED_INT, nullptr));
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) {
glsafe(::glDeleteBuffers(1, &m_wireframe_IBO_id));
m_wireframe_IBO_id = 0;
}
}
} // namespace Slic3r::GUI

View file

@ -4,11 +4,14 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
// which overrides our localization "L" macro.
#include "GLGizmoBase.hpp"
#include "GLGizmoPainterBase.hpp" // for render wireframe
#include "admesh/stl.h" // indexed_triangle_set
#include <thread>
#include <optional>
#include <atomic>
#include <GL/glew.h> // GLUint
namespace Slic3r {
class ModelVolume;
@ -16,7 +19,7 @@ class ModelVolume;
namespace GUI {
class GLGizmoSimplify : public GLGizmoBase
class GLGizmoSimplify: public GLGizmoBase, public GLGizmoTransparentRender // GLGizmoBase
{
public:
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
@ -31,6 +34,8 @@ protected:
virtual bool on_is_selectable() const override { return false; }
virtual void on_set_state() override;
// GLGizmoPainterBase
virtual void render_painter_gizmo() const override{ render_wireframe(); }
private:
void after_apply();
void close();
@ -38,8 +43,10 @@ private:
void set_its(indexed_triangle_set &its);
void create_gui_cfg();
void request_rerender();
bool is_selected_object(int *object_idx_ptr = nullptr);
ModelVolume *get_selected_volume(int *object_idx = nullptr);
ModelVolume *get_selected_volume(int *object_idx = nullptr) const;
// 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
@ -49,6 +56,7 @@ private:
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;
@ -101,6 +109,13 @@ private:
const std::string tr_preview;
const std::string tr_detail_level;
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;
};
} // namespace GUI

View file

@ -52,8 +52,6 @@ std::vector<size_t> GLGizmosManager::get_selectable_idxs() const
return out;
}
size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const
{
if (! m_enabled)
@ -485,7 +483,7 @@ void GLGizmosManager::render_painter_gizmo() const
if (!m_enabled || m_current == Undefined)
return;
auto* gizmo = dynamic_cast<GLGizmoPainterBase*>(get_current());
auto *gizmo = dynamic_cast<GLGizmoTransparentRender*>(get_current());
assert(gizmo); // check the precondition
gizmo->render_painter_gizmo();
}
@ -1222,13 +1220,15 @@ bool GLGizmosManager::activate_gizmo(EType type)
if (! m_parent.get_gizmos_manager().is_serializing()
&& old_gizmo->wants_enter_leave_snapshots())
Plater::TakeSnapshot snapshot(wxGetApp().plater(),
Slic3r::format(_utf8("Leaving %1%"), old_gizmo->get_name(false)));
Slic3r::format(_utf8("Leaving %1%"), old_gizmo->get_name(false)),
UndoRedo::SnapshotType::LeavingGizmoWithAction);
}
if (new_gizmo && ! m_parent.get_gizmos_manager().is_serializing()
&& new_gizmo->wants_enter_leave_snapshots())
Plater::TakeSnapshot snapshot(wxGetApp().plater(),
Slic3r::format(_utf8("Entering %1%"), new_gizmo->get_name(false)));
Slic3r::format(_utf8("Entering %1%"), new_gizmo->get_name(false)),
UndoRedo::SnapshotType::EnteringGizmo);
m_current = type;

View file

@ -620,7 +620,8 @@ void MainFrame::update_title()
// m_plater->get_project_filename() produces file name including path, but excluding extension.
// Don't try to remove the extension, it would remove part of the file name after the last dot!
wxString project = from_path(into_path(m_plater->get_project_filename()).filename());
wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : "";
// wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : "";
wxString dirty_marker = m_plater->is_project_dirty() ? "*" : "";
if (!dirty_marker.empty() || !project.empty()) {
if (!dirty_marker.empty() && project.empty())
project = _L("Untitled");

View file

@ -1595,7 +1595,7 @@ struct Plater::priv
}
return res;
}
void reset_project_dirty_after_save() { dirty_state.reset_after_save(); }
void reset_project_dirty_after_save() { m_undo_redo_stack_main.mark_current_as_saved(); dirty_state.reset_after_save(); }
void reset_project_dirty_initial_presets() { dirty_state.reset_initial_presets(); }
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
@ -2064,6 +2064,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// Initialize the Undo / Redo stack with a first snapshot.
this->take_snapshot(_L("New Project"), UndoRedo::SnapshotType::ProjectSeparator);
// Reset the "dirty project" flag.
m_undo_redo_stack_main.mark_current_as_saved();
dirty_state.update_from_undo_redo_stack(false);
this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent& evt) {
BOOST_LOG_TRIVIAL(trace) << "Received load from other instance event.";
@ -4699,10 +4702,25 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
}
this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data);
const GLGizmosManager& gizmos = view3D->get_canvas3d()->get_gizmos_manager();
if (snapshot_type == UndoRedo::SnapshotType::ProjectSeparator && wxGetApp().app_config->get("clear_undo_redo_stack_on_new_project") == "1")
this->undo_redo_stack().clear();
this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), gizmos, snapshot_data);
if (snapshot_type == UndoRedo::SnapshotType::LeavingGizmoWithAction) {
// Filter all but the last UndoRedo::SnapshotType::GizmoAction in a row between the last UndoRedo::SnapshotType::EnteringGizmo and UndoRedo::SnapshotType::LeavingGizmoWithAction.
// The remaining snapshot will be renamed to a more generic name,
// depending on what gizmo is being left.
assert(gizmos.get_current() != nullptr);
std::string new_name = gizmos.get_current()->get_action_snapshot_name();
this->undo_redo_stack().reduce_noisy_snapshots(new_name);
} else if (snapshot_type == UndoRedo::SnapshotType::ProjectSeparator) {
// Reset the "dirty project" flag.
m_undo_redo_stack_main.mark_current_as_saved();
}
this->undo_redo_stack().release_least_recently_used();
dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot);
dirty_state.update_from_undo_redo_stack(m_undo_redo_stack_main.project_modified());
// Save the last active preset name of a particular printer technology.
((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
@ -4839,7 +4857,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
}
dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo);
dirty_state.update_from_undo_redo_stack(m_undo_redo_stack_main.project_modified());
}
void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */)
@ -6750,7 +6768,6 @@ bool Plater::can_mirror() const { return p->can_mirror(); }
bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); }
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); }
const UndoRedo::Stack& Plater::undo_redo_stack_active() const { return p->undo_redo_stack(); }
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); }

View file

@ -254,7 +254,6 @@ public:
// For the memory statistics.
const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const;
void clear_undo_redo_stack_main();
const Slic3r::UndoRedo::Stack& undo_redo_stack_active() const;
// Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo.
void enter_gizmos_stack();
void leave_gizmos_stack();

View file

@ -238,6 +238,14 @@ void PreferencesDialog::build(size_t selected_tab)
option = Option(def, "show_splash_screen");
m_optgroup_general->append_single_option_line(option);
// Clear Undo / Redo stack on new project
def.label = L("Clear Undo / Redo stack on new project");
def.type = coBool;
def.tooltip = L("Clear Undo / Redo stack on new project or when an existing project is loaded.");
def.set_default_value(new ConfigOptionBool{ app_config->get("clear_undo_redo_stack_on_new_project") == "1" });
option = Option(def, "clear_undo_redo_stack_on_new_project");
m_optgroup_general->append_single_option_line(option);
#if defined(_WIN32) || defined(__APPLE__)
def.label = L("Enable support for legacy 3DConnexion devices");
def.type = coBool;

View file

@ -6,7 +6,6 @@
#include "MainFrame.hpp"
#include "I18N.hpp"
#include "Plater.hpp"
#include "../Utils/UndoRedo.hpp"
#include <boost/algorithm/string/predicate.hpp>
@ -16,226 +15,38 @@
namespace Slic3r {
namespace GUI {
enum class EStackType
void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty)
{
Main,
Gizmo
};
// returns the current active snapshot (the topmost snapshot in the undo part of the stack) in the given stack
static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stack) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
const size_t active_snapshot_time = stack.active_snapshot_time();
const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time));
const int idx = it - snapshots.begin() - 1;
const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && idx < int(snapshots.size()) - 1) ?
&snapshots[idx] : nullptr;
assert(ret != nullptr);
return ret;
}
// returns the last saveable snapshot (the topmost snapshot in the undo part of the stack that can be saved) in the given stack
static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack,
const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos, size_t last_save_main) {
// returns true if the given snapshot is not saveable
auto skip_main = [&gizmos, last_save_main, &stack](const UndoRedo::Snapshot& snapshot) {
auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) {
if (boost::starts_with(snapshot.name, _utf8("Entering"))) {
if (gizmos.current)
return true;
std::string topmost_redo;
wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
if (boost::starts_with(topmost_redo, _utf8("Leaving"))) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(snapshot.timestamp + 1)));
if (gizmos.is_used_and_modified(*leaving_snapshot))
return true;
}
}
return false;
};
if (snapshot.name == _utf8("New Project"))
return true;
else if (snapshot.name == _utf8("Reset Project"))
return true;
else if (boost::starts_with(snapshot.name, _utf8("Load Project")))
return true;
else if (boost::starts_with(snapshot.name, _utf8("Selection")))
return true;
else if (boost::starts_with(snapshot.name, _utf8("Entering"))) {
if (last_save_main != snapshot.timestamp + 1 && !is_gizmo_with_modifications(snapshot))
return true;
}
else if (boost::starts_with(snapshot.name, _utf8("Leaving"))) {
if (last_save_main != snapshot.timestamp && !gizmos.is_used_and_modified(snapshot))
return true;
}
return false;
};
// returns true if the given snapshot is not saveable
auto skip_gizmo = [](const UndoRedo::Snapshot& snapshot) {
// put here any needed condition to skip the snapshot
return false;
};
const UndoRedo::Snapshot* curr = get_active_snapshot(stack);
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
size_t shift = 1;
while (curr->timestamp > 0 && ((type == EStackType::Main && skip_main(*curr)) || (type == EStackType::Gizmo && skip_gizmo(*curr)))) {
const UndoRedo::Snapshot* temp = curr;
curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift)));
shift = (curr == temp) ? shift + 1 : 1;
}
if (type == EStackType::Main) {
if (boost::starts_with(curr->name, _utf8("Entering")) && last_save_main == curr->timestamp + 1) {
std::string topmost_redo;
wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
if (boost::starts_with(topmost_redo, _utf8("Leaving")))
curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp + 1)));
}
}
return curr->timestamp > 0 ? curr : nullptr;
}
// returns the name of the gizmo contained in the given string
static std::string extract_gizmo_name(const std::string& s) {
static const std::array<std::string, 2> prefixes = { _utf8("Entering"), _utf8("Leaving") };
std::string ret;
for (const std::string& prefix : prefixes) {
if (boost::starts_with(s, prefix))
ret = s.substr(prefix.length() + 1);
if (!ret.empty())
break;
}
return ret;
}
void ProjectDirtyStateManager::DirtyState::Gizmos::add_used(const UndoRedo::Snapshot& snapshot)
{
const std::string name = extract_gizmo_name(snapshot.name);
auto it = used.find(name);
if (it == used.end())
it = used.insert({ name, { {} } }).first;
it->second.modified_timestamps.push_back(snapshot.timestamp);
}
void ProjectDirtyStateManager::DirtyState::Gizmos::remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack)
{
const std::vector<UndoRedo::Snapshot>& snapshots = main_stack.snapshots();
for (auto& item : used) {
auto it = item.second.modified_timestamps.begin();
while (it != item.second.modified_timestamps.end()) {
size_t timestamp = *it;
auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [timestamp](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == timestamp; });
if (snapshot_it == snapshots.end())
it = item.second.modified_timestamps.erase(it);
else
++it;
}
}
}
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
bool ProjectDirtyStateManager::DirtyState::Gizmos::any_used_modified() const
{
for (auto& [name, gizmo] : used) {
if (!gizmo.modified_timestamps.empty())
return true;
}
return false;
}
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
// returns true if the given snapshot is contained in any of the gizmos caches
bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const
{
for (const auto& item : used) {
for (size_t i : item.second.modified_timestamps) {
if (i == snapshot.timestamp)
return true;
}
}
return false;
}
void ProjectDirtyStateManager::DirtyState::Gizmos::reset()
{
used.clear();
}
void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type)
{
if (!wxGetApp().initialized())
return;
const Plater* plater = wxGetApp().plater();
if (plater == nullptr)
return;
const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main();
const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active();
if (&main_stack == &active_stack)
update_from_undo_redo_main_stack(type, main_stack);
else
update_from_undo_redo_gizmo_stack(type, active_stack);
wxGetApp().mainframe->update_title();
m_plater_dirty = dirty;
if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized())
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::update_from_presets()
{
m_state.presets = false;
m_presets_dirty = false;
// check switching of the presets only for exist/loaded project, but not for new
if (!wxGetApp().plater()->get_project_filename().IsEmpty()) {
for (const auto& [type, name] : wxGetApp().get_selected_presets())
m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
m_presets_dirty |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
}
m_state.presets |= wxGetApp().has_unsaved_preset_changes();
m_presets_dirty |= wxGetApp().has_unsaved_preset_changes();
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::reset_after_save()
{
const Plater* plater = wxGetApp().plater();
const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main();
const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active();
if (&main_stack == &active_stack) {
const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main);
m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0;
}
else {
// Gizmo is active with its own Undo / Redo stack (for example the SLA support point editing gizmo).
const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack);
if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) {
if (m_state.gizmos.current)
m_last_save.main = main_active_snapshot->timestamp + 1;
}
const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos, m_last_save.main);
m_last_save.gizmo = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0;
}
reset_initial_presets();
m_state.reset();
this->reset_initial_presets();
m_plater_dirty = false;
m_presets_dirty = false;
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::reset_initial_presets()
{
m_initial_presets = std::array<std::string, Preset::TYPE_COUNT>();
for (const auto& [type, name] : wxGetApp().get_selected_presets()) {
m_initial_presets.fill(std::string{});
for (const auto& [type, name] : wxGetApp().get_selected_presets())
m_initial_presets[type] = name;
}
}
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
@ -324,89 +135,10 @@ void ProjectDirtyStateManager::render_debug_window() const
}
}
if (m_state.gizmos.any_used_modified()) {
if (ImGui::CollapsingHeader("Gizmos", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(10.0f);
for (const auto& [name, gizmo] : m_state.gizmos.used) {
if (!gizmo.modified_timestamps.empty()) {
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
std::string modified_timestamps;
for (size_t i = 0; i < gizmo.modified_timestamps.size(); ++i) {
if (i > 0)
modified_timestamps += " | ";
modified_timestamps += std::to_string(gizmo.modified_timestamps[i]);
}
imgui.text(modified_timestamps);
}
}
}
ImGui::Unindent(10.0f);
}
}
imgui.end();
}
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack)
{
m_state.plater = false;
if (type == UpdateType::TakeSnapshot) {
if (m_last_save.main != 0) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [this](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == m_last_save.main; });
if (snapshot_it == snapshots.end())
m_last_save.main = 0;
}
m_state.gizmos.remove_obsolete_used(stack);
}
const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack);
if (active_snapshot->name == _utf8("New Project") ||
active_snapshot->name == _utf8("Reset Project") ||
boost::starts_with(active_snapshot->name, _utf8("Load Project")))
return;
if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) {
if (type == UpdateType::UndoRedoTo) {
std::string topmost_redo;
wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
if (boost::starts_with(topmost_redo, _utf8("Leaving"))) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot->timestamp + 1)));
if (m_state.gizmos.is_used_and_modified(*leaving_snapshot)) {
m_state.plater = (leaving_snapshot != nullptr && leaving_snapshot->timestamp != m_last_save.main);
return;
}
}
}
m_state.gizmos.current = false;
m_last_save.gizmo = 0;
}
else if (boost::starts_with(active_snapshot->name, _utf8("Leaving"))) {
if (m_state.gizmos.current)
m_state.gizmos.add_used(*active_snapshot);
m_state.gizmos.current = false;
m_last_save.gizmo = 0;
}
const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos, m_last_save.main);
m_state.plater = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.main);
}
void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack)
{
m_state.gizmos.current = false;
const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack);
if (active_snapshot->name == "Gizmos-Initial")
return;
const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos, m_last_save.main);
m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo);
}
} // namespace GUI
} // namespace Slic3r

View file

@ -4,89 +4,32 @@
#include "libslic3r/Preset.hpp"
namespace Slic3r {
namespace UndoRedo {
class Stack;
struct Snapshot;
} // namespace UndoRedo
namespace GUI {
class ProjectDirtyStateManager
{
public:
enum class UpdateType : unsigned char
{
TakeSnapshot,
UndoRedoTo
};
struct DirtyState
{
struct Gizmos
{
struct Gizmo
{
std::vector<size_t> modified_timestamps;
};
bool current{ false };
std::map<std::string, Gizmo> used;
void add_used(const UndoRedo::Snapshot& snapshot);
void remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack);
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
bool any_used_modified() const;
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
bool is_used_and_modified(const UndoRedo::Snapshot& snapshot) const;
void reset();
};
bool plater{ false };
bool presets{ false };
Gizmos gizmos;
bool is_dirty() const { return plater || presets || gizmos.current; }
void reset() {
plater = false;
presets = false;
gizmos.current = false;
}
};
private:
struct LastSaveTimestamps
{
size_t main{ 0 };
size_t gizmo{ 0 };
void reset() {
main = 0;
gizmo = 0;
}
};
DirtyState m_state;
LastSaveTimestamps m_last_save;
// keeps track of initial selected presets
std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
public:
bool is_dirty() const { return m_state.is_dirty(); }
void update_from_undo_redo_stack(UpdateType type);
public:
void update_from_undo_redo_stack(bool dirty);
void update_from_presets();
void reset_after_save();
void reset_initial_presets();
bool is_dirty() const { return m_plater_dirty || m_presets_dirty; }
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void render_debug_window() const;
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
private:
void update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack);
void update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack);
// Does the Undo / Redo stack indicate the project is dirty?
bool m_plater_dirty { false };
// Do the presets indicate the project is dirty?
bool m_presets_dirty { false };
// Keeps track of preset names selected at the time of last project save.
std::array<std::string, Preset::TYPE_COUNT> m_initial_presets;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_ProjectDirtyStateManager_hpp_