diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 69ea01ad48..ba41e05fa0 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -298,8 +298,9 @@ int CLI::run(int argc, char **argv) // When loading an AMF or 3MF, config is imported as well, including the printer technology. DynamicPrintConfig config; ConfigSubstitutionContext config_substitutions(config_substitution_rule); + boost::optional prusaslicer_generator_version; //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? - model = FileReader::read_from_file(file, &config, &config_substitutions, FileReader::LoadAttribute::AddDefaultInstances); + model = FileReader::load_model_with_config(file, &config, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::AddDefaultInstances); PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; diff --git a/src/libslic3r/FileReader.cpp b/src/libslic3r/FileReader.cpp index b0d0fd14f2..cf05144e56 100644 --- a/src/libslic3r/FileReader.cpp +++ b/src/libslic3r/FileReader.cpp @@ -13,6 +13,7 @@ ///|/ #include "FileReader.hpp" #include "Model.hpp" +#include "ModelProcessing.hpp" #include "TriangleMesh.hpp" #include "Format/AMF.hpp" @@ -23,19 +24,19 @@ #include "Format/SVG.hpp" #include "Format/PrintRequest.hpp" +#include + +#include "I18N.hpp" + namespace Slic3r::FileReader { // Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. -Model read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options) +static Model read_model_from_file(const std::string& input_file, LoadAttributes options) { Model model; DynamicPrintConfig temp_config; ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent); - if (config == nullptr) - config = &temp_config; - if (config_substitutions == nullptr) - config_substitutions = &temp_config_substitutions_context; bool result = false; if (boost::algorithm::iends_with(input_file, ".stl")) @@ -45,23 +46,25 @@ Model read_from_file(const std::string& input_file, DynamicPrintConfig* config, else if (boost::algorithm::iends_with(input_file, ".step") || boost::algorithm::iends_with(input_file, ".stp")) result = load_step(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) - result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); +//? result = load_amf(input_file.c_str(), &temp_config, &temp_config_substitutions_context, &model, options & LoadAttribute::CheckVersion); +//? LoadAttribute::CheckVersion is needed here, when we loading just a geometry + result = load_amf(input_file.c_str(), &temp_config, &temp_config_substitutions_context, &model, false); else if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip")) { //FIXME options & LoadAttribute::CheckVersion ? boost::optional prusaslicer_generator_version; - result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false, prusaslicer_generator_version); + result = load_3mf(input_file.c_str(), temp_config, temp_config_substitutions_context, &model, false, prusaslicer_generator_version); } else if (boost::algorithm::iends_with(input_file, ".svg")) result = load_svg(input_file, model); else if (boost::ends_with(input_file, ".printRequest")) result = load_printRequest(input_file.c_str(), &model); else - throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip)."); + throw Slic3r::RuntimeError(L("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip).")); if (!result) - throw Slic3r::RuntimeError("Loading of a model file failed."); + throw Slic3r::RuntimeError(L("Loading of a model file failed.")); if (model.objects.empty()) - throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); + throw Slic3r::RuntimeError(L("The supplied file couldn't be read because it's empty")); if (!boost::ends_with(input_file, ".printRequest")) for (ModelObject* o : model.objects) @@ -70,47 +73,37 @@ Model read_from_file(const std::string& input_file, DynamicPrintConfig* config, if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); - for (CustomGCode::Info& info : model.get_custom_gcode_per_print_z_vector()) { - CustomGCode::update_custom_gcode_per_print_z_from_config(info, config); - CustomGCode::check_mode_for_custom_gcode_per_print_z(info); - } - - sort_remove_duplicates(config_substitutions->substitutions); return model; } -// Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ). -Model read_from_archive(const std::string& input_file, - DynamicPrintConfig* config, - ConfigSubstitutionContext*, - config_substitutions, - boost::optional &prusaslicer_generator_version, - LoadAttributes options) +// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. +static Model read_all_from_file(const std::string& input_file, + DynamicPrintConfig* config, + ConfigSubstitutionContext* config_substitutions, + boost::optional &prusaslicer_generator_version, + LoadAttributes options) { + const bool is_project_file = boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip"); + assert(is_project_file); assert(config != nullptr); assert(config_substitutions != nullptr); Model model; bool result = false; - if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip")) + if (is_project_file) result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion, prusaslicer_generator_version); - else if (boost::algorithm::iends_with(input_file, ".zip.amf")) - result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); else - throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); + throw Slic3r::RuntimeError(L("Unknown file format. Input file must have .3mf extension.")); if (!result) - throw Slic3r::RuntimeError("Loading of a model file failed."); + throw Slic3r::RuntimeError(L("Loading of a model file failed.")); - for (ModelObject* o : model.objects) { - //if (boost::algorithm::iends_with(input_file, ".zip.amf")) { - // // we remove the .zip part of the extension to avoid it be added to filenames when exporting - // o->input_file = boost::ireplace_last_copy(input_file, ".zip.", "."); - //} - //else + if (model.objects.empty()) + throw Slic3r::RuntimeError(L("The supplied file couldn't be read because it's empty")); + + for (ModelObject* o : model.objects) o->input_file = input_file; - } if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); @@ -119,37 +112,141 @@ Model read_from_archive(const std::string& input_file, CustomGCode::update_custom_gcode_per_print_z_from_config(info, config); CustomGCode::check_mode_for_custom_gcode_per_print_z(info); } - handle_legacy_sla(*config); - - return model; -} - -Model load_model(const std::string& input_file, std::string& errors) -{ - Model model; - try { - model = read_from_file(input_file); - } - catch (std::exception& e) { - errors = input_file + " : " + e.what(); - return model; - } - - return model; -} - -Model load_model(const std::string& input_file) -{ - std::string errors; - Model model = load_model(input_file, errors); + sort_remove_duplicates(config_substitutions->substitutions); return model; } TriangleMesh load_mesh(const std::string& input_file) { - Model model = load_model(input_file); + Model model; + try { + model = read_model_from_file(input_file, LoadAttribute::AddDefaultInstances); + } + catch (std::exception&) { + throw Slic3r::RuntimeError(L("Error! Invalid model")); + } + return model.mesh(); } +static bool looks_like_multipart_object(const Model& model) +{ + if (model.objects.size() <= 1) + return false; + + BoundingBoxf3 tbb; + + for (const ModelObject* obj : model.objects) { + if (obj->volumes.size() > 1 || obj->config.keys().size() > 1) + return false; + + BoundingBoxf3 bb_this = obj->volumes[0]->mesh().bounding_box(); + + // FIXME: There is sadly the case when instances are empty (AMF files). The normalization of instances in that + // case is performed only after this function is called. For now (shortly before the 2.7.2 release, let's + // just do this non-invasive check. Reordering all the functions could break it much more. + BoundingBoxf3 tbb_this = (!obj->instances.empty() ? obj->instances[0]->transform_bounding_box(bb_this) : bb_this); + + if (!tbb.defined) + tbb = tbb_this; + else if (tbb.intersects(tbb_this) || tbb.shares_boundary(tbb_this)) + // The volumes has intersects bounding boxes or share some boundary + return true; + } + return false; +} + +static bool looks_like_imperial_units(const Model& model) +{ + if (model.objects.empty()) + return false; + + for (ModelObject* obj : model.objects) + if (ModelProcessing::get_object_mesh_stats(obj).volume < ModelProcessing::volume_threshold_inches) { + if (!obj->is_cut()) + return true; + bool all_cut_parts_look_like_imperial_units = true; + for (ModelObject* obj_other : model.objects) { + if (obj_other == obj) + continue; + if (obj_other->cut_id.is_equal(obj->cut_id) && ModelProcessing::get_object_mesh_stats(obj_other).volume >= ModelProcessing::volume_threshold_inches) { + all_cut_parts_look_like_imperial_units = false; + break; + } + } + if (all_cut_parts_look_like_imperial_units) + return true; + } + + return false; +} + +static bool looks_like_saved_in_meters(const Model& model) +{ + if (model.objects.size() == 0) + return false; + + for (ModelObject* obj : model.objects) + if (ModelProcessing::get_object_mesh_stats(obj).volume < ModelProcessing::volume_threshold_meters) + return true; + + return false; +} + +static constexpr const double zero_volume = 0.0000000001; + +static int removed_objects_with_zero_volume(Model& model) +{ + if (model.objects.size() == 0) + return 0; + + int removed = 0; + for (int i = int(model.objects.size()) - 1; i >= 0; i--) + if (ModelProcessing::get_object_mesh_stats(model.objects[i]).volume < zero_volume) { + model.delete_object(size_t(i)); + removed++; + } + return removed; +} + +Model load_model(const std::string& input_file, + LoadAttributes options/* = LoadAttribute::AddDefaultInstances*/, + LoadStats* stats/*= nullptr*/) +{ + Model model = read_model_from_file(input_file, options); + + for (auto obj : model.objects) + if (obj->name.empty()) + obj->name = boost::filesystem::path(obj->input_file).filename().string(); + + if (stats) { + // 3mf contains information about units, so there is no need to detect possible convertions for these files + bool from_3mf = boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip"); + + stats->deleted_objects_cnt = removed_objects_with_zero_volume(model); + stats->looks_like_multipart_object = looks_like_multipart_object(model); + stats->looks_like_saved_in_meters = !from_3mf && looks_like_saved_in_meters(model); + stats->looks_like_imperial_units = !from_3mf && looks_like_imperial_units(model); + } + + return model; +} + +Model load_model_with_config(const std::string& input_file, + DynamicPrintConfig* config, + ConfigSubstitutionContext* config_substitutions, + boost::optional& prusaslicer_generator_version, + LoadAttributes options, + LoadStats* stats) +{ + Model model = read_all_from_file(input_file, config, config_substitutions, prusaslicer_generator_version, options); + + if (stats && !model.mesh().empty()) { + stats->deleted_objects_cnt = removed_objects_with_zero_volume(model); + stats->looks_like_multipart_object = looks_like_multipart_object(model); + } + + return model; +} } diff --git a/src/libslic3r/FileReader.hpp b/src/libslic3r/FileReader.hpp index a3b4ef9a82..ab3cb63132 100644 --- a/src/libslic3r/FileReader.hpp +++ b/src/libslic3r/FileReader.hpp @@ -12,8 +12,6 @@ ///|/ #pragma once -//#include "TriangleMesh.hpp" - #include "PrintConfig.hpp" #include "enum_bitmask.hpp" @@ -30,19 +28,32 @@ namespace FileReader }; using LoadAttributes = enum_bitmask; - Model read_from_file(const std::string& input_file, - DynamicPrintConfig* config = nullptr, - ConfigSubstitutionContext* config_substitutions = nullptr, - LoadAttributes options = LoadAttribute::AddDefaultInstances); - Model read_from_archive(const std::string& input_file, - DynamicPrintConfig* config, - ConfigSubstitutionContext* config_substitutions, - boost::optional &prusaslicer_generator_version, - LoadAttributes options = LoadAttribute::AddDefaultInstances); + struct LoadStats { + int deleted_objects_cnt { 0 }; + bool looks_like_saved_in_meters { false }; + bool looks_like_imperial_units { false }; + bool looks_like_multipart_object { false }; + }; - Model load_model(const std::string& input_file); - Model load_model(const std::string& input_file, std::string& errors); + // Load model from input file and return the its mesh. + // Throw RuntimeError if some problem was detected during model loading TriangleMesh load_mesh(const std::string& input_file); + + // Load model from input file and fill statistics if it's required. + // In respect to the params will be applied needed convertions over the model. + // Exceptions don't catched inside + Model load_model(const std::string& input_file, + LoadAttributes options = LoadAttribute::AddDefaultInstances, + LoadStats* statistics = nullptr); + + // Load model, config and config substitutions from input file and fill statistics if it's required. + // Exceptions don't catched inside + Model load_model_with_config(const std::string& input_file, + DynamicPrintConfig* config, + ConfigSubstitutionContext* config_substitutions, + boost::optional &prusaslicer_generator_version, + LoadAttributes options, + LoadStats* statistics = nullptr); } ENABLE_ENUM_BITMASK_OPERATORS(FileReader::LoadAttribute) diff --git a/src/libslic3r/ModelProcessing.cpp b/src/libslic3r/ModelProcessing.cpp index 46940a30f6..7fb2fc62a6 100644 --- a/src/libslic3r/ModelProcessing.cpp +++ b/src/libslic3r/ModelProcessing.cpp @@ -14,35 +14,11 @@ #include "Model.hpp" #include "ModelProcessing.hpp" +#include +#include + namespace Slic3r::ModelProcessing { -bool looks_like_multipart_object(const Model& model) -{ - if (model.objects.size() <= 1) - return false; - - BoundingBoxf3 tbb; - - for (const ModelObject* obj : model.objects) { - if (obj->volumes.size() > 1 || obj->config.keys().size() > 1) - return false; - - BoundingBoxf3 bb_this = obj->volumes[0]->mesh().bounding_box(); - - // FIXME: There is sadly the case when instances are empty (AMF files). The normalization of instances in that - // case is performed only after this function is called. For now (shortly before the 2.7.2 release, let's - // just do this non-invasive check. Reordering all the functions could break it much more. - BoundingBoxf3 tbb_this = (!obj->instances.empty() ? obj->instances[0]->transform_bounding_box(bb_this) : bb_this); - - if (!tbb.defined) - tbb = tbb_this; - else if (tbb.intersects(tbb_this) || tbb.shares_boundary(tbb_this)) - // The volumes has intersects bounding boxes or share some boundary - return true; - } - return false; -} - // Generate next extruder ID string, in the range of (1, max_extruders). static inline int auto_extruder_id(unsigned int max_extruders, unsigned int& cntr) { @@ -100,33 +76,6 @@ void convert_to_multipart_object(Model& model, unsigned int max_extruders) model.add_object(*object); } -static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3; - -bool looks_like_imperial_units(const Model& model) -{ - if (model.objects.empty()) - return false; - - for (ModelObject* obj : model.objects) - if (get_object_mesh_stats(obj).volume < volume_threshold_inches) { - if (!obj->is_cut()) - return true; - bool all_cut_parts_look_like_imperial_units = true; - for (ModelObject* obj_other : model.objects) { - if (obj_other == obj) - continue; - if (obj_other->cut_id.is_equal(obj->cut_id) && get_object_mesh_stats(obj_other).volume >= volume_threshold_inches) { - all_cut_parts_look_like_imperial_units = false; - break; - } - } - if (all_cut_parts_look_like_imperial_units) - return true; - } - - return false; -} - void convert_from_imperial_units(Model& model, bool only_small_volumes) { static constexpr const float in_to_mm = 25.4f; @@ -148,20 +97,6 @@ void convert_from_imperial_units(ModelVolume* volume) volume->source.is_converted_from_inches = true; } -static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1 - -bool looks_like_saved_in_meters(const Model& model) -{ - if (model.objects.size() == 0) - return false; - - for (ModelObject* obj : model.objects) - if (get_object_mesh_stats(obj).volume < volume_threshold_meters) - return true; - - return false; -} - void convert_from_meters(Model& model, bool only_small_volumes) { static constexpr const double m_to_mm = 1000; @@ -244,23 +179,6 @@ void convert_units(Model& model_to, ModelObject* object_from, ConversionType con BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - end"; } - -static constexpr const double zero_volume = 0.0000000001; - -int removed_objects_with_zero_volume(Model& model) -{ - if (model.objects.size() == 0) - return 0; - - int removed = 0; - for (int i = int(model.objects.size()) - 1; i >= 0; i--) - if (get_object_mesh_stats(model.objects[i]).volume < zero_volume) { - model.delete_object(size_t(i)); - removed++; - } - return removed; -} - TriangleMeshStats get_object_mesh_stats(const ModelObject* object) { TriangleMeshStats full_stats; diff --git a/src/libslic3r/ModelProcessing.hpp b/src/libslic3r/ModelProcessing.hpp index 60485619f9..816bf83a5b 100644 --- a/src/libslic3r/ModelProcessing.hpp +++ b/src/libslic3r/ModelProcessing.hpp @@ -30,21 +30,19 @@ enum class ConversionType : int { namespace ModelProcessing { - bool looks_like_multipart_object(const Model& model); + static constexpr const double volume_threshold_inches = 9.0; // 9 = 3*3*3; + static constexpr const double volume_threshold_meters = 0.001; // 0.001 = 0.1*0.1*0.1 + void convert_to_multipart_object(Model& model, unsigned int max_extruders); - bool looks_like_imperial_units(const Model& model); void convert_from_imperial_units(Model& model, bool only_small_volumes); void convert_from_imperial_units(ModelVolume* volume); - bool looks_like_saved_in_meters(const Model& model); void convert_from_meters(Model& model, bool only_small_volumes); void convert_from_meters(ModelVolume* volume); void convert_units(Model& model_to, ModelObject* object_from, ConversionType conv_type, std::vector volume_idxs); - int removed_objects_with_zero_volume(Model& model); - // Get full stl statistics for all object's meshes TriangleMeshStats get_object_mesh_stats(const ModelObject* object); // Get count of errors in the mesh diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 93efb19381..27315f8078 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -800,9 +800,7 @@ void GUI_App::post_init() if (plater()->load_files(fns) && this->init_params->input_files.size() == 1) { // Update application titlebar when opening a project file const std::string& filename = this->init_params->input_files.front(); - if (boost::algorithm::iends_with(filename, ".amf") || - boost::algorithm::iends_with(filename, ".amf.xml") || - boost::algorithm::iends_with(filename, ".3mf")) + if (boost::algorithm::iends_with(filename, ".3mf")) this->plater()->set_project_filename(from_u8(filename)); } if (this->init_params->delete_after_load) { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 23a49fe0ca..766a121f56 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -612,11 +612,9 @@ private: bool show_warning_dialog { false }; }; -const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); +const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|3mf)", std::regex::icase); const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase); -const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase); const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase); -const std::regex Plater::priv::pattern_prusa(".*prusa", std::regex::icase); const std::regex Plater::priv::pattern_zip(".*zip", std::regex::icase); const std::regex Plater::priv::pattern_printRequest(".*printRequest", std::regex::icase); @@ -1207,7 +1205,7 @@ void Plater::notify_about_installed_presets() std::vector Plater::priv::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units/* = false*/) { - if (input_files.empty()) { return std::vector(); } + if (input_files.empty()) { return std::vector(); } auto *nozzle_dmrs = config->opt("nozzle_diameter"); @@ -1241,7 +1239,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ wxBusyCursor busy; - auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model(); + auto *extra_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model(); std::vector obj_idxs; boost::optional prusaslicer_generator_version; @@ -1270,9 +1268,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } const bool type_3mf = std::regex_match(path.string(), pattern_3mf) || std::regex_match(path.string(), pattern_zip); - const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf); const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf); - const bool type_prusa = std::regex_match(path.string(), pattern_prusa); const bool type_printRequest = std::regex_match(path.string(), pattern_printRequest); if (type_printRequest && printer_technology != ptSLA) { @@ -1283,96 +1279,38 @@ std::vector Plater::priv::load_files(const std::vector& input_ } Slic3r::Model model; - bool is_project_file = type_prusa; + bool is_project_file = false; + +#ifdef __linux__ + // On Linux Constructor of the ProgressDialog calls DisableOtherWindows() function which causes a disabling of all children of the find_toplevel_parent(q) + // And a destructor of the ProgressDialog calls ReenableOtherWindows() function which revert previously disabled children. + // But if printer technology will be changes during project loading, + // then related SLA Print and Materials Settings or FFF Print and Filaments Settings will be unparent from the wxNoteBook + // and that is why they will never be enabled after destruction of the ProgressDialog. + // So, distroy progress_gialog if we are loading project file + if (input_files_size == 1 && progress_dlg) { + progress_dlg->Destroy(); + progress_dlg = nullptr; + } +#endif + + DynamicPrintConfig config; + PrinterTechnology loaded_printer_technology{ ptFFF }; + + DynamicPrintConfig config_loaded; + ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable }; + + if (!type_3mf) + load_config = false; // just 3mf file contains config + + FileReader::LoadStats load_stats; try { - if (type_3mf || type_zip_amf) { -#ifdef __linux__ - // On Linux Constructor of the ProgressDialog calls DisableOtherWindows() function which causes a disabling of all children of the find_toplevel_parent(q) - // And a destructor of the ProgressDialog calls ReenableOtherWindows() function which revert previously disabled children. - // But if printer technology will be changes during project loading, - // then related SLA Print and Materials Settings or FFF Print and Filaments Settings will be unparent from the wxNoteBook - // and that is why they will never be enabled after destruction of the ProgressDialog. - // So, distroy progress_gialog if we are loading project file - if (input_files_size == 1 && progress_dlg) { - progress_dlg->Destroy(); - progress_dlg = nullptr; - } -#endif - DynamicPrintConfig config; - PrinterTechnology loaded_printer_technology {ptFFF}; - { - DynamicPrintConfig config_loaded; - ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable }; - model = FileReader::read_from_archive( - path.string(), - &config_loaded, - &config_substitutions, - prusaslicer_generator_version, - only_if(load_config, Model::LoadAttribute::CheckVersion) - ); - if (load_config && !config_loaded.empty()) { - // Based on the printer technology field found in the loaded config, select the base for the config, - loaded_printer_technology = Preset::printer_technology(config_loaded); - - config.apply(loaded_printer_technology == ptFFF ? - static_cast(FullPrintConfig::defaults()) : - static_cast(SLAFullPrintConfig::defaults())); - // Set all the nullable values in defaults to nils. - config.null_nullables(); - // and place the loaded config over the base. - config += std::move(config_loaded); - } - if (! config_substitutions.empty()) - show_substitutions_info(config_substitutions.substitutions, filename.string()); - - if (load_config) { - this->model.get_custom_gcode_per_print_z_vector() = model.get_custom_gcode_per_print_z_vector(); - this->model.get_wipe_tower_vector() = model.get_wipe_tower_vector(); - } - } - - if (load_config) { - if (!config.empty()) { - const auto* post_process = config.opt("post_process"); - if (post_process != nullptr && !post_process->values.empty()) { - // TRN The placeholder is either "3MF" or "AMF" - wxString msg = GUI::format_wxstr(_L("The selected %1% file contains a post-processing script.\n" - "Please review the script carefully before exporting G-code."), type_3mf ? "3MF" : "AMF" ); - std::string text; - for (const std::string& s : post_process->values) - text += s; - - InfoDialog msg_dlg(nullptr, msg, from_u8(text), true, wxOK | wxICON_WARNING); - msg_dlg.set_caption(wxString(SLIC3R_APP_NAME " - ") + _L("Attention!")); - msg_dlg.ShowModal(); - } - - Preset::normalize(config); - PresetBundle* preset_bundle = wxGetApp().preset_bundle; - preset_bundle->load_config_model(filename.string(), std::move(config)); - q->notify_about_installed_presets(); - - //if (loaded_printer_technology == ptFFF) - // CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z(), &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); - // Update filament colors for the MM-printer profile in the full config - // to avoid black (default) colors for Extruders in the ObjectList, - // when for extruder colors are used filament colors - q->update_filament_colors_in_full_config(); - is_project_file = true; - } - if (!in_temp) - wxGetApp().app_config->update_config_dir(path.parent_path().string()); - } + if (load_config) { + model = FileReader::load_model_with_config(path.string(), &config_loaded, &config_substitutions, prusaslicer_generator_version, FileReader::LoadAttribute::CheckVersion, &load_stats); } - else { - model = FileReader::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, FileReader::LoadAttribute::CheckVersion)); - for (auto obj : model.objects) - if (obj->name.empty()) - obj->name = fs::path(obj->input_file).filename().string(); + else if (load_model) { + model = FileReader::load_model(path.string(), FileReader::LoadAttributes{}, &load_stats); } } catch (const ConfigurationError &e) { std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what(); @@ -1383,33 +1321,80 @@ std::vector Plater::priv::load_files(const std::vector& input_ continue; } - if (load_model) { - // The model should now be initialized + if (load_config) { - auto convert_from_imperial_units = [](Model& model, bool only_small_volumes) { - ModelProcessing::convert_from_imperial_units(model, only_small_volumes); -// wxGetApp().app_config->set("use_inches", "1"); -// wxGetApp().sidebar().update_ui_from_settings(); - }; + if (!config_loaded.empty()) { + // Based on the printer technology field found in the loaded config, select the base for the config, + loaded_printer_technology = Preset::printer_technology(config_loaded); + + config.apply(loaded_printer_technology == ptFFF ? + static_cast(FullPrintConfig::defaults()) : + static_cast(SLAFullPrintConfig::defaults())); + // Set all the nullable values in defaults to nils. + config.null_nullables(); + // and place the loaded config over the base. + config += std::move(config_loaded); + } + if (!config_substitutions.empty()) + show_substitutions_info(config_substitutions.substitutions, filename.string()); + + if (!config.empty()) { + const auto* post_process = config.opt("post_process"); + if (post_process != nullptr && !post_process->values.empty()) { + wxString msg = _L("The selected 3MF file contains a post-processing script.\n" + "Please review the script carefully before exporting G-code."); + std::string text; + for (const std::string& s : post_process->values) + text += s; + + InfoDialog msg_dlg(nullptr, msg, from_u8(text), true, wxOK | wxICON_WARNING); + msg_dlg.set_caption(wxString(SLIC3R_APP_NAME " - ") + _L("Attention!")); + msg_dlg.ShowModal(); + } + + Preset::normalize(config); //??? + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + preset_bundle->load_config_model(filename.string(), std::move(config)); + q->notify_about_installed_presets(); + + //if (loaded_printer_technology == ptFFF && load_model) + // CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z(), &preset_bundle->project_config); + + // For exporting from the 3mf we shouldn't check printer_presets for the containing information about "Print Host upload" + wxGetApp().load_current_presets(false); + // Update filament colors for the MM-printer profile in the full config + // to avoid black (default) colors for Extruders in the ObjectList, + // when for extruder colors are used filament colors + q->update_filament_colors_in_full_config(); + is_project_file = true; + } + + this->model.get_custom_gcode_per_print_z_vector() = model.get_custom_gcode_per_print_z_vector(); + this->model.get_wipe_tower_vector() = model.get_wipe_tower_vector(); + + if (!in_temp) + wxGetApp().app_config->update_config_dir(path.parent_path().string()); + } + + if (load_model) { if (!is_project_file) { - if (int deleted_objects = removed_objects_with_zero_volume(model); deleted_objects > 0) { + + if (load_stats.deleted_objects_cnt > 0) { MessageDialog(q, format_wxstr(_L_PLURAL( "Object size from file %s appears to be zero.\n" "This object has been removed from the model", "Objects size from file %s appears to be zero.\n" - "These objects have been removed from the model", deleted_objects), from_path(filename)) + "\n", + "These objects have been removed from the model", load_stats.deleted_objects_cnt), from_path(filename)) + "\n", _L("The size of the object is zero"), wxICON_INFORMATION | wxOK).ShowModal(); } - if (imperial_units) + + if (imperial_units) { // Convert even if the object is big. - convert_from_imperial_units(model, false); - else if (!type_3mf && looks_like_saved_in_meters(model)) { - auto convert_model_if = [](Model& model, bool condition) { - if (condition) - //FIXME up-scale only the small parts? - convert_from_meters(model, true); - }; + // It's a case, when we load model from STL, saved in imperial units + ModelProcessing::convert_from_imperial_units(model, false); + } + else if (load_stats.looks_like_saved_in_meters) { if (answer_convert_from_meters == wxOK_DEFAULT) { RichMessageDialog dlg(q, format_wxstr(_L_PLURAL( "The dimensions of the object from file %s seem to be defined in meters.\n" @@ -1421,17 +1406,12 @@ std::vector Plater::priv::load_files(const std::vector& input_ int answer = dlg.ShowModal(); if (dlg.IsCheckBoxChecked()) answer_convert_from_meters = answer; - else - convert_model_if(model, answer == wxID_YES); } - convert_model_if(model, answer_convert_from_meters == wxID_YES); + if (answer_convert_from_meters == wxID_YES) + //FIXME up-scale only the small parts? + ModelProcessing::convert_from_meters(model, true); } - else if (!type_3mf && looks_like_imperial_units(model)) { - auto convert_model_if = [convert_from_imperial_units](Model& model, bool condition) { - if (condition) - //FIXME up-scale only the small parts? - convert_from_imperial_units(model, true); - }; + else if (load_stats.looks_like_imperial_units) { if (answer_convert_from_imperial_units == wxOK_DEFAULT) { RichMessageDialog dlg(q, format_wxstr(_L_PLURAL( "The dimensions of the object from file %s seem to be defined in inches.\n" @@ -1443,13 +1423,13 @@ std::vector Plater::priv::load_files(const std::vector& input_ int answer = dlg.ShowModal(); if (dlg.IsCheckBoxChecked()) answer_convert_from_imperial_units = answer; - else - convert_model_if(model, answer == wxID_YES); } - convert_model_if(model, answer_convert_from_imperial_units == wxID_YES); + if (answer_convert_from_imperial_units == wxID_YES) + //FIXME up-scale only the small parts? + ModelProcessing::convert_from_imperial_units(model, true); } - if (!type_printRequest && looks_like_multipart_object(model)) { + if (load_stats.looks_like_multipart_object) { if (answer_consider_as_multi_part_objects == wxOK_DEFAULT) { RichMessageDialog dlg(q, _L( "This file contains several objects positioned at multiple heights.\n" @@ -1460,14 +1440,13 @@ std::vector Plater::priv::load_files(const std::vector& input_ int answer = dlg.ShowModal(); if (dlg.IsCheckBoxChecked()) answer_consider_as_multi_part_objects = answer; - if (answer == wxID_YES) - convert_to_multipart_object(model, nozzle_dmrs->size()); } - else if (answer_consider_as_multi_part_objects == wxID_YES) - convert_to_multipart_object(model, nozzle_dmrs->size()); + if (/*!type_printRequest && */answer_consider_as_multi_part_objects == wxID_YES) //! type_printRequest is no need here, SLA allow multipart object now + ModelProcessing::convert_to_multipart_object(model, nozzle_dmrs->size()); } } - if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) { + + if ((wxGetApp().get_mode() == comSimple) && type_3mf && model_has_advanced_features(model)) { MessageDialog msg_dlg(q, _L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?")+"\n", _L("Detected advanced data"), wxICON_WARNING | wxOK | wxCANCEL); if (msg_dlg.ShowModal() == wxID_OK) { @@ -1475,11 +1454,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ view3D->set_as_dirty(); } else - return obj_idxs; + continue;// return obj_idxs; } for (ModelObject* model_object : model.objects) { - if (!type_3mf && !type_zip_amf) { + if (!type_3mf) { model_object->center_around_origin(false); if (type_any_amf && model_object->instances.empty()) { ModelInstance* instance = model_object->add_instance(); @@ -1494,12 +1473,12 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } } - if (type_printRequest) { + + if (type_printRequest ) { assert(model.materials.size()); for (const auto& material : model.materials) { - std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, - Preset::remove_suffix_modified(material.first)); + std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, material.first); Preset* prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); if (!prst) { //did not find compatible profile // try find alias of material comaptible with another print profile - if exists, use the print profile @@ -1511,8 +1490,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (it->name != edited_print_name) { BOOST_LOG_TRIVIAL(error) << it->name; wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(it->name, false); - preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, - Preset::remove_suffix_modified(material.first)); + preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, material.first); prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); if (prst) { found = true; @@ -1542,14 +1520,14 @@ std::vector Plater::priv::load_files(const std::vector& input_ } if (one_by_one) { - if ((type_3mf && !is_project_file) || (type_any_amf && !type_zip_amf)) + if ((type_3mf && !is_project_file) || type_any_amf) model.center_instances_around_point(this->bed.build_volume().bed_center()); auto loaded_idxs = load_model_objects(model.objects, is_project_file); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); } else { // This must be an .stl or .obj file, which may contain a maximum of one volume. for (const ModelObject* model_object : model.objects) { - new_model->add_object(*model_object); + extra_model->add_object(*model_object); } } @@ -1558,17 +1536,17 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } - if (new_model != nullptr && new_model->objects.size() > 1) { + if (extra_model != nullptr && extra_model->objects.size() > 1) { MessageDialog msg_dlg(q, _L( "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" "these files to represent a single object having multiple parts?") + "\n", _L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO); if (msg_dlg.ShowModal() == wxID_YES) { - convert_to_multipart_object(*new_model, nozzle_dmrs->values.size()); + ModelProcessing::convert_to_multipart_object(*extra_model, nozzle_dmrs->values.size()); } - auto loaded_idxs = load_model_objects(new_model->objects); + auto loaded_idxs = load_model_objects(extra_model->objects); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); } @@ -2677,9 +2655,9 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) - convert_from_imperial_units(new_volume); + ModelProcessing::convert_from_imperial_units(new_volume); else if (old_volume->source.is_converted_from_meters) - convert_from_meters(new_volume); + ModelProcessing::convert_from_meters(new_volume); if (old_volume->mesh().its == new_volume->mesh().its) { // This function is called both from reload_from_disk and replace_with_stl. @@ -2972,9 +2950,9 @@ void Plater::priv::reload_from_disk() new_volume->source.volume_idx = old_volume->source.volume_idx; assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) - convert_from_imperial_units(new_volume); + ModelProcessing::convert_from_imperial_units(new_volume); else if (old_volume->source.is_converted_from_meters) - convert_from_meters(new_volume); + ModelProcessing::convert_from_meters(new_volume); std::swap(old_model_object->volumes[vol_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); if (!sinking) @@ -5051,86 +5029,6 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path) { return false; } -#if 0 - // 1 project, 0 models - behave like drag n drop - if (project_paths.size() == 1 && non_project_paths.empty()) - { - wxArrayString aux; - aux.Add(from_u8(project_paths.front().string())); - load_files(aux); - //load_files(project_paths, true, true); - boost::system::error_code ec; - fs::remove(project_paths.front(), ec); - if (ec) - BOOST_LOG_TRIVIAL(error) << ec.message(); - return true; - } - // 1 model (or more and other instances are not allowed), 0 projects - open geometry - if (project_paths.empty() && (non_project_paths.size() == 1 || wxGetApp().app_config->get_bool("single_instance"))) - { - load_files(non_project_paths, true, false); - boost::system::error_code ec; - fs::remove(non_project_paths.front(), ec); - if (ec) - BOOST_LOG_TRIVIAL(error) << ec.message(); - return true; - } - - bool delete_after = true; - - LoadProjectsDialog dlg(project_paths); - if (dlg.ShowModal() == wxID_OK) { - LoadProjectsDialog::LoadProjectOption option = static_cast(dlg.get_action()); - switch (option) - { - case LoadProjectsDialog::LoadProjectOption::AllGeometry: { - load_files(project_paths, true, false); - load_files(non_project_paths, true, false); - break; - } - case LoadProjectsDialog::LoadProjectOption::AllNewWindow: { - delete_after = false; - for (const fs::path& path : project_paths) { - wxString f = from_path(path); - start_new_slicer(&f, false); - } - for (const fs::path& path : non_project_paths) { - wxString f = from_path(path); - start_new_slicer(&f, false); - } - break; - } - case LoadProjectsDialog::LoadProjectOption::OneProject: { - int pos = dlg.get_selected(); - assert(pos >= 0 && pos < project_paths.size()); - if (wxGetApp().can_load_project()) - load_project(from_path(project_paths[pos])); - project_paths.erase(project_paths.begin() + pos); - load_files(project_paths, true, false); - load_files(non_project_paths, true, false); - break; - } - case LoadProjectsDialog::LoadProjectOption::OneConfig: { - int pos = dlg.get_selected(); - assert(pos >= 0 && pos < project_paths.size()); - std::vector aux; - aux.push_back(project_paths[pos]); - load_files(aux, false, true); - project_paths.erase(project_paths.begin() + pos); - load_files(project_paths, true, false); - load_files(non_project_paths, true, false); - break; - } - case LoadProjectsDialog::LoadProjectOption::Unknown: - default: - assert(false); - break; - } - } - - if (!delete_after) - return true; -#else // 1 project file and some models - behave like drag n drop of 3mf and then load models if (project_paths.size() == 1) { @@ -5157,7 +5055,6 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path) // load all projects and all models as geometry load_files(project_paths, true, false); load_files(non_project_paths, true, false); -#endif // 0 for (const fs::path& path : project_paths) { @@ -5311,7 +5208,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/* for (std::vector::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) { std::string filename = (*it).filename().string(); - bool handle_as_project = (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")); + bool handle_as_project = boost::algorithm::iends_with(filename, ".3mf"); if (boost::algorithm::iends_with(filename, ".zip") && is_project_3mf(it->string())) { BOOST_LOG_TRIVIAL(warning) << "File with .zip extension is 3mf project, opening as it would have .3mf extension: " << *it; handle_as_project = true; @@ -5319,8 +5216,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/* if (handle_as_project && load_just_one_file) { ProjectDropDialog::LoadType load_type = ProjectDropDialog::LoadType::Unknown; { - if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string())) || - (boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) + if (boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string())) load_type = ProjectDropDialog::LoadType::LoadGeometry; else { if (wxGetApp().app_config->get_bool("show_drop_project_dialog")) {