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

This commit is contained in:
Enrico Turri 2019-07-17 13:17:50 +02:00
commit ba7df2a64e
106 changed files with 4200 additions and 1928 deletions

View file

@ -148,6 +148,8 @@ set(SLIC3R_GUI_SOURCES
Utils/PresetUpdater.hpp
Utils/Time.cpp
Utils/Time.hpp
Utils/UndoRedo.cpp
Utils/UndoRedo.hpp
Utils/HexFile.cpp
Utils/HexFile.hpp
)
@ -161,7 +163,7 @@ endif ()
add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
target_link_libraries(libslic3r_gui libslic3r avrdude imgui ${GLEW_LIBRARIES})
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES})
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
endif ()

View file

@ -604,6 +604,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas)
{
if (last_object_id >= 0) {
if (m_layer_height_profile_modified) {
wxGetApp().plater()->take_snapshot(_(L("Layers heights")));
const_cast<ModelObject*>(m_model_object)->layer_height_profile = m_layer_height_profile;
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
@ -1188,6 +1189,8 @@ wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
: m_canvas(canvas)
@ -1200,6 +1203,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar
, m_camera(camera)
, m_view_toolbar(view_toolbar)
, m_toolbar(GLToolbar::Normal, "Top")
, m_gizmos(*this)
, m_use_clipping_planes(false)
, m_sidebar_field("")
, m_keep_dirty(false)
@ -1316,7 +1320,7 @@ bool GLCanvas3D::init()
// if (!m_volumes.empty())
// m_volumes.finalize_geometry();
if (m_gizmos.is_enabled() && !m_gizmos.init(*this))
if (m_gizmos.is_enabled() && !m_gizmos.init())
std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl;
if (!_init_toolbar())
@ -1717,7 +1721,7 @@ void GLCanvas3D::deselect_all()
m_selection.set_mode(Selection::Instance);
wxGetApp().obj_manipul()->set_dirty();
m_gizmos.reset_all_states();
m_gizmos.update_data(*this);
m_gizmos.update_data();
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
}
@ -1791,7 +1795,7 @@ std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx)
void GLCanvas3D::mirror_selection(Axis axis)
{
m_selection.mirror(axis);
do_mirror();
do_mirror("Mirror Object");
wxGetApp().obj_manipul()->set_dirty();
}
@ -1812,14 +1816,14 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
struct ModelVolumeState {
ModelVolumeState(const GLVolume *volume) :
model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {}
ModelVolumeState(const ModelVolume *model_volume, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) :
ModelVolumeState(const ModelVolume *model_volume, const ObjectID &instance_id, const GLVolume::CompositeID &composite_id) :
model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id) :
ModelVolumeState(const ObjectID &volume_id, const ObjectID &instance_id) :
model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
bool new_geometry() const { return this->volume_idx == size_t(-1); }
const ModelVolume *model_volume;
// ModelID of ModelVolume + ModelID of ModelInstance
// or timestamp of an SLAPrintObjectStep + ModelID of ModelInstance
// ObjectID of ModelVolume + ObjectID of ModelInstance
// or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance
std::pair<size_t, size_t> geometry_id;
GLVolume::CompositeID composite_id;
// Volume index in the new GLVolume vector.
@ -2014,9 +2018,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id);
if (it->new_geometry())
instances[istep].emplace_back(std::pair<size_t, size_t>(instance_idx, print_instance_idx));
else
// Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
else {
// Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation());
}
}
}
@ -2038,11 +2044,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
// Should the wipe tower be visualized ?
unsigned int extruders_count = (unsigned int)dynamic_cast<const ConfigOptionFloats*>(m_config->option("nozzle_diameter"))->values.size();
bool semm = dynamic_cast<const ConfigOptionBool*>(m_config->option("single_extruder_multi_material"))->value;
bool wt = dynamic_cast<const ConfigOptionBool*>(m_config->option("wipe_tower"))->value;
bool co = dynamic_cast<const ConfigOptionBool*>(m_config->option("complete_objects"))->value;
if ((extruders_count > 1) && semm && wt && !co)
if ((extruders_count > 1) && wt && !co)
{
// Height of a print (Show at least a slab)
double height = std::max(m_model->bounding_box().max(2), 10.0);
@ -2079,8 +2084,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
m_selection.volumes_changed(map_glvolume_old_to_new);
}
m_gizmos.update_data(*this);
m_gizmos.refresh_on_off_state(m_selection);
m_gizmos.update_data();
m_gizmos.refresh_on_off_state();
// Update the toolbar
if (update_object_list)
@ -2317,7 +2322,10 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
return;
}
if (m_gizmos.on_char(evt, *this))
if ((keyCode == WXK_ESCAPE) && _deactivate_undo_redo_toolbar_items())
return;
if (m_gizmos.on_char(evt))
return;
//#ifdef __APPLE__
@ -2349,6 +2357,25 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE));
break;
#ifdef __APPLE__
case 'y':
case 'Y':
#else /* __APPLE__ */
case WXK_CONTROL_Y:
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLCANVAS_REDO));
break;
#ifdef __APPLE__
case 'z':
case 'Z':
#else /* __APPLE__ */
case WXK_CONTROL_Z:
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLCANVAS_UNDO));
break;
#ifdef __APPLE__
case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead.
#else /* __APPLE__ */
@ -2369,7 +2396,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE));
break;
case WXK_ESCAPE: { deselect_all(); break; }
case '0': { select_view("iso"); break; }
case '1': { select_view("top"); break; }
@ -2426,7 +2452,7 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
}
else
{
if (!m_gizmos.on_key(evt, *this))
if (!m_gizmos.on_key(evt))
{
if (evt.GetEventType() == wxEVT_KEY_UP) {
if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) {
@ -2536,7 +2562,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
}
// Inform gizmos about the event so they have the opportunity to react.
if (m_gizmos.on_mouse_wheel(evt, *this))
if (m_gizmos.on_mouse_wheel(evt))
return;
// Calculate the zoom delta and apply it to the current zoom factor
@ -2664,7 +2690,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
return;
}
if (m_gizmos.on_mouse(evt, *this))
if (m_gizmos.on_mouse(evt))
{
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
mouse_up_cleanup();
@ -2716,12 +2742,17 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
else if (evt.Leaving())
{
_deactivate_undo_redo_toolbar_items();
// to remove hover on objects when the mouse goes out of this canvas
m_mouse.position = Vec2d(-1.0, -1.0);
m_dirty = true;
}
else if (evt.LeftDown() || evt.RightDown())
else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown())
{
if (_deactivate_undo_redo_toolbar_items())
return;
// If user pressed left or right button we first check whether this happened
// on a volume or not.
m_layers_editing.state = LayersEditing::Unknown;
@ -2786,9 +2817,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_selection.is_empty())
m_gizmos.reset_all_states();
else
m_gizmos.refresh_on_off_state(m_selection);
m_gizmos.refresh_on_off_state();
m_gizmos.update_data(*this);
m_gizmos.update_data();
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
m_dirty = true;
}
@ -2919,9 +2950,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging)
{
m_regenerate_volumes = false;
do_move();
do_move("Move Object");
wxGetApp().obj_manipul()->set_dirty();
// Let the platter know that the dragging finished, so a delayed refresh
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
}
@ -2956,9 +2987,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
{
// forces the selection of the volume
m_selection.add(volume_idx);
m_gizmos.refresh_on_off_state(m_selection);
m_gizmos.refresh_on_off_state();
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
m_gizmos.update_data(*this);
m_gizmos.update_data();
wxGetApp().obj_manipul()->set_dirty();
// forces a frame render to update the view before the context menu is shown
render();
@ -3078,11 +3109,14 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const
}
void GLCanvas3D::do_move()
void GLCanvas3D::do_move(const std::string& snapshot_type)
{
if (m_model == nullptr)
return;
if (!snapshot_type.empty())
wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
std::set<std::pair<int, int>> done; // keeps track of modified instances
bool object_moved = false;
Vec3d wipe_tower_origin = Vec3d::Zero();
@ -3133,13 +3167,18 @@ void GLCanvas3D::do_move()
if (wipe_tower_origin != Vec3d::Zero())
post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin)));
m_dirty = true;
}
void GLCanvas3D::do_rotate()
void GLCanvas3D::do_rotate(const std::string& snapshot_type)
{
if (m_model == nullptr)
return;
if (!snapshot_type.empty())
wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
std::set<std::pair<int, int>> done; // keeps track of modified instances
Selection::EMode selection_mode = m_selection.get_mode();
@ -3188,13 +3227,18 @@ void GLCanvas3D::do_rotate()
if (!done.empty())
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
m_dirty = true;
}
void GLCanvas3D::do_scale()
void GLCanvas3D::do_scale(const std::string& snapshot_type)
{
if (m_model == nullptr)
return;
if (!snapshot_type.empty())
wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
std::set<std::pair<int, int>> done; // keeps track of modified instances
Selection::EMode selection_mode = m_selection.get_mode();
@ -3240,18 +3284,27 @@ void GLCanvas3D::do_scale()
if (!done.empty())
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
m_dirty = true;
}
void GLCanvas3D::do_flatten()
void GLCanvas3D::do_flatten(const Vec3d& normal, const std::string& snapshot_type)
{
do_rotate();
if (!snapshot_type.empty())
wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
m_selection.flattening_rotate(normal);
do_rotate(""); // avoid taking another snapshot
}
void GLCanvas3D::do_mirror()
void GLCanvas3D::do_mirror(const std::string& snapshot_type)
{
if (m_model == nullptr)
return;
if (!snapshot_type.empty())
wxGetApp().plater()->take_snapshot(_(L(snapshot_type)));
std::set<std::pair<int, int>> done; // keeps track of modified instances
Selection::EMode selection_mode = m_selection.get_mode();
@ -3290,6 +3343,8 @@ void GLCanvas3D::do_mirror()
}
post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
m_dirty = true;
}
void GLCanvas3D::set_camera_zoom(double zoom)
@ -3302,8 +3357,8 @@ void GLCanvas3D::set_camera_zoom(double zoom)
void GLCanvas3D::update_gizmos_on_off_state()
{
set_as_dirty();
m_gizmos.update_data(*this);
m_gizmos.refresh_on_off_state(get_selection());
m_gizmos.update_data();
m_gizmos.refresh_on_off_state();
}
void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on)
@ -3413,6 +3468,40 @@ bool GLCanvas3D::_is_shown_on_screen() const
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
}
// Getter for the const char*[]
static bool string_getter(const bool is_undo, int idx, const char** out_text)
{
return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text);
}
void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
{
const wxString& stack_name = _(is_undo ? L("Undo") : L("Redo"));
ImGuiWrapper* imgui = wxGetApp().imgui();
const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width();
imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
imgui->set_next_window_bg_alpha(0.5f);
imgui->begin(wxString::Format(_(L("%s Stack")), stack_name),
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
int hovered = m_imgui_undo_redo_hovered_pos;
int selected = -1;
const float em = static_cast<float>(wxGetApp().em_unit());
if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected))
m_imgui_undo_redo_hovered_pos = hovered;
else
m_imgui_undo_redo_hovered_pos = -1;
if (selected >= 0)
is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected);
imgui->text(wxString::Format(_(L("%s %d Action")), stack_name, hovered + 1));
imgui->end();
}
bool GLCanvas3D::_init_toolbar()
{
if (!m_toolbar.is_enabled())
@ -3445,7 +3534,7 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "add.svg";
item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]";
item.sprite_id = 0;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); };
if (!m_toolbar.add_item(item))
return false;
@ -3453,8 +3542,8 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "remove.svg";
item.tooltip = _utf8(L("Delete")) + " [Del]";
item.sprite_id = 1;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete(); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); };
if (!m_toolbar.add_item(item))
return false;
@ -3462,8 +3551,8 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "delete_all.svg";
item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]";
item.sprite_id = 2;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); };
if (!m_toolbar.add_item(item))
return false;
@ -3471,8 +3560,8 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "arrange.svg";
item.tooltip = _utf8(L("Arrange")) + " [A]";
item.sprite_id = 3;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
if (!m_toolbar.add_item(item))
return false;
@ -3483,8 +3572,8 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "copy.svg";
item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]";
item.sprite_id = 4;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); };
if (!m_toolbar.add_item(item))
return false;
@ -3492,8 +3581,8 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "paste.svg";
item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]";
item.sprite_id = 5;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); };
if (!m_toolbar.add_item(item))
return false;
@ -3504,9 +3593,10 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "instance_add.svg";
item.tooltip = _utf8(L("Add instance")) + " [+]";
item.sprite_id = 6;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); };
item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); };
if (!m_toolbar.add_item(item))
return false;
@ -3514,9 +3604,9 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "instance_remove.svg";
item.tooltip = _utf8(L("Remove instance")) + " [-]";
item.sprite_id = 7;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); };
item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); };
if (!m_toolbar.add_item(item))
return false;
@ -3527,9 +3617,9 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "split_objects.svg";
item.tooltip = _utf8(L("Split to objects"));
item.sprite_id = 8;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); };
item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); };
if (!m_toolbar.add_item(item))
return false;
@ -3537,9 +3627,9 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "split_parts.svg";
item.tooltip = _utf8(L("Split to parts"));
item.sprite_id = 9;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); };
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); };
item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); };
if (!m_toolbar.add_item(item))
return false;
@ -3550,10 +3640,38 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "layers_white.svg";
item.tooltip = _utf8(L("Layers editing"));
item.sprite_id = 10;
item.is_toggable = true;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
item.left.toggable = true;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
if (!m_toolbar.add_item(item))
return false;
if (!m_toolbar.add_separator())
return false;
item.name = "undo";
item.icon_filename = "undo_toolbar.svg";
item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]";
item.sprite_id = 11;
item.left.toggable = false;
item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); };
item.right.toggable = true;
item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; };
item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); };
item.visibility_callback = []()->bool { return true; };
item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); };
if (!m_toolbar.add_item(item))
return false;
item.name = "redo";
item.icon_filename = "redo_toolbar.svg";
item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]";
item.sprite_id = 12;
item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); };
item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; };
item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); };
item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); };
if (!m_toolbar.add_item(item))
return false;
@ -3646,7 +3764,7 @@ void GLCanvas3D::_picking_pass() const
if (m_camera_clipping_plane.is_active())
::glDisable(GL_CLIP_PLANE0);
m_gizmos.render_current_gizmo_for_picking_pass(m_selection);
m_gizmos.render_current_gizmo_for_picking_pass();
if (m_multisample_allowed)
glsafe(::glEnable(GL_MULTISAMPLE));
@ -3956,7 +4074,7 @@ void GLCanvas3D::_render_volumes_for_picking() const
void GLCanvas3D::_render_current_gizmo() const
{
m_gizmos.render_current_gizmo(m_selection);
m_gizmos.render_current_gizmo();
}
void GLCanvas3D::_render_gizmos_overlay() const
@ -3972,7 +4090,7 @@ void GLCanvas3D::_render_gizmos_overlay() const
m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment
#endif /* __WXMSW__ */
m_gizmos.render_overlay(*this, m_selection);
m_gizmos.render_overlay();
}
void GLCanvas3D::_render_toolbar() const
@ -4617,7 +4735,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
{
const Print *print;
const std::vector<float> *tool_colors;
WipeTower::xy wipe_tower_pos;
Vec2f wipe_tower_pos;
float wipe_tower_angle;
// Number of vertices (each vertex is 6x4=24 bytes long)
@ -4648,12 +4766,13 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
ctxt.print = print;
ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming)
ctxt.priming.emplace_back(*print->wipe_tower_data().priming.get());
for (int i=0; i<print->wipe_tower_data().priming.get()->size(); ++i)
ctxt.priming.emplace_back(print->wipe_tower_data().priming.get()->at(i));
if (print->wipe_tower_data().final_purge)
ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get());
ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI;
ctxt.wipe_tower_pos = WipeTower::xy(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value);
ctxt.wipe_tower_pos = Vec2f(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value);
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start";
@ -4715,19 +4834,19 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
WipeTower::Extrusion e_prev = extrusions.extrusions[i-1];
if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation
e_prev.pos.rotate(ctxt.wipe_tower_angle);
e_prev.pos.translate(ctxt.wipe_tower_pos);
e_prev.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e_prev.pos;
e_prev.pos += ctxt.wipe_tower_pos;
}
for (; i < j; ++i) {
WipeTower::Extrusion e = extrusions.extrusions[i];
assert(e.width > 0.f);
if (!extrusions.priming) {
e.pos.rotate(ctxt.wipe_tower_angle);
e.pos.translate(ctxt.wipe_tower_pos);
e.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e.pos;
e.pos += ctxt.wipe_tower_pos;
}
lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y));
lines.emplace_back(Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Point::new_scale(e.pos.x(), e.pos.y()));
widths.emplace_back(e.width);
e_prev = e;
@ -5271,7 +5390,7 @@ void GLCanvas3D::_load_fff_shells()
double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2);
const PrintConfig& config = print->config();
unsigned int extruders_count = config.nozzle_diameter.size();
if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) {
float depth = print->get_wipe_tower_depth();
// Calculate wipe tower brim spacing.
@ -5544,13 +5663,29 @@ void GLCanvas3D::_update_selection_from_hover()
if (m_selection.is_empty())
m_gizmos.reset_all_states();
else
m_gizmos.refresh_on_off_state(m_selection);
m_gizmos.refresh_on_off_state();
m_gizmos.update_data(*this);
m_gizmos.update_data();
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
m_dirty = true;
}
bool GLCanvas3D::_deactivate_undo_redo_toolbar_items()
{
if (m_toolbar.is_item_pressed("undo"))
{
m_toolbar.force_right_action(m_toolbar.get_item_id("undo"), *this);
return true;
}
else if (m_toolbar.is_item_pressed("redo"))
{
m_toolbar.force_right_action(m_toolbar.get_item_id("redo"), *this);
return true;
}
return false;
}
const Print* GLCanvas3D::fff_print() const
{
return (m_process == nullptr) ? nullptr : m_process->fff_print();

View file

@ -127,6 +127,8 @@ wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
class GLCanvas3D
{
@ -485,6 +487,8 @@ private:
RenderStats m_render_stats;
#endif // ENABLE_RENDER_STATISTICS
int m_imgui_undo_redo_hovered_pos{ -1 };
public:
GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar);
~GLCanvas3D();
@ -513,6 +517,9 @@ public:
const Selection& get_selection() const { return m_selection; }
Selection& get_selection() { return m_selection; }
const GLGizmosManager& get_gizmos_manager() const { return m_gizmos; }
GLGizmosManager& get_gizmos_manager() { return m_gizmos; }
void bed_shape_changed();
void set_clipping_plane(unsigned int id, const ClippingPlane& plane)
@ -596,11 +603,12 @@ public:
void set_tooltip(const std::string& tooltip) const;
void do_move();
void do_rotate();
void do_scale();
void do_flatten();
void do_mirror();
// the following methods add a snapshot to the undo/redo stack, unless the given string is empty
void do_move(const std::string& snapshot_type);
void do_rotate(const std::string& snapshot_type);
void do_scale(const std::string& snapshot_type);
void do_flatten(const Vec3d& normal, const std::string& snapshot_type);
void do_mirror(const std::string& snapshot_type);
void set_camera_zoom(double zoom);
@ -673,6 +681,7 @@ private:
#endif // ENABLE_SHOW_CAMERA_TARGET
void _render_sla_slices() const;
void _render_selection_sidebar_hints() const;
void _render_undo_redo_stack(const bool is_undo, float pos_x);
void _update_volumes_hover_state() const;
@ -731,6 +740,8 @@ private:
// updates the selection from the content of m_hover_volume_idxs
void _update_selection_from_hover();
bool _deactivate_undo_redo_toolbar_items();
static std::vector<float> _parse_colors(const std::vector<std::string>& colors);
public:

View file

@ -34,18 +34,24 @@ wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent);
const GLToolbarItem::ActionCallback GLToolbarItem::Default_Action_Callback = [](){};
const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callback = []()->bool { return true; };
const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_Callback = []()->bool { return true; };
const GLToolbarItem::EnablingCallback GLToolbarItem::Default_Enabling_Callback = []()->bool { return true; };
const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](float, float, float, float){};
GLToolbarItem::Data::Option::Option()
: toggable(false)
, action_callback(Default_Action_Callback)
, render_callback(nullptr)
{
}
GLToolbarItem::Data::Data()
: name("")
, icon_filename("")
, tooltip("")
, sprite_id(-1)
, is_toggable(false)
, visible(true)
, action_callback(Default_Action_Callback)
, visibility_callback(Default_Visibility_Callback)
, enabled_state_callback(Default_Enabled_State_Callback)
, enabling_callback(Default_Enabling_Callback)
{
}
@ -53,6 +59,7 @@ GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Dat
: m_type(type)
, m_state(Normal)
, m_data(data)
, m_last_action_type(Undefined)
{
}
@ -68,7 +75,7 @@ bool GLToolbarItem::update_visibility()
bool GLToolbarItem::update_enabled_state()
{
bool enabled = m_data.enabled_state_callback();
bool enabled = m_data.enabling_callback();
bool ret = (is_enabled() != enabled);
if (ret)
m_state = enabled ? GLToolbarItem::Normal : GLToolbarItem::Disabled;
@ -79,6 +86,14 @@ bool GLToolbarItem::update_enabled_state()
void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const
{
GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size));
if (is_pressed())
{
if ((m_last_action_type == Left) && m_data.left.can_render())
m_data.left.render_callback(left, right, bottom, top);
else if ((m_last_action_type == Right) && m_data.right.can_render())
m_data.right.render_callback(left, right, bottom, top);
}
}
GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const
@ -136,6 +151,7 @@ GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name)
, m_enabled(false)
, m_icons_texture_dirty(true)
, m_tooltip("")
, m_pressed_toggable_id(-1)
{
}
@ -295,7 +311,7 @@ void GLToolbar::select_item(const std::string& name)
bool GLToolbar::is_item_pressed(const std::string& name) const
{
for (GLToolbarItem* item : m_items)
for (const GLToolbarItem* item : m_items)
{
if (item->get_name() == name)
return item->is_pressed();
@ -306,7 +322,7 @@ bool GLToolbar::is_item_pressed(const std::string& name) const
bool GLToolbar::is_item_disabled(const std::string& name) const
{
for (GLToolbarItem* item : m_items)
for (const GLToolbarItem* item : m_items)
{
if (item->get_name() == name)
return item->is_disabled();
@ -317,7 +333,7 @@ bool GLToolbar::is_item_disabled(const std::string& name) const
bool GLToolbar::is_item_visible(const std::string& name) const
{
for (GLToolbarItem* item : m_items)
for (const GLToolbarItem* item : m_items)
{
if (item->get_name() == name)
return item->is_visible();
@ -326,11 +342,46 @@ bool GLToolbar::is_item_visible(const std::string& name) const
return false;
}
bool GLToolbar::is_any_item_pressed() const
{
for (const GLToolbarItem* item : m_items)
{
if (item->is_pressed())
return true;
}
return false;
}
unsigned int GLToolbar::get_item_id(const std::string& name) const
{
for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
{
if (m_items[i]->get_name() == name)
return i;
}
return -1;
}
void GLToolbar::force_left_action(unsigned int item_id, GLCanvas3D& parent)
{
do_action(GLToolbarItem::Left, item_id, parent, false);
}
void GLToolbar::force_right_action(unsigned int item_id, GLCanvas3D& parent)
{
do_action(GLToolbarItem::Right, item_id, parent, false);
}
bool GLToolbar::update_items_state()
{
bool ret = false;
ret |= update_items_visibility();
ret |= update_items_enabled_state();
if (!is_any_item_pressed())
m_pressed_toggable_id = -1;
return ret;
}
@ -392,10 +443,11 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
m_mouse_capture.left = true;
m_mouse_capture.parent = &parent;
processed = true;
if ((item_id != -2) && !m_items[item_id]->is_separator())
if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left)))
{
// mouse is inside an icon
do_action((unsigned int)item_id, parent);
do_action(GLToolbarItem::Left, (unsigned int)item_id, parent, true);
parent.set_as_dirty();
}
}
else if (evt.MiddleDown())
@ -407,6 +459,13 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
{
m_mouse_capture.right = true;
m_mouse_capture.parent = &parent;
processed = true;
if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right)))
{
// mouse is inside an icon
do_action(GLToolbarItem::Right, (unsigned int)item_id, parent, true);
parent.set_as_dirty();
}
}
else if (evt.LeftUp())
processed = true;
@ -477,38 +536,61 @@ float GLToolbar::get_main_size() const
return size * m_layout.scale;
}
void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent)
void GLToolbar::do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover)
{
if (item_id < (unsigned int)m_items.size())
if ((m_pressed_toggable_id == -1) || (m_pressed_toggable_id == item_id))
{
GLToolbarItem* item = m_items[item_id];
if ((item != nullptr) && !item->is_separator() && item->is_hovered())
if (item_id < (unsigned int)m_items.size())
{
if (item->is_toggable())
GLToolbarItem* item = m_items[item_id];
if ((item != nullptr) && !item->is_separator() && (!check_hover || item->is_hovered()))
{
GLToolbarItem::EState state = item->get_state();
if (state == GLToolbarItem::Hover)
item->set_state(GLToolbarItem::HoverPressed);
else if (state == GLToolbarItem::HoverPressed)
item->set_state(GLToolbarItem::Hover);
parent.render();
item->do_action();
}
else
{
if (m_type == Radio)
select_item(item->get_name());
else
item->set_state(GLToolbarItem::HoverPressed);
parent.render();
item->do_action();
if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled))
if (((type == GLToolbarItem::Right) && item->is_right_toggable()) ||
((type == GLToolbarItem::Left) && item->is_left_toggable()))
{
// the item may get disabled during the action, if not, set it back to hover state
item->set_state(GLToolbarItem::Hover);
GLToolbarItem::EState state = item->get_state();
if (state == GLToolbarItem::Hover)
item->set_state(GLToolbarItem::HoverPressed);
else if (state == GLToolbarItem::HoverPressed)
item->set_state(GLToolbarItem::Hover);
else if (state == GLToolbarItem::Pressed)
item->set_state(GLToolbarItem::Normal);
else if (state == GLToolbarItem::Normal)
item->set_state(GLToolbarItem::Pressed);
m_pressed_toggable_id = item->is_pressed() ? item_id : -1;
item->reset_last_action_type();
parent.render();
switch (type)
{
default:
case GLToolbarItem::Left: { item->do_left_action(); break; }
case GLToolbarItem::Right: { item->do_right_action(); break; }
}
}
else
{
if (m_type == Radio)
select_item(item->get_name());
else
item->set_state(item->is_hovered() ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed);
item->reset_last_action_type();
parent.render();
switch (type)
{
default:
case GLToolbarItem::Left: { item->do_left_action(); break; }
case GLToolbarItem::Right: { item->do_right_action(); break; }
}
if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled))
{
// the item may get disabled during the action, if not, set it back to hover state
item->set_state(GLToolbarItem::Hover);
parent.render();
}
}
}
}
@ -1212,9 +1294,15 @@ bool GLToolbar::update_items_enabled_state()
{
bool ret = false;
for (GLToolbarItem* item : m_items)
for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
{
GLToolbarItem* item = m_items[i];
ret |= item->update_enabled_state();
if (item->is_enabled() && (m_pressed_toggable_id != -1) && (m_pressed_toggable_id != i))
{
ret = true;
item->set_state(GLToolbarItem::Disabled);
}
}
if (ret)

View file

@ -36,7 +36,8 @@ class GLToolbarItem
public:
typedef std::function<void()> ActionCallback;
typedef std::function<bool()> VisibilityCallback;
typedef std::function<bool()> EnabledStateCallback;
typedef std::function<bool()> EnablingCallback;
typedef std::function<void(float, float, float, float)> RenderCallback;
enum EType : unsigned char
{
@ -45,6 +46,14 @@ public:
Num_Types
};
enum EActionType : unsigned char
{
Undefined,
Left,
Right,
Num_Action_Types
};
enum EState : unsigned char
{
Normal,
@ -57,27 +66,42 @@ public:
struct Data
{
struct Option
{
bool toggable;
ActionCallback action_callback;
RenderCallback render_callback;
Option();
bool can_render() const { return toggable && (render_callback != nullptr); }
};
std::string name;
std::string icon_filename;
std::string tooltip;
unsigned int sprite_id;
bool is_toggable;
// mouse left click
Option left;
// mouse right click
Option right;
bool visible;
ActionCallback action_callback;
VisibilityCallback visibility_callback;
EnabledStateCallback enabled_state_callback;
EnablingCallback enabling_callback;
Data();
};
static const ActionCallback Default_Action_Callback;
static const VisibilityCallback Default_Visibility_Callback;
static const EnabledStateCallback Default_Enabled_State_Callback;
static const EnablingCallback Default_Enabling_Callback;
static const RenderCallback Default_Render_Callback;
private:
EType m_type;
EState m_state;
Data m_data;
EActionType m_last_action_type;
public:
GLToolbarItem(EType type, const Data& data);
@ -89,17 +113,25 @@ public:
const std::string& get_icon_filename() const { return m_data.icon_filename; }
const std::string& get_tooltip() const { return m_data.tooltip; }
void do_action() { m_data.action_callback(); }
void do_left_action() { m_last_action_type = Left; m_data.left.action_callback(); }
void do_right_action() { m_last_action_type = Right; m_data.right.action_callback(); }
bool is_enabled() const { return m_state != Disabled; }
bool is_disabled() const { return m_state == Disabled; }
bool is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed); }
bool is_pressed() const { return (m_state == Pressed) || (m_state == HoverPressed); }
bool is_toggable() const { return m_data.is_toggable; }
bool is_visible() const { return m_data.visible; }
bool is_separator() const { return m_type == Separator; }
bool is_left_toggable() const { return m_data.left.toggable; }
bool is_right_toggable() const { return m_data.right.toggable; }
bool has_left_render_callback() const { return m_data.left.render_callback != nullptr; }
bool has_right_render_callback() const { return m_data.right.render_callback != nullptr; }
EActionType get_last_action_type() const { return m_last_action_type; }
void reset_last_action_type() { m_last_action_type = Undefined; }
// returns true if the state changes
bool update_visibility();
// returns true if the state changes
@ -212,6 +244,7 @@ private:
MouseCapture m_mouse_capture;
std::string m_tooltip;
unsigned int m_pressed_toggable_id;
public:
GLToolbar(EType type, const std::string& name);
@ -246,6 +279,13 @@ public:
bool is_item_disabled(const std::string& name) const;
bool is_item_visible(const std::string& name) const;
bool is_any_item_pressed() const;
unsigned int get_item_id(const std::string& name) const;
void force_left_action(unsigned int item_id, GLCanvas3D& parent);
void force_right_action(unsigned int item_id, GLCanvas3D& parent);
const std::string& get_tooltip() const { return m_tooltip; }
// returns true if any item changed its state
@ -262,7 +302,7 @@ private:
float get_height_horizontal() const;
float get_height_vertical() const;
float get_main_size() const;
void do_action(unsigned int item_id, GLCanvas3D& parent);
void do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover);
std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent);
std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent);
std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent);

View file

@ -25,7 +25,7 @@ namespace GUI
wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent);
// pt_FFF
FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF =
SettingsBundle FREQ_SETTINGS_BUNDLE_FFF =
{
{ L("Layers and Perimeters"), { "layer_height" , "perimeters", "top_solid_layers", "bottom_solid_layers" } },
{ L("Infill") , { "fill_density", "fill_pattern" } },
@ -36,7 +36,7 @@ FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF =
};
// pt_SLA
FreqSettingsBundle FREQ_SETTINGS_BUNDLE_SLA =
SettingsBundle FREQ_SETTINGS_BUNDLE_SLA =
{
{ L("Pad and Support") , { "supports_enable", "pad_enable" } }
};
@ -66,6 +66,14 @@ static int extruders_count()
return wxGetApp().extruders_cnt();
}
static void take_snapshot(const wxString& snapshot_name)
{
wxGetApp().plater()->take_snapshot(snapshot_name);
}
static void suppress_snapshots(){ wxGetApp().plater()->suppress_snapshots(); }
static void allow_snapshots() { wxGetApp().plater()->allow_snapshots(); }
ObjectList::ObjectList(wxWindow* parent) :
wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE),
m_parent(parent)
@ -138,20 +146,24 @@ ObjectList::ObjectList(wxWindow* parent) :
// Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this);
{
// Accelerators
wxAcceleratorEntry entries[6];
wxAcceleratorEntry entries[8];
entries[0].Set(wxACCEL_CTRL, (int) 'C', wxID_COPY);
entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_CUT);
entries[2].Set(wxACCEL_CTRL, (int) 'V', wxID_PASTE);
entries[3].Set(wxACCEL_CTRL, (int) 'A', wxID_SELECTALL);
entries[4].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE);
entries[5].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE);
wxAcceleratorTable accel(6, entries);
entries[4].Set(wxACCEL_CTRL, (int) 'Z', wxID_UNDO);
entries[5].Set(wxACCEL_CTRL, (int) 'Y', wxID_REDO);
entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE);
entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE);
wxAcceleratorTable accel(8, entries);
SetAcceleratorTable(accel);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->paste(); }, wxID_PASTE);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo(); }, wxID_UNDO);
this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO);
}
#else __WXOSX__
Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX
@ -655,7 +667,6 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol
if (volumes.empty())
return;
ModelObject& model_object = *(*m_objects)[obj_idx];
const auto object_item = m_objects_model->GetItemById(obj_idx);
wxDataViewItemArray items;
@ -665,10 +676,7 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol
const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, wxString::FromUTF8(volume->name.c_str()), volume->type(),
volume->get_mesh_errors_count()>0 ,
volume->config.has("extruder") ? volume->config.option<ConfigOptionInt>("extruder")->value : 0);
auto opt_keys = volume->config.keys();
if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder")))
select_item(m_objects_model->AddSettingsChild(vol_item));
add_settings_item(vol_item, &volume->config);
items.Add(vol_item);
}
@ -805,6 +813,16 @@ void ObjectList::paste()
wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE));
}
void ObjectList::undo()
{
wxGetApp().plater()->undo();
}
void ObjectList::redo()
{
wxGetApp().plater()->redo();
}
#ifndef __WXOSX__
void ObjectList::key_event(wxKeyEvent& event)
{
@ -823,6 +841,10 @@ void ObjectList::key_event(wxKeyEvent& event)
copy();
else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL))
paste();
else if (wxGetKeyState(wxKeyCode('Y')) && wxGetKeyState(WXK_CONTROL))
redo();
else if (wxGetKeyState(wxKeyCode('Z')) && wxGetKeyState(WXK_CONTROL))
undo();
else
event.Skip();
}
@ -908,6 +930,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event)
if (m_dragged_data.type() == itInstance)
{
take_snapshot(_(L("Instances to Separated Objects")));
instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs());
m_dragged_data.clear();
return;
@ -925,6 +948,8 @@ void ObjectList::OnDrop(wxDataViewEvent &event)
// if (to_volume_id > from_volume_id) to_volume_id--;
// #endif // __WXGTK__
take_snapshot(_(L("Remov Volume(s)")));
auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes;
auto delta = to_volume_id < from_volume_id ? -1 : 1;
int cnt = 0;
@ -963,7 +988,7 @@ std::vector<std::string> ObjectList::get_options(const bool is_part)
const std::vector<std::string>& ObjectList::get_options_for_bundle(const wxString& bundle_name)
{
const FreqSettingsBundle& bundle = printer_technology() == ptSLA ?
const SettingsBundle& bundle = printer_technology() == ptSLA ?
FREQ_SETTINGS_BUNDLE_SLA : FREQ_SETTINGS_BUNDLE_FFF;
for (auto& it : bundle)
@ -973,7 +998,7 @@ const std::vector<std::string>& ObjectList::get_options_for_bundle(const wxStrin
}
#if 0
// if "Quick menu" is selected
FreqSettingsBundle& bundle_quick = printer_technology() == ptSLA ?
SettingsBundle& bundle_quick = printer_technology() == ptSLA ?
m_freq_settings_sla: m_freq_settings_fff;
for (auto& it : bundle_quick)
@ -1049,7 +1074,7 @@ void ObjectList::get_settings_choice(const wxString& category_name)
if (selection_cnt > 0)
{
// Add selected items to the "Quick menu"
FreqSettingsBundle& freq_settings = printer_technology() == ptSLA ?
SettingsBundle& freq_settings = printer_technology() == ptSLA ?
m_freq_settings_sla : m_freq_settings_fff;
bool changed_existing = false;
@ -1090,6 +1115,8 @@ void ObjectList::get_settings_choice(const wxString& category_name)
}
#endif
take_snapshot(wxString::Format(_(L("Add Settings for %s")), is_part ? _(L("Sub-object")) : _(L("Object"))));
std::vector <std::string> selected_options;
selected_options.reserve(selection_cnt);
for (auto sel : selections)
@ -1119,8 +1146,8 @@ void ObjectList::get_settings_choice(const wxString& category_name)
}
// Add settings item for object
update_settings_item();
// Add settings item for object/sub-object and show them
show_settings(add_settings_item(GetSelection(), m_config));
}
void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
@ -1140,6 +1167,8 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
assert(m_config);
auto opt_keys = m_config->keys();
take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(GetSelection()) & itObject ? _(L("Object")) : _(L("Sub-object"))));
const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
for (auto& opt_key : options)
{
@ -1154,13 +1183,21 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name)
}
}
// Add settings item for object
update_settings_item();
// Add settings item for object/sub-object and show them
show_settings(add_settings_item(GetSelection(), m_config));
}
void ObjectList::update_settings_item()
void ObjectList::show_settings(const wxDataViewItem settings_item)
{
auto item = GetSelection();
if (!settings_item)
return;
select_item(settings_item);
// update object selection on Plater
if (!m_prevent_canvas_selection_update)
update_selections_on_canvas();
/* auto item = GetSelection();
if (item) {
if (m_objects_model->GetItemType(item) == itInstance)
item = m_objects_model->GetTopParent(item);
@ -1172,12 +1209,14 @@ void ObjectList::update_settings_item()
if (!m_prevent_canvas_selection_update)
update_selections_on_canvas();
}
else {
else {
//# ys_FIXME ??? use case ???
auto panel = wxGetApp().sidebar().scrolled_panel();
panel->Freeze();
wxGetApp().obj_settings()->UpdateAndShow(true);
panel->Thaw();
}
*/
}
wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) {
@ -1488,7 +1527,7 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
void ObjectList::create_freq_settings_popupmenu(wxMenu *menu)
{
// Add default settings bundles
const FreqSettingsBundle& bundle = printer_technology() == ptFFF ?
const SettingsBundle& bundle = printer_technology() == ptFFF ?
FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA;
const int extruders_cnt = extruders_count();
@ -1503,7 +1542,7 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu)
}
#if 0
// Add "Quick" settings bundles
const FreqSettingsBundle& bundle_quick = printer_technology() == ptFFF ?
const SettingsBundle& bundle_quick = printer_technology() == ptFFF ?
m_freq_settings_fff : m_freq_settings_sla;
for (auto& it : bundle_quick) {
@ -1539,6 +1578,8 @@ void ObjectList::load_subobject(ModelVolumeType type)
if (m_objects_model->GetItemType(item)&itInstance)
item = m_objects_model->GetItemById(obj_idx);
take_snapshot(_(L("Load Part")));
std::vector<std::pair<wxString, bool>> volumes_info;
load_part((*m_objects)[obj_idx], volumes_info, type);
@ -1614,6 +1655,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
if (instance_idx == -1)
return;
take_snapshot(_(L("Add Generic Subobject")));
// Selected object
ModelObject &model_object = *(*m_objects)[obj_idx];
// Bounding box of the selected instance in world coordinate system including the translation, without modifiers.
@ -1720,6 +1763,8 @@ void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item)
is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height"))
return;
take_snapshot(_(L("Delete Settings")));
int extruder = -1;
if (m_config->has("extruder"))
extruder = m_config->option<ConfigOptionInt>("extruder")->value;
@ -1742,6 +1787,8 @@ void ObjectList::del_instances_from_object(const int obj_idx)
if (instances.size() <= 1)
return;
take_snapshot(_(L("Delete All Instances from Object")));
while ( instances.size()> 1)
instances.pop_back();
@ -1755,6 +1802,8 @@ void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_r
const auto del_range = object(obj_idx)->layer_config_ranges.find(layer_range);
if (del_range == object(obj_idx)->layer_config_ranges.end())
return;
take_snapshot(_(L("Delete Layers Range")));
object(obj_idx)->layer_config_ranges.erase(del_range);
@ -1789,6 +1838,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
return false;
}
take_snapshot(_(L("Delete Subobject")));
object->delete_volume(idx);
if (object->volumes.size() == 1)
@ -1805,6 +1856,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object.")));
return false;
}
take_snapshot(_(L("Delete Instance")));
object->delete_instance(idx);
}
else
@ -1832,6 +1885,8 @@ void ObjectList::split()
return;
}
take_snapshot(_(L("Split to Parts")));
wxBusyCursor wait;
auto model_object = (*m_objects)[obj_idx];
@ -1850,11 +1905,7 @@ void ObjectList::split()
volume->config.option<ConfigOptionInt>("extruder")->value : 0,
false);
// add settings to the part, if it has those
auto opt_keys = volume->config.keys();
if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) {
select_item(m_objects_model->AddSettingsChild(vol_item));
Expand(vol_item);
}
add_settings_item(vol_item, &volume->config);
}
if (parent == item)
@ -1879,8 +1930,10 @@ void ObjectList::layers_editing()
t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
// set some default value
if (ranges.empty())
if (ranges.empty()) {
take_snapshot(_(L("Add Layers")));
ranges[{ 0.0f, 2.0f }] = get_default_layer_config(obj_idx);
}
// create layer root item
layers_item = add_layer_root_item(obj_item);
@ -1912,6 +1965,7 @@ wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item)
for (const auto range : object(obj_idx)->layer_config_ranges)
add_layer_item(range.first, layers_item);
Expand(layers_item);
return layers_item;
}
@ -2097,7 +2151,79 @@ void ObjectList::part_selection_changed()
panel.Thaw();
}
void ObjectList::add_object_to_list(size_t obj_idx)
SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings)
{
auto opt_keys = config->keys();
if (opt_keys.empty())
return SettingsBundle();
update_opt_keys(opt_keys); // update options list according to print technology
if (opt_keys.size() == 1 && opt_keys[0] == "extruder" ||
is_layers_range_settings && opt_keys.size() == 2)
return SettingsBundle();
const int extruders_cnt = wxGetApp().extruders_edited_cnt();
SettingsBundle bundle;
for (auto& opt_key : opt_keys)
{
auto category = config->def()->get(opt_key)->category;
if (category.empty() || (category == "Extruders" && extruders_cnt == 1))
continue;
std::vector< std::string > new_category;
auto& cat_opt = bundle.find(category) == bundle.end() ? new_category : bundle.at(category);
cat_opt.push_back(opt_key);
if (cat_opt.size() == 1)
bundle[category] = cat_opt;
}
return bundle;
}
// Add new SettingsItem for parent_item if it doesn't exist, or just update a digest according to new config
wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config)
{
wxDataViewItem ret = wxDataViewItem(0);
if (!parent_item)
return ret;
const bool is_layers_range_settings = m_objects_model->GetItemType(parent_item) == itLayer;
SettingsBundle cat_options = get_item_settings_bundle(config, is_layers_range_settings);
if (cat_options.empty())
return ret;
std::vector<std::string> categories;
categories.reserve(cat_options.size());
for (auto& cat : cat_options)
{
if (cat.second.size() == 1 &&
(cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height"))
continue;
categories.push_back(cat.first);
}
if (categories.empty())
return ret;
if (m_objects_model->GetItemType(parent_item) & itInstance)
parent_item = m_objects_model->GetTopParent(parent_item);
ret = m_objects_model->IsSettingsItem(parent_item) ? parent_item : m_objects_model->GetSettingsItem(parent_item);
if (!ret) ret = m_objects_model->AddSettingsChild(parent_item);
m_objects_model->UpdateSettingsDigest(ret, categories);
Expand(parent_item);
return ret;
}
void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
{
auto model_object = (*m_objects)[obj_idx];
const wxString& item_name = from_u8(model_object->name);
@ -2116,11 +2242,7 @@ void ObjectList::add_object_to_list(size_t obj_idx)
!volume->config.has("extruder") ? 0 :
volume->config.option<ConfigOptionInt>("extruder")->value,
false);
auto opt_keys = volume->config.keys();
if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) {
select_item(m_objects_model->AddSettingsChild(vol_item));
Expand(vol_item);
}
add_settings_item(vol_item, &volume->config);
}
Expand(item);
}
@ -2130,17 +2252,14 @@ void ObjectList::add_object_to_list(size_t obj_idx)
increase_object_instances(obj_idx, model_object->instances.size());
// add settings to the object, if it has those
auto opt_keys = model_object->config.keys();
if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) {
select_item(m_objects_model->AddSettingsChild(item));
Expand(item);
}
add_settings_item(item, &model_object->config);
// Add layers if it has
add_layer_root_item(item);
#ifndef __WXOSX__
selection_changed();
if (call_selection_changed)
selection_changed();
#endif //__WXMSW__
}
@ -2175,6 +2294,8 @@ void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i
if ( !(type&(itObject|itVolume|itInstance)) )
return;
take_snapshot(_(L("Delete Selected Item")));
if (type&itObject) {
del_object(obj_idx);
delete_object_from_list(obj_idx);
@ -2285,6 +2406,9 @@ void ObjectList::remove()
wxDataViewItem parent = wxDataViewItem(0);
take_snapshot(_(L("Delete Selected")));
suppress_snapshots();
for (auto& item : sels)
{
if (m_objects_model->GetParent(item) == wxDataViewItem(0))
@ -2305,6 +2429,8 @@ void ObjectList::remove()
if (parent)
select_item(parent);
allow_snapshots();
}
void ObjectList::del_layer_range(const t_layer_height_range& range)
@ -2350,6 +2476,8 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
if (current_range == last_range)
{
take_snapshot(_(L("Add New Layers Range")));
const t_layer_height_range& new_range = { last_range.second, last_range.second + 2.0f };
ranges[new_range] = get_default_layer_config(obj_idx);
add_layer_item(new_range, layers_item);
@ -2377,22 +2505,28 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
t_layer_height_range new_range = { midl_layer, next_range.second };
take_snapshot(_(L("Add New Layers Range")));
suppress_snapshots();
// create new 2 layers instead of deleted one
// delete old layer
wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range);
del_subobject_item(layer_item);
// create new 2 layers instead of deleted one
ranges[new_range] = old_config;
add_layer_item(new_range, layers_item, layer_idx);
new_range = { current_range.second, midl_layer };
ranges[new_range] = get_default_layer_config(obj_idx);
add_layer_item(new_range, layers_item, layer_idx);
allow_snapshots();
}
else
{
take_snapshot(_(L("Add New Layers Range")));
const t_layer_height_range new_range = { current_range.second, next_range.first };
ranges[new_range] = get_default_layer_config(obj_idx);
add_layer_item(new_range, layers_item, layer_idx);
@ -2420,9 +2554,7 @@ void ObjectList::add_layer_item(const t_layer_height_range& range,
range,
config.opt_int("extruder"),
layer_idx);
if (config.keys().size() > 2)
select_item(m_objects_model->AddSettingsChild(layer_item));
add_settings_item(layer_item, &config);
}
bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height)
@ -2452,6 +2584,8 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay
const int obj_idx = get_selected_obj_idx();
if (obj_idx < 0) return false;
take_snapshot(_(L("Edit Layers Range")));
const ItemType sel_type = m_objects_model->GetItemType(GetSelection());
t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges;
@ -2918,6 +3052,8 @@ void ObjectList::change_part_type()
if (new_type == type || new_type == ModelVolumeType::INVALID)
return;
take_snapshot(_(L("Paste from Clipboard")));
const auto item = GetSelection();
volume->set_type(new_type);
m_objects_model->SetVolumeType(item, new_type);
@ -2933,18 +3069,17 @@ void ObjectList::change_part_type()
}
else if (!settings_item &&
(new_type == ModelVolumeType::MODEL_PART || new_type == ModelVolumeType::PARAMETER_MODIFIER)) {
select_item(m_objects_model->AddSettingsChild(item));
add_settings_item(item, &volume->config);
}
}
void ObjectList::last_volume_is_deleted(const int obj_idx)
{
if (obj_idx < 0 || m_objects->empty() ||
obj_idx <= m_objects->size() ||
(*m_objects)[obj_idx]->volumes.empty())
if (obj_idx < 0 || obj_idx >= m_objects->size() || (*m_objects)[obj_idx]->volumes.empty())
return;
auto volume = (*m_objects)[obj_idx]->volumes[0];
auto volume = (*m_objects)[obj_idx]->volumes.front();
// clear volume's config values
volume->config.clear();
@ -2966,6 +3101,7 @@ bool ObjectList::has_multi_part_objects()
return false;
}
/* #lm_FIXME_delete_after_testing
void ObjectList::update_settings_items()
{
m_prevent_canvas_selection_update = true;
@ -2991,22 +3127,52 @@ void ObjectList::update_settings_items()
SetSelections(sel);
m_prevent_canvas_selection_update = false;
}
*/
void ObjectList::update_and_show_object_settings_item()
{
const wxDataViewItem item = GetSelection();
if (!item) return;
const wxDataViewItem& obj_item = m_objects_model->IsSettingsItem(item) ? m_objects_model->GetParent(item) : item;
select_item(add_settings_item(obj_item, &get_item_config(obj_item)));
}
// Update settings item for item had it
void ObjectList::update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections)
{
const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item);
select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item));
const wxDataViewItem old_settings_item = m_objects_model->GetSettingsItem(item);
const wxDataViewItem new_settings_item = add_settings_item(item, &get_item_config(item));
// If settings item was deleted from the list,
// it's need to be deleted from selection array, if it was there
if (settings_item != m_objects_model->GetSettingsItem(item) &&
selections.Index(settings_item) != wxNOT_FOUND) {
selections.Remove(settings_item);
if (!new_settings_item && old_settings_item)
m_objects_model->Delete(old_settings_item);
// Select item, if settings_item doesn't exist for item anymore, but was selected
if (selections.Index(item) == wxNOT_FOUND)
selections.Add(item);
// if ols settings item was is selected area
if (selections.Index(old_settings_item) != wxNOT_FOUND)
{
// If settings item was just updated
if (old_settings_item == new_settings_item)
{
Sidebar& panel = wxGetApp().sidebar();
panel.Freeze();
// update settings list
wxGetApp().obj_settings()->UpdateAndShow(true);
panel.Layout();
panel.Thaw();
}
else
// If settings item was deleted from the list,
// it's need to be deleted from selection array, if it was there
{
selections.Remove(old_settings_item);
// Select item, if settings_item doesn't exist for item anymore, but was selected
if (selections.Index(item) == wxNOT_FOUND) {
selections.Add(item);
select_item(item); // to correct update of the SettingsList and ManipulationPanel sizers
}
}
}
}
@ -3360,6 +3526,35 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const
wxGetApp().plater()->update();
}
void ObjectList::update_after_undo_redo()
{
m_prevent_list_events = true;
m_prevent_canvas_selection_update = true;
suppress_snapshots();
// Unselect all objects before deleting them, so that no change of selection is emitted during deletion.
this->UnselectAll();
m_objects_model->DeleteAll();
size_t obj_idx = 0;
while (obj_idx < m_objects->size()) {
add_object_to_list(obj_idx, false);
++obj_idx;
}
allow_snapshots();
#ifndef __WXOSX__
selection_changed();
#endif /* __WXOSX__ */
update_selections();
m_prevent_canvas_selection_update = false;
m_prevent_list_events = false;
}
ModelObject* ObjectList::object(const int obj_idx) const
{
if (obj_idx < 0)

View file

@ -26,7 +26,7 @@ enum class ModelVolumeType : int;
// FIXME: broken build on mac os because of this is missing:
typedef std::vector<std::string> t_config_option_keys;
typedef std::map<std::string, std::vector<std::string>> FreqSettingsBundle;
typedef std::map<std::string, std::vector<std::string>> SettingsBundle;
// category -> vector ( option ; label )
typedef std::map< std::string, std::vector< std::pair<std::string, std::string> > > settings_menu_hierarchy;
@ -152,8 +152,8 @@ class ObjectList : public wxDataViewCtrl
wxDataViewItem m_last_selected_item {nullptr};
#if 0
FreqSettingsBundle m_freq_settings_fff;
FreqSettingsBundle m_freq_settings_sla;
SettingsBundle m_freq_settings_fff;
SettingsBundle m_freq_settings_sla;
#endif
public:
@ -204,10 +204,12 @@ public:
void copy();
void paste();
void undo();
void redo();
void get_settings_choice(const wxString& category_name);
void get_freq_settings_choice(const wxString& bundle_name);
void update_settings_item();
void show_settings(const wxDataViewItem settings_item);
wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type);
void append_menu_items_add_volume(wxMenu* menu);
@ -245,6 +247,7 @@ public:
void layers_editing();
wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item);
wxDataViewItem add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config);
DynamicPrintConfig get_default_layer_config(const int obj_idx);
bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume);
@ -256,12 +259,13 @@ public:
wxBoxSizer* get_sizer() {return m_sizer;}
int get_selected_obj_idx() const;
DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const;
SettingsBundle get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings);
void changed_object(const int obj_idx = -1) const;
void part_selection_changed();
// Add object to the list
void add_object_to_list(size_t obj_idx);
void add_object_to_list(size_t obj_idx, bool call_selection_changed = true);
// Delete object from the list
void delete_object_from_list();
void delete_object_from_list(const size_t obj_idx);
@ -315,6 +319,7 @@ public:
void last_volume_is_deleted(const int obj_idx);
bool has_multi_part_objects();
void update_settings_items();
void update_and_show_object_settings_item();
void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
void update_object_list_by_printer_technology();
void update_object_menu();
@ -333,6 +338,8 @@ public:
void msw_rescale();
void update_after_undo_redo();
private:
#ifdef __WXOSX__
// void OnChar(wxKeyEvent& event);

View file

@ -242,8 +242,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_mirror();
canvas->set_as_dirty();
canvas->do_mirror("Set Mirror");
UpdateAndShow(true);
});
return sizer;
@ -324,7 +323,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_volumes();
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_rotate();
canvas->do_rotate("Set Rotation");
UpdateAndShow(true);
});
@ -710,7 +709,7 @@ void ObjectManipulation::change_position_value(int axis, double value)
Selection& selection = canvas->get_selection();
selection.start_dragging();
selection.translate(position - m_cache.position, selection.requires_local_axes());
canvas->do_move();
canvas->do_move("Set Position");
m_cache.position = position;
m_cache.position_rounded(axis) = DBL_MAX;
@ -741,7 +740,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
selection.rotate(
(M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation),
transformation_type);
canvas->do_rotate();
canvas->do_rotate("Set Orientation");
m_cache.rotation = rotation;
m_cache.rotation_rounded(axis) = DBL_MAX;
@ -806,7 +805,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
selection.start_dragging();
selection.scale(scaling_factor * 0.01, transformation_type);
wxGetApp().plater()->canvas3D()->do_scale();
wxGetApp().plater()->canvas3D()->do_scale("Set Scale");
}
void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value)

View file

@ -63,20 +63,28 @@ ObjectSettings::ObjectSettings(wxWindow* parent) :
m_bmp_delete = ScalableBitmap(parent, "cross");
}
void ObjectSettings::update_settings_list()
bool ObjectSettings::update_settings_list()
{
m_settings_list_sizer->Clear(true);
m_og_settings.resize(0);
auto objects_ctrl = wxGetApp().obj_list();
auto objects_model = wxGetApp().obj_list()->GetModel();
auto config = wxGetApp().obj_list()->config();
const auto item = objects_ctrl->GetSelection();
const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer;
if (!item || !objects_model->IsSettingsItem(item) || !config || objects_ctrl->multiple_selection())
return false;
const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer;
SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(config, is_layers_range_settings);
if (!cat_options.empty())
{
std::vector<std::string> categories;
categories.reserve(cat_options.size());
if (item && !objects_ctrl->multiple_selection() &&
config && objects_model->IsSettingsItem(item))
{
auto extra_column = [config, this](wxWindow* parent, const Line& line)
{
auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line
@ -96,86 +104,61 @@ void ObjectSettings::update_settings_list()
return btn;
};
std::map<std::string, std::vector<std::string>> cat_options;
auto opt_keys = config->keys();
objects_ctrl->update_opt_keys(opt_keys); // update options list according to print technology
m_og_settings.resize(0);
std::vector<std::string> categories;
if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return;
for (auto& cat : cat_options)
{
const int extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 :
wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
if (cat.second.size() == 1 &&
(cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height"))
continue;
for (auto& opt_key : opt_keys) {
auto category = config->def()->get(opt_key)->category;
if (category.empty() ||
(category == "Extruders" && extruders_cnt == 1)) continue;
categories.push_back(cat.first);
std::vector< std::string > new_category;
auto optgroup = std::make_shared<ConfigOptionsGroup>(m_og->ctrl_parent(), _(cat.first), config, false, extra_column);
optgroup->label_width = 15;
optgroup->sidetext_width = 5.5;
auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category);
cat_opt.push_back(opt_key);
if (cat_opt.size() == 1)
cat_options[category] = cat_opt;
}
optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) {
wxGetApp().obj_list()->changed_object(); };
for (auto& cat : cat_options) {
if (cat.second.size() == 1 &&
(cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height"))
// call back for rescaling of the extracolumn control
optgroup->rescale_extra_column_item = [this](wxWindow* win) {
auto *ctrl = dynamic_cast<ScalableButton*>(win);
if (ctrl == nullptr)
return;
ctrl->SetBitmap_(m_bmp_delete);
};
const bool is_extruders_cat = cat.first == "Extruders";
for (auto& opt : cat.second)
{
if (opt == "extruder" || is_layers_range_settings && opt == "layer_height")
continue;
auto optgroup = std::make_shared<ConfigOptionsGroup>(m_og->ctrl_parent(), _(cat.first), config, false, extra_column);
optgroup->label_width = 15;
optgroup->sidetext_width = 5.5;
optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) {
wxGetApp().obj_list()->changed_object(); };
const bool is_extruders_cat = cat.first == "Extruders";
for (auto& opt : cat.second)
{
if (opt == "extruder" || is_layers_range_settings && opt == "layer_height")
continue;
Option option = optgroup->get_option(opt);
option.opt.width = 12;
if (is_extruders_cat)
option.opt.max = wxGetApp().extruders_cnt();
optgroup->append_single_option_line(option);
}
optgroup->reload_config();
m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0);
// call back for rescaling of the extracolumn control
optgroup->rescale_extra_column_item = [this](wxWindow* win) {
auto *ctrl = dynamic_cast<ScalableButton*>(win);
if (ctrl == nullptr)
return;
ctrl->SetBitmap_(m_bmp_delete);
};
m_og_settings.push_back(optgroup);
categories.push_back(cat.first);
Option option = optgroup->get_option(opt);
option.opt.width = 12;
if (is_extruders_cat)
option.opt.max = wxGetApp().extruders_edited_cnt();
optgroup->append_single_option_line(option);
}
optgroup->reload_config();
m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0);
m_og_settings.push_back(optgroup);
}
if (m_og_settings.empty()) {
objects_ctrl->select_item(objects_model->Delete(item));
}
else {
if (!categories.empty())
objects_model->UpdateSettingsDigest(item, categories);
}
}
if (!categories.empty())
objects_model->UpdateSettingsDigest(item, categories);
}
else
{
objects_ctrl->select_item(objects_model->Delete(item));
return false;
}
return true;
}
void ObjectSettings::UpdateAndShow(const bool show)
{
if (show)
update_settings_list();
OG_Settings::UpdateAndShow(show);
OG_Settings::UpdateAndShow(show ? update_settings_list() : false);
}
void ObjectSettings::msw_rescale()

View file

@ -44,7 +44,7 @@ public:
ObjectSettings(wxWindow* parent);
~ObjectSettings() {}
void update_settings_list();
bool update_settings_list();
void UpdateAndShow(const bool show) override;
void msw_rescale();
};

View file

@ -132,6 +132,7 @@ void GLGizmoBase::Grabber::render_face(float half_size) const
glsafe(::glEnd());
}
GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: m_parent(parent)
, m_group_id(-1)
@ -179,7 +180,7 @@ void GLGizmoBase::disable_grabber(unsigned int id)
on_disable_grabber(id);
}
void GLGizmoBase::start_dragging(const Selection& selection)
void GLGizmoBase::start_dragging()
{
m_dragging = true;
@ -188,7 +189,7 @@ void GLGizmoBase::start_dragging(const Selection& selection)
m_grabbers[i].dragging = (m_hover_id == i);
}
on_start_dragging(selection);
on_start_dragging();
}
void GLGizmoBase::stop_dragging()
@ -203,10 +204,10 @@ void GLGizmoBase::stop_dragging()
on_stop_dragging();
}
void GLGizmoBase::update(const UpdateData& data, const Selection& selection)
void GLGizmoBase::update(const UpdateData& data)
{
if (m_hover_id != -1)
on_update(data, selection);
on_update(data);
}
std::array<float, 3> GLGizmoBase::picking_color_component(unsigned int id) const

View file

@ -6,6 +6,7 @@
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/Selection.hpp"
#include <cereal/archives/binary.hpp>
class wxWindow;
class GLUquadric;
@ -75,10 +76,10 @@ public:
struct UpdateData
{
const Linef3 mouse_ray;
const Point* mouse_pos;
const Linef3& mouse_ray;
const Point& mouse_pos;
UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr)
UpdateData(const Linef3& mouse_ray, const Point& mouse_pos)
: mouse_ray(mouse_ray), mouse_pos(mouse_pos)
{}
};
@ -105,6 +106,9 @@ public:
bool init() { return on_init(); }
void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); }
void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); }
std::string get_name() const { return on_get_name(); }
int get_group_id() const { return m_group_id; }
@ -118,7 +122,7 @@ public:
const std::string& get_icon_filename() const { return m_icon_filename; }
bool is_activable(const Selection& selection) const { return on_is_activable(selection); }
bool is_activable() const { return on_is_activable(); }
bool is_selectable() const { return on_is_selectable(); }
unsigned int get_sprite_id() const { return m_sprite_id; }
@ -131,32 +135,34 @@ public:
void enable_grabber(unsigned int id);
void disable_grabber(unsigned int id);
void start_dragging(const Selection& selection);
void start_dragging();
void stop_dragging();
bool is_dragging() const { return m_dragging; }
void update(const UpdateData& data, const Selection& selection);
void update(const UpdateData& data);
void render(const Selection& selection) const { on_render(selection); }
void render_for_picking(const Selection& selection) const { on_render_for_picking(selection); }
void render_input_window(float x, float y, float bottom_limit, const Selection& selection) { on_render_input_window(x, y, bottom_limit, selection); }
void render() const { on_render(); }
void render_for_picking() const { on_render_for_picking(); }
void render_input_window(float x, float y, float bottom_limit) { on_render_input_window(x, y, bottom_limit); }
protected:
virtual bool on_init() = 0;
virtual void on_load(cereal::BinaryInputArchive& ar) {}
virtual void on_save(cereal::BinaryOutputArchive& ar) const {}
virtual std::string on_get_name() const = 0;
virtual void on_set_state() {}
virtual void on_set_hover_id() {}
virtual bool on_is_activable(const Selection& selection) const { return true; }
virtual bool on_is_activable() const { return true; }
virtual bool on_is_selectable() const { return true; }
virtual void on_enable_grabber(unsigned int id) {}
virtual void on_disable_grabber(unsigned int id) {}
virtual void on_start_dragging(const Selection& selection) {}
virtual void on_start_dragging() {}
virtual void on_stop_dragging() {}
virtual void on_update(const UpdateData& data, const Selection& selection) = 0;
virtual void on_render(const Selection& selection) const = 0;
virtual void on_render_for_picking(const Selection& selection) const = 0;
virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) {}
virtual void on_update(const UpdateData& data) {}
virtual void on_render() const = 0;
virtual void on_render_for_picking() const = 0;
virtual void on_render_input_window(float x, float y, float bottom_limit) {}
// Returns the picking color for the given id, based on the BASE_ID constant
// No check is made for clashing with other picking color (i.e. GLVolumes)

View file

@ -28,7 +28,6 @@ GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, uns
, m_rotate_lower(false)
{}
bool GLGizmoCut::on_init()
{
m_grabbers.emplace_back();
@ -49,15 +48,18 @@ void GLGizmoCut::on_set_state()
}
}
bool GLGizmoCut::on_is_activable(const Selection& selection) const
bool GLGizmoCut::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
return selection.is_single_full_instance() && !selection.is_wipe_tower();
}
void GLGizmoCut::on_start_dragging(const Selection& selection)
void GLGizmoCut::on_start_dragging()
{
if (m_hover_id == -1) { return; }
if (m_hover_id == -1)
return;
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
m_start_z = m_cut_z;
update_max_z(selection);
@ -66,19 +68,21 @@ void GLGizmoCut::on_start_dragging(const Selection& selection)
m_drag_center(2) = m_cut_z;
}
void GLGizmoCut::on_update(const UpdateData& data, const Selection& selection)
void GLGizmoCut::on_update(const UpdateData& data)
{
if (m_hover_id != -1) {
set_cut_z(m_start_z + calc_projection(data.mouse_ray));
}
}
void GLGizmoCut::on_render(const Selection& selection) const
void GLGizmoCut::on_render() const
{
if (m_grabbers[0].dragging) {
set_tooltip("Z: " + format(m_cut_z, 2));
}
const Selection& selection = m_parent.get_selection();
update_max_z(selection);
const BoundingBoxf3& box = selection.get_bounding_box();
@ -124,14 +128,13 @@ void GLGizmoCut::on_render(const Selection& selection) const
m_grabbers[0].render(m_hover_id == 0, (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0));
}
void GLGizmoCut::on_render_for_picking(const Selection& selection) const
void GLGizmoCut::on_render_for_picking() const
{
glsafe(::glDisable(GL_DEPTH_TEST));
render_grabbers_for_picking(selection.get_bounding_box());
render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
}
void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection)
void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
{
const float approx_height = m_imgui->scaled(11.0f);
y = std::min(y, bottom_limit - approx_height);
@ -154,7 +157,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co
m_imgui->end();
if (cut_clicked && (m_keep_upper || m_keep_lower)) {
perform_cut(selection);
perform_cut(m_parent.get_selection());
}
}

View file

@ -27,14 +27,16 @@ public:
protected:
virtual bool on_init();
virtual void on_load(cereal::BinaryInputArchive& ar) { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
virtual void on_save(cereal::BinaryOutputArchive& ar) const { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
virtual std::string on_get_name() const;
virtual void on_set_state();
virtual bool on_is_activable(const Selection& selection) const;
virtual void on_start_dragging(const Selection& selection);
virtual void on_update(const UpdateData& data, const Selection& selection);
virtual void on_render(const Selection& selection) const;
virtual void on_render_for_picking(const Selection& selection) const;
virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection);
virtual bool on_is_activable() const;
virtual void on_start_dragging();
virtual void on_update(const UpdateData& data);
virtual void on_render() const;
virtual void on_render_for_picking() const;
virtual void on_render_input_window(float x, float y, float bottom_limit);
private:
void update_max_z(const Selection& selection) const;

View file

@ -1,5 +1,6 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoFlatten.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include <numeric>
@ -27,23 +28,25 @@ std::string GLGizmoFlatten::on_get_name() const
return (_(L("Place on face")) + " [F]").ToUTF8().data();
}
bool GLGizmoFlatten::on_is_activable(const Selection& selection) const
bool GLGizmoFlatten::on_is_activable() const
{
return selection.is_single_full_instance();
return m_parent.get_selection().is_single_full_instance();
}
void GLGizmoFlatten::on_start_dragging(const Selection& selection)
void GLGizmoFlatten::on_start_dragging()
{
if (m_hover_id != -1)
{
assert(m_planes_valid);
m_normal = m_planes[m_hover_id].normal;
m_starting_center = selection.get_bounding_box().center();
m_starting_center = m_parent.get_selection().get_bounding_box().center();
}
}
void GLGizmoFlatten::on_render(const Selection& selection) const
void GLGizmoFlatten::on_render() const
{
const Selection& selection = m_parent.get_selection();
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
@ -78,8 +81,10 @@ void GLGizmoFlatten::on_render(const Selection& selection) const
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoFlatten::on_render_for_picking(const Selection& selection) const
void GLGizmoFlatten::on_render_for_picking() const
{
const Selection& selection = m_parent.get_selection();
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glDisable(GL_BLEND));

View file

@ -45,11 +45,10 @@ public:
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
virtual bool on_is_activable(const Selection& selection) const;
virtual void on_start_dragging(const Selection& selection);
virtual void on_update(const UpdateData& data, const Selection& selection) {}
virtual void on_render(const Selection& selection) const;
virtual void on_render_for_picking(const Selection& selection) const;
virtual bool on_is_activable() const;
virtual void on_start_dragging();
virtual void on_render() const;
virtual void on_render_for_picking() const;
virtual void on_set_state()
{
if (m_state == On && is_plane_update_necessary())

View file

@ -1,5 +1,6 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoMove.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include <GL/glew.h>
@ -47,12 +48,12 @@ std::string GLGizmoMove3D::on_get_name() const
return (_(L("Move")) + " [M]").ToUTF8().data();
}
void GLGizmoMove3D::on_start_dragging(const Selection& selection)
void GLGizmoMove3D::on_start_dragging()
{
if (m_hover_id != -1)
{
m_displacement = Vec3d::Zero();
const BoundingBoxf3& box = selection.get_bounding_box();
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_starting_box_center = box.center();
m_starting_box_bottom_center = box.center();
@ -65,7 +66,7 @@ void GLGizmoMove3D::on_stop_dragging()
m_displacement = Vec3d::Zero();
}
void GLGizmoMove3D::on_update(const UpdateData& data, const Selection& selection)
void GLGizmoMove3D::on_update(const UpdateData& data)
{
if (m_hover_id == 0)
m_displacement(0) = calc_projection(data);
@ -75,8 +76,10 @@ void GLGizmoMove3D::on_update(const UpdateData& data, const Selection& selection
m_displacement(2) = calc_projection(data);
}
void GLGizmoMove3D::on_render(const Selection& selection) const
void GLGizmoMove3D::on_render() const
{
const Selection& selection = m_parent.get_selection();
bool show_position = selection.is_single_full_instance();
const Vec3d& position = selection.get_bounding_box().center();
@ -152,20 +155,21 @@ void GLGizmoMove3D::on_render(const Selection& selection) const
}
}
void GLGizmoMove3D::on_render_for_picking(const Selection& selection) const
void GLGizmoMove3D::on_render_for_picking() const
{
glsafe(::glDisable(GL_DEPTH_TEST));
const BoundingBoxf3& box = selection.get_bounding_box();
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
render_grabbers_for_picking(box);
render_grabber_extension(X, box, true);
render_grabber_extension(Y, box, true);
render_grabber_extension(Z, box, true);
}
void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection)
{
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit)
{
const Selection& selection = m_parent.get_selection();
bool show_position = selection.is_single_full_instance();
const Vec3d& position = selection.get_bounding_box().center();
@ -178,8 +182,8 @@ void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit,
m_imgui->input_vec3("", displacement, 100.0f, "%.2f");
m_imgui->end();
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
}
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
double GLGizmoMove3D::calc_projection(const UpdateData& data) const
{

View file

@ -33,12 +33,14 @@ public:
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
virtual void on_start_dragging(const Selection& selection);
virtual void on_start_dragging();
virtual void on_stop_dragging();
virtual void on_update(const UpdateData& data, const Selection& selection);
virtual void on_render(const Selection& selection) const;
virtual void on_render_for_picking(const Selection& selection) const;
virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection);
virtual void on_update(const UpdateData& data);
virtual void on_render() const;
virtual void on_render_for_picking() const;
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
virtual void on_render_input_window(float x, float y, float bottom_limit);
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
private:
double calc_projection(const UpdateData& data) const;

View file

@ -1,5 +1,6 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoRotate.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include <GL/glew.h>
@ -72,9 +73,9 @@ bool GLGizmoRotate::on_init()
return true;
}
void GLGizmoRotate::on_start_dragging(const Selection& selection)
void GLGizmoRotate::on_start_dragging()
{
const BoundingBoxf3& box = selection.get_bounding_box();
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_center = box.center();
m_radius = Offset + box.radius();
m_snap_coarse_in_radius = m_radius / 3.0f;
@ -83,9 +84,9 @@ void GLGizmoRotate::on_start_dragging(const Selection& selection)
m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
}
void GLGizmoRotate::on_update(const UpdateData& data, const Selection& selection)
void GLGizmoRotate::on_update(const UpdateData& data)
{
Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, selection));
Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection()));
Vec2d orig_dir = Vec2d::UnitX();
Vec2d new_dir = mouse_pos.normalized();
@ -118,11 +119,12 @@ void GLGizmoRotate::on_update(const UpdateData& data, const Selection& selection
m_angle = theta;
}
void GLGizmoRotate::on_render(const Selection& selection) const
void GLGizmoRotate::on_render() const
{
if (!m_grabbers[0].enabled)
return;
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
std::string axis;
@ -175,8 +177,10 @@ void GLGizmoRotate::on_render(const Selection& selection) const
glsafe(::glPopMatrix());
}
void GLGizmoRotate::on_render_for_picking(const Selection& selection) const
void GLGizmoRotate::on_render_for_picking() const
{
const Selection& selection = m_parent.get_selection();
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
@ -445,10 +449,10 @@ std::string GLGizmoRotate3D::on_get_name() const
return (_(L("Rotate")) + " [R]").ToUTF8().data();
}
void GLGizmoRotate3D::on_start_dragging(const Selection& selection)
void GLGizmoRotate3D::on_start_dragging()
{
if ((0 <= m_hover_id) && (m_hover_id < 3))
m_gizmos[m_hover_id].start_dragging(selection);
m_gizmos[m_hover_id].start_dragging();
}
void GLGizmoRotate3D::on_stop_dragging()
@ -457,23 +461,23 @@ void GLGizmoRotate3D::on_stop_dragging()
m_gizmos[m_hover_id].stop_dragging();
}
void GLGizmoRotate3D::on_render(const Selection& selection) const
void GLGizmoRotate3D::on_render() const
{
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
if ((m_hover_id == -1) || (m_hover_id == 0))
m_gizmos[X].render(selection);
m_gizmos[X].render();
if ((m_hover_id == -1) || (m_hover_id == 1))
m_gizmos[Y].render(selection);
m_gizmos[Y].render();
if ((m_hover_id == -1) || (m_hover_id == 2))
m_gizmos[Z].render(selection);
m_gizmos[Z].render();
}
void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection)
{
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit)
{
Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle()));
wxString label = _(L("Rotation (deg)"));
@ -482,8 +486,8 @@ void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limi
m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
m_imgui->input_vec3("", rotation, 100.0f, "%.2f");
m_imgui->end();
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
}
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI

View file

@ -52,10 +52,10 @@ public:
protected:
virtual bool on_init();
virtual std::string on_get_name() const { return ""; }
virtual void on_start_dragging(const Selection& selection);
virtual void on_update(const UpdateData& data, const Selection& selection);
virtual void on_render(const Selection& selection) const;
virtual void on_render_for_picking(const Selection& selection) const;
virtual void on_start_dragging();
virtual void on_update(const UpdateData& data);
virtual void on_render() const;
virtual void on_render_for_picking() const;
private:
void render_circle() const;
@ -98,7 +98,6 @@ protected:
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
}
}
virtual bool on_is_activable(const Selection& selection) const { return true; }
virtual void on_enable_grabber(unsigned int id)
{
if ((0 <= id) && (id < 3))
@ -109,25 +108,26 @@ protected:
if ((0 <= id) && (id < 3))
m_gizmos[id].disable_grabber(0);
}
virtual void on_start_dragging(const Selection& selection);
virtual void on_start_dragging();
virtual void on_stop_dragging();
virtual void on_update(const UpdateData& data, const Selection& selection)
virtual void on_update(const UpdateData& data)
{
for (GLGizmoRotate& g : m_gizmos)
{
g.update(data, selection);
g.update(data);
}
}
virtual void on_render(const Selection& selection) const;
virtual void on_render_for_picking(const Selection& selection) const
virtual void on_render() const;
virtual void on_render_for_picking() const
{
for (const GLGizmoRotate& g : m_gizmos)
{
g.render_for_picking(selection);
g.render_for_picking();
}
}
virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection);
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
virtual void on_render_input_window(float x, float y, float bottom_limit);
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
};

View file

@ -1,7 +1,6 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoScale.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include <GL/glew.h>
@ -48,13 +47,18 @@ std::string GLGizmoScale3D::on_get_name() const
return (_(L("Scale")) + " [S]").ToUTF8().data();
}
void GLGizmoScale3D::on_start_dragging(const Selection& selection)
bool GLGizmoScale3D::on_is_activable() const
{
return !m_parent.get_selection().is_wipe_tower();
}
void GLGizmoScale3D::on_start_dragging()
{
if (m_hover_id != -1)
{
m_starting.drag_position = m_grabbers[m_hover_id].center;
m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL);
m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : selection.get_bounding_box();
m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box();
const Vec3d& center = m_starting.box.center();
m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max(0), center(1), center(2));
@ -66,7 +70,7 @@ void GLGizmoScale3D::on_start_dragging(const Selection& selection)
}
}
void GLGizmoScale3D::on_update(const UpdateData& data, const Selection& selection)
void GLGizmoScale3D::on_update(const UpdateData& data)
{
if ((m_hover_id == 0) || (m_hover_id == 1))
do_scale_along_axis(X, data);
@ -78,8 +82,10 @@ void GLGizmoScale3D::on_update(const UpdateData& data, const Selection& selectio
do_scale_uniform(data);
}
void GLGizmoScale3D::on_render(const Selection& selection) const
void GLGizmoScale3D::on_render() const
{
const Selection& selection = m_parent.get_selection();
bool single_instance = selection.is_single_full_instance();
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
bool single_selection = single_instance || single_volume;
@ -272,16 +278,16 @@ void GLGizmoScale3D::on_render(const Selection& selection) const
}
}
void GLGizmoScale3D::on_render_for_picking(const Selection& selection) const
void GLGizmoScale3D::on_render_for_picking() const
{
glsafe(::glDisable(GL_DEPTH_TEST));
render_grabbers_for_picking(selection.get_bounding_box());
render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
}
void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection)
{
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit)
{
const Selection& selection = m_parent.get_selection();
bool single_instance = selection.is_single_full_instance();
wxString label = _(L("Scale (%)"));
@ -290,8 +296,8 @@ void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit
m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f");
m_imgui->end();
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
}
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const
{

View file

@ -45,12 +45,14 @@ public:
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
virtual bool on_is_activable(const Selection& selection) const { return !selection.is_wipe_tower(); }
virtual void on_start_dragging(const Selection& selection);
virtual void on_update(const UpdateData& data, const Selection& selection);
virtual void on_render(const Selection& selection) const;
virtual void on_render_for_picking(const Selection& selection) const;
virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection);
virtual bool on_is_activable() const;
virtual void on_start_dragging();
virtual void on_update(const UpdateData& data);
virtual void on_render() const;
virtual void on_render_for_picking() const;
#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
virtual void on_render_input_window(float x, float y, float bottom_limit);
#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI
private:
void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const;

View file

@ -94,8 +94,10 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S
}
}
void GLGizmoSlaSupports::on_render(const Selection& selection) const
void GLGizmoSlaSupports::on_render() const
{
const Selection& selection = m_parent.get_selection();
// If current m_model_object does not match selection, ask GLCanvas3D to turn us off
if (m_state == On
&& (m_model_object != selection.get_model()->objects[selection.get_object_idx()]
@ -252,8 +254,9 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
}
void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const
void GLGizmoSlaSupports::on_render_for_picking() const
{
const Selection& selection = m_parent.get_selection();
#if ENABLE_RENDER_PICKING_PASS
m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
#endif
@ -379,7 +382,7 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
{
return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty())
&& ((m_model_object->id() != m_current_mesh_model_id) || m_its == nullptr);
&& ((m_model_object->id() != m_current_mesh_object_id) || m_its == nullptr);
}
void GLGizmoSlaSupports::update_mesh()
@ -389,7 +392,7 @@ void GLGizmoSlaSupports::update_mesh()
// This mesh does not account for the possible Z up SLA offset.
m_mesh = &m_model_object->volumes.front()->mesh();
m_its = &m_mesh->its;
m_current_mesh_model_id = m_model_object->id();
m_current_mesh_object_id = m_model_object->id();
m_editing_mode = false;
m_AABB.deinit();
@ -702,12 +705,12 @@ void GLGizmoSlaSupports::delete_selected_points(bool force)
//m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
void GLGizmoSlaSupports::on_update(const UpdateData& data, const Selection& selection)
void GLGizmoSlaSupports::on_update(const UpdateData& data)
{
if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) {
if (m_editing_mode && m_hover_id != -1 && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) {
std::pair<Vec3f, Vec3f> pos_and_normal;
try {
pos_and_normal = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1)));
pos_and_normal = unproject_on_mesh(data.mouse_pos.cast<double>());
}
catch (...) { return; }
m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first;
@ -822,7 +825,7 @@ void GLGizmoSlaSupports::make_line_segments() const
*/
void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection)
void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit)
{
if (!m_model_object)
return;
@ -923,10 +926,12 @@ RENDER_AGAIN:
}
if (value_changed) { // Update side panel
wxTheApp->CallAfter([]() {
wxGetApp().obj_settings()->UpdateAndShow(true);
wxGetApp().obj_list()->update_settings_items();
});
/* wxTheApp->CallAfter([]() {
* wxGetApp().obj_settings()->UpdateAndShow(true);
* wxGetApp().obj_list()->update_settings_items();
* });
* #lm_FIXME_delete_after_testing */
wxGetApp().obj_list()->update_and_show_object_settings_item();
}
bool generate = m_imgui->button(m_desc.at("auto_generate"));
@ -1000,11 +1005,13 @@ RENDER_AGAIN:
m_parent.set_as_dirty();
}
bool GLGizmoSlaSupports::on_is_activable(const Selection& selection) const
bool GLGizmoSlaSupports::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA
|| !selection.is_from_single_instance())
return false;
return false;
// Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
const Selection::IndicesList& list = selection.get_volume_idxs();
@ -1073,7 +1080,7 @@ void GLGizmoSlaSupports::on_set_state()
void GLGizmoSlaSupports::on_start_dragging(const Selection& selection)
void GLGizmoSlaSupports::on_start_dragging()
{
if (m_hover_id != -1) {
select_point(NoPoints);

View file

@ -26,7 +26,7 @@ class GLGizmoSlaSupports : public GLGizmoBase
{
private:
ModelObject* m_model_object = nullptr;
ModelID m_current_mesh_model_id = 0;
ObjectID m_current_mesh_object_id = 0;
int m_active_instance = -1;
float m_active_instance_bb_radius; // to cache the bb
mutable float m_z_shift = 0.f;
@ -70,9 +70,9 @@ public:
private:
bool on_init();
void on_update(const UpdateData& data, const Selection& selection);
virtual void on_render(const Selection& selection) const;
virtual void on_render_for_picking(const Selection& selection) const;
void on_update(const UpdateData& data);
virtual void on_render() const;
virtual void on_render_for_picking() const;
//void render_selection_rectangle() const;
void render_points(const Selection& selection, bool picking = false) const;
@ -133,11 +133,11 @@ protected:
if ((int)m_editing_mode_cache.size() <= m_hover_id)
m_hover_id = -1;
}
void on_start_dragging(const Selection& selection) override;
virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override;
void on_start_dragging() override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
virtual std::string on_get_name() const;
virtual bool on_is_activable(const Selection& selection) const;
virtual bool on_is_activable() const;
virtual bool on_is_selectable() const;
};

View file

@ -14,8 +14,9 @@ namespace GUI {
const float GLGizmosManager::Default_Icons_Size = 64;
GLGizmosManager::GLGizmosManager()
: m_enabled(false)
GLGizmosManager::GLGizmosManager(GLCanvas3D& parent)
: m_parent(parent)
, m_enabled(false)
, m_icons_texture_dirty(true)
, m_current(Undefined)
, m_overlay_icons_size(Default_Icons_Size)
@ -23,6 +24,7 @@ GLGizmosManager::GLGizmosManager()
, m_overlay_border(5.0f)
, m_overlay_gap_y(5.0f)
, m_tooltip("")
, m_serializing(false)
{
}
@ -31,7 +33,7 @@ GLGizmosManager::~GLGizmosManager()
reset();
}
bool GLGizmosManager::init(GLCanvas3D& parent)
bool GLGizmosManager::init()
{
m_background_texture.metadata.filename = "toolbar_background.png";
m_background_texture.metadata.left = 16;
@ -48,7 +50,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent)
}
}
GLGizmoBase* gizmo = new GLGizmoMove3D(parent, "move.svg", 0);
GLGizmoBase* gizmo = new GLGizmoMove3D(m_parent, "move.svg", 0);
if (gizmo == nullptr)
return false;
@ -57,7 +59,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent)
m_gizmos.insert(GizmosMap::value_type(Move, gizmo));
gizmo = new GLGizmoScale3D(parent, "scale.svg", 1);
gizmo = new GLGizmoScale3D(m_parent, "scale.svg", 1);
if (gizmo == nullptr)
return false;
@ -66,7 +68,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent)
m_gizmos.insert(GizmosMap::value_type(Scale, gizmo));
gizmo = new GLGizmoRotate3D(parent, "rotate.svg", 2);
gizmo = new GLGizmoRotate3D(m_parent, "rotate.svg", 2);
if (gizmo == nullptr)
{
reset();
@ -81,7 +83,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent)
m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo));
gizmo = new GLGizmoFlatten(parent, "place.svg", 3);
gizmo = new GLGizmoFlatten(m_parent, "place.svg", 3);
if (gizmo == nullptr)
return false;
@ -92,7 +94,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent)
m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo));
gizmo = new GLGizmoCut(parent, "cut.svg", 4);
gizmo = new GLGizmoCut(m_parent, "cut.svg", 4);
if (gizmo == nullptr)
return false;
@ -103,7 +105,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent)
m_gizmos.insert(GizmosMap::value_type(Cut, gizmo));
gizmo = new GLGizmoSlaSupports(parent, "sla_supports.svg", 5);
gizmo = new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 5);
if (gizmo == nullptr)
return false;
@ -135,12 +137,15 @@ void GLGizmosManager::set_overlay_scale(float scale)
}
}
void GLGizmosManager::refresh_on_off_state(const Selection& selection)
void GLGizmosManager::refresh_on_off_state()
{
if (m_serializing)
return;
GizmosMap::iterator it = m_gizmos.find(m_current);
if ((it != m_gizmos.end()) && (it->second != nullptr))
{
if (!it->second->is_activable(selection))
if (!it->second->is_activable())
{
it->second->set_state(GLGizmoBase::Off);
m_current = Undefined;
@ -153,6 +158,9 @@ void GLGizmosManager::reset_all_states()
if (!m_enabled)
return;
if (m_serializing)
return;
for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it)
{
if (it->second != nullptr)
@ -192,22 +200,22 @@ void GLGizmosManager::enable_grabber(EType type, unsigned int id, bool enable)
}
}
void GLGizmosManager::update(const Linef3& mouse_ray, const Selection& selection, const Point* mouse_pos)
void GLGizmosManager::update(const Linef3& mouse_ray, const Point& mouse_pos)
{
if (!m_enabled)
return;
GLGizmoBase* curr = get_current();
if (curr != nullptr)
curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos), selection);
curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos));
}
void GLGizmosManager::update_data(GLCanvas3D& canvas)
void GLGizmosManager::update_data()
{
if (!m_enabled)
return;
const Selection& selection = canvas.get_selection();
const Selection& selection = m_parent.get_selection();
bool is_wipe_tower = selection.is_wipe_tower();
enable_grabber(Move, 2, !is_wipe_tower);
@ -228,7 +236,7 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas)
set_rotation(Vec3d::Zero());
ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()];
set_flattening_data(model_object);
set_sla_support_data(model_object, selection);
set_sla_support_data(model_object);
}
else if (selection.is_single_volume() || selection.is_single_modifier())
{
@ -236,7 +244,7 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas)
set_scale(volume->get_volume_scaling_factor());
set_rotation(Vec3d::Zero());
set_flattening_data(nullptr);
set_sla_support_data(nullptr, selection);
set_sla_support_data(nullptr);
}
else if (is_wipe_tower)
{
@ -244,14 +252,14 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas)
set_scale(Vec3d::Ones());
set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value));
set_flattening_data(nullptr);
set_sla_support_data(nullptr, selection);
set_sla_support_data(nullptr);
}
else
{
set_scale(Vec3d::Ones());
set_rotation(Vec3d::Zero());
set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
set_sla_support_data(nullptr, selection);
set_sla_support_data(nullptr);
}
}
@ -264,9 +272,12 @@ bool GLGizmosManager::is_running() const
return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false;
}
bool GLGizmosManager::handle_shortcut(int key, const Selection& selection)
bool GLGizmosManager::handle_shortcut(int key)
{
if (!m_enabled || selection.is_empty())
if (!m_enabled)
return false;
if (m_parent.get_selection().is_empty())
return false;
EType old_current = m_current;
@ -278,7 +289,7 @@ bool GLGizmosManager::handle_shortcut(int key, const Selection& selection)
int it_key = it->second->get_shortcut_key();
if (it->second->is_activable(selection) && ((it_key == key - 64) || (it_key == key - 96)))
if (it->second->is_activable() && ((it_key == key - 64) || (it_key == key - 96)))
{
if ((it->second->get_state() == GLGizmoBase::On))
{
@ -314,14 +325,14 @@ bool GLGizmosManager::is_dragging() const
return (curr != nullptr) ? curr->is_dragging() : false;
}
void GLGizmosManager::start_dragging(const Selection& selection)
void GLGizmosManager::start_dragging()
{
if (!m_enabled)
return;
GLGizmoBase* curr = get_current();
if (curr != nullptr)
curr->start_dragging(selection);
curr->start_dragging();
}
void GLGizmosManager::stop_dragging()
@ -409,14 +420,14 @@ void GLGizmosManager::set_flattening_data(const ModelObject* model_object)
reinterpret_cast<GLGizmoFlatten*>(it->second)->set_flattening_data(model_object);
}
void GLGizmosManager::set_sla_support_data(ModelObject* model_object, const Selection& selection)
void GLGizmosManager::set_sla_support_data(ModelObject* model_object)
{
if (!m_enabled)
return;
GizmosMap::const_iterator it = m_gizmos.find(SlaSupports);
if (it != m_gizmos.end())
reinterpret_cast<GLGizmoSlaSupports*>(it->second)->set_sla_support_data(model_object, selection);
reinterpret_cast<GLGizmoSlaSupports*>(it->second)->set_sla_support_data(model_object, m_parent.get_selection());
}
// Returns true if the gizmo used the event to do something, false otherwise.
@ -445,27 +456,27 @@ ClippingPlane GLGizmosManager::get_sla_clipping_plane() const
}
void GLGizmosManager::render_current_gizmo(const Selection& selection) const
void GLGizmosManager::render_current_gizmo() const
{
if (!m_enabled)
return;
GLGizmoBase* curr = get_current();
if (curr != nullptr)
curr->render(selection);
curr->render();
}
void GLGizmosManager::render_current_gizmo_for_picking_pass(const Selection& selection) const
void GLGizmosManager::render_current_gizmo_for_picking_pass() const
{
if (!m_enabled)
return;
GLGizmoBase* curr = get_current();
if (curr != nullptr)
curr->render_for_picking(selection);
curr->render_for_picking();
}
void GLGizmosManager::render_overlay(const GLCanvas3D& canvas, const Selection& selection) const
void GLGizmosManager::render_overlay() const
{
if (!m_enabled)
return;
@ -473,10 +484,10 @@ void GLGizmosManager::render_overlay(const GLCanvas3D& canvas, const Selection&
if (m_icons_texture_dirty)
generate_icons_texture();
do_render_overlay(canvas, selection);
do_render_overlay();
}
bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas)
bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
{
bool processed = false;
@ -489,14 +500,12 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas)
return processed;
}
bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
{
Point pos(evt.GetX(), evt.GetY());
Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY());
Selection& selection = canvas.get_selection();
Selection& selection = m_parent.get_selection();
int selected_object_idx = selection.get_object_idx();
bool processed = false;
@ -512,7 +521,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
// mouse anywhere
if (evt.Moving())
m_tooltip = update_hover_state(canvas, mouse_pos);
m_tooltip = update_hover_state(mouse_pos);
else if (evt.LeftUp())
m_mouse_capture.left = false;
else if (evt.MiddleUp())
@ -523,7 +532,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
// if the button down was done on this toolbar, prevent from dragging into the scene
processed = true;
if (!overlay_contains_mouse(canvas, mouse_pos))
if (!overlay_contains_mouse(mouse_pos))
{
// mouse is outside the toolbar
m_tooltip = "";
@ -535,41 +544,40 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
processed = true;
else if (!selection.is_empty() && grabber_contains_mouse())
{
update_data(canvas);
update_data();
selection.start_dragging();
start_dragging(selection);
start_dragging();
if (m_current == Flatten)
{
// Rotate the object so the normal points downward:
selection.flattening_rotate(get_flattening_normal());
canvas.do_flatten();
m_parent.do_flatten(get_flattening_normal(), "Gizmo-Place on Face");
wxGetApp().obj_manipul()->set_dirty();
}
canvas.set_as_dirty();
m_parent.set_as_dirty();
processed = true;
}
}
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown))
// event was taken care of by the SlaSupports gizmo
processed = true;
else if (evt.Dragging() && (canvas.get_move_volume_id() != -1) && (m_current == SlaSupports))
// don't allow dragging objects with the Sla gizmo on
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports))
// don't allow dragging objects with the Sla gizmo on
processed = true;
else if (evt.Dragging() && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
{
// the gizmo got the event and took some action, no need to do anything more here
canvas.set_as_dirty();
m_parent.set_as_dirty();
processed = true;
}
else if (evt.Dragging() && is_dragging())
{
if (!canvas.get_wxglcanvas()->HasCapture())
canvas.get_wxglcanvas()->CaptureMouse();
if (!m_parent.get_wxglcanvas()->HasCapture())
m_parent.get_wxglcanvas()->CaptureMouse();
canvas.set_mouse_as_dragging();
update(canvas.mouse_ray(pos), selection, &pos);
m_parent.set_mouse_as_dragging();
update(m_parent.mouse_ray(pos), pos);
switch (m_current)
{
@ -606,7 +614,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
break;
}
canvas.set_as_dirty();
m_parent.set_as_dirty();
processed = true;
}
else if (evt.LeftUp() && is_dragging())
@ -615,18 +623,18 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
{
case Move:
{
canvas.disable_regenerate_volumes();
canvas.do_move();
m_parent.disable_regenerate_volumes();
m_parent.do_move("Gizmo-Move");
break;
}
case Scale:
{
canvas.do_scale();
m_parent.do_scale("Gizmo-Scale");
break;
}
case Rotate:
{
canvas.do_rotate();
m_parent.do_rotate("Gizmo-Rotate");
break;
}
default:
@ -634,25 +642,25 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
}
stop_dragging();
update_data(canvas);
update_data();
wxGetApp().obj_manipul()->set_dirty();
// Let the platter know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
canvas.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
canvas.refresh_camera_scene_box();
m_parent.refresh_camera_scene_box();
processed = true;
}
else if (evt.LeftUp() && (m_current == SlaSupports) && !canvas.is_mouse_dragging())
else if (evt.LeftUp() && (m_current == SlaSupports) && !m_parent.is_mouse_dragging())
{
// in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither
// object moving or selecting is suppressed in that case
gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown());
processed = true;
}
else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_first_hover_volume_idx() != -1) || grabber_contains_mouse()))
else if (evt.LeftUp() && (m_current == Flatten) && ((m_parent.get_first_hover_volume_idx() != -1) || grabber_contains_mouse()))
{
// to avoid to loose the selection when user clicks an object while the Flatten gizmo is active
processed = true;
@ -664,24 +672,24 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
if (evt.LeftDown() || evt.LeftDClick())
{
m_mouse_capture.left = true;
m_mouse_capture.parent = &canvas;
m_mouse_capture.parent = &m_parent;
processed = true;
if (!selection.is_empty())
{
update_on_off_state(canvas, mouse_pos, selection);
update_data(canvas);
canvas.set_as_dirty();
update_on_off_state(mouse_pos);
update_data();
m_parent.set_as_dirty();
}
}
else if (evt.MiddleDown())
{
m_mouse_capture.middle = true;
m_mouse_capture.parent = &canvas;
m_mouse_capture.parent = &m_parent;
}
else if (evt.RightDown())
{
m_mouse_capture.right = true;
m_mouse_capture.parent = &canvas;
m_mouse_capture.parent = &m_parent;
}
else if (evt.LeftUp())
processed = true;
@ -690,7 +698,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
return processed;
}
bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas)
bool GLGizmosManager::on_char(wxKeyEvent& evt)
{
// see include/wx/defs.h enum wxKeyCode
int keyCode = evt.GetKeyCode();
@ -798,20 +806,20 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas)
if (!processed && !evt.HasModifiers())
{
if (handle_shortcut(keyCode, canvas.get_selection()))
if (handle_shortcut(keyCode))
{
update_data(canvas);
update_data();
processed = true;
}
}
if (processed)
canvas.set_as_dirty();
m_parent.set_as_dirty();
return processed;
}
bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas)
bool GLGizmosManager::on_key(wxKeyEvent& evt)
{
const int keyCode = evt.GetKeyCode();
bool processed = false;
@ -837,23 +845,29 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas)
}
// if (processed)
// canvas.set_cursor(GLCanvas3D::Standard);
// m_parent.set_cursor(GLCanvas3D::Standard);
}
else if (evt.GetEventType() == wxEVT_KEY_DOWN)
{
if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) && reinterpret_cast<GLGizmoSlaSupports*>(get_current())->is_in_editing_mode())
{
// canvas.set_cursor(GLCanvas3D::Cross);
// m_parent.set_cursor(GLCanvas3D::Cross);
processed = true;
}
}
if (processed)
canvas.set_as_dirty();
m_parent.set_as_dirty();
return processed;
}
void GLGizmosManager::update_after_undo_redo()
{
update_data();
m_serializing = false;
}
void GLGizmosManager::reset()
{
for (GizmosMap::value_type& gizmo : m_gizmos)
@ -865,14 +879,14 @@ void GLGizmosManager::reset()
m_gizmos.clear();
}
void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const
void GLGizmosManager::do_render_overlay() const
{
if (m_gizmos.empty())
return;
float cnv_w = (float)canvas.get_canvas_size().get_width();
float cnv_h = (float)canvas.get_canvas_size().get_height();
float zoom = (float)canvas.get_camera().get_zoom();
float cnv_w = (float)m_parent.get_canvas_size().get_width();
float cnv_h = (float)m_parent.get_canvas_size().get_height();
float zoom = (float)m_parent.get_camera().get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float height = get_total_overlay_height();
@ -985,8 +999,8 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio
GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } });
if (it->second->get_state() == GLGizmoBase::On) {
float toolbar_top = (float)cnv_h - canvas.get_view_toolbar_height();
it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection);
float toolbar_top = (float)cnv_h - m_parent.get_view_toolbar_height();
it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top);
}
top_y -= scaled_stride_y;
}
@ -1048,12 +1062,12 @@ bool GLGizmosManager::generate_icons_texture() const
return res;
}
void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection)
void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos)
{
if (!m_enabled)
return;
float cnv_h = (float)canvas.get_canvas_size().get_height();
float cnv_h = (float)m_parent.get_canvas_size().get_height();
float height = get_total_overlay_height();
float scaled_icons_size = m_overlay_icons_size * m_overlay_scale;
@ -1068,7 +1082,7 @@ void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d&
continue;
bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size);
if (it->second->is_activable(selection) && inside)
if (it->second->is_activable() && inside)
{
if ((it->second->get_state() == GLGizmoBase::On))
{
@ -1092,16 +1106,14 @@ void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d&
it->second->set_state(GLGizmoBase::On);
}
std::string GLGizmosManager::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos)
std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos)
{
std::string name = "";
if (!m_enabled)
return name;
const Selection& selection = canvas.get_selection();
float cnv_h = (float)canvas.get_canvas_size().get_height();
float cnv_h = (float)m_parent.get_canvas_size().get_height();
float height = get_total_overlay_height();
float scaled_icons_size = m_overlay_icons_size * m_overlay_scale;
float scaled_border = m_overlay_border * m_overlay_scale;
@ -1118,7 +1130,7 @@ std::string GLGizmosManager::update_hover_state(const GLCanvas3D& canvas, const
if (inside)
name = it->second->get_name();
if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On))
if (it->second->is_activable() && (it->second->get_state() != GLGizmoBase::On))
it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off);
top_y += scaled_stride_y;
@ -1127,12 +1139,12 @@ std::string GLGizmosManager::update_hover_state(const GLCanvas3D& canvas, const
return name;
}
bool GLGizmosManager::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const
bool GLGizmosManager::overlay_contains_mouse(const Vec2d& mouse_pos) const
{
if (!m_enabled)
return false;
float cnv_h = (float)canvas.get_canvas_size().get_height();
float cnv_h = (float)m_parent.get_canvas_size().get_height();
float height = get_total_overlay_height();
float scaled_icons_size = m_overlay_icons_size * m_overlay_scale;

View file

@ -4,14 +4,13 @@
#include "slic3r/GUI/GLTexture.hpp"
#include "slic3r/GUI/GLToolbar.hpp"
#include "slic3r/GUI/Gizmos/GLGizmos.hpp"
#include "libslic3r/ObjectID.hpp"
#include <map>
namespace Slic3r {
namespace GUI {
class Selection;
class GLGizmoBase;
class GLCanvas3D;
class ClippingPlane;
@ -43,7 +42,7 @@ public:
float get_height() const { return m_top - m_bottom; }
};
class GLGizmosManager
class GLGizmosManager : public Slic3r::ObjectBase
{
public:
static const float Default_Icons_Size;
@ -61,6 +60,7 @@ public:
};
private:
GLCanvas3D& m_parent;
bool m_enabled;
typedef std::map<EType, GLGizmoBase*> GizmosMap;
GizmosMap m_gizmos;
@ -89,12 +89,44 @@ private:
MouseCapture m_mouse_capture;
std::string m_tooltip;
bool m_serializing;
public:
GLGizmosManager();
explicit GLGizmosManager(GLCanvas3D& parent);
~GLGizmosManager();
bool init(GLCanvas3D& parent);
bool init();
template<class Archive>
void load(Archive& ar)
{
if (!m_enabled)
return;
m_serializing = true;
ar(m_current);
GLGizmoBase* curr = get_current();
if (curr != nullptr)
{
curr->set_state(GLGizmoBase::On);
curr->load(ar);
}
}
template<class Archive>
void save(Archive& ar) const
{
if (!m_enabled)
return;
ar(m_current);
GLGizmoBase* curr = get_current();
if (curr != nullptr)
curr->save(ar);
}
bool is_enabled() const { return m_enabled; }
void set_enabled(bool enable) { m_enabled = enable; }
@ -102,23 +134,22 @@ public:
void set_overlay_icon_size(float size);
void set_overlay_scale(float scale);
void refresh_on_off_state(const Selection& selection);
void refresh_on_off_state();
void reset_all_states();
void set_hover_id(int id);
void enable_grabber(EType type, unsigned int id, bool enable);
void update(const Linef3& mouse_ray, const Selection& selection, const Point* mouse_pos = nullptr);
void update_data(GLCanvas3D& canvas);
void update(const Linef3& mouse_ray, const Point& mouse_pos);
void update_data();
Rect get_reset_rect_viewport(const GLCanvas3D& canvas) const;
EType get_current_type() const { return m_current; }
bool is_running() const;
bool handle_shortcut(int key, const Selection& selection);
bool handle_shortcut(int key);
bool is_dragging() const;
void start_dragging(const Selection& selection);
void start_dragging();
void stop_dragging();
Vec3d get_displacement() const;
@ -135,26 +166,28 @@ public:
void set_flattening_data(const ModelObject* model_object);
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
void set_sla_support_data(ModelObject* model_object);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false);
ClippingPlane get_sla_clipping_plane() const;
void render_current_gizmo(const Selection& selection) const;
void render_current_gizmo_for_picking_pass(const Selection& selection) const;
void render_current_gizmo() const;
void render_current_gizmo_for_picking_pass() const;
void render_overlay(const GLCanvas3D& canvas, const Selection& selection) const;
void render_overlay() const;
const std::string& get_tooltip() const { return m_tooltip; }
bool on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas);
bool on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas);
bool on_char(wxKeyEvent& evt, GLCanvas3D& canvas);
bool on_key(wxKeyEvent& evt, GLCanvas3D& canvas);
bool on_mouse(wxMouseEvent& evt);
bool on_mouse_wheel(wxMouseEvent& evt);
bool on_char(wxKeyEvent& evt);
bool on_key(wxKeyEvent& evt);
void update_after_undo_redo();
private:
void reset();
void do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const;
void do_render_overlay() const;
float get_total_overlay_height() const;
float get_total_overlay_width() const;
@ -163,13 +196,18 @@ private:
bool generate_icons_texture() const;
void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection);
std::string update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos);
bool overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const;
void update_on_off_state(const Vec2d& mouse_pos);
std::string update_hover_state(const Vec2d& mouse_pos);
bool overlay_contains_mouse(const Vec2d& mouse_pos) const;
bool grabber_contains_mouse() const;
};
} // namespace GUI
} // namespace Slic3r
namespace cereal
{
template <class Archive> struct specialize<Archive, Slic3r::GUI::GLGizmosManager, cereal::specialization::member_load_save> {};
}
#endif // slic3r_GUI_GLGizmosManager_hpp_

View file

@ -233,9 +233,9 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text)
return size;
}
void ImGuiWrapper::set_next_window_pos(float x, float y, int flag)
void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y)
{
ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag);
ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y));
ImGui::SetNextWindowSize(ImVec2(0.0, 0.0));
}
@ -342,6 +342,32 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>&
return res;
}
bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool , int , const char**), int& hovered, int& selected)
{
bool is_hovered = false;
ImGui::ListBoxHeader("", size);
int i=0;
const char* item_text;
while (items_getter(is_undo, i, &item_text))
{
ImGui::Selectable(item_text, i < hovered);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(item_text);
hovered = i;
is_hovered = true;
}
if (ImGui::IsItemClicked())
selected = i;
i++;
}
ImGui::ListBoxFooter();
return is_hovered;
}
void ImGuiWrapper::disabled_begin(bool disabled)
{
wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call");

View file

@ -51,7 +51,7 @@ public:
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
ImVec2 calc_text_size(const wxString &text);
void set_next_window_pos(float x, float y, int flag);
void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
void set_next_window_bg_alpha(float alpha);
bool begin(const std::string &name, int flags = 0);
@ -67,6 +67,7 @@ public:
void text(const std::string &label);
void text(const wxString &label);
bool combo(const wxString& label, const std::vector<std::string>& options, int& selection); // Use -1 to not mark any option as selected
bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected);
void disabled_begin(bool disabled);
void disabled_end();

View file

@ -537,6 +537,14 @@ void MainFrame::init_menubar()
_(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); },
menu_icon("delete_all_menu"), nullptr, [this](){return can_delete_all(); }, this);
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _(L("&Undo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z",
_(L("Undo")), [this](wxCommandEvent&) { m_plater->undo(); },
"undo", nullptr, [this](){return m_plater->can_undo(); }, this);
append_menu_item(editMenu, wxID_ANY, _(L("&Redo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y",
_(L("Redo")), [this](wxCommandEvent&) { m_plater->redo(); },
"redo", nullptr, [this](){return m_plater->can_redo(); }, this);
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C",
_(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); },

View file

@ -70,6 +70,7 @@
#include "../Utils/ASCIIFolding.hpp"
#include "../Utils/PrintHost.hpp"
#include "../Utils/FixModelByWin10.hpp"
#include "../Utils/UndoRedo.hpp"
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
#include "WipeTowerDialog.hpp"
@ -1254,6 +1255,8 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)",
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
{
plater->take_snapshot(_(L("Load Files")));
std::vector<fs::path> paths;
for (const auto &filename : filenames) {
@ -1319,7 +1322,12 @@ struct Plater::priv
Slic3r::Model model;
PrinterTechnology printer_technology = ptFFF;
Slic3r::GCodePreviewData gcode_preview_data;
Slic3r::UndoRedo::Stack undo_redo_stack;
int m_prevent_snapshots = 0; /* Used for avoid of excess "snapshoting".
* Like for "delete selected" or "set numbers of copies"
* we should call tack_snapshot just ones
* instead of calls for each action separately
* */
// GUI elements
wxSizer* panel_sizer{ nullptr };
wxPanel* current_panel{ nullptr };
@ -1619,6 +1627,23 @@ struct Plater::priv
void split_object();
void split_volume();
void scale_selection_to_fit_print_volume();
void take_snapshot(const std::string& snapshot_name)
{
if (this->m_prevent_snapshots > 0)
return;
assert(this->m_prevent_snapshots >= 0);
this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager());
}
void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); }
int get_active_snapshot_index();
void undo();
void redo();
void undo_to(size_t time_to_load);
void redo_to(size_t time_to_load);
void suppress_snapshots() { this->m_prevent_snapshots++; }
void allow_snapshots() { this->m_prevent_snapshots--; }
bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; }
void update_print_volume_state();
void schedule_background_process();
@ -1709,6 +1734,7 @@ private:
void update_fff_scene();
void update_sla_scene();
void update_after_undo_redo();
// path to project file stored with no extension
wxString m_project_filename;
@ -1811,6 +1837,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this);
view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); });
view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); });
view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); });
// 3DScene/Toolbar:
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
@ -1849,6 +1877,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// updates camera type from .ini file
camera.set_type(get_config("use_perspective_camera"));
// Initialize the Undo / Redo stack with a first snapshot.
this->take_snapshot(_(L("New Project")));
}
Plater::priv::~priv()
@ -2219,7 +2250,7 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
// the size of the object is too big -> this could lead to overflow when moving to clipper coordinates,
// so scale down the mesh
double inv = 1. / max_ratio;
object->scale_mesh(Vec3d(inv, inv, inv));
object->scale_mesh_after_creation(Vec3d(inv, inv, inv));
object->origin_translation = Vec3d::Zero();
object->center_around_origin();
scaled_down = true;
@ -2398,12 +2429,15 @@ void Plater::priv::object_list_changed()
void Plater::priv::select_all()
{
// this->take_snapshot(_(L("Select All")));
view3D->select_all();
this->sidebar->obj_list()->update_selections();
}
void Plater::priv::deselect_all()
{
// this->take_snapshot(_(L("Deselect All")));
view3D->deselect_all();
}
@ -2425,6 +2459,7 @@ void Plater::priv::remove(size_t obj_idx)
void Plater::priv::delete_object_from_model(size_t obj_idx)
{
this->take_snapshot(_(L("Delete Object")));
model.delete_object(obj_idx);
update();
object_list_changed();
@ -2432,6 +2467,8 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
void Plater::priv::reset()
{
this->take_snapshot(_(L("Reset Project")));
set_project_filename(wxEmptyString);
// Prevent toolpaths preview from rendering while we modify the Print object
@ -2457,17 +2494,20 @@ void Plater::priv::reset()
void Plater::priv::mirror(Axis axis)
{
this->take_snapshot(_(L("Mirror")));
view3D->mirror_selection(axis);
}
void Plater::priv::arrange()
{
this->take_snapshot(_(L("Arrange")));
m_ui_jobs.start(Jobs::Arrange);
}
// This method will find an optimal orientation for the currently selected item
// Very similar in nature to the arrange method above...
void Plater::priv::sla_optimize_rotation() {
this->take_snapshot(_(L("Optimize Rotation")));
m_ui_jobs.start(Jobs::Rotoptimize);
}
@ -2623,6 +2663,8 @@ void Plater::priv::split_object()
Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part.")));
else
{
this->take_snapshot(_(L("Split to Objects")));
unsigned int counter = 1;
for (ModelObject* m : new_objects)
m->name = current_model_object->name + "_" + std::to_string(counter++);
@ -2861,6 +2903,8 @@ void Plater::priv::update_sla_scene()
void Plater::priv::reload_from_disk()
{
this->take_snapshot(_(L("Reload from Disk")));
const auto &selection = get_selection();
const auto obj_orig_idx = selection.get_object_idx();
if (selection.is_wipe_tower() || obj_orig_idx == -1) { return; }
@ -2894,6 +2938,9 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
{
if (obj_idx < 0)
return;
this->take_snapshot(_(L("Fix Throught NetFabb")));
fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx);
this->update();
this->object_list_changed();
@ -3139,6 +3186,8 @@ void Plater::priv::on_action_layersediting(SimpleEvent&)
void Plater::priv::on_object_select(SimpleEvent& evt)
{
// this->take_snapshot(_(L("Object Selection")));
wxGetApp().obj_list()->update_selections();
selection_changed();
}
@ -3414,8 +3463,7 @@ void Plater::priv::init_view_toolbar()
item.icon_filename = "editor.svg";
item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]";
item.sprite_id = 0;
item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); };
item.is_toggable = false;
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); };
if (!view_toolbar.add_item(item))
return;
@ -3423,8 +3471,7 @@ void Plater::priv::init_view_toolbar()
item.icon_filename = "preview.svg";
item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]";
item.sprite_id = 1;
item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); };
item.is_toggable = false;
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); };
if (!view_toolbar.add_item(item))
return;
@ -3554,6 +3601,52 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const
}
}
int Plater::priv::get_active_snapshot_index()
{
const size_t& active_snapshot_time = this->undo_redo_stack.active_snapshot_time();
const std::vector<UndoRedo::Snapshot>& ss_stack = this->undo_redo_stack.snapshots();
const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time));
return it - ss_stack.begin();
}
void Plater::priv::undo()
{
if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager()))
this->update_after_undo_redo();
}
void Plater::priv::redo()
{
if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager()))
this->update_after_undo_redo();
}
void Plater::priv::undo_to(size_t time_to_load)
{
if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load))
this->update_after_undo_redo();
}
void Plater::priv::redo_to(size_t time_to_load)
{
if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load))
this->update_after_undo_redo();
}
void Plater::priv::update_after_undo_redo()
{
this->view3D->get_canvas3d()->get_selection().clear();
this->update(false); // update volumes from the deserializd model
//YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time)
this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances);
this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo();
wxGetApp().obj_list()->update_after_undo_redo();
//FIXME what about the state of the manipulators?
//FIXME what about the focus? Cursor in the side panel?
}
void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const
{
switch (btn_type)
@ -3591,6 +3684,8 @@ void Plater::new_project()
void Plater::load_project()
{
this->take_snapshot(_(L("Load Project")));
wxString input_file;
wxGetApp().load_project(this, input_file);
load_project(input_file);
@ -3616,6 +3711,8 @@ void Plater::add_model()
if (input_files.empty())
return;
this->take_snapshot(_(L("Add object(s)")));
std::vector<fs::path> input_paths;
for (const auto &file : input_files) {
input_paths.push_back(into_path(file));
@ -3673,13 +3770,18 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo
void Plater::remove_selected()
{
this->take_snapshot(_(L("Delete Selected Objects")));
this->suppress_snapshots();
this->p->view3D->delete_selected();
this->allow_snapshots();
}
void Plater::increase_instances(size_t num)
{
if (! can_increase_instances()) { return; }
this->take_snapshot(_(L("Increase Instances")));
int obj_idx = p->get_selected_object_idx();
ModelObject* model_object = p->model.objects[obj_idx];
@ -3714,6 +3816,8 @@ void Plater::decrease_instances(size_t num)
{
if (! can_decrease_instances()) { return; }
this->take_snapshot(_(L("Decrease Instances")));
int obj_idx = p->get_selected_object_idx();
ModelObject* model_object = p->model.objects[obj_idx];
@ -3748,11 +3852,16 @@ void Plater::set_number_of_copies(/*size_t num*/)
if (num < 0)
return;
this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num));
this->suppress_snapshots();
int diff = (int)num - (int)model_object->instances.size();
if (diff > 0)
increase_instances(diff);
else if (diff < 0)
decrease_instances(-diff);
this->allow_snapshots();
}
bool Plater::is_selection_empty() const
@ -3776,6 +3885,8 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe
return;
}
this->take_snapshot(_(L("Gizmo-Cut")));
wxBusyCursor wait;
const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
@ -4065,6 +4176,45 @@ void Plater::send_gcode()
}
}
void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot(snapshot_name); }
void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); }
void Plater::suppress_snapshots() { p->suppress_snapshots(); }
void Plater::allow_snapshots() { p->allow_snapshots(); }
void Plater::undo() { p->undo(); }
void Plater::redo() { p->redo(); }
void Plater::undo_to(int selection)
{
if (selection == 0) {
p->undo();
return;
}
const int idx = p->get_active_snapshot_index() - selection - 1;
p->undo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
}
void Plater::redo_to(int selection)
{
if (selection == 0) {
p->redo();
return;
}
const int idx = p->get_active_snapshot_index() + selection + 1;
p->redo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
}
bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text)
{
const std::vector<UndoRedo::Snapshot>& ss_stack = p->undo_redo_stack.snapshots();
const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx);
if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) {
*out_text = ss_stack[idx_in_ss_stack].name.c_str();
return true;
}
return false;
}
void Plater::on_extruders_change(int num_extruders)
{
auto& choices = sidebar().combos_filament();
@ -4290,8 +4440,11 @@ void Plater::copy_selection_to_clipboard()
void Plater::paste_from_clipboard()
{
if (can_paste_from_clipboard())
p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
if (!can_paste_from_clipboard())
return;
this->take_snapshot(_(L("Paste From Clipboard")));
p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
}
void Plater::msw_rescale()
@ -4356,6 +4509,16 @@ bool Plater::can_copy_to_clipboard() const
return true;
}
bool Plater::can_undo() const
{
return p->undo_redo_stack.has_undo_snapshot();
}
bool Plater::can_redo() const
{
return p->undo_redo_stack.has_redo_snapshot();
}
SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() :
m_was_running(wxGetApp().plater()->is_background_process_running())
{

View file

@ -184,6 +184,16 @@ public:
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
void send_gcode();
void take_snapshot(const std::string &snapshot_name);
void take_snapshot(const wxString &snapshot_name);
void suppress_snapshots();
void allow_snapshots();
void undo();
void redo();
void undo_to(int selection);
void redo_to(int selection);
bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text);
void on_extruders_change(int extruders_count);
void on_config_change(const DynamicPrintConfig &config);
// On activating the parent window.
@ -218,6 +228,8 @@ public:
bool can_layers_editing() const;
bool can_paste_from_clipboard() const;
bool can_copy_to_clipboard() const;
bool can_undo() const;
bool can_redo() const;
void msw_rescale();

View file

@ -1366,7 +1366,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
continue;
c << std::endl << "[" << presets->section_name() << ":" << preset.name << "]" << std::endl;
for (const std::string &opt_key : preset.config.keys())
c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl;
c << opt_key << " = " << preset.config.opt_serialize(opt_key) << std::endl;
}
}

View file

@ -312,6 +312,22 @@ void Selection::add_all()
this->set_bounding_boxes_dirty();
}
void Selection::set_deserialized(EMode mode, const std::vector<std::pair<size_t, size_t>> &volumes_and_instances)
{
if (! m_valid)
return;
m_mode = mode;
for (unsigned int i : m_list)
(*m_volumes)[i]->selected = false;
m_list.clear();
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i)
if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id))
this->do_add_volume(i);
update_type();
this->set_bounding_boxes_dirty();
}
void Selection::clear()
{
if (!m_valid)
@ -790,6 +806,8 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config)
double s = std::min(sx, std::min(sy, sz));
if (s != 1.0)
{
wxGetApp().plater()->take_snapshot(_(L("Scale To Fit")));
TransformationType type;
type.set_world();
type.set_relative();
@ -798,12 +816,12 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config)
// apply scale
start_dragging();
scale(s * Vec3d::Ones(), type);
wxGetApp().plater()->canvas3D()->do_scale();
wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot
// center selection on print bed
start_dragging();
translate(print_volume.center() - get_bounding_box().center());
wxGetApp().plater()->canvas3D()->do_move();
wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot
wxGetApp().obj_manipul()->set_dirty();
}
@ -1177,7 +1195,7 @@ void Selection::copy_to_clipboard()
ModelObject* dst_object = m_clipboard.add_object();
dst_object->name = src_object->name;
dst_object->input_file = src_object->input_file;
dst_object->config = src_object->config;
static_cast<DynamicPrintConfig&>(dst_object->config) = static_cast<const DynamicPrintConfig&>(src_object->config);
dst_object->sla_support_points = src_object->sla_support_points;
dst_object->sla_points_status = src_object->sla_points_status;
dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment
@ -2016,6 +2034,10 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const
void Selection::paste_volumes_from_clipboard()
{
#ifdef _DEBUG
check_model_ids_validity(*m_model);
#endif /* _DEBUG */
int dst_obj_idx = get_object_idx();
if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx))
return;
@ -2057,6 +2079,9 @@ void Selection::paste_volumes_from_clipboard()
}
volumes.push_back(dst_volume);
#ifdef _DEBUG
check_model_ids_validity(*m_model);
#endif /* _DEBUG */
}
// keeps relative position of multivolume selections
@ -2070,10 +2095,18 @@ void Selection::paste_volumes_from_clipboard()
wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes);
}
#ifdef _DEBUG
check_model_ids_validity(*m_model);
#endif /* _DEBUG */
}
void Selection::paste_objects_from_clipboard()
{
#ifdef _DEBUG
check_model_ids_validity(*m_model);
#endif /* _DEBUG */
std::vector<size_t> object_idxs;
const ModelObjectPtrs& src_objects = m_clipboard.get_objects();
for (const ModelObject* src_object : src_objects)
@ -2087,9 +2120,16 @@ void Selection::paste_objects_from_clipboard()
}
object_idxs.push_back(m_model->objects.size() - 1);
#ifdef _DEBUG
check_model_ids_validity(*m_model);
#endif /* _DEBUG */
}
wxGetApp().obj_list()->paste_objects_into_list(object_idxs);
#ifdef _DEBUG
check_model_ids_validity(*m_model);
#endif /* _DEBUG */
}
} // namespace GUI

View file

@ -171,7 +171,7 @@ private:
Vec3d dragging_center;
// Map from indices of ModelObject instances in Model::objects
// to a set of indices of ModelVolume instances in ModelObject::instances
// Here the index means a position inside the respective std::vector, not ModelID.
// Here the index means a position inside the respective std::vector, not ObjectID.
ObjectIdxsToInstanceIdxsMap content;
};
@ -237,6 +237,9 @@ public:
void add_all();
// To be called after Undo or Redo once the volumes are updated.
void set_deserialized(EMode mode, const std::vector<std::pair<size_t, size_t>> &volumes_and_instances);
// Update the selection based on the new instance IDs.
void instances_changed(const std::vector<size_t> &instance_ids_selected);
// Update the selection based on the map from old indices to new indices after m_volumes changed.

View file

@ -874,11 +874,10 @@ void Tab::update_wiping_button_visibility() {
return; // ys_FIXME
bool wipe_tower_enabled = dynamic_cast<ConfigOptionBool*>( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value;
bool multiple_extruders = dynamic_cast<ConfigOptionFloats*>((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1;
bool single_extruder_mm = dynamic_cast<ConfigOptionBool*>( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value;
auto wiping_dialog_button = wxGetApp().sidebar().get_wiping_dialog_button();
if (wiping_dialog_button) {
wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders && single_extruder_mm);
wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders);
wiping_dialog_button->GetParent()->Layout();
}
}
@ -1557,6 +1556,9 @@ void TabFilament::build()
};
optgroup->append_line(line);
optgroup = page->new_optgroup(_(L("Wipe tower parameters")));
optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower");
optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers")));
optgroup->append_single_option_line("filament_loading_speed_start");
optgroup->append_single_option_line("filament_loading_speed");
@ -1568,7 +1570,6 @@ void TabFilament::build()
optgroup->append_single_option_line("filament_cooling_moves");
optgroup->append_single_option_line("filament_cooling_initial_speed");
optgroup->append_single_option_line("filament_cooling_final_speed");
optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower");
line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" };
line.widget = [this](wxWindow* parent) {

View file

@ -172,7 +172,7 @@ void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_
wxSize text_size = GetTextExtent(info);
auto info_str = new wxStaticText(page, wxID_ANY, info ,wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
info_str->Wrap(int(0.6*text_size.x));
sizer->Add( info_str, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND);
sizer->Add( info_str, 0, wxEXPAND);
auto table_sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift);
table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50);

View file

@ -806,8 +806,12 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item)
if (node_parent) {
if (node->m_type & (itInstanceRoot|itLayerRoot))
{
for (int i = node->GetChildCount() - 1; i >= (node->m_type & itInstanceRoot ? 1 : 0); i--)
// node can be deleted by the Delete, let's check its type while we safely can
bool is_instance_root = (node->m_type & itInstanceRoot);
for (int i = node->GetChildCount() - 1; i >= (is_instance_root ? 1 : 0); i--)
Delete(wxDataViewItem(node->GetNthChild(i)));
return parent;
}

View file

@ -0,0 +1,819 @@
#include "UndoRedo.hpp"
#include <algorithm>
#include <iostream>
#include <fstream>
#include <memory>
#include <typeinfo>
#include <cassert>
#include <cstddef>
#include <cereal/types/polymorphic.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/utility.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/archives/binary.hpp>
#define CEREAL_FUTURE_EXPERIMENTAL
#include <cereal/archives/adapters.hpp>
#include <boost/foreach.hpp>
#ifndef NDEBUG
// #define SLIC3R_UNDOREDO_DEBUG
#endif /* NDEBUG */
namespace Slic3r {
namespace UndoRedo {
static std::string topmost_snapsnot_name = "@@@ Topmost @@@";
bool Snapshot::is_topmost() const
{
return this->name == topmost_snapsnot_name;
}
// Time interval, start is closed, end is open.
struct Interval
{
public:
Interval(size_t begin, size_t end) : m_begin(begin), m_end(end) {}
size_t begin() const { return m_begin; }
size_t end() const { return m_end; }
bool is_valid() const { return m_begin >= 0 && m_begin < m_end; }
// This interval comes strictly before the rhs interval.
bool strictly_before(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && m_end <= rhs.m_begin; }
// This interval comes strictly after the rhs interval.
bool strictly_after(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && rhs.m_end <= m_begin; }
bool operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); }
bool operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; }
void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); }
void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; }
private:
size_t m_begin;
size_t m_end;
};
// History of a single object tracked by the Undo / Redo stack. The object may be mutable or immutable.
class ObjectHistoryBase
{
public:
virtual ~ObjectHistoryBase() {}
// Is the object captured by this history mutable or immutable?
virtual bool is_mutable() const = 0;
virtual bool is_immutable() const = 0;
virtual const void* immutable_object_ptr() const { return nullptr; }
// If the history is empty, the ObjectHistory object could be released.
virtual bool empty() = 0;
// Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
virtual void release_after_timestamp(size_t timestamp) = 0;
#ifdef SLIC3R_UNDOREDO_DEBUG
// Human readable debug information.
virtual std::string format() = 0;
#endif /* SLIC3R_UNDOREDO_DEBUG */
#ifndef NDEBUG
virtual bool valid() = 0;
#endif /* NDEBUG */
};
template<typename T> class ObjectHistory : public ObjectHistoryBase
{
public:
~ObjectHistory() override {}
// If the history is empty, the ObjectHistory object could be released.
bool empty() override { return m_history.empty(); }
// Release all data after the given timestamp. The shared pointer is NOT released.
void release_after_timestamp(size_t timestamp) override {
assert(! m_history.empty());
assert(this->valid());
// it points to an interval which either starts with timestamp, or follows the timestamp.
auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
if (it != m_history.begin()) {
auto it_prev = it;
-- it_prev;
assert(it_prev->begin() < timestamp);
// Trim the last interval with timestamp.
it_prev->trim_end(timestamp);
}
m_history.erase(it, m_history.end());
assert(this->valid());
}
protected:
std::vector<T> m_history;
};
// Big objects (mainly the triangle meshes) are tracked by Slicer using the shared pointers
// and they are immutable.
// The Undo / Redo stack therefore may keep a shared pointer to these immutable objects
// and as long as the ref counter of these objects is higher than 1 (1 reference is held
// by the Undo / Redo stack), there is no cost associated to holding the object
// at the Undo / Redo stack. Once the reference counter drops to 1 (only the Undo / Redo
// stack holds the reference), the shared pointer may get serialized (and possibly compressed)
// and the shared pointer may be released.
// The history of a single immutable object may not be continuous, as an immutable object may
// be removed from the scene while being kept at the Copy / Paste stack.
template<typename T>
class ImmutableObjectHistory : public ObjectHistory<Interval>
{
public:
ImmutableObjectHistory(std::shared_ptr<const T> shared_object) : m_shared_object(shared_object) {}
~ImmutableObjectHistory() override {}
bool is_mutable() const override { return false; }
bool is_immutable() const override { return true; }
const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); }
void save(size_t active_snapshot_time, size_t current_time) {
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time ||
// The snapshot of an immutable object may have already been taken from another mutable object.
(m_history.back().begin() <= active_snapshot_time && m_history.back().end() == current_time + 1));
if (m_history.empty() || m_history.back().end() < active_snapshot_time)
m_history.emplace_back(active_snapshot_time, current_time + 1);
else
m_history.back().extend_end(current_time + 1);
}
bool has_snapshot(size_t timestamp) {
if (m_history.empty())
return false;
auto it = std::lower_bound(m_history.begin(), m_history.end(), Interval(timestamp, timestamp));
if (it == m_history.end() || it->begin() > timestamp) {
if (it == m_history.begin())
return false;
-- it;
}
return timestamp >= it->begin() && timestamp < it->end();
}
bool is_serialized() const { return m_shared_object.get() == nullptr; }
const std::string& serialized_data() const { return m_serialized; }
std::shared_ptr<const T>& shared_ptr(StackImpl &stack);
#ifdef SLIC3R_UNDOREDO_DEBUG
std::string format() override {
std::string out = typeid(T).name();
out += this->is_serialized() ?
std::string(" len:") + std::to_string(m_serialized.size()) :
std::string(" shared_ptr:") + ptr_to_string(m_shared_object.get());
for (const Interval &interval : m_history)
out += std::string(", <") + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")";
return out;
}
#endif /* SLIC3R_UNDOREDO_DEBUG */
#ifndef NDEBUG
bool valid() override;
#endif /* NDEBUG */
private:
// Either the source object is held by a shared pointer and the m_serialized field is empty,
// or the shared pointer is null and the object is being serialized into m_serialized.
std::shared_ptr<const T> m_shared_object;
std::string m_serialized;
};
struct MutableHistoryInterval
{
private:
struct Data
{
// Reference counter of this data chunk. We may have used shared_ptr, but the shared_ptr is thread safe
// with the associated cost of CPU cache invalidation on refcount change.
size_t refcnt;
size_t size;
char data[1];
bool matches(const std::string& rhs) { return this->size == rhs.size() && memcmp(this->data, rhs.data(), this->size) == 0; }
};
Interval m_interval;
Data *m_data;
public:
MutableHistoryInterval(const Interval &interval, const std::string &input_data) : m_interval(interval), m_data(nullptr) {
m_data = (Data*)new char[offsetof(Data, data) + input_data.size()];
m_data->refcnt = 1;
m_data->size = input_data.size();
memcpy(m_data->data, input_data.data(), input_data.size());
}
MutableHistoryInterval(const Interval &interval, MutableHistoryInterval &other) : m_interval(interval), m_data(other.m_data) {
++ m_data->refcnt;
}
// as a key for std::lower_bound
MutableHistoryInterval(const size_t begin, const size_t end) : m_interval(begin, end), m_data(nullptr) {}
MutableHistoryInterval(MutableHistoryInterval&& rhs) : m_interval(rhs.m_interval), m_data(rhs.m_data) { rhs.m_data = nullptr; }
MutableHistoryInterval& operator=(MutableHistoryInterval&& rhs) { m_interval = rhs.m_interval; m_data = rhs.m_data; rhs.m_data = nullptr; return *this; }
~MutableHistoryInterval() {
if (m_data != nullptr && -- m_data->refcnt == 0)
delete[] (char*)m_data;
}
const Interval& interval() const { return m_interval; }
size_t begin() const { return m_interval.begin(); }
size_t end() const { return m_interval.end(); }
void trim_end(size_t timestamp) { m_interval.trim_end(timestamp); }
void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); }
bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; }
bool operator==(const MutableHistoryInterval& rhs) const { return m_interval == rhs.m_interval; }
const char* data() const { return m_data->data; }
size_t size() const { return m_data->size; }
size_t refcnt() const { return m_data->refcnt; }
bool matches(const std::string& data) { return m_data->matches(data); }
private:
MutableHistoryInterval(const MutableHistoryInterval &rhs);
MutableHistoryInterval& operator=(const MutableHistoryInterval &rhs);
};
static inline std::string ptr_to_string(const void* ptr)
{
char buf[64];
sprintf(buf, "%p", ptr);
return buf;
}
// Smaller objects (Model, ModelObject, ModelInstance, ModelVolume, DynamicPrintConfig)
// are mutable and there is not tracking of the changes, therefore a snapshot needs to be
// taken every time and compared to the previous data at the Undo / Redo stack.
// The serialized data is stored if it is different from the last value on the stack, otherwise
// the serialized data is discarded.
// The history of a single mutable object may not be continuous, as an mutable object may
// be removed from the scene while being kept at the Copy / Paste stack, therefore an object snapshot
// with the same serialized object data may be shared by multiple history intervals.
template<typename T>
class MutableObjectHistory : public ObjectHistory<MutableHistoryInterval>
{
public:
~MutableObjectHistory() override {}
bool is_mutable() const override { return true; }
bool is_immutable() const override { return false; }
void save(size_t active_snapshot_time, size_t current_time, const std::string &data) {
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
if (m_history.empty() || m_history.back().end() < active_snapshot_time) {
if (! m_history.empty() && m_history.back().matches(data))
// Share the previous data by reference counting.
m_history.emplace_back(Interval(current_time, current_time + 1), m_history.back());
else
// Allocate new data.
m_history.emplace_back(Interval(current_time, current_time + 1), data);
} else {
assert(! m_history.empty());
assert(m_history.back().end() == active_snapshot_time);
if (m_history.back().matches(data))
// Just extend the last interval using the old data.
m_history.back().extend_end(current_time + 1);
else
// Allocate new data time continuous with the previous data.
m_history.emplace_back(Interval(active_snapshot_time, current_time + 1), data);
}
}
std::string load(size_t timestamp) const {
assert(! m_history.empty());
auto it = std::lower_bound(m_history.begin(), m_history.end(), MutableHistoryInterval(timestamp, timestamp));
if (it == m_history.end() || it->begin() > timestamp) {
assert(it != m_history.begin());
-- it;
}
assert(timestamp >= it->begin() && timestamp < it->end());
return std::string(it->data(), it->data() + it->size());
}
#ifdef SLIC3R_UNDOREDO_DEBUG
std::string format() override {
std::string out = typeid(T).name();
for (const MutableHistoryInterval &interval : m_history)
out += std::string(", ptr:") + ptr_to_string(interval.data()) + " len:" + std::to_string(interval.size()) + " <" + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")";
return out;
}
#endif /* SLIC3R_UNDOREDO_DEBUG */
#ifndef NDEBUG
bool valid() override;
#endif /* NDEBUG */
};
#ifndef NDEBUG
template<typename T>
bool ImmutableObjectHistory<T>::valid()
{
// The immutable object content is captured either by a shared object, or by its serialization, but not both.
assert(! m_shared_object == ! m_serialized.empty());
// Verify that the history intervals are sorted and do not overlap.
if (! m_history.empty())
for (size_t i = 1; i < m_history.size(); ++ i)
assert(m_history[i - 1].strictly_before(m_history[i]));
return true;
}
#endif /* NDEBUG */
#ifndef NDEBUG
template<typename T>
bool MutableObjectHistory<T>::valid()
{
// Verify that the history intervals are sorted and do not overlap, and that the data reference counters are correct.
if (! m_history.empty()) {
std::map<const char*, size_t> refcntrs;
assert(m_history.front().data() != nullptr);
++ refcntrs[m_history.front().data()];
for (size_t i = 1; i < m_history.size(); ++ i) {
assert(m_history[i - 1].interval().strictly_before(m_history[i].interval()));
++ refcntrs[m_history[i].data()];
}
for (const auto &hi : m_history) {
assert(hi.data() != nullptr);
assert(refcntrs[hi.data()] == hi.refcnt());
}
}
return true;
}
#endif /* NDEBUG */
class StackImpl
{
public:
// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
StackImpl() : m_active_snapshot_time(0), m_current_time(0) {}
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos);
void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos);
bool has_undo_snapshot() const;
bool has_redo_snapshot() const;
bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time);
bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time);
// Snapshot history (names with timestamps).
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
// Timestamp of the active snapshot.
size_t active_snapshot_time() const { return m_active_snapshot_time; }
const Selection& selection_deserialized() const { return m_selection; }
//protected:
template<typename T, typename T_AS> ObjectID save_mutable_object(const T &object);
template<typename T> ObjectID save_immutable_object(std::shared_ptr<const T> &object);
template<typename T> T* load_mutable_object(const Slic3r::ObjectID id);
template<typename T> std::shared_ptr<const T> load_immutable_object(const Slic3r::ObjectID id);
template<typename T, typename T_AS> void load_mutable_object(const Slic3r::ObjectID id, T &target);
#ifdef SLIC3R_UNDOREDO_DEBUG
std::string format() const {
std::string out = "Objects\n";
for (const std::pair<const ObjectID, std::unique_ptr<ObjectHistoryBase>> &kvp : m_objects)
out += std::string("ObjectID:") + std::to_string(kvp.first.id) + " " + kvp.second->format() + "\n";
out += "Snapshots\n";
for (const Snapshot &snapshot : m_snapshots) {
if (snapshot.timestamp == m_active_snapshot_time)
out += ">>> ";
out += std::string("Name: \"") + snapshot.name + "\", timestamp: " + std::to_string(snapshot.timestamp) +
", Model ID:" + ((snapshot.model_id == 0) ? "Invalid" : std::to_string(snapshot.model_id)) + "\n";
}
if (m_active_snapshot_time > m_snapshots.back().timestamp)
out += ">>>\n";
out += "Current time: " + std::to_string(m_current_time) + "\n";
return out;
}
void print() const {
std::cout << "Undo / Redo stack" << std::endl;
std::cout << this->format() << std::endl;
}
#endif /* SLIC3R_UNDOREDO_DEBUG */
#ifndef NDEBUG
bool valid() const {
assert(! m_snapshots.empty());
auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time);
assert(m_active_snapshot_time < m_snapshots.back().timestamp || m_snapshots.back().is_topmost());
for (auto it = m_objects.begin(); it != m_objects.end(); ++ it)
assert(it->second->valid());
return true;
}
#endif /* NDEBUG */
private:
template<typename T> ObjectID immutable_object_id(const std::shared_ptr<const T> &ptr) {
return this->immutable_object_id_impl((const void*)ptr.get());
}
ObjectID immutable_object_id_impl(const void *ptr) {
auto it = m_shared_ptr_to_object_id.find(ptr);
if (it == m_shared_ptr_to_object_id.end()) {
// Allocate a new temporary ObjectID for this shared pointer.
ObjectBase object_with_id;
it = m_shared_ptr_to_object_id.insert(it, std::make_pair(ptr, object_with_id.id()));
}
return it->second;
}
void collect_garbage();
// Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh)
// is stored with its own history, referenced by the ObjectID. Immutable objects do not provide
// their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id.
std::map<ObjectID, std::unique_ptr<ObjectHistoryBase>> m_objects;
std::map<const void*, ObjectID> m_shared_ptr_to_object_id;
// Snapshot history (names with timestamps).
std::vector<Snapshot> m_snapshots;
// Timestamp of the active snapshot.
size_t m_active_snapshot_time;
// Logical time counter. m_current_time is being incremented with each snapshot taken.
size_t m_current_time;
// Last selection serialized or deserialized.
Selection m_selection;
};
using InputArchive = cereal::UserDataAdapter<StackImpl, cereal::BinaryInputArchive>;
using OutputArchive = cereal::UserDataAdapter<StackImpl, cereal::BinaryOutputArchive>;
} // namespace UndoRedo
class Model;
class ModelObject;
class ModelVolume;
class ModelInstance;
class ModelMaterial;
class ModelConfig;
class DynamicPrintConfig;
class TriangleMesh;
} // namespace Slic3r
namespace cereal
{
// Let cereal know that there are load / save non-member functions declared for ModelObject*, ignore serialization of pointers triggering
// static assert, that cereal does not support serialization of raw pointers.
template <class Archive> struct specialize<Archive, Slic3r::Model*, cereal::specialization::non_member_load_save> {};
template <class Archive> struct specialize<Archive, Slic3r::ModelObject*, cereal::specialization::non_member_load_save> {};
template <class Archive> struct specialize<Archive, Slic3r::ModelVolume*, cereal::specialization::non_member_load_save> {};
template <class Archive> struct specialize<Archive, Slic3r::ModelInstance*, cereal::specialization::non_member_load_save> {};
template <class Archive> struct specialize<Archive, Slic3r::ModelMaterial*, cereal::specialization::non_member_load_save> {};
template <class Archive> struct specialize<Archive, Slic3r::ModelConfig, cereal::specialization::non_member_load_save> {};
template <class Archive> struct specialize<Archive, std::shared_ptr<Slic3r::TriangleMesh>, cereal::specialization::non_member_load_save> {};
// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
// store just the ObjectID to this stream.
template <class T> void save(BinaryOutputArchive& ar, T* const& ptr)
{
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T, T>(*ptr));
}
// Load ObjectBase derived class from the Undo / Redo stack as a separate object
// based on the ObjectID loaded from this stream.
template <class T> void load(BinaryInputArchive& ar, T*& ptr)
{
Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
size_t id;
ar(id);
ptr = stack.load_mutable_object<T>(Slic3r::ObjectID(id));
}
// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
// store just the ObjectID to this stream.
template <class T> void save(BinaryOutputArchive &ar, const std::unique_ptr<T> &ptr)
{
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T>(*ptr.get()));
}
// Load ObjectBase derived class from the Undo / Redo stack as a separate object
// based on the ObjectID loaded from this stream.
template <class T> void load(BinaryInputArchive &ar, std::unique_ptr<T> &ptr)
{
Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
size_t id;
ar(id);
ptr.reset(stack.load_mutable_object<T>(Slic3r::ObjectID(id)));
}
// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
// store just the ObjectID to this stream.
void save(BinaryOutputArchive& ar, const Slic3r::ModelConfig &cfg)
{
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<Slic3r::ModelConfig, Slic3r::DynamicPrintConfig>(cfg));
}
// Load ObjectBase derived class from the Undo / Redo stack as a separate object
// based on the ObjectID loaded from this stream.
void load(BinaryInputArchive& ar, Slic3r::ModelConfig &cfg)
{
Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
size_t id;
ar(id);
stack.load_mutable_object<Slic3r::ModelConfig, Slic3r::DynamicPrintConfig>(Slic3r::ObjectID(id), cfg);
}
// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
// store just the ObjectID to this stream.
template <class T> void save(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr)
{
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr)));
}
// Load ObjectBase derived class from the Undo / Redo stack as a separate object
// based on the ObjectID loaded from this stream.
template <class T> void load(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr)
{
Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
size_t id;
ar(id);
ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id));
}
}
#include <libslic3r/Model.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <slic3r/GUI/Selection.hpp>
#include <slic3r/GUI/Gizmos/GLGizmosManager.hpp>
namespace Slic3r {
namespace UndoRedo {
template<typename T> std::shared_ptr<const T>& ImmutableObjectHistory<T>::shared_ptr(StackImpl &stack)
{
if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) {
// Deserialize the object.
std::istringstream iss(m_serialized);
{
Slic3r::UndoRedo::InputArchive archive(stack, iss);
typedef typename std::remove_const<T>::type Type;
std::unique_ptr<Type> mesh(new Type());
archive(*mesh.get());
m_shared_object = std::move(mesh);
}
}
return m_shared_object;
}
template<typename T, typename T_AS> ObjectID StackImpl::save_mutable_object(const T &object)
{
// First find or allocate a history stack for the ObjectID of this object instance.
auto it_object_history = m_objects.find(object.id());
if (it_object_history == m_objects.end())
it_object_history = m_objects.insert(it_object_history, std::make_pair(object.id(), std::unique_ptr<MutableObjectHistory<T>>(new MutableObjectHistory<T>())));
auto *object_history = static_cast<MutableObjectHistory<T>*>(it_object_history->second.get());
// Then serialize the object into a string.
std::ostringstream oss;
{
Slic3r::UndoRedo::OutputArchive archive(*this, oss);
archive(static_cast<const T_AS&>(object));
}
object_history->save(m_active_snapshot_time, m_current_time, oss.str());
return object.id();
}
template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<const T> &object)
{
// First allocate a temporary ObjectID for this pointer.
ObjectID object_id = this->immutable_object_id(object);
// and find or allocate a history stack for the ObjectID associated to this shared_ptr.
auto it_object_history = m_objects.find(object_id);
if (it_object_history == m_objects.end())
it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr<ImmutableObjectHistory<T>>(new ImmutableObjectHistory<T>(object)));
// Then save the interval.
static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time);
return object_id;
}
template<typename T> T* StackImpl::load_mutable_object(const Slic3r::ObjectID id)
{
T *target = new T();
this->load_mutable_object<T, T>(id, *target);
return target;
}
template<typename T> std::shared_ptr<const T> StackImpl::load_immutable_object(const Slic3r::ObjectID id)
{
// First find a history stack for the ObjectID of this object instance.
auto it_object_history = m_objects.find(id);
assert(it_object_history != m_objects.end());
auto *object_history = static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get());
assert(object_history->has_snapshot(m_active_snapshot_time));
return object_history->shared_ptr(*this);
}
template<typename T, typename T_AS> void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target)
{
// First find a history stack for the ObjectID of this object instance.
auto it_object_history = m_objects.find(id);
assert(it_object_history != m_objects.end());
auto *object_history = static_cast<const MutableObjectHistory<T>*>(it_object_history->second.get());
// Then get the data associated with the object history and m_active_snapshot_time.
std::istringstream iss(object_history->load(m_active_snapshot_time));
Slic3r::UndoRedo::InputArchive archive(*this, iss);
target.m_id = id;
archive(static_cast<T_AS&>(target));
}
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos)
{
// Release old snapshot data.
assert(m_active_snapshot_time <= m_current_time);
for (auto &kvp : m_objects)
kvp.second->release_after_timestamp(m_active_snapshot_time);
{
auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
m_snapshots.erase(it, m_snapshots.end());
}
// Take new snapshots.
this->save_mutable_object<Slic3r::Model, Slic3r::Model>(model);
m_selection.volumes_and_instances.clear();
m_selection.volumes_and_instances.reserve(selection.get_volume_idxs().size());
m_selection.mode = selection.get_mode();
for (unsigned int volume_idx : selection.get_volume_idxs())
m_selection.volumes_and_instances.emplace_back(selection.get_volume(volume_idx)->geometry_id);
this->save_mutable_object<Selection, Selection>(m_selection);
this->save_mutable_object<Slic3r::GUI::GLGizmosManager, Slic3r::GUI::GLGizmosManager>(gizmos);
// Save the snapshot info.
m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id);
m_active_snapshot_time = m_current_time;
// Save snapshot info of the last "current" aka "top most" state, that is only being serialized
// if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet.
m_snapshots.emplace_back(topmost_snapsnot_name, m_active_snapshot_time, 0);
// Release empty objects from the history.
this->collect_garbage();
assert(this->valid());
#ifdef SLIC3R_UNDOREDO_DEBUG
std::cout << "After snapshot" << std::endl;
this->print();
#endif /* SLIC3R_UNDOREDO_DEBUG */
}
void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos)
{
// Find the snapshot by time. It must exist.
const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp));
if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp)
throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str());
m_active_snapshot_time = timestamp;
model.clear_objects();
model.clear_materials();
this->load_mutable_object<Slic3r::Model, Slic3r::Model>(ObjectID(it_snapshot->model_id), model);
model.update_links_bottom_up_recursive();
m_selection.volumes_and_instances.clear();
this->load_mutable_object<Selection, Selection>(m_selection.id(), m_selection);
gizmos.reset_all_states();
this->load_mutable_object<Slic3r::GUI::GLGizmosManager, Slic3r::GUI::GLGizmosManager>(gizmos.id(), gizmos);
// Sort the volumes so that we may use binary search.
std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end());
this->m_active_snapshot_time = timestamp;
assert(this->valid());
}
bool StackImpl::has_undo_snapshot() const
{
assert(this->valid());
auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
return -- it != m_snapshots.begin();
}
bool StackImpl::has_redo_snapshot() const
{
assert(this->valid());
auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
return ++ it != m_snapshots.end();
}
bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load)
{
assert(this->valid());
if (time_to_load == SIZE_MAX) {
auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
if (-- it_current == m_snapshots.begin())
return false;
time_to_load = it_current->timestamp;
}
assert(time_to_load < m_active_snapshot_time);
assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load)));
if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) {
// The current state is temporary. The current state needs to be captured to be redoable.
this->take_snapshot(topmost_snapsnot_name, model, selection, gizmos);
// The line above entered another topmost_snapshot_name.
assert(m_snapshots.back().is_topmost());
assert(! m_snapshots.back().is_topmost_captured());
// Pop it back, it is not needed as there is now a captured topmost state.
m_snapshots.pop_back();
// current_time was extended, but it should not cause any harm. Resetting it back may complicate the logic unnecessarily.
//-- m_current_time;
assert(m_snapshots.back().is_topmost());
assert(m_snapshots.back().is_topmost_captured());
}
this->load_snapshot(time_to_load, model, gizmos);
#ifdef SLIC3R_UNDOREDO_DEBUG
std::cout << "After undo" << std::endl;
this->print();
#endif /* SLIC3R_UNDOREDO_DEBUG */
return true;
}
bool StackImpl::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load)
{
assert(this->valid());
if (time_to_load == SIZE_MAX) {
auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
if (++ it_current == m_snapshots.end())
return false;
time_to_load = it_current->timestamp;
}
assert(time_to_load > m_active_snapshot_time);
assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load)));
this->load_snapshot(time_to_load, model, gizmos);
#ifdef SLIC3R_UNDOREDO_DEBUG
std::cout << "After redo" << std::endl;
this->print();
#endif /* SLIC3R_UNDOREDO_DEBUG */
return true;
}
void StackImpl::collect_garbage()
{
// Purge objects with empty histories.
for (auto it = m_objects.begin(); it != m_objects.end();) {
if (it->second->empty()) {
if (it->second->immutable_object_ptr() != nullptr)
// Release the immutable object from the ptr to ObjectID map.
m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr());
it = m_objects.erase(it);
} else
++ it;
}
}
// Wrappers of the private implementation.
Stack::Stack() : pimpl(new StackImpl()) {}
Stack::~Stack() {}
void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) { pimpl->take_snapshot(snapshot_name, model, selection, gizmos); }
bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); }
bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); }
bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->undo(model, selection, gizmos, time_to_load); }
bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, time_to_load); }
const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); }
const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); }
size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); }
} // namespace UndoRedo
} // namespace Slic3r
//FIXME we should have unit tests for testing serialization of basic types as DynamicPrintConfig.
#if 0
#include "libslic3r/Config.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
bool test_dynamic_print_config_serialization() {
FullPrintConfig full_print_config;
DynamicPrintConfig cfg;
cfg.apply(full_print_config, false);
std::string serialized;
try {
std::ostringstream ss;
cereal::BinaryOutputArchive oarchive(ss);
oarchive(cfg);
serialized = ss.str();
} catch (std::runtime_error e) {
e.what();
}
DynamicPrintConfig cfg2;
try {
std::stringstream ss(serialized);
cereal::BinaryInputArchive iarchive(ss);
iarchive(cfg2);
} catch (std::runtime_error e) {
e.what();
}
if (cfg == cfg2) {
printf("Yes!\n");
return true;
}
printf("No!\n");
return false;
}
} // namespace Slic3r
#endif

View file

@ -0,0 +1,95 @@
#ifndef slic3r_Utils_UndoRedo_hpp_
#define slic3r_Utils_UndoRedo_hpp_
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include <cassert>
#include <libslic3r/ObjectID.hpp>
namespace Slic3r {
class Model;
namespace GUI {
class Selection;
class GLGizmosManager;
} // namespace GUI
namespace UndoRedo {
struct Snapshot
{
Snapshot(size_t timestamp) : timestamp(timestamp) {}
Snapshot(const std::string &name, size_t timestamp, size_t model_id) : name(name), timestamp(timestamp), model_id(model_id) {}
std::string name;
size_t timestamp;
size_t model_id;
bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; }
bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; }
// The topmost snapshot represents the current state when going forward.
bool is_topmost() const;
// The topmost snapshot is not being serialized to the Undo / Redo stack until going back in time,
// when the top most state is being serialized, so we can redo back to the top most state.
bool is_topmost_captured() const { assert(this->is_topmost()); return model_id > 0; }
};
// Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack.
struct Selection : public Slic3r::ObjectBase {
unsigned char mode;
std::vector<std::pair<size_t, size_t>> volumes_and_instances;
template<class Archive> void serialize(Archive &ar) { ar(mode, volumes_and_instances); }
};
class StackImpl;
class Stack
{
public:
// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
// The first "New Project" snapshot shall not be removed.
Stack();
~Stack();
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos);
// To be queried to enable / disable the Undo / Redo buttons at the UI.
bool has_undo_snapshot() const;
bool has_redo_snapshot() const;
// Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated.
// Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible.
bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX);
// Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated.
bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX);
// Snapshot history (names with timestamps).
// Each snapshot indicates start of an interval in which this operation is performed.
// There is one additional snapshot taken at the very end, which indicates the current unnamed state.
const std::vector<Snapshot>& snapshots() const;
// Timestamp of the active snapshot. One of the snapshots of this->snapshots() shall have Snapshot::timestamp equal to this->active_snapshot_time().
// The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore
// the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations.
size_t active_snapshot_time() const;
// After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted
// into the list of GLVolume pointers once the 3D scene is updated.
const Selection& selection_deserialized() const;
private:
friend class StackImpl;
std::unique_ptr<StackImpl> pimpl;
};
}; // namespace UndoRedo
}; // namespace Slic3r
#endif /* slic3r_Utils_UndoRedo_hpp_ */