diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index ca177e6c5..f74545e98 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -271,6 +271,105 @@ namespace Slic3r { //! return same string #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) +void XMLCALL PrusaFileParser::start_element_handler(void *userData, const char *name, const char **attributes) +{ + PrusaFileParser *prusa_parser = (PrusaFileParser *) userData; + if (prusa_parser != nullptr) { prusa_parser->_start_element_handler(name, attributes); } +} + +void XMLCALL PrusaFileParser::characters_handler(void *userData, const XML_Char *s, int len) +{ + PrusaFileParser *prusa_parser = (PrusaFileParser *) userData; + if (prusa_parser != nullptr) { prusa_parser->_characters_handler(s, len); } +} + +bool PrusaFileParser::check_3mf_from_prusa(const std::string filename) +{ + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + if (!open_zip_reader(&archive, filename)) { + // throw Slic3r::RuntimeError("Loading 3mf file failed."); + return false; + } + + const std::string sub_relationship_file = "3D/_rels/3dmodel.model.rels"; + int sub_index = mz_zip_reader_locate_file(&archive, sub_relationship_file.c_str(), nullptr, 0); + if (sub_index == -1) { + const std::string model_file = "3D/3dmodel.model"; + int model_file_index = mz_zip_reader_locate_file(&archive, model_file.c_str(), nullptr, 0); + if (model_file_index != -1) { + int depth = 0; + m_parser = XML_ParserCreate(nullptr); + XML_SetUserData(m_parser, (void *) this); + XML_SetElementHandler(m_parser, start_element_handler, nullptr); + XML_SetCharacterDataHandler(m_parser, characters_handler); + + mz_zip_archive_file_stat stat; + if (!mz_zip_reader_file_stat(&archive, model_file_index, &stat)) goto EXIT; + + void *parser_buffer = XML_GetBuffer(m_parser, (int) stat.m_uncomp_size); + if (parser_buffer == nullptr) goto EXIT; + + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t) stat.m_uncomp_size, 0); + if (res == 0) goto EXIT; + + XML_ParseBuffer(m_parser, (int) stat.m_uncomp_size, 1); + } + } + +EXIT: + close_zip_reader(&archive); + return m_from_prusa; +} + +void PrusaFileParser::_characters_handler(const XML_Char *s, int len) +{ + if (m_is_application_key) { + std::string str(s, len); + if (!str.empty() && str.find("PrusaSlicer") != std::string::npos) m_from_prusa = true; + } +} + +void PrusaFileParser::_start_element_handler(const char *name, const char **attributes) +{ + if (::strcmp(name, "metadata") == 0) { + unsigned int num_attributes = (unsigned int) XML_GetSpecifiedAttributeCount(m_parser); + + std::string str_name = get_attribute_value_string(attributes, num_attributes, "name"); + if (!str_name.empty() && str_name.find("Application") != std::string::npos) { m_is_application_key = true; } + } +} + +const char *PrusaFileParser::get_attribute_value_charptr(const char **attributes, unsigned int attributes_size, const char *attribute_key) +{ + if ((attributes == nullptr) || (attributes_size == 0) || (attributes_size % 2 != 0) || (attribute_key == nullptr)) return nullptr; + + for (unsigned int a = 0; a < attributes_size; a += 2) { + if (::strcmp(attributes[a], attribute_key) == 0) return attributes[a + 1]; + } + + return nullptr; +} + +std::string PrusaFileParser::get_attribute_value_string(const char **attributes, unsigned int attributes_size, const char *attribute_key) +{ + const char *text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? text : ""; +} + +ModelVolumeType type_from_string(const std::string &s) +{ + // Legacy support + if (s == "1") return ModelVolumeType::PARAMETER_MODIFIER; + // New type (supporting the support enforcers & blockers) + if (s == "ModelPart") return ModelVolumeType::MODEL_PART; + if (s == "NegativeVolume") return ModelVolumeType::NEGATIVE_VOLUME; + if (s == "ParameterModifier") return ModelVolumeType::PARAMETER_MODIFIER; + if (s == "SupportEnforcer") return ModelVolumeType::SUPPORT_ENFORCER; + if (s == "SupportBlocker") return ModelVolumeType::SUPPORT_BLOCKER; + // Default value if invalud type string received. + return ModelVolumeType::MODEL_PART; +} // Base class with error messages management class _3MF_Base @@ -668,6 +767,7 @@ namespace Slic3r { std::string name(stat.m_filename); std::replace(name.begin(), name.end(), '\\', '/'); + /* if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { // extract slic3r layer heights profile file _extract_layer_heights_profile_config_from_archive(archive, stat); @@ -692,7 +792,9 @@ namespace Slic3r { // extract slic3r layer config ranges file _extract_custom_gcode_per_print_z_from_archive(archive, stat); } - else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { + */ + // only read the model config for Prusa 3mf + if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { // extract slic3r model config file if (!_extract_model_config_from_archive(archive, stat, model)) { close_zip_reader(&archive); @@ -1913,6 +2015,27 @@ namespace Slic3r { std::string key = get_attribute_value_string(attributes, num_attributes, KEY_ATTR); std::string value = get_attribute_value_string(attributes, num_attributes, VALUE_ATTR); + // filter the prusa model config keys + std::vector valid_keys = { + "name", + "volume_type", + "matrix", + "source_file", + "source_object_id", + "source_volume_id", + "source_offset_x", + "source_offset_y", + "source_offset_z", + "extruder", + "modifier" + }; + + auto itor = std::find(valid_keys.begin(), valid_keys.end(), key); + if (itor == valid_keys.end()) { + // do nothing if not valid keys + return true; + } + if (type == OBJECT_TYPE) object->second.metadata.emplace_back(key, value); else if (type == VOLUME_TYPE) { @@ -2044,7 +2167,7 @@ namespace Slic3r { else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); else if (metadata.key == VOLUME_TYPE_KEY) - volume->set_type(ModelVolume::type_from_string(metadata.value)); + volume->set_type(type_from_string(metadata.value)); else if (metadata.key == SOURCE_FILE_KEY) volume->source.input_file = metadata.value; else if (metadata.key == SOURCE_OBJECT_ID_KEY) diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index b91e90da7..9509b2c70 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -1,7 +1,30 @@ #ifndef slic3r_Format_3mf_hpp_ #define slic3r_Format_3mf_hpp_ +#include "../expat.h" namespace Slic3r { +// PrusaFileParser is used to check 3mf file is from Prusa +class PrusaFileParser +{ +public: + PrusaFileParser() {} + ~PrusaFileParser() {} + + bool check_3mf_from_prusa(const std::string filename); + void _start_element_handler(const char *name, const char **attributes); + void _characters_handler(const XML_Char *s, int len); + +private: + const char *get_attribute_value_charptr(const char **attributes, unsigned int attributes_size, const char *attribute_key); + std::string get_attribute_value_string(const char **attributes, unsigned int attributes_size, const char *attribute_key); + + static void XMLCALL start_element_handler(void *userData, const char *name, const char **attributes); + static void XMLCALL characters_handler(void *userData, const XML_Char *s, int len); +private: + bool m_from_prusa = false; + bool m_is_application_key = false; + XML_Parser m_parser; +}; /* The format for saving the SLA points was changing in the past. This enum holds the latest version that is being currently used. * Examples of the Slic3r_PE_sla_support_points.txt for historically used versions: diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2160ff627..34a97cf9c 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -33,6 +33,7 @@ // BBS: for segment #include "MeshBoolean.hpp" +#include "Format/3mf.hpp" namespace Slic3r { // BBS initialization of static variables @@ -202,7 +203,7 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c //BBS: add part plate related logic // BBS: backup & restore // Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ). -Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadStrategy options, PlateDataPtrs* plate_data, std::vector* project_presets, bool *is_bbl_3mf, Semver* file_version, Import3mfProgressFn proFn, BBLProject *project) +Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, En3mfType& out_file_type, LoadStrategy options, PlateDataPtrs* plate_data, std::vector* project_presets, Semver* file_version, Import3mfProgressFn proFn, BBLProject *project) { assert(config != nullptr); assert(config_substitutions != nullptr); @@ -210,15 +211,28 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig Model model; bool result = false; - if (boost::algorithm::iends_with(input_file, ".3mf")) - //BBS: add part plate related logic - // BBS: backup & restore - result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, is_bbl_3mf, file_version, proFn, options, project); + bool is_bbl_3mf; + if (boost::algorithm::iends_with(input_file, ".3mf")) { + PrusaFileParser prusa_file_parser; + if (prusa_file_parser.check_3mf_from_prusa(input_file)) { + // for Prusa 3mf + result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, true); + out_file_type = En3mfType::From_Prusa; + } else { + // BBS: add part plate related logic + // BBS: backup & restore + result = load_bbs_3mf(input_file.c_str(), config, config_substitutions, &model, plate_data, project_presets, &is_bbl_3mf, file_version, proFn, options, project); + } + } else if (boost::algorithm::iends_with(input_file, ".zip.amf")) - result = load_amf(input_file.c_str(), config, config_substitutions, &model, is_bbl_3mf); + result = load_amf(input_file.c_str(), config, config_substitutions, &model, &is_bbl_3mf); else throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); + if (out_file_type != En3mfType::From_Prusa) { + out_file_type = is_bbl_3mf ? En3mfType::From_BBS : En3mfType::From_Other; + } + if (!result) throw Slic3r::RuntimeError("Loading of a model file failed."); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 3a5b7e7f6..ed2774913 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -577,6 +577,12 @@ enum class ConversionType : int { CONV_FROM_METER, }; +enum class En3mfType : int { + From_BBS, + From_Prusa, + From_Other +}; + class FacetsAnnotation final : public ObjectWithTimestamp { public: // Assign the content if the timestamp differs, don't assign an ObjectID. @@ -1280,8 +1286,8 @@ public: // BBS: backup static Model read_from_archive( const std::string& input_file, - DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, - LoadStrategy options = LoadStrategy::AddDefaultInstances, PlateDataPtrs* plate_data = nullptr, std::vector* project_presets = nullptr, bool* is_bbl_3mf = nullptr, Semver* file_version = nullptr, Import3mfProgressFn proFn = nullptr, BBLProject* project = nullptr); + DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, En3mfType& out_file_type, + LoadStrategy options = LoadStrategy::AddDefaultInstances, PlateDataPtrs* plate_data = nullptr, std::vector* project_presets = nullptr, Semver* file_version = nullptr, Import3mfProgressFn proFn = nullptr, BBLProject* project = nullptr); // Add a new ModelObject to this Model, generate a new ID for this ModelObject. ModelObject* add_object(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 834a1a266..f7b7508b3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2642,11 +2642,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ // BBS: add part plate related logic PlateDataPtrs plate_data; - bool is_bbs_3mf; + En3mfType en_3mf_file_type = En3mfType::From_BBS; ConfigSubstitutionContext config_substitutions{ForwardCompatibilitySubstitutionRule::Enable}; std::vector project_presets; // BBS: backup & restore - model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, strategy, &plate_data, &project_presets, &is_bbs_3mf, + model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, en_3mf_file_type, strategy, &plate_data, &project_presets, &file_version, [this, &dlg, real_filename, progress_percent](int import_stage, int current, int total, bool &cancel) { bool cont = true; @@ -2657,11 +2657,40 @@ std::vector Plater::priv::load_files(const std::vector& input_ BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", plate_data.size %1%, project_preset.size %2%, is_bbs_3mf %3%, file_version %4% \n") % plate_data.size() % - project_presets.size() % is_bbs_3mf % file_version.to_string(); + project_presets.size() % (en_3mf_file_type == En3mfType::From_BBS) % file_version.to_string(); + + // add extruder for prusa model if the number of existing extruders is not enough + if (en_3mf_file_type == En3mfType::From_Prusa) { + std::set extruderIds; + for (ModelObject *o : model.objects) { + if (o->config.option("extruder")) extruderIds.insert(o->config.extruder()); + for (auto volume : o->volumes) { + if (volume->config.option("extruder")) extruderIds.insert(volume->config.extruder()); + for (int extruder : volume->get_extruders()) { extruderIds.insert(extruder); } + } + } + int size = extruderIds.size() == 0 ? 0 : *(extruderIds.rbegin()); + + int filament_size = sidebar->combos_filament().size(); + while (filament_size < 16 && filament_size < size) { + int filament_count = filament_size + 1; + wxColour new_col = Plater::get_next_color_for_filament(); + std::string new_color = new_col.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); + wxGetApp().preset_bundle->set_num_filaments(filament_count, new_color); + wxGetApp().plater()->on_filaments_change(filament_count); + ++filament_size; + } + wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); + } // BBS: version check Semver app_version = *(Semver::parse(SLIC3R_VERSION)); - if (load_config && (file_version.maj() != app_version.maj())) { + if (en_3mf_file_type == En3mfType::From_Prusa) { + // do not reset the model config + load_config = false; + show_info(q, _L("the 3mf is not compatible, load geometry data only!"), _L("Incompatible 3mf")); + } + else if (load_config && (file_version.maj() != app_version.maj())) { // version mismatch, only load geometries load_config = false; if (!load_model) {