diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 503ebd24f..a7d2953dc 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -127,6 +127,12 @@ void AppConfig::set_defaults() if (get("default_action_on_select_preset").empty()) set("default_action_on_select_preset", "none"); // , "transfer", "discard" or "save" + + if (get("default_action_preset_on_new_project").empty()) + set("default_action_preset_on_new_project", "1"); + + if (get("default_action_on_new_project").empty()) + set("default_action_on_new_project", "1"); } #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN else { diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index bd7c32f8e..e9735fd19 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1834,7 +1834,7 @@ public: // An UnknownOptionException is thrown in case some option keys are not defined by this->def(), // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false); - bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } + bool equals(const ConfigBase &other) const { return this->keys().size() == other.keys().size() && this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; t_config_option_keys equal(const ConfigBase &other) const; std::string opt_serialize(const t_config_option_key &opt_key) const; diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index e4d07a211..234123fa8 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2097,7 +2097,7 @@ namespace Slic3r { return false; } - if ((thumbnail_data != nullptr) && thumbnail_data->is_valid()) + if (!model.objects.empty() && thumbnail_data != nullptr && thumbnail_data->is_valid()) { // Adds the file Metadata/thumbnail.png. if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data)) @@ -2121,12 +2121,13 @@ namespace Slic3r { // Adds model file ("3D/3dmodel.model"). // This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes. IdToObjectDataMap objects_data; - if (!_add_model_file_to_archive(filename, archive, model, objects_data)) - { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } + if(!model.objects.empty()) + if (!_add_model_file_to_archive(filename, archive, model, objects_data)) + { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } // Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt"). // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 24530269d..1f38e3b94 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1185,6 +1185,16 @@ Transformation Transformation::operator * (const Transformation& other) const return Transformation(get_matrix() * other.get_matrix()); } + +bool Transformation::operator==(const Transformation& other) const +{ + return m_offset == other.m_offset + && m_rotation == other.m_rotation + && m_scaling_factor == other.m_scaling_factor + && m_mirror == other.m_mirror + && m_matrix.isApprox(other.m_matrix); +} + Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox) { Transformation out; diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 722cefa00..0429fe279 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -446,6 +446,8 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; Transformation operator * (const Transformation& other) const; + bool operator==(const Transformation& trsf) const; + bool operator!=(const Transformation& trsf) const { return !operator==(trsf); } // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity // as possible in least squares norm in regard to the 8 corners of bbox. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d3d01c7f1..7246dde30 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -97,6 +97,30 @@ void Model::update_links_bottom_up_recursive() } } +bool Model::equals(const Model& rhs) const { + // check materials + if(this->materials.size() != rhs.materials.size()) + return false; + for (const std::pair& m : this->materials) { + // check the ID and m_model. + if (rhs.materials.find(m.first) == rhs.materials.end() || rhs.materials.at(m.first)->operator==(*m.second)) + return false; + } + // check objects + if (this->objects.size() != rhs.objects.size()) + return false; + for (int i = 0; i < rhs.objects.size(); i++) { + // Copy including the ID, leave ID set to invalid (zero). + if (rhs.objects[i]->equals(*objects[i])) + return false; + } + + // copy custom code per height + if (this->custom_gcode_per_print_z != rhs.custom_gcode_per_print_z) + return false; + return true; +} + Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) { Model model; @@ -152,8 +176,9 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig if (!result) throw Slic3r::RuntimeError("Loading of a model file failed."); - if (model.objects.empty()) - throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); + //it can read the config only + //if (model.objects.empty()) + //throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) { @@ -614,6 +639,33 @@ void ModelObject::assign_new_unique_ids_recursive() this->layer_height_profile.set_new_unique_id(); } +bool ModelObject::equals(const ModelObject& rhs) { + + if (this->id() != rhs.id() || this->config.id() != rhs.config.id()) return false; + + if (this->name != rhs.name) return false; + if (this->input_file != rhs.input_file) return false; + // Copies the config's ID + if (this->config != rhs.config) return false; + if (this->sla_support_points != rhs.sla_support_points) return false; + if (this->sla_points_status != rhs.sla_points_status) return false; + if (this->sla_drain_holes != rhs.sla_drain_holes) return false; + if (this->layer_config_ranges != rhs.layer_config_ranges) return false; + if (this->layer_height_profile != rhs.layer_height_profile) return false; + if (this->printable != rhs.printable) return false; + if (this->origin_translation != rhs.origin_translation) return false; + if (m_bounding_box != rhs.m_bounding_box) return false; + if (m_bounding_box_valid != rhs.m_bounding_box_valid) return false; + if (m_raw_bounding_box != rhs.m_raw_bounding_box) return false; + if (m_raw_bounding_box_valid != rhs.m_raw_bounding_box_valid) return false; + if (m_raw_mesh_bounding_box != rhs.m_raw_mesh_bounding_box) return false; + if (m_raw_mesh_bounding_box_valid != rhs.m_raw_mesh_bounding_box_valid) return false; + + if (this->volumes != rhs.volumes) return false; + if (this->instances != rhs.instances) return false; + return true; +} + // Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original. // Called by Print::apply() to clone the Model / ModelObject hierarchy to the back end for background processing. //ModelObject* ModelObject::clone(Model *parent) @@ -1824,6 +1876,14 @@ void ModelVolume::convert_from_imperial_units() this->source.is_converted_from_inches = true; } +bool ModelVolume::operator!=(const ModelVolume& mm) const +{ + if (object->id() != mm.object->id()) return false; + if (m_type != mm.m_type || m_material_id != mm.m_material_id) return false; + if (m_transformation != mm.m_transformation) return false; + return true; +} + void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { mesh->transform(get_matrix(dont_translate)); @@ -1874,6 +1934,9 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } +bool ModelInstance::operator==(const ModelInstance& other) const { + return m_transformation == other.m_transformation && print_volume_state == other.print_volume_state && printable == other.printable; +} arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const { @@ -1903,6 +1966,8 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const return ret; } + + indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const { TriangleSelector selector(mv.mesh()); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 235bfc258..b1b86f4a6 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -146,6 +146,9 @@ public: void apply(const t_model_material_attributes &attributes) { this->attributes.insert(attributes.begin(), attributes.end()); } + bool operator==(const ModelMaterial& mm) const { return attributes == mm.attributes && config == mm.config; } + bool operator!=(const ModelMaterial& mm) const { return !operator==(mm); } + private: // Parent, owning this material. Model *m_model; @@ -194,6 +197,10 @@ public: ar(cereal::base_class(this), m_data); } + + bool operator==(const LayerHeightProfile& other) const { return object_id_and_timestamp_match(other) && m_data == other.m_data; } + bool operator!=(const LayerHeightProfile& other) const { return !this->operator==(other); } + private: // Constructors to be only called by derived classes. // Default constructor to assign a unique ID. @@ -282,6 +289,8 @@ public: const BoundingBoxf3& bounding_box() const; void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } + bool equals(const ModelObject &other); + // A mesh containing all transformed instances of this object. TriangleMesh mesh() const; // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. @@ -670,6 +679,8 @@ public: this->seam_facets.set_new_unique_id(); } + bool operator!=(const ModelVolume& mm) const; + protected: friend class Print; friend class SLAPrint; @@ -677,7 +688,7 @@ protected: friend class ModelObject; // Copies IDs of both the ModelVolume and its config. - explicit ModelVolume(const ModelVolume &rhs) = default; + explicit ModelVolume(const ModelVolume& rhs) = default; void set_model_object(ModelObject *model_object) { object = model_object; } void assign_new_unique_ids_recursive() override; void transform_this_mesh(const Transform3d& t, bool fix_left_handed); @@ -888,6 +899,9 @@ public: this->object->invalidate_bounding_box(); } + bool operator==(const ModelInstance& other) const; + bool operator!=(const ModelInstance& other) const { return !operator==(other); } + protected: friend class Print; friend class SLAPrint; @@ -985,6 +999,8 @@ public: static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false); static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false); + bool equals(const Model& rhs) const; + // Add a new ModelObject to this Model, generate a new ID for this ModelObject. ModelObject* add_object(); ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index eb2ecd00d..df8e1a126 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1369,7 +1369,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloats { 2.2f }); + def->set_default_value(new ConfigOptionFloats { 2.2 }); def = this->add("filament_minimal_purge_on_wipe_tower", coFloats); def->label = L("Minimal purge on wipe tower"); @@ -1380,7 +1380,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm³"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloats { 15.f }); + def->set_default_value(new ConfigOptionFloats { 15. }); def = this->add("filament_cooling_final_speed", coFloats); def->label = L("Speed of the last cooling move"); @@ -1388,7 +1388,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm/s"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloats { 3.4f }); + def->set_default_value(new ConfigOptionFloats { 3.4 }); def = this->add("filament_load_time", coFloats); def->label = L("Filament load time"); @@ -1396,7 +1396,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("s"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloats { 0.0f }); + def->set_default_value(new ConfigOptionFloats { 0.0 }); def = this->add("filament_ramming_parameters", coStrings); def->label = L("Ramming parameters"); @@ -1411,7 +1411,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("s"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloats { 0.0f }); + def->set_default_value(new ConfigOptionFloats { 0.0 }); def = this->add("filament_diameter", coFloats); def->label = L("Diameter"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 0736c457b..fab0e1d50 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1878,7 +1878,9 @@ public: auto cbegin() const { return m_data.cbegin(); } auto cend() const { return m_data.cend(); } t_config_option_keys keys() const { return m_data.keys(); } - bool has(const t_config_option_key &opt_key) const { return m_data.has(opt_key); } + bool has(const t_config_option_key& opt_key) const { return m_data.has(opt_key); } + bool operator==(const ModelConfig& other) const { return m_data.equals(other.m_data); } + bool operator!=(const ModelConfig& other) const { return !this->operator==(other); } const ConfigOption* option(const t_config_option_key &opt_key) const { return m_data.option(opt_key); } int opt_int(const t_config_option_key &opt_key) const { return m_data.opt_int(opt_key); } int extruder() const { return opt_int("extruder"); } diff --git a/src/slic3r/GUI/CalibrationBedDialog.cpp b/src/slic3r/GUI/CalibrationBedDialog.cpp index 5be7305cb..d0d1de8fd 100644 --- a/src/slic3r/GUI/CalibrationBedDialog.cpp +++ b/src/slic3r/GUI/CalibrationBedDialog.cpp @@ -33,7 +33,9 @@ void CalibrationBedDialog::create_buttons(wxStdDialogButtonSizer* buttons){ void CalibrationBedDialog::create_geometry(wxCommandEvent& event_args) { Plater* plat = this->main_frame->plater(); Model& model = plat->model(); - plat->reset(); + if (!plat->new_project("First layer calibration")) + return; + bool autocenter = gui_app->app_config->get("autocenter") == "1"; if(autocenter) { //disable aut-ocenter for this calibration. diff --git a/src/slic3r/GUI/CalibrationBridgeDialog.cpp b/src/slic3r/GUI/CalibrationBridgeDialog.cpp index 10e8d83ba..ba481cdd4 100644 --- a/src/slic3r/GUI/CalibrationBridgeDialog.cpp +++ b/src/slic3r/GUI/CalibrationBridgeDialog.cpp @@ -53,7 +53,8 @@ void CalibrationBridgeDialog::create_buttons(wxStdDialogButtonSizer* buttons){ void CalibrationBridgeDialog::create_geometry(std::string setting_to_test, bool add) { Plater* plat = this->main_frame->plater(); Model& model = plat->model(); - plat->reset(); + if (!plat->new_project("Bridge calibration")) + return; bool autocenter = gui_app->app_config->get("autocenter") == "1"; if (autocenter) { diff --git a/src/slic3r/GUI/CalibrationCubeDialog.cpp b/src/slic3r/GUI/CalibrationCubeDialog.cpp index 93f72a7f9..f42cad315 100644 --- a/src/slic3r/GUI/CalibrationCubeDialog.cpp +++ b/src/slic3r/GUI/CalibrationCubeDialog.cpp @@ -54,7 +54,9 @@ void CalibrationCubeDialog::create_buttons(wxStdDialogButtonSizer* buttons){ void CalibrationCubeDialog::create_geometry(std::string calibration_path) { Plater* plat = this->main_frame->plater(); Model& model = plat->model(); - plat->reset(); + if (!plat->new_project("Calibration cube")) + return; + std::vector objs_idx = plat->load_files(std::vector{ Slic3r::resources_dir()+"/calibration/cube/"+ calibration_path}, true, false, false); diff --git a/src/slic3r/GUI/CalibrationFlowDialog.cpp b/src/slic3r/GUI/CalibrationFlowDialog.cpp index a254299a4..e48f9e6fc 100644 --- a/src/slic3r/GUI/CalibrationFlowDialog.cpp +++ b/src/slic3r/GUI/CalibrationFlowDialog.cpp @@ -38,7 +38,8 @@ void CalibrationFlowDialog::create_buttons(wxStdDialogButtonSizer* buttons){ void CalibrationFlowDialog::create_geometry(float start, float delta) { Plater* plat = this->main_frame->plater(); Model& model = plat->model(); - plat->reset(); + if (!plat->new_project("Flow calibration")) + return; bool autocenter = gui_app->app_config->get("autocenter") == "1"; if (autocenter) { diff --git a/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp b/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp index 42f0d3fb2..80eade96c 100644 --- a/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp +++ b/src/slic3r/GUI/CalibrationOverBridgeDialog.cpp @@ -43,7 +43,9 @@ void CalibrationOverBridgeDialog::create_geometry2(wxCommandEvent& event_args) { void CalibrationOverBridgeDialog::create_geometry(bool over_bridge) { Plater* plat = this->main_frame->plater(); Model& model = plat->model(); - plat->reset(); + if (!plat->new_project("Over-bridge calibration")) + return; + bool autocenter = gui_app->app_config->get("autocenter") == "1"; if (autocenter) { //disable aut-ocenter for this calibration. diff --git a/src/slic3r/GUI/CalibrationRetractionDialog.cpp b/src/slic3r/GUI/CalibrationRetractionDialog.cpp index 902f24700..cbe9bf79d 100644 --- a/src/slic3r/GUI/CalibrationRetractionDialog.cpp +++ b/src/slic3r/GUI/CalibrationRetractionDialog.cpp @@ -100,7 +100,8 @@ void CalibrationRetractionDialog::remove_slowdown(wxCommandEvent& event_args) { void CalibrationRetractionDialog::create_geometry(wxCommandEvent& event_args) { Plater* plat = this->main_frame->plater(); Model& model = plat->model(); - plat->reset(); + if (!plat->new_project("Retraction calibration")) + return; bool autocenter = gui_app->app_config->get("autocenter") == "1"; if (autocenter) { diff --git a/src/slic3r/GUI/CalibrationTempDialog.cpp b/src/slic3r/GUI/CalibrationTempDialog.cpp index e485e8270..f0941f4b1 100644 --- a/src/slic3r/GUI/CalibrationTempDialog.cpp +++ b/src/slic3r/GUI/CalibrationTempDialog.cpp @@ -56,7 +56,9 @@ void CalibrationTempDialog::create_buttons(wxStdDialogButtonSizer* buttons){ void CalibrationTempDialog::create_geometry(wxCommandEvent& event_args) { Plater* plat = this->main_frame->plater(); Model& model = plat->model(); - plat->reset(); + if (!plat->new_project("Temperature calibration")) + return; + std::vector objs_idx = plat->load_files(std::vector{ Slic3r::resources_dir()+"/calibration/filament_temp/Smart_compact_temperature_calibration_item.amf"}, true, false, false); diff --git a/src/slic3r/GUI/FreeCADDialog.cpp b/src/slic3r/GUI/FreeCADDialog.cpp index 87356dc04..2fe699672 100644 --- a/src/slic3r/GUI/FreeCADDialog.cpp +++ b/src/slic3r/GUI/FreeCADDialog.cpp @@ -1026,7 +1026,7 @@ void FreeCADDialog::create_geometry(wxCommandEvent& event_args) { Plater* plat = this->main_frame->plater(); Model& model = plat->model(); if(cmb_add_replace->GetSelection() == 0) - plat->reset(); + plat->new_project(); std::vector objs_idx = plat->load_files(std::vector{ object_path.generic_string() }, true, false, false); if (objs_idx.empty()) return; //don't save in the temp directory: erase the link to it diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9ef72c147..8400d1259 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4511,7 +4511,7 @@ bool GLCanvas3D::_init_main_toolbar() item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.sprite_id = 2; 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(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()/*->can_delete_all()*/; }; if (!m_main_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6f945fc04..04abc8488 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -208,6 +208,10 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S event.Veto(); return; } + if (event.CanVeto() && plater() && !plater()->check_project_unsaved_changes()) { + event.Veto(); + return; + } if (event.CanVeto() && !wxGetApp().check_print_host_queue()) { event.Veto(); return; @@ -486,12 +490,18 @@ void MainFrame::shutdown() void MainFrame::update_title() { wxString title = wxEmptyString; + bool has_name = false; if (m_plater != nullptr) { // m_plater->get_project_filename() produces file name including path, but excluding extension. // Don't try to remove the extension, it would remove part of the file name after the last dot! wxString project = from_path(into_path(m_plater->get_project_filename()).filename()); - if (!project.empty()) + if (project.empty()) { + project = m_plater->get_project_filename(); + } + if (!project.empty()) { + has_name = true; title += (project + " - "); + } } std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; @@ -511,7 +521,7 @@ void MainFrame::update_title() } title += wxString(SLIC3R_APP_NAME) + "_" + wxString(SLIC3R_VERSION) ; - if (wxGetApp().is_editor()) + if (wxGetApp().is_editor() && !has_name) title += (" " + _L("based on PrusaSlicer & Slic3r")); SetTitle(title); @@ -664,12 +674,12 @@ bool MainFrame::is_active_and_shown_tab(Tab* tab) bool MainFrame::can_start_new_project() const { - return (m_plater != nullptr) && !m_plater->model().objects.empty(); + return (m_plater != nullptr); } bool MainFrame::can_save() const { - return (m_plater != nullptr) && !m_plater->model().objects.empty(); + return (m_plater != nullptr); } bool MainFrame::can_export_model() const @@ -944,7 +954,7 @@ void MainFrame::init_menubar_as_editor() wxMenu* fileMenu = new wxMenu; { append_menu_item(fileMenu, wxID_ANY, _L("&New Project") + "\tCtrl+N", _L("Start a new project"), - [this](wxCommandEvent&) { if (m_plater) m_plater->new_project(); }, "", nullptr, + [this](wxCommandEvent&) { if (m_plater) m_plater->ask_for_new_project(); }, "", nullptr, [this](){return m_plater != nullptr && can_start_new_project(); }, this); append_menu_item(fileMenu, wxID_ANY, _L("&Open Project") + dots + "\tCtrl+O", _L("Open a project file"), [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "open", nullptr, @@ -986,14 +996,14 @@ void MainFrame::init_menubar_as_editor() Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr, + [this](wxCommandEvent&) { if (m_plater) m_plater->save_project_as_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); #ifdef __APPLE__ append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"), #else append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"), #endif // __APPLE__ - [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr, + [this](wxCommandEvent&) { if (m_plater) m_plater->save_project_as_3mf(); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); fileMenu->AppendSeparator(); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index cb31e059c..403508917 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -634,8 +634,8 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, value = get_config_value(config, opt_short_key, opt_index); } - set_value(opt_key, value); - on_change_OG(opt_key, get_value(opt_key)); + if(set_value(opt_key, value)) + on_change_OG(opt_key, get_value(opt_key)); } void ConfigOptionsGroup::on_kill_focus(const std::string& opt_key) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 082dd1a77..c67bd7d71 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1777,8 +1777,9 @@ struct Plater::priv void select_all(); void deselect_all(); void remove(size_t obj_idx); + void remove_all(); void delete_object_from_model(size_t obj_idx); - void reset(); + void reset(std::string name = ""); void mirror(Axis axis); void split_object(); void split_volume(); @@ -1902,6 +1903,20 @@ struct Plater::priv // extension should contain the leading dot, i.e.: ".3mf" wxString get_project_filename(const wxString& extension = wxEmptyString) const; void set_project_filename(const wxString& filename); + void set_saved_project(const DynamicPrintConfig& config, const Model& model) { m_project_last_saved_cfg = config; m_saved_model = model; } + bool has_project_change(const DynamicPrintConfig& config, const Model& model) const { + if (m_project_last_saved_cfg.keys().empty()) { + //new project, unsaved + //check if current preset is dirty + PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : wxGetApp().tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) { + return false; + } + } else if(!m_project_last_saved_cfg.equals(config)) + return false; + return !m_saved_model.equals(model); + } // Caching last value of show_action_buttons parameter for show_action_buttons(), so that a callback which does not know this state will not override it. mutable bool ready_to_slice = { false }; @@ -1928,6 +1943,8 @@ private: // path to project file stored with no extension wxString m_project_filename; + Model m_saved_model; + DynamicPrintConfig m_project_last_saved_cfg; Slic3r::UndoRedo::Stack m_undo_redo_stack_main; Slic3r::UndoRedo::Stack m_undo_redo_stack_gizmos; Slic3r::UndoRedo::Stack *m_undo_redo_stack_active = &m_undo_redo_stack_main; @@ -2807,6 +2824,18 @@ void Plater::priv::remove(size_t obj_idx) object_list_changed(); } +void Plater::priv::remove_all() +{ + if (view3D->is_layers_editing_enabled()) + view3D->enable_layers_editing(false); + + model.clear_objects(); + update(); + // Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model. + sidebar->obj_list()->delete_all_objects_from_list(); + object_list_changed(); +} + void Plater::priv::delete_object_from_model(size_t obj_idx) { @@ -2819,13 +2848,14 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) object_list_changed(); } -void Plater::priv::reset() +void Plater::priv::reset(std::string name) { - Plater::TakeSnapshot snapshot(q, _L("Reset Project")); + Plater::TakeSnapshot snapshot(q, _L(name.empty()? "Reset Project" : name)); clear_warnings(); - set_project_filename(wxEmptyString); + set_project_filename(name.empty() ? wxEmptyString : _L(name)); + set_saved_project(DynamicPrintConfig{}, Model{}); if (view3D->is_layers_editing_enabled()) view3D->enable_layers_editing(false); @@ -4800,10 +4830,60 @@ const PrintBase* Plater::current_print() const { return printer_technology() == ptFFF ? (PrintBase*)&p->fff_print : (PrintBase*)&p->sla_print; } -void Plater::new_project() +bool Plater::check_project_unsaved_changes() { + if (wxGetApp().app_config->get("default_action_on_new_project") == "1" && p->has_project_change(wxGetApp().preset_bundle->full_config_secure(), p->model)) + { + wxMessageDialog diag = wxMessageDialog(static_cast(this), _L("You have unsaved Changed, do you want to save your project or to remove all settings and objects?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE); + diag.SetYesNoLabels(_L("Discard"), _L("Save")); + //diag.SetOKLabel(_L("Always discard")); + int result = diag.ShowModal(); + if (result == wxID_CANCEL) + return false; + else if (result == wxID_NO) + save_project_as_3mf(into_path(get_project_filename(".3mf"))); + //else if (result == wxID_OK) + //wxGetApp().app_config->set("default_action_on_new_project", "0"); + } + return true; +} + +bool Plater::ask_for_new_project(std::string project_name) { + if (!check_project_unsaved_changes()) + return false; + return new_project(project_name); +} + +bool Plater::new_project(std::string project_name) +{ + //ask to know what to do with unsaved conf change: + // if discard or save, we can make sur it's reset + // if cancel, then do not touch them + if (wxGetApp().app_config->get("default_action_preset_on_new_project") == "0" && wxGetApp().check_unsaved_changes()) { + + //if (!config.empty()) { + // Preset::normalize(config); + // wxGetApp().preset_bundle-> + // wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config)); + // if (printer_technology == ptFFF) + // CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, &wxGetApp().preset_bundle->project_config); + // // For exporting from the amf/3mf we shouldn't check printer_presets for the containing information about "Print Host upload" + // wxGetApp().load_current_presets(false); + // is_project_file = true; + //} + PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : wxGetApp().tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) { + std::vector dirty_options = tab->get_presets()->current_dirty_options(); + for (std::string key : dirty_options) { + tab->get_presets()->get_edited_preset().config.set_key_value(key, tab->get_presets()->get_selected_preset().config.option(key)->clone()); + } + tab->update_dirty(); + } + } p->select_view_3D("3D"); - wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); + p->reset(project_name); + return true; } void Plater::load_project() @@ -4829,6 +4909,7 @@ void Plater::load_project(const wxString& filename) input_paths.push_back(into_path(filename)); std::vector res = load_files(input_paths); + p->set_saved_project(wxGetApp().preset_bundle->full_config_secure(), p->model); // if res is empty no data has been loaded if (!res.empty()) @@ -5156,11 +5237,15 @@ void Plater::select_all() { p->select_all(); } void Plater::deselect_all() { p->deselect_all(); } void Plater::remove(size_t obj_idx) { p->remove(obj_idx); } -void Plater::reset() { p->reset(); } void Plater::reset_with_confirm() { - if (wxMessageDialog(static_cast(this), _L("All objects will be removed, continue?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Delete all"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) - reset(); + wxMessageDialog dialog(static_cast(this), _L("All objects will be removed, continue?"), wxString(SLIC3R_APP_NAME) + " - " + _L("Delete all"), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE); + dialog.SetYesNoLabels("New Project", "Erase all objects"); + int result = dialog.ShowModal(); + if (result == wxID_YES) + ask_for_new_project(); + else if (result == wxID_NO) + p->remove_all(); } void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); } @@ -5539,12 +5624,11 @@ void Plater::export_amf() } } -void Plater::export_3mf(const boost::filesystem::path& output_path) +void Plater::save_project_as_3mf(const boost::filesystem::path& output_path) { - if (p->model.objects.empty()) { return; } + //if (p->model.objects.empty()) { return; } wxString path; - bool export_config = true; if (output_path.empty()) { path = p->get_export_file(FT_3MF); @@ -5574,10 +5658,11 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) !show_support_on_thumbnails, // parts_only show_bed_on_thumbnails, // show_bed true); // transparent_background - if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) { + if (Slic3r::store_3mf(path_u8.c_str(), &p->model, &cfg, full_pathnames, &thumbnail_data)) { // Success p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); - p->set_project_filename(path); + p->set_project_filename(path); + p->set_saved_project(cfg, p->model); } else { // Failure diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index ed428b8c3..1b5e80740 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -139,7 +139,9 @@ public: SLAPrint& sla_print(); const PrintBase* current_print() const; - void new_project(); + bool check_project_unsaved_changes(); + bool ask_for_new_project(std::string project_name = ""); + bool new_project(std::string project_name = ""); void load_project(); void load_project(const wxString& filename); void add_model(bool imperial_units = false); @@ -184,7 +186,6 @@ public: void select_all(); void deselect_all(); void remove(size_t obj_idx); - void reset(); void reset_with_confirm(); void delete_object_from_model(size_t obj_idx); void remove_selected(); @@ -201,7 +202,7 @@ public: void export_gcode(bool prefer_removable); void export_stl(bool extended = false, bool selection_only = false); void export_amf(); - void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); + void save_project_as_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); void reload_from_disk(); void reload_all_from_disk(); bool has_toolpaths_to_export() const; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 3ee5b91e7..85695978b 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -191,6 +191,20 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_select_preset") == "none" }); option = Option(def, "default_action_on_select_preset"); m_optgroup_general->append_single_option_line(option); + + def.label = L("Always keep current preset changes on a new project"); + def.type = coBool; + def.tooltip = L("When you create a new project, it will keep the current preset state, and won't open the preset change dialog."); + def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_preset_on_new_project") == "1" }); + option = Option(def, "default_action_preset_on_new_project"); + m_optgroup_general->append_single_option_line(option); + + def.label = L("Ask for unsaved project changes"); + def.type = coBool; + def.tooltip = L("Always ask if you want to save your project change if you are going to loose some changes. Or it will discard them by deafult."); + def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_new_project") == "1" }); + option = Option(def, "default_action_on_new_project"); + m_optgroup_general->append_single_option_line(option); } #if ENABLE_CUSTOMIZABLE_FILES_ASSOCIATION_ON_WIN #ifdef _WIN32