#949 Project is the current .3mf (or lack of)

- ask for saving the project if unsaved changes
 - ask for preset reset if new project
 - delete all : new project or just delete?
This commit is contained in:
remi durand 2021-03-21 04:38:49 +01:00
parent e47de1b315
commit 2c8e531731
23 changed files with 275 additions and 52 deletions

View File

@ -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 {

View File

@ -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;

View File

@ -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.

View File

@ -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;

View File

@ -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.

View File

@ -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<const t_model_material_id, ModelMaterial*>& 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());

View File

@ -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<ObjectWithTimestamp>(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);

View File

@ -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");

View File

@ -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"); }

View File

@ -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.

View File

@ -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) {

View File

@ -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<size_t> objs_idx = plat->load_files(std::vector<std::string>{
Slic3r::resources_dir()+"/calibration/cube/"+ calibration_path}, true, false, false);

View File

@ -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) {

View File

@ -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.

View File

@ -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) {

View File

@ -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<size_t> objs_idx = plat->load_files(std::vector<std::string>{
Slic3r::resources_dir()+"/calibration/filament_temp/Smart_compact_temperature_calibration_item.amf"}, true, false, false);

View File

@ -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<size_t> objs_idx = plat->load_files(std::vector<std::string>{ object_path.generic_string() }, true, false, false);
if (objs_idx.empty()) return;
//don't save in the temp directory: erase the link to it

View File

@ -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;

View File

@ -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();

View File

@ -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)

View File

@ -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<wxWindow*>(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<std::string> 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<size_t> 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<wxWindow*>(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<wxWindow*>(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

View File

@ -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;

View File

@ -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