#include "../libslic3r.h" #include "../Exception.hpp" #include "../Model.hpp" #include "../Preset.hpp" #include "../Utils.hpp" #include "../LocalesUtils.hpp" #include "../GCode.hpp" #include "../Geometry.hpp" #include "../GCode/ThumbnailData.hpp" #include "../Semver.hpp" #include "../Time.hpp" #include "../I18N.hpp" #include "bbs_3mf.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace pt = boost::property_tree; #include #include #include #include #include #include "miniz_extension.hpp" #include "nlohmann/json.hpp" #include // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, // https://github.com/boostorg/spirit/pull/586 // where the exported string is one digit shorter than it should be to guarantee lossless round trip. // The code is left here for the ocasion boost guys improve. #define EXPORT_3MF_USE_SPIRIT_KARMA_FP 0 #define WRITE_ZIP_LANGUAGE_ENCODING 1 // @see https://commons.apache.org/proper/commons-compress/apidocs/src-html/org/apache/commons/compress/archivers/zip/AbstractUnicodeExtraField.html struct ZipUnicodePathExtraField { static std::string encode(std::string const& u8path, std::string const& path) { std::string extra; if (u8path != path) { // 0x7075 - for Unicode filenames extra.push_back('\x75'); extra.push_back('\x70'); boost::uint16_t len = 5 + u8path.length(); extra.push_back((char)(len & 0xff)); extra.push_back((char)(len >> 8)); auto crc = mz_crc32(0, (unsigned char *) path.c_str(), path.length()); extra.push_back('\x01'); // version 1 extra.append((char *)&crc, (char *)&crc + 4); // Little Endian extra.append(u8path); } return extra; } static std::string decode(std::string const& extra, std::string const& path = {}) { char const * p = extra.data(); char const * e = p + extra.length(); while (p + 4 < e) { boost::uint16_t len = ((boost::uint16_t)p[2]) | ((boost::uint16_t)p[3] << 8); if (p[0] == '\x75' && p[1] == '\x70' && len >= 5 && p + 4 + len < e && p[4] == '\x01') { return std::string(p + 9, p + 4 + len); } else { p += 4 + len; } } return Slic3r::decode_path(path.c_str()); } }; // VERSION NUMBERS // 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. // 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files. // 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, meshes transformed back to their coordinate system on loading. // WARNING !! -> the version number has been rolled back to 1 // the next change should use 3 const unsigned int VERSION_BBS_3MF = 1; // Allow loading version 2 file as well. const unsigned int VERSION_BBS_3MF_COMPATIBLE = 2; const char* BBS_3MF_VERSION1 = "bamboo_slicer:Version3mf"; // definition of the metadata name saved into .model file const char* BBS_3MF_VERSION = "BambuStudio:3mfVersion"; //compatible with prusa currently // Painting gizmos data version numbers // 0 : initial version of fdm, seam, mm const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 0; const unsigned int SEAM_PAINTING_VERSION = 0; const unsigned int MM_PAINTING_VERSION = 0; const std::string BBS_FDM_SUPPORTS_PAINTING_VERSION = "BambuStudio:FdmSupportsPaintingVersion"; const std::string BBS_SEAM_PAINTING_VERSION = "BambuStudio:SeamPaintingVersion"; const std::string BBS_MM_PAINTING_VERSION = "BambuStudio:MmPaintingVersion"; const std::string BBL_MODEL_ID_TAG = "model_id"; const std::string BBL_MODEL_NAME_TAG = "Title"; const std::string BBL_ORIGIN_TAG = "Origin"; const std::string BBL_DESIGNER_TAG = "Designer"; const std::string BBL_DESIGNER_USER_ID_TAG = "DesignerUserId"; const std::string BBL_DESIGN_ID_TAG = "DesignId"; //const std::string BBL_DESIGNER_MODEL_ID_TAG = "DesignModelId"; const std::string BBL_DESIGNER_COVER_FILE_TAG = "DesignerCover"; const std::string BBL_DESCRIPTION_TAG = "Description"; const std::string BBL_COPYRIGHT_TAG = "CopyRight"; const std::string BBL_COPYRIGHT_NORMATIVE_TAG = "Copyright"; const std::string BBL_LICENSE_TAG = "License"; const std::string BBL_REGION_TAG = "Region"; const std::string BBL_MODIFICATION_TAG = "ModificationDate"; const std::string BBL_CREATION_DATE_TAG = "CreationDate"; const std::string BBL_APPLICATION_TAG = "Application"; const std::string BBL_MAKERLAB_TAG = "MakerLab"; const std::string BBL_MAKERLAB_VERSION_TAG = "MakerLabVersion"; const std::string BBL_PROFILE_TITLE_TAG = "ProfileTitle"; const std::string BBL_PROFILE_COVER_TAG = "ProfileCover"; const std::string BBL_PROFILE_DESCRIPTION_TAG = "ProfileDescription"; const std::string BBL_PROFILE_USER_ID_TAG = "ProfileUserId"; const std::string BBL_PROFILE_USER_NAME_TAG = "ProfileUserName"; const std::string MODEL_FOLDER = "3D/"; const std::string MODEL_EXTENSION = ".model"; const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA const std::string MODEL_RELS_FILE = "3D/_rels/3dmodel.model.rels"; //BBS: add metadata_folder const std::string METADATA_DIR = "Metadata/"; const std::string ACCESOR_DIR = "accesories/"; const std::string GCODE_EXTENSION = ".gcode"; const std::string THUMBNAIL_EXTENSION = ".png"; const std::string CALIBRATION_INFO_EXTENSION = ".json"; const std::string CONTENT_TYPES_FILE = "[Content_Types].xml"; const std::string RELATIONSHIPS_FILE = "_rels/.rels"; const std::string THUMBNAIL_FILE = "Metadata/plate_1.png"; const std::string THUMBNAIL_FOR_PRINTER_FILE = "Metadata/bbl_thumbnail.png"; const std::string PRINTER_THUMBNAIL_SMALL_FILE = "/Auxiliaries/.thumbnails/thumbnail_small.png"; const std::string PRINTER_THUMBNAIL_MIDDLE_FILE = "/Auxiliaries/.thumbnails/thumbnail_middle.png"; const std::string _3MF_COVER_FILE = "/Auxiliaries/.thumbnails/thumbnail_3mf.png"; //const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; //const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; const std::string BBS_PRINT_CONFIG_FILE = "Metadata/print_profile.config"; const std::string BBS_PROJECT_CONFIG_FILE = "Metadata/project_settings.config"; const std::string BBS_MODEL_CONFIG_FILE = "Metadata/model_settings.config"; const std::string BBS_MODEL_CONFIG_RELS_FILE = "Metadata/_rels/model_settings.config.rels"; const std::string SLICE_INFO_CONFIG_FILE = "Metadata/slice_info.config"; const std::string BBS_LAYER_HEIGHTS_PROFILE_FILE = "Metadata/layer_heights_profile.txt"; const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/layer_config_ranges.xml"; /*const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";*/ const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/custom_gcode_per_layer.xml"; const std::string AUXILIARY_DIR = "Auxiliaries/"; const std::string PROJECT_EMBEDDED_PRINT_PRESETS_FILE = "Metadata/print_setting_"; const std::string PROJECT_EMBEDDED_SLICE_PRESETS_FILE = "Metadata/process_settings_"; const std::string PROJECT_EMBEDDED_FILAMENT_PRESETS_FILE = "Metadata/filament_settings_"; const std::string PROJECT_EMBEDDED_PRINTER_PRESETS_FILE = "Metadata/machine_settings_"; const std::string CUT_INFORMATION_FILE = "Metadata/cut_information.xml"; const unsigned int AUXILIARY_STR_LEN = 12; const unsigned int METADATA_STR_LEN = 9; static constexpr const char* MODEL_TAG = "model"; static constexpr const char* RESOURCES_TAG = "resources"; static constexpr const char* COLOR_GROUP_TAG = "m:colorgroup"; static constexpr const char* COLOR_TAG = "m:color"; static constexpr const char* OBJECT_TAG = "object"; static constexpr const char* MESH_TAG = "mesh"; static constexpr const char* MESH_STAT_TAG = "mesh_stat"; static constexpr const char* VERTICES_TAG = "vertices"; static constexpr const char* VERTEX_TAG = "vertex"; static constexpr const char* TRIANGLES_TAG = "triangles"; static constexpr const char* TRIANGLE_TAG = "triangle"; static constexpr const char* COMPONENTS_TAG = "components"; static constexpr const char* COMPONENT_TAG = "component"; static constexpr const char* BUILD_TAG = "build"; static constexpr const char* ITEM_TAG = "item"; static constexpr const char* METADATA_TAG = "metadata"; static constexpr const char* FILAMENT_TAG = "filament"; static constexpr const char* SLICE_WARNING_TAG = "warning"; static constexpr const char* WARNING_MSG_TAG = "msg"; static constexpr const char *FILAMENT_ID_TAG = "id"; static constexpr const char* FILAMENT_TYPE_TAG = "type"; static constexpr const char *FILAMENT_COLOR_TAG = "color"; static constexpr const char *FILAMENT_USED_M_TAG = "used_m"; static constexpr const char *FILAMENT_USED_G_TAG = "used_g"; static constexpr const char *FILAMENT_TRAY_INFO_ID_TAG = "tray_info_idx"; static constexpr const char* CONFIG_TAG = "config"; static constexpr const char* VOLUME_TAG = "volume"; static constexpr const char* PART_TAG = "part"; static constexpr const char* PLATE_TAG = "plate"; static constexpr const char* INSTANCE_TAG = "model_instance"; //BBS static constexpr const char* ASSEMBLE_TAG = "assemble"; static constexpr const char* ASSEMBLE_ITEM_TAG = "assemble_item"; static constexpr const char* SLICE_HEADER_TAG = "header"; static constexpr const char* SLICE_HEADER_ITEM_TAG = "header_item"; // text_info static constexpr const char* TEXT_INFO_TAG = "text_info"; static constexpr const char* TEXT_ATTR = "text"; static constexpr const char* FONT_NAME_ATTR = "font_name"; static constexpr const char* FONT_INDEX_ATTR = "font_index"; static constexpr const char* FONT_SIZE_ATTR = "font_size"; static constexpr const char* THICKNESS_ATTR = "thickness"; static constexpr const char* EMBEDED_DEPTH_ATTR = "embeded_depth"; static constexpr const char* ROTATE_ANGLE_ATTR = "rotate_angle"; static constexpr const char* TEXT_GAP_ATTR = "text_gap"; static constexpr const char* BOLD_ATTR = "bold"; static constexpr const char* ITALIC_ATTR = "italic"; static constexpr const char* SURFACE_TEXT_ATTR = "surface_text"; static constexpr const char* KEEP_HORIZONTAL_ATTR = "keep_horizontal"; static constexpr const char* HIT_MESH_ATTR = "hit_mesh"; static constexpr const char* HIT_POSITION_ATTR = "hit_position"; static constexpr const char* HIT_NORMAL_ATTR = "hit_normal"; // BBS: encrypt static constexpr const char* RELATIONSHIP_TAG = "Relationship"; static constexpr const char* PID_ATTR = "pid"; static constexpr const char* PUUID_ATTR = "p:UUID"; static constexpr const char* PUUID_LOWER_ATTR = "p:uuid"; static constexpr const char* PPATH_ATTR = "p:path"; static constexpr const char *OBJECT_UUID_SUFFIX = "-61cb-4c03-9d28-80fed5dfa1dc"; static constexpr const char *OBJECT_UUID_SUFFIX2 = "-71cb-4c03-9d28-80fed5dfa1dc"; static constexpr const char *SUB_OBJECT_UUID_SUFFIX = "-81cb-4c03-9d28-80fed5dfa1dc"; static constexpr const char *COMPONENT_UUID_SUFFIX = "-b206-40ff-9872-83e8017abed1"; static constexpr const char* BUILD_UUID = "2c7c17d8-22b5-4d84-8835-1976022ea369"; static constexpr const char* BUILD_UUID_SUFFIX = "-b1ec-4553-aec9-835e5b724bb4"; static constexpr const char* TARGET_ATTR = "Target"; static constexpr const char* RELS_TYPE_ATTR = "Type"; static constexpr const char* UNIT_ATTR = "unit"; static constexpr const char* NAME_ATTR = "name"; static constexpr const char* COLOR_ATTR = "color"; static constexpr const char* TYPE_ATTR = "type"; static constexpr const char* ID_ATTR = "id"; static constexpr const char* X_ATTR = "x"; static constexpr const char* Y_ATTR = "y"; static constexpr const char* Z_ATTR = "z"; static constexpr const char* V1_ATTR = "v1"; static constexpr const char* V2_ATTR = "v2"; static constexpr const char* V3_ATTR = "v3"; static constexpr const char* OBJECTID_ATTR = "objectid"; static constexpr const char* TRANSFORM_ATTR = "transform"; // BBS static constexpr const char* OFFSET_ATTR = "offset"; static constexpr const char* PRINTABLE_ATTR = "printable"; static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; static constexpr const char* CUSTOM_SUPPORTS_ATTR = "paint_supports"; static constexpr const char* CUSTOM_SEAM_ATTR = "paint_seam"; static constexpr const char* MMU_SEGMENTATION_ATTR = "paint_color"; // BBS static constexpr const char* FACE_PROPERTY_ATTR = "face_property"; static constexpr const char* KEY_ATTR = "key"; static constexpr const char* VALUE_ATTR = "value"; static constexpr const char* FIRST_TRIANGLE_ID_ATTR = "firstid"; static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid"; static constexpr const char* SUBTYPE_ATTR = "subtype"; static constexpr const char* LOCK_ATTR = "locked"; static constexpr const char* BED_TYPE_ATTR = "bed_type"; static constexpr const char* PRINT_SEQUENCE_ATTR = "print_sequence"; static constexpr const char* FIRST_LAYER_PRINT_SEQUENCE_ATTR = "first_layer_print_sequence"; static constexpr const char* OTHER_LAYERS_PRINT_SEQUENCE_ATTR = "other_layers_print_sequence"; static constexpr const char* OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR = "other_layers_print_sequence_nums"; static constexpr const char* SPIRAL_VASE_MODE = "spiral_mode"; static constexpr const char* GCODE_FILE_ATTR = "gcode_file"; static constexpr const char* THUMBNAIL_FILE_ATTR = "thumbnail_file"; static constexpr const char* NO_LIGHT_THUMBNAIL_FILE_ATTR = "thumbnail_no_light_file"; static constexpr const char* TOP_FILE_ATTR = "top_file"; static constexpr const char* PICK_FILE_ATTR = "pick_file"; static constexpr const char* PATTERN_FILE_ATTR = "pattern_file"; static constexpr const char* PATTERN_BBOX_FILE_ATTR = "pattern_bbox_file"; static constexpr const char* OBJECT_ID_ATTR = "object_id"; static constexpr const char* INSTANCEID_ATTR = "instance_id"; static constexpr const char* IDENTIFYID_ATTR = "identify_id"; static constexpr const char* PLATERID_ATTR = "plater_id"; static constexpr const char* PLATER_NAME_ATTR = "plater_name"; static constexpr const char* PLATE_IDX_ATTR = "index"; static constexpr const char* PRINTER_MODEL_ID_ATTR = "printer_model_id"; static constexpr const char* NOZZLE_DIAMETERS_ATTR = "nozzle_diameters"; static constexpr const char* SLICE_PREDICTION_ATTR = "prediction"; static constexpr const char* SLICE_WEIGHT_ATTR = "weight"; static constexpr const char* TIMELAPSE_TYPE_ATTR = "timelapse_type"; static constexpr const char* TIMELAPSE_ERROR_CODE_ATTR = "timelapse_error_code"; static constexpr const char* OUTSIDE_ATTR = "outside"; static constexpr const char* SUPPORT_USED_ATTR = "support_used"; static constexpr const char* LABEL_OBJECT_ENABLED_ATTR = "label_object_enabled"; static constexpr const char* SKIPPED_ATTR = "skipped"; static constexpr const char* OBJECT_TYPE = "object"; static constexpr const char* VOLUME_TYPE = "volume"; static constexpr const char* PART_TYPE = "part"; static constexpr const char* NAME_KEY = "name"; static constexpr const char* VOLUME_TYPE_KEY = "volume_type"; static constexpr const char* PART_TYPE_KEY = "part_type"; static constexpr const char* MATRIX_KEY = "matrix"; static constexpr const char* SOURCE_FILE_KEY = "source_file"; static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; static constexpr const char* SOURCE_IN_INCHES = "source_in_inches"; static constexpr const char* SOURCE_IN_METERS = "source_in_meters"; static constexpr const char* MESH_SHARED_KEY = "mesh_shared"; static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed"; static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets"; static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed"; static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed"; static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges"; const unsigned int BBS_VALID_OBJECT_TYPES_COUNT = 2; const char* BBS_VALID_OBJECT_TYPES[] = { "model", "other" }; const char* BBS_INVALID_OBJECT_TYPES[] = { "solidsupport", "support", "surface" }; template struct hex_wrap { T t; }; namespace std { template basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& ostr, const hex_wrap<_Arg>& wrap) { // insert by calling function with output stream and argument auto of = ostr.fill('0'); ostr << setw(sizeof(_Arg) * 2) << std::hex << wrap.t; ostr << std::dec << setw(0); ostr.fill(of); return ostr; } } class version_error : public Slic3r::FileIOError { public: version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {} version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {} }; const char* bbs_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 bbs_get_attribute_value_string(const char** attributes, unsigned int attributes_size, const char* attribute_key) { const char* text = bbs_get_attribute_value_charptr(attributes, attributes_size, attribute_key); return (text != nullptr) ? text : ""; } float bbs_get_attribute_value_float(const char** attributes, unsigned int attributes_size, const char* attribute_key) { float value = 0.0f; if (const char *text = bbs_get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) fast_float::from_chars(text, text + strlen(text), value); return value; } int bbs_get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key) { int value = 0; if (const char *text = bbs_get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) boost::spirit::qi::parse(text, text + strlen(text), boost::spirit::qi::int_, value); return value; } bool bbs_get_attribute_value_bool(const char** attributes, unsigned int attributes_size, const char* attribute_key) { const char* text = bbs_get_attribute_value_charptr(attributes, attributes_size, attribute_key); return (text != nullptr) ? (bool)::atoi(text) : true; } void add_vec3(std::stringstream &stream, const Slic3r::Vec3f &tr) { for (unsigned r = 0; r < 3; ++r) { stream << tr(r); if (r != 2) stream << " "; } } Slic3r::Vec3f get_vec3_from_string(const std::string &pos_str) { Slic3r::Vec3f pos(0, 0, 0); if (pos_str.empty()) return pos; std::vector values; boost::split(values, pos_str, boost::is_any_of(" "), boost::token_compress_on); if (values.size() != 3) return pos; for (int i = 0; i < 3; ++i) pos(i) = ::atof(values[i].c_str()); return pos; } Slic3r::Transform3d bbs_get_transform_from_3mf_specs_string(const std::string& mat_str) { // check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md // to see how matrices are stored inside 3mf according to specifications Slic3r::Transform3d ret = Slic3r::Transform3d::Identity(); if (mat_str.empty()) // empty string means default identity matrix return ret; std::vector mat_elements_str; boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on); unsigned int size = (unsigned int)mat_elements_str.size(); if (size != 12) // invalid data, return identity matrix return ret; unsigned int i = 0; // matrices are stored into 3mf files as 4x3 // we need to transpose them for (unsigned int c = 0; c < 4; ++c) { for (unsigned int r = 0; r < 3; ++r) { ret(r, c) = ::atof(mat_elements_str[i++].c_str()); } } return ret; } Slic3r::Vec3d bbs_get_offset_from_3mf_specs_string(const std::string& vec_str) { Slic3r::Vec3d ofs2ass(0, 0, 0); if (vec_str.empty()) // empty string means default zero offset return ofs2ass; std::vector vec_elements_str; boost::split(vec_elements_str, vec_str, boost::is_any_of(" "), boost::token_compress_on); unsigned int size = (unsigned int)vec_elements_str.size(); if (size != 3) // invalid data, return zero offset return ofs2ass; for (unsigned int i = 0; i < 3; i++) { ofs2ass(i) = ::atof(vec_elements_str[i].c_str()); } return ofs2ass; } float bbs_get_unit_factor(const std::string& unit) { const char* text = unit.c_str(); if (::strcmp(text, "micron") == 0) return 0.001f; else if (::strcmp(text, "centimeter") == 0) return 10.0f; else if (::strcmp(text, "inch") == 0) return 25.4f; else if (::strcmp(text, "foot") == 0) return 304.8f; else if (::strcmp(text, "meter") == 0) return 1000.0f; else // default "millimeters" (see specification) return 1.0f; } bool bbs_is_valid_object_type(const std::string& type) { // if the type is empty defaults to "model" (see specification) if (type.empty()) return true; for (unsigned int i = 0; i < BBS_VALID_OBJECT_TYPES_COUNT; ++i) { if (::strcmp(type.c_str(), BBS_VALID_OBJECT_TYPES[i]) == 0) return true; } return false; } namespace Slic3r { void PlateData::parse_filament_info(GCodeProcessorResult *result) { if (!result) return; PrintEstimatedStatistics &ps = result->print_statistics; std::vector m_filament_diameters = result->filament_diameters; std::vector m_filament_densities = result->filament_densities; auto get_used_filament_from_volume = [m_filament_diameters, m_filament_densities](double volume, int extruder_id) { double koef = 0.001; std::pair ret = {koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])), volume * m_filament_densities[extruder_id] * 0.001}; return ret; }; for (auto it = ps.total_volumes_per_extruder.begin(); it != ps.total_volumes_per_extruder.end(); it++) { double volume = it->second; auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, it->first); FilamentInfo info; info.id = it->first; info.used_g = used_filament_g; info.used_m = used_filament_m; slice_filaments_info.push_back(info); } /* only for test GCodeProcessorResult::SliceWarning sw; sw.msg = BED_TEMP_TOO_HIGH_THAN_FILAMENT; sw.level = 1; result->warnings.push_back(sw); */ warnings = result->warnings; } //! macro used to mark string used at localization, //! return same string #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) // Base class with error messages management class _BBS_3MF_Base { mutable boost::mutex mutex; mutable std::vector m_errors; protected: void add_error(const std::string& error) const { boost::unique_lock l(mutex); m_errors.push_back(error); } void clear_errors() { m_errors.clear(); } public: void log_errors() { for (const std::string& error : m_errors) BOOST_LOG_TRIVIAL(error) << error; } }; class _BBS_3MF_Importer : public _BBS_3MF_Base { typedef std::pair Id; // BBS: encrypt struct Component { Id object_id; Transform3d transform; explicit Component(Id object_id) : object_id(object_id) , transform(Transform3d::Identity()) { } Component(Id object_id, const Transform3d& transform) : object_id(object_id) , transform(transform) { } }; typedef std::vector ComponentsList; struct Geometry { std::vector vertices; std::vector triangles; std::vector custom_supports; std::vector custom_seam; std::vector mmu_segmentation; // BBS std::vector face_properties; bool empty() { return vertices.empty() || triangles.empty(); } // backup & restore void swap(Geometry& o) { std::swap(vertices, o.vertices); std::swap(triangles, o.triangles); std::swap(custom_supports, o.custom_supports); std::swap(custom_seam, o.custom_seam); } void reset() { vertices.clear(); triangles.clear(); custom_supports.clear(); custom_seam.clear(); mmu_segmentation.clear(); } }; struct CurrentObject { // ID of the object inside the 3MF file, 1 based. int id; // Index of the ModelObject in its respective Model, zero based. int model_object_idx; Geometry geometry; ModelObject* object; ComponentsList components; //BBS: sub object id //int subobject_id; std::string name; std::string uuid; int pid{-1}; //bool is_model_object; CurrentObject() { reset(); } void reset() { id = -1; model_object_idx = -1; geometry.reset(); object = nullptr; components.clear(); //BBS: sub object id uuid.clear(); name.clear(); } }; struct CurrentConfig { int object_id {-1}; int volume_id {-1}; }; struct CurrentInstance { int object_id; int instance_id; int identify_id; }; struct Instance { ModelInstance* instance; Transform3d transform; Instance(ModelInstance* instance, const Transform3d& transform) : instance(instance) , transform(transform) { } }; struct Metadata { std::string key; std::string value; Metadata(const std::string& key, const std::string& value) : key(key) , value(value) { } }; typedef std::vector MetadataList; struct ObjectMetadata { struct VolumeMetadata { //BBS: refine the part logic unsigned int first_triangle_id; unsigned int last_triangle_id; int subobject_id; MetadataList metadata; RepairedMeshErrors mesh_stats; ModelVolumeType part_type; TextInfo text_info; VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id, ModelVolumeType type = ModelVolumeType::MODEL_PART) : first_triangle_id(first_triangle_id) , last_triangle_id(last_triangle_id) , part_type(type) , subobject_id(-1) { } VolumeMetadata(int sub_id, ModelVolumeType type = ModelVolumeType::MODEL_PART) : subobject_id(sub_id) , part_type(type) , first_triangle_id(0) , last_triangle_id(0) { } }; typedef std::vector VolumeMetadataList; MetadataList metadata; VolumeMetadataList volumes; }; struct CutObjectInfo { struct Connector { int volume_id; int type; float radius; float height; float r_tolerance; float h_tolerance; }; CutObjectBase id; std::vector connectors; }; // Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects. //typedef std::pair Id; // BBS: encrypt typedef std::map IdToCurrentObjectMap; typedef std::map IndexToPathMap; typedef std::map IdToModelObjectMap; //typedef std::map IdToAliasesMap; typedef std::vector InstancesList; typedef std::map IdToMetadataMap; typedef std::map IdToCutObjectInfoMap; //typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; typedef std::map IdToLayerConfigRangesMap; /*typedef std::map> IdToSlaSupportPointsMap; typedef std::map> IdToSlaDrainHolesMap;*/ struct ObjectImporter { IdToCurrentObjectMap object_list; CurrentObject *current_object{nullptr}; std::string object_path; std::string zip_path; _BBS_3MF_Importer *top_importer{nullptr}; XML_Parser object_xml_parser; bool obj_parse_error { false }; std::string obj_parse_error_message; //local parsed datas std::string obj_curr_metadata_name; std::string obj_curr_characters; float object_unit_factor; int object_current_color_group{-1}; std::map object_group_id_to_color; bool is_bbl_3mf { false }; ObjectImporter(_BBS_3MF_Importer *importer, std::string file_path, std::string obj_path) { top_importer = importer; object_path = obj_path; zip_path = file_path; } ~ObjectImporter() { _destroy_object_xml_parser(); } void _destroy_object_xml_parser() { if (object_xml_parser != nullptr) { XML_ParserFree(object_xml_parser); object_xml_parser = nullptr; } } void _stop_object_xml_parser(const std::string& msg = std::string()) { assert(! obj_parse_error); assert(obj_parse_error_message.empty()); assert(object_xml_parser != nullptr); obj_parse_error = true; obj_parse_error_message = msg; XML_StopParser(object_xml_parser, false); } bool object_parse_error() const { return obj_parse_error; } const char* object_parse_error_message() const { return obj_parse_error ? // The error was signalled by the user code, not the expat parser. (obj_parse_error_message.empty() ? "Invalid 3MF format" : obj_parse_error_message.c_str()) : // The error was signalled by the expat parser. XML_ErrorString(XML_GetErrorCode(object_xml_parser)); } bool _extract_object_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); bool extract_object_model() { mz_zip_archive archive; mz_zip_archive_file_stat stat; mz_zip_zero_struct(&archive); if (!open_zip_reader(&archive, zip_path)) { top_importer->add_error("Unable to open the zipfile "+ zip_path); return false; } if (!top_importer->_extract_from_archive(archive, object_path, [this] (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { return _extract_object_from_archive(archive, stat); }, top_importer->m_load_restore)) { std::string error_msg = std::string("Archive does not contain a valid model for ") + object_path; top_importer->add_error(error_msg); close_zip_reader(&archive); return false; } close_zip_reader(&archive); if (obj_parse_error) { //already add_error inside //top_importer->add_error(object_parse_error_message()); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Found error while extrace object %1%\n")%object_path; return false; } return true; } bool _handle_object_start_model(const char** attributes, unsigned int num_attributes); bool _handle_object_end_model(); bool _handle_object_start_resources(const char** attributes, unsigned int num_attributes); bool _handle_object_end_resources(); bool _handle_object_start_object(const char** attributes, unsigned int num_attributes); bool _handle_object_end_object(); bool _handle_object_start_color_group(const char **attributes, unsigned int num_attributes); bool _handle_object_end_color_group(); bool _handle_object_start_color(const char **attributes, unsigned int num_attributes); bool _handle_object_end_color(); bool _handle_object_start_mesh(const char** attributes, unsigned int num_attributes); bool _handle_object_end_mesh(); bool _handle_object_start_vertices(const char** attributes, unsigned int num_attributes); bool _handle_object_end_vertices(); bool _handle_object_start_vertex(const char** attributes, unsigned int num_attributes); bool _handle_object_end_vertex(); bool _handle_object_start_triangles(const char** attributes, unsigned int num_attributes); bool _handle_object_end_triangles(); bool _handle_object_start_triangle(const char** attributes, unsigned int num_attributes); bool _handle_object_end_triangle(); bool _handle_object_start_components(const char** attributes, unsigned int num_attributes); bool _handle_object_end_components(); bool _handle_object_start_component(const char** attributes, unsigned int num_attributes); bool _handle_object_end_component(); bool _handle_object_start_metadata(const char** attributes, unsigned int num_attributes); bool _handle_object_end_metadata(); void _handle_object_start_model_xml_element(const char* name, const char** attributes); void _handle_object_end_model_xml_element(const char* name); void _handle_object_xml_characters(const XML_Char* s, int len); // callbacks to parse the .model file of an object static void XMLCALL _handle_object_start_model_xml_element(void* userData, const char* name, const char** attributes); static void XMLCALL _handle_object_end_model_xml_element(void* userData, const char* name); static void XMLCALL _handle_object_xml_characters(void* userData, const XML_Char* s, int len); }; // Version of the 3mf file unsigned int m_version; bool m_check_version = false; bool m_load_model = false; bool m_load_aux = false; bool m_load_config = false; // backup & restore bool m_load_restore = false; std::string m_backup_path; std::string m_origin_file; // Semantic version of Bambu Studio, that generated this 3MF. boost::optional m_bambuslicer_generator_version; unsigned int m_fdm_supports_painting_version = 0; unsigned int m_seam_painting_version = 0; unsigned int m_mm_painting_version = 0; std::string m_model_id; std::string m_contry_code; std::string m_designer; std::string m_designer_id; std::string m_design_id; std::string m_designer_user_id; std::string m_designer_cover; ModelInfo model_info; BBLProject project_info; std::string m_profile_title; std::string m_profile_cover; std::string m_Profile_description; std::string m_profile_user_id; std::string m_profile_user_name; XML_Parser m_xml_parser; // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state // after returning from XML_Parse() function, thus we keep the error state here. bool m_parse_error { false }; std::string m_parse_error_message; Model* m_model; float m_unit_factor; CurrentObject* m_curr_object{nullptr}; IdToCurrentObjectMap m_current_objects; IndexToPathMap m_index_paths; IdToModelObjectMap m_objects; //IdToAliasesMap m_objects_aliases; InstancesList m_instances; //IdToGeometryMap m_geometries; //IdToGeometryMap m_orig_geometries; // backup & restore CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; IdToCutObjectInfoMap m_cut_object_infos; IdToLayerHeightsProfileMap m_layer_heights_profiles; IdToLayerConfigRangesMap m_layer_config_ranges; /*IdToSlaSupportPointsMap m_sla_support_points; IdToSlaDrainHolesMap m_sla_drain_holes;*/ std::string m_curr_metadata_name; std::string m_curr_characters; std::string m_name; std::string m_sub_model_path; std::string m_start_part_path; std::string m_thumbnail_path; std::string m_thumbnail_middle; std::string m_thumbnail_small; std::vector m_sub_model_paths; std::vector m_object_importers; std::map m_shared_meshes; //BBS: plater related structures bool m_is_bbl_3mf { false }; bool m_parsing_slice_info { false }; PlateDataMaps m_plater_data; PlateData* m_curr_plater; CurrentInstance m_curr_instance; int m_current_color_group{-1}; std::map m_group_id_to_color; public: _BBS_3MF_Importer(); ~_BBS_3MF_Importer(); //BBS: add plate data related logic // add backup & restore logic bool load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool& is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, BBLProject *project = nullptr, int plate_id = 0); bool get_thumbnail(const std::string &filename, std::string &data); bool load_gcode_3mf_from_stream(std::istream & data, Model& model, PlateDataPtrs& plate_data_list, DynamicPrintConfig& config, Semver& file_version); unsigned int version() const { return m_version; } private: void _destroy_xml_parser(); void _stop_xml_parser(const std::string& msg = std::string()); bool parse_error() const { return m_parse_error; } const char* parse_error_message() const { return m_parse_error ? // The error was signalled by the user code, not the expat parser. (m_parse_error_message.empty() ? "Invalid 3MF format" : m_parse_error_message.c_str()) : // The error was signalled by the expat parser. XML_ErrorString(XML_GetErrorCode(m_xml_parser)); } //BBS: add plate data related logic // add backup & restore logic bool _load_model_from_file(std::string filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Import3mfProgressFn proFn = nullptr, BBLProject* project = nullptr, int plate_id = 0); bool _extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function, bool restore = false); bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler); bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); //BBS: add project config file logic void _extract_project_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, Model& model); //BBS: extract project embedded presets void _extract_project_embedded_presets_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, std::vector&project_presets, Model& model, Preset::Type type, bool use_json = true); void _extract_auxiliary_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); void _extract_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); // handlers to parse the .model file void _handle_start_model_xml_element(const char* name, const char** attributes); void _handle_end_model_xml_element(const char* name); void _handle_xml_characters(const XML_Char* s, int len); // handlers to parse the MODEL_CONFIG_FILE file void _handle_start_config_xml_element(const char* name, const char** attributes); void _handle_end_config_xml_element(const char* name); bool _handle_start_model(const char** attributes, unsigned int num_attributes); bool _handle_end_model(); bool _handle_start_resources(const char** attributes, unsigned int num_attributes); bool _handle_end_resources(); bool _handle_start_object(const char** attributes, unsigned int num_attributes); bool _handle_end_object(); bool _handle_start_color_group(const char **attributes, unsigned int num_attributes); bool _handle_end_color_group(); bool _handle_start_color(const char **attributes, unsigned int num_attributes); bool _handle_end_color(); bool _handle_start_mesh(const char** attributes, unsigned int num_attributes); bool _handle_end_mesh(); bool _handle_start_vertices(const char** attributes, unsigned int num_attributes); bool _handle_end_vertices(); bool _handle_start_vertex(const char** attributes, unsigned int num_attributes); bool _handle_end_vertex(); bool _handle_start_triangles(const char** attributes, unsigned int num_attributes); bool _handle_end_triangles(); bool _handle_start_triangle(const char** attributes, unsigned int num_attributes); bool _handle_end_triangle(); bool _handle_start_components(const char** attributes, unsigned int num_attributes); bool _handle_end_components(); bool _handle_start_component(const char** attributes, unsigned int num_attributes); bool _handle_end_component(); bool _handle_start_build(const char** attributes, unsigned int num_attributes); bool _handle_end_build(); bool _handle_start_item(const char** attributes, unsigned int num_attributes); bool _handle_end_item(); bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); bool _handle_end_metadata(); bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); void _apply_transform(ModelInstance& instance, const Transform3d& transform); bool _handle_start_config(const char** attributes, unsigned int num_attributes); bool _handle_end_config(); bool _handle_start_config_object(const char** attributes, unsigned int num_attributes); bool _handle_end_config_object(); bool _handle_start_config_volume(const char** attributes, unsigned int num_attributes); bool _handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes); bool _handle_end_config_volume(); bool _handle_end_config_volume_mesh(); bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes); bool _handle_end_config_metadata(); bool _handle_start_config_filament(const char** attributes, unsigned int num_attributes); bool _handle_end_config_filament(); bool _handle_start_config_warning(const char** attributes, unsigned int num_attributes); bool _handle_end_config_warning(); //BBS: add plater config parse functions bool _handle_start_config_plater(const char** attributes, unsigned int num_attributes); bool _handle_end_config_plater(); bool _handle_start_config_plater_instance(const char** attributes, unsigned int num_attributes); bool _handle_end_config_plater_instance(); bool _handle_start_assemble(const char** attributes, unsigned int num_attributes); bool _handle_end_assemble(); bool _handle_start_assemble_item(const char** attributes, unsigned int num_attributes); bool _handle_end_assemble_item(); bool _handle_start_text_info_item(const char **attributes, unsigned int num_attributes); bool _handle_end_text_info_item(); // BBS: callbacks to parse the .rels file static void XMLCALL _handle_start_relationships_element(void* userData, const char* name, const char** attributes); static void XMLCALL _handle_end_relationships_element(void* userData, const char* name); void _handle_start_relationships_element(const char* name, const char** attributes); void _handle_end_relationships_element(const char* name); bool _handle_start_relationship(const char** attributes, unsigned int num_attributes); void _generate_current_object_list(std::vector &sub_objects, Id object_id, IdToCurrentObjectMap& current_objects); bool _generate_volumes_new(ModelObject& object, const std::vector &sub_objects, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); // callbacks to parse the .model file static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name); static void XMLCALL _handle_xml_characters(void* userData, const XML_Char* s, int len); // callbacks to parse the MODEL_CONFIG_FILE file static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes); static void XMLCALL _handle_end_config_xml_element(void* userData, const char* name); }; _BBS_3MF_Importer::_BBS_3MF_Importer() : m_version(0) , m_check_version(false) , m_xml_parser(nullptr) , m_model(nullptr) , m_unit_factor(1.0f) , m_curr_metadata_name("") , m_curr_characters("") , m_name("") , m_curr_plater(nullptr) { } _BBS_3MF_Importer::~_BBS_3MF_Importer() { _destroy_xml_parser(); clear_errors(); if (m_curr_object) { delete m_curr_object; m_curr_object = nullptr; } m_current_objects.clear(); m_index_paths.clear(); m_objects.clear(); m_instances.clear(); m_objects_metadata.clear(); m_curr_metadata_name.clear(); m_curr_characters.clear(); std::map::iterator it = m_plater_data.begin(); while (it != m_plater_data.end()) { delete it->second; it++; } m_plater_data.clear(); } //BBS: add plate data related logic // add backup & restore logic bool _BBS_3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool& is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn, BBLProject *project, int plate_id) { m_version = 0; m_fdm_supports_painting_version = 0; m_seam_painting_version = 0; m_mm_painting_version = 0; m_check_version = strategy & LoadStrategy::CheckVersion; //BBS: auxiliary data m_load_model = strategy & LoadStrategy::LoadModel; m_load_aux = strategy & LoadStrategy::LoadAuxiliary; m_load_restore = strategy & LoadStrategy::Restore; m_load_config = strategy & LoadStrategy::LoadConfig; m_model = &model; m_unit_factor = 1.0f; m_curr_object = nullptr; m_current_objects.clear(); m_index_paths.clear(); m_objects.clear(); //m_objects_aliases.clear(); m_instances.clear(); //m_geometries.clear(); m_curr_config.object_id = -1; m_curr_config.volume_id = -1; m_objects_metadata.clear(); m_layer_heights_profiles.clear(); m_layer_config_ranges.clear(); //m_sla_support_points.clear(); m_curr_metadata_name.clear(); m_curr_characters.clear(); //BBS: plater data init m_plater_data.clear(); m_curr_instance.object_id = -1; m_curr_instance.instance_id = -1; m_curr_instance.identify_id = 0; clear_errors(); // restore if (m_load_restore) { m_backup_path = filename.substr(0, filename.size() - 5); model.set_backup_path(m_backup_path); try { if (boost::filesystem::exists(model.get_backup_path() + "/origin.txt")) boost::filesystem::load_string_file(model.get_backup_path() + "/origin.txt", m_origin_file); } catch (...) {} boost::filesystem::save_string_file( model.get_backup_path() + "/lock.txt", boost::lexical_cast(get_current_pid())); } else { m_backup_path = model.get_backup_path(); } bool result = _load_model_from_file(filename, model, plate_data_list, project_presets, config, config_substitutions, proFn, project, plate_id); is_bbl_3mf = m_is_bbl_3mf; if (m_bambuslicer_generator_version) file_version = *m_bambuslicer_generator_version; // save for restore if (result && m_load_aux && !m_load_restore) { boost::filesystem::save_string_file(model.get_backup_path() + "/origin.txt", filename); } if (m_load_restore && !result) // not clear failed backup data for later analyze model.set_backup_path("detach"); return result; } bool _BBS_3MF_Importer::get_thumbnail(const std::string &filename, std::string &data) { mz_zip_archive archive; mz_zip_zero_struct(&archive); struct close_lock { mz_zip_archive *archive; void close() { if (archive) { close_zip_reader(archive); archive = nullptr; } } ~close_lock() { close(); } } lock{&archive}; if (!open_zip_reader(&archive, filename)) { add_error("Unable to open the file"+filename); return false; } // BBS: load relationships if (!_extract_xml_from_archive(archive, RELATIONSHIPS_FILE, _handle_start_relationships_element, _handle_end_relationships_element)) return false; if (m_thumbnail_middle.empty()) m_thumbnail_middle = m_thumbnail_path; if (!m_thumbnail_middle.empty()) { return _extract_from_archive(archive, m_thumbnail_middle, [&data](auto &archive, auto const &stat) -> bool { data.resize(stat.m_uncomp_size); return mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, data.data(), data.size(), 0); }); } return _extract_from_archive(archive, THUMBNAIL_FILE, [&data](auto &archive, auto const &stat) -> bool { data.resize(stat.m_uncomp_size); return mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, data.data(), data.size(), 0); }); } static size_t mz_zip_read_istream(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) { auto & is = *reinterpret_cast(pOpaque); is.seekg(file_ofs, std::istream::beg); if (!is) return 0; is.read((char *)pBuf, n); return is.gcount(); } bool _BBS_3MF_Importer::load_gcode_3mf_from_stream(std::istream &data, Model &model, PlateDataPtrs &plate_data_list, DynamicPrintConfig &config, Semver &file_version) { mz_zip_archive archive; mz_zip_zero_struct(&archive); archive.m_pRead = mz_zip_read_istream; archive.m_pIO_opaque = &data; data.seekg(0, std::istream::end); mz_uint64 size = data.tellg(); data.seekg(0, std::istream::beg); if (!mz_zip_reader_init(&archive, size, 0)) return false; struct close_lock { mz_zip_archive *archive; void close() { if (archive) { mz_zip_reader_end(archive); archive = nullptr; } } ~close_lock() { close(); } } lock{&archive}; // BBS: load relationships if (!_extract_xml_from_archive(archive, RELATIONSHIPS_FILE, _handle_start_relationships_element, _handle_end_relationships_element)) return false; if (m_start_part_path.empty()) return false; //extract model files m_model = &model; if (!_extract_from_archive(archive, m_start_part_path, [this] (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { return _extract_model_from_archive(archive, stat); })) { add_error("Archive does not contain a valid model"); return false; } if (!m_designer.empty()) { m_model->design_info = std::make_shared(); m_model->design_info->DesignerId = m_designer_id; m_model->design_info->DesignerUserId = m_designer_user_id; m_model->design_info->Designer = m_designer; } m_model->profile_info = std::make_shared(); m_model->profile_info->ProfileTile = m_profile_title; m_model->profile_info->ProfileCover = m_profile_cover; m_model->profile_info->ProfileDescription = m_Profile_description; m_model->profile_info->ProfileUserId = m_profile_user_id; m_model->profile_info->ProfileUserName = m_profile_user_name; m_model->design_id = m_design_id; m_model->model_info = std::make_shared(); m_model->model_info->load(model_info); if (m_thumbnail_middle.empty()) m_thumbnail_middle = m_thumbnail_path; if (m_thumbnail_small.empty()) m_thumbnail_small = m_thumbnail_path; if (!m_thumbnail_small.empty() && m_thumbnail_small.front() == '/') m_thumbnail_small.erase(m_thumbnail_small.begin()); if (!m_thumbnail_middle.empty() && m_thumbnail_middle.front() == '/') m_thumbnail_middle.erase(m_thumbnail_middle.begin()); m_model->model_info->metadata_items.emplace("Thumbnail", m_thumbnail_middle); m_model->model_info->metadata_items.emplace("Poster", m_thumbnail_middle); // we then loop again the entries to read other files stored in the archive mz_uint num_entries = mz_zip_reader_get_num_files(&archive); mz_zip_archive_file_stat stat; for (mz_uint i = 0; i < num_entries; ++i) { if (mz_zip_reader_file_stat(&archive, i, &stat)) { std::string name(stat.m_filename); std::replace(name.begin(), name.end(), '\\', '/'); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("extract %1%th file %2%, total=%3%\n")%(i+1)%name%num_entries; if (boost::algorithm::iequals(name, BBS_PROJECT_CONFIG_FILE)) { // extract slic3r print config file ConfigSubstitutionContext config_substitutions(ForwardCompatibilitySubstitutionRule::Disable); _extract_project_config_from_archive(archive, stat, config, config_substitutions, model); } else if (boost::algorithm::iequals(name, BBS_MODEL_CONFIG_FILE)) { // extract slic3r model config file if (!_extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element)) { add_error("Archive does not contain a valid model config"); return false; } } else if (boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) { m_parsing_slice_info = true; //extract slice info from archive _extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element); m_parsing_slice_info = false; } } } //BBS: load the plate info into plate_data_list std::map::iterator it = m_plater_data.begin(); plate_data_list.clear(); plate_data_list.reserve(m_plater_data.size()); for (unsigned int i = 0; i < m_plater_data.size(); i++) { PlateData* plate = new PlateData(); plate_data_list.push_back(plate); } while (it != m_plater_data.end()) { if (it->first > m_plater_data.size()) { add_error("invalid plate index"); return false; } PlateData * plate = plate_data_list[it->first-1]; plate->locked = it->second->locked; plate->plate_index = it->second->plate_index-1; plate->obj_inst_map = it->second->obj_inst_map; plate->gcode_file = it->second->gcode_file; plate->gcode_prediction = it->second->gcode_prediction; plate->gcode_weight = it->second->gcode_weight; plate->toolpath_outside = it->second->toolpath_outside; plate->is_support_used = it->second->is_support_used; plate->is_label_object_enabled = it->second->is_label_object_enabled; plate->skipped_objects = it->second->skipped_objects; plate->slice_filaments_info = it->second->slice_filaments_info; plate->printer_model_id = it->second->printer_model_id; plate->nozzle_diameters = it->second->nozzle_diameters; plate->warnings = it->second->warnings; plate->thumbnail_file = it->second->thumbnail_file; if (plate->thumbnail_file.empty()) { plate->thumbnail_file = plate->gcode_file; boost::algorithm::replace_all(plate->thumbnail_file, ".gcode", ".png"); } //plate->pattern_file = it->second->pattern_file; plate->no_light_thumbnail_file = it->second->no_light_thumbnail_file; plate->top_file = it->second->top_file; plate->pick_file = it->second->pick_file.empty(); plate->pattern_bbox_file = it->second->pattern_bbox_file.empty(); plate->config = it->second->config; if (!plate->thumbnail_file.empty()) _extract_from_archive(archive, plate->thumbnail_file, [&pixels = plate_data_list[it->first - 1]->plate_thumbnail.pixels](auto &archive, auto const &stat) -> bool { pixels.resize(stat.m_uncomp_size); return mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, pixels.data(), pixels.size(), 0); }); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", plate %1%, thumbnail_file=%2%, no_light_thumbnail_file=%3%")%it->first %plate->thumbnail_file %plate->no_light_thumbnail_file; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_file=%1%, pick_thumbnail_file=%2%")%plate->top_file %plate->pick_file; it++; } lock.close(); return true; } void _BBS_3MF_Importer::_destroy_xml_parser() { if (m_xml_parser != nullptr) { XML_ParserFree(m_xml_parser); m_xml_parser = nullptr; } } void _BBS_3MF_Importer::_stop_xml_parser(const std::string &msg) { assert(! m_parse_error); assert(m_parse_error_message.empty()); assert(m_xml_parser != nullptr); m_parse_error = true; m_parse_error_message = msg; XML_StopParser(m_xml_parser, false); } //BBS: add plate data related logic bool _BBS_3MF_Importer::_load_model_from_file( std::string filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Import3mfProgressFn proFn, BBLProject *project, int plate_id) { bool cb_cancel = false; //BBS progress point // prepare restore if (m_load_restore) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_RESTORE\n"); if (proFn) { proFn(IMPORT_STAGE_RESTORE, 0, 1, cb_cancel); if (cb_cancel) return false; } } //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_OPEN, m_load_restore=%1%\n")%m_load_restore; if (proFn) { proFn(IMPORT_STAGE_OPEN, 0, 1, cb_cancel); if (cb_cancel) return false; } mz_zip_archive archive; mz_zip_zero_struct(&archive); struct close_lock { mz_zip_archive * archive; void close() { if (archive) { close_zip_reader(archive); archive = nullptr; } } ~close_lock() { close(); } } lock{ &archive }; if (!open_zip_reader(&archive, filename)) { add_error("Unable to open the file"+filename); return false; } mz_uint num_entries = mz_zip_reader_get_num_files(&archive); mz_zip_archive_file_stat stat; m_name = boost::filesystem::path(filename).stem().string(); //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_READ_FILES\n"); if (proFn) { proFn(IMPORT_STAGE_READ_FILES, 0, 3, cb_cancel); if (cb_cancel) return false; } // BBS: load relationships if (!_extract_xml_from_archive(archive, RELATIONSHIPS_FILE, _handle_start_relationships_element, _handle_end_relationships_element)) return false; if (m_start_part_path.empty()) return false; // BBS: load sub models (Production Extension) std::string sub_rels = m_start_part_path; sub_rels.insert(boost::find_last(sub_rels, "/").end() - sub_rels.begin(), "_rels/"); sub_rels.append(".rels"); if (sub_rels.front() == '/') sub_rels = sub_rels.substr(1); //check whether sub relation file is exist or not int sub_index = mz_zip_reader_locate_file(&archive, sub_rels.c_str(), nullptr, 0); if (sub_index == -1) { //no submodule files found, use only one 3dmodel.model } else { _extract_xml_from_archive(archive, sub_rels, _handle_start_relationships_element, _handle_end_relationships_element); int index = 0; #if 0 for (auto path : m_sub_model_paths) { if (proFn) { proFn(IMPORT_STAGE_READ_FILES, ++index, 3 + m_sub_model_paths.size(), cb_cancel); if (cb_cancel) return false; } else ++index; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", read %1%th sub model file %2%\n")%index %path; m_sub_model_path = path; if (!_extract_from_archive(archive, path, [this] (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { return _extract_model_from_archive(archive, stat); }, m_load_restore)) { add_error("Archive does not contain a valid model"); return false; } m_sub_model_path.clear(); } #else for (auto path : m_sub_model_paths) { ObjectImporter *object_importer = new ObjectImporter(this, filename, path); m_object_importers.push_back(object_importer); } bool object_load_result = true; boost::mutex mutex; tbb::parallel_for( tbb::blocked_range(0, m_object_importers.size()), [this, &mutex, &object_load_result](const tbb::blocked_range& importer_range) { CNumericLocalesSetter locales_setter; for (size_t object_index = importer_range.begin(); object_index < importer_range.end(); ++ object_index) { bool result = m_object_importers[object_index]->extract_object_model(); { boost::unique_lock l(mutex); object_load_result &= result; } } } ); if (!object_load_result) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", loading sub-objects error\n"); return false; } //merge these objects into one for (auto obj_importer : m_object_importers) { for (const IdToCurrentObjectMap::value_type& obj : obj_importer->object_list) m_current_objects.insert({ std::move(obj.first), std::move(obj.second)}); for (auto group_color : obj_importer->object_group_id_to_color) m_group_id_to_color.insert(std::move(group_color)); delete obj_importer; } m_object_importers.clear(); #endif // BBS: load root model if (proFn) { proFn(IMPORT_STAGE_READ_FILES, 2, 3, cb_cancel); if (cb_cancel) return false; } } //extract model files if (!_extract_from_archive(archive, m_start_part_path, [this] (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { return _extract_model_from_archive(archive, stat); })) { add_error("Archive does not contain a valid model"); return false; } if (!m_designer.empty()) { m_model->design_info = std::make_shared(); m_model->design_info->DesignerId = m_designer_id; m_model->design_info->DesignerUserId = m_designer_user_id; m_model->design_info->Designer = m_designer; } m_model->profile_info = std::make_shared(); m_model->profile_info->ProfileTile = m_profile_title; m_model->profile_info->ProfileCover = m_profile_cover; m_model->profile_info->ProfileDescription = m_Profile_description; m_model->profile_info->ProfileUserId = m_profile_user_id; m_model->profile_info->ProfileUserName = m_profile_user_name; m_model->design_id = m_design_id; m_model->model_info = std::make_shared(); m_model->model_info->load(model_info); if (!m_thumbnail_small.empty()) m_model->model_info->metadata_items.emplace("Thumbnail_Small", m_thumbnail_small); if (!m_thumbnail_middle.empty()) m_model->model_info->metadata_items.emplace("Thumbnail_Middle", m_thumbnail_middle); //got project id if (project) { project->project_model_id = m_model_id; project->project_country_code = m_contry_code; } //BBS: version check bool dont_load_config = !m_load_config; if (m_bambuslicer_generator_version) { Semver app_version = *(Semver::parse(SLIC3R_VERSION)); Semver file_version = *m_bambuslicer_generator_version; if (file_version.maj() > app_version.maj()) dont_load_config = true; } else { m_bambuslicer_generator_version = Semver::parse("0.0.0.0"); dont_load_config = true; } // we then loop again the entries to read other files stored in the archive for (mz_uint i = 0; i < num_entries; ++i) { if (mz_zip_reader_file_stat(&archive, i, &stat)) { //BBS progress point if (proFn) { proFn(IMPORT_STAGE_EXTRACT, i, num_entries, cb_cancel); if (cb_cancel) return false; } std::string name(stat.m_filename); std::replace(name.begin(), name.end(), '\\', '/'); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("extract %1%th file %2%, total=%3%")%(i+1)%name%num_entries; if (name.find("/../") != std::string::npos) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", find file path including /../, not valid, skip it\n"); continue; } if (boost::algorithm::iequals(name, BBS_LAYER_HEIGHTS_PROFILE_FILE)) { // extract slic3r layer heights profile file _extract_layer_heights_profile_config_from_archive(archive, stat); } else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) { // extract slic3r layer config ranges file _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); } //BBS: disable SLA related files currently /*else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { // extract sla support points file _extract_sla_support_points_from_archive(archive, stat); } else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE)) { // extract sla support points file _extract_sla_drain_holes_from_archive(archive, stat); }*/ //BBS: project setting file //if (!dont_load_config && boost::algorithm::iequals(name, BBS_PRINT_CONFIG_FILE)) { // extract slic3r print config file // _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename); //} else if (!dont_load_config && boost::algorithm::iequals(name, BBS_PROJECT_CONFIG_FILE)) { // extract slic3r print config file _extract_project_config_from_archive(archive, stat, config, config_substitutions, model); } else if (boost::algorithm::iequals(name, CUT_INFORMATION_FILE)) { // extract object cut info _extract_cut_information_from_archive(archive, stat, config_substitutions); } //BBS: project embedded presets else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_PRINT_PRESETS_FILE)) { // extract slic3r layer config ranges file _extract_project_embedded_presets_from_archive(archive, stat, project_presets, model, Preset::TYPE_PRINT, false); } else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_SLICE_PRESETS_FILE)) { // extract slic3r layer config ranges file _extract_project_embedded_presets_from_archive(archive, stat, project_presets, model, Preset::TYPE_PRINT); } else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_FILAMENT_PRESETS_FILE)) { // extract slic3r layer config ranges file _extract_project_embedded_presets_from_archive(archive, stat, project_presets, model, Preset::TYPE_FILAMENT); } else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_PRINTER_PRESETS_FILE)) { // extract slic3r layer config ranges file _extract_project_embedded_presets_from_archive(archive, stat, project_presets, model, Preset::TYPE_PRINTER); } else if (!dont_load_config && boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) { // extract slic3r layer config ranges file _extract_custom_gcode_per_print_z_from_archive(archive, stat); } else if (boost::algorithm::iequals(name, BBS_MODEL_CONFIG_FILE)) { // extract slic3r model config file if (!_extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element)) { add_error("Archive does not contain a valid model config"); return false; } } else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) { m_parsing_slice_info = true; //extract slice info from archive _extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element); m_parsing_slice_info = false; } else if (boost::algorithm::istarts_with(name, AUXILIARY_DIR)) { // extract auxiliary directory to temp directory, do nothing for restore if (m_load_aux && !m_load_restore) _extract_auxiliary_file_from_archive(archive, stat, model); } else if (!dont_load_config && boost::algorithm::istarts_with(name, METADATA_DIR) && boost::algorithm::iends_with(name, GCODE_EXTENSION)) { //load gcode files _extract_file_from_archive(archive, stat); } else if (!dont_load_config && boost::algorithm::istarts_with(name, METADATA_DIR) && boost::algorithm::iends_with(name, THUMBNAIL_EXTENSION)) { //BBS parsing pattern thumbnail and plate thumbnails _extract_file_from_archive(archive, stat); } else if (!dont_load_config && boost::algorithm::istarts_with(name, METADATA_DIR) && boost::algorithm::iends_with(name, CALIBRATION_INFO_EXTENSION)) { //BBS parsing pattern config files _extract_file_from_archive(archive, stat); } else { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", %1% skipped, already parsed or a directory or not supported\n")%name; } } } lock.close(); if (!m_is_bbl_3mf) { // if the 3mf was not produced by BambuStudio and there is more than one instance, // split the object in as many objects as instances BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", found 3mf from other vendor, split as instance"); for (const IdToModelObjectMap::value_type& object : m_objects) { if (object.second >= int(m_model->objects.size())) { add_error("3rd 3mf, invalid object, id: "+std::to_string(object.first.second)); return false; } ModelObject* model_object = m_model->objects[object.second]; if (model_object->instances.size() > 1) { IdToCurrentObjectMap::const_iterator current_object = m_current_objects.find(object.first); if (current_object == m_current_objects.end()) { add_error("3rd 3mf, can not find object, id " + std::to_string(object.first.second)); return false; } std::vector object_id_list; _generate_current_object_list(object_id_list, object.first, m_current_objects); ObjectMetadata::VolumeMetadataList volumes; ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; for (int k = 0; k < object_id_list.size(); k++) { Id object_id = object_id_list[k].object_id; volumes.emplace_back(object_id.second); } // select as volumes volumes_ptr = &volumes; // for each instance after the 1st, create a new model object containing only that instance // and copy into it the geometry while (model_object->instances.size() > 1) { ModelObject* new_model_object = m_model->add_object(*model_object); new_model_object->clear_instances(); new_model_object->add_instance(*model_object->instances.back()); model_object->delete_last_instance(); if (!_generate_volumes_new(*new_model_object, object_id_list, *volumes_ptr, config_substitutions)) return false; } } } } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", process group colors, size %1%\n")%m_group_id_to_color.size(); std::map color_group_id_to_extruder_id_map; std::map color_to_extruder_id_map; int extruder_id = 0; for (auto group_iter = m_group_id_to_color.begin(); group_iter != m_group_id_to_color.end(); ++group_iter) { auto color_iter = color_to_extruder_id_map.find(group_iter->second); if (color_iter == color_to_extruder_id_map.end()) { ++extruder_id; color_to_extruder_id_map[group_iter->second] = extruder_id; color_group_id_to_extruder_id_map[group_iter->first] = extruder_id; } else { color_group_id_to_extruder_id_map[group_iter->first] = color_iter->second; } } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", begin to assemble objects, size %1%\n")%m_objects.size(); //only load objects in plate_id PlateData* current_plate_data = nullptr; if ((plate_id > 0) && (plate_id <= m_plater_data.size())) { std::map::iterator it =m_plater_data.find(plate_id); if (it != m_plater_data.end()) { current_plate_data = it->second; } } for (const IdToModelObjectMap::value_type& object : m_objects) { if (object.second >= int(m_model->objects.size())) { add_error("invalid object, id: "+std::to_string(object.first.second)); return false; } if (current_plate_data) { std::map>::iterator it = current_plate_data->obj_inst_map.find(object.first.second); if (it == current_plate_data->obj_inst_map.end()) { //not in current plate, skip BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", could not find object %1% in plate %2%, skip it\n")%object.first.second %plate_id; continue; } } ModelObject* model_object = m_model->objects[object.second]; /*IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); if (obj_geometry == m_geometries.end()) { add_error("Unable to find object geometry"); return false; }*/ // m_layer_heights_profiles are indexed by a 1 based model object index. IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.second + 1); if (obj_layer_heights_profile != m_layer_heights_profiles.end()) model_object->layer_height_profile.set(std::move(obj_layer_heights_profile->second)); // m_layer_config_ranges are indexed by a 1 based model object index. IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); if (obj_layer_config_ranges != m_layer_config_ranges.end()) model_object->layer_config_ranges = std::move(obj_layer_config_ranges->second); // m_sla_support_points are indexed by a 1 based model object index. /*IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { model_object->sla_support_points = std::move(obj_sla_support_points->second); model_object->sla_points_status = sla::PointsStatus::UserModified; } IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1); if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) { model_object->sla_drain_holes = std::move(obj_drain_holes->second); }*/ std::vector object_id_list; _generate_current_object_list(object_id_list, object.first, m_current_objects); ObjectMetadata::VolumeMetadataList volumes; ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second); if (obj_metadata != m_objects_metadata.end()) { // config data has been found, this model was saved using slic3r pe // apply object's name and config data for (const Metadata& metadata : obj_metadata->second.metadata) { if (metadata.key == "name") model_object->name = metadata.value; //BBS: add module name else if (metadata.key == "module") model_object->module_name = metadata.value; else model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } // select object's detected volumes volumes_ptr = &obj_metadata->second.volumes; } else { // config data not found, this model was not saved using slic3r pe // add the entire geometry as the single volume to generate //volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1); for (int k = 0; k < object_id_list.size(); k++) { Id object_id = object_id_list[k].object_id; volumes.emplace_back(object_id.second); } IdToCurrentObjectMap::const_iterator current_object = m_current_objects.find(object.first); if (current_object != m_current_objects.end()) { // get name if (!current_object->second.name.empty()) model_object->name = current_object->second.name; else model_object->name = "Object_"+std::to_string(object.second+1); // get color auto extruder_itor = color_group_id_to_extruder_id_map.find(current_object->second.pid); if (extruder_itor != color_group_id_to_extruder_id_map.end()) { model_object->config.set_key_value("extruder", new ConfigOptionInt(extruder_itor->second)); } } // select as volumes volumes_ptr = &volumes; } if (!_generate_volumes_new(*model_object, object_id_list, *volumes_ptr, config_substitutions)) return false; // Apply cut information for object if any was loaded // m_cut_object_ids are indexed by a 1 based model object index. IdToCutObjectInfoMap::iterator cut_object_info = m_cut_object_infos.find(object.second + 1); if (cut_object_info != m_cut_object_infos.end()) { model_object->cut_id = cut_object_info->second.id; for (auto connector : cut_object_info->second.connectors) { assert(0 <= connector.volume_id && connector.volume_id <= int(model_object->volumes.size())); model_object->volumes[connector.volume_id]->cut_info = ModelVolume::CutInfo(CutConnectorType(connector.type), connector.radius, connector.height, connector.r_tolerance, connector.h_tolerance, true); } } } // If instances contain a single volume, the volume offset should be 0,0,0 // This equals to say that instance world position and volume world position should match // Correct all instances/volumes for which this does not hold for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { ModelObject *o = model.objects[obj_id]; if (o->volumes.size() == 1) { ModelVolume * v = o->volumes.front(); const Slic3r::Geometry::Transformation &first_inst_trafo = o->instances.front()->get_transformation(); const Vec3d world_vol_offset = (first_inst_trafo * v->get_transformation()).get_offset(); const Vec3d world_inst_offset = first_inst_trafo.get_offset(); if (!world_vol_offset.isApprox(world_inst_offset)) { const Slic3r::Geometry::Transformation &vol_trafo = v->get_transformation(); for (int inst_id = 0; inst_id < int(o->instances.size()); ++inst_id) { ModelInstance * i = o->instances[inst_id]; const Slic3r::Geometry::Transformation &inst_trafo = i->get_transformation(); i->set_offset((inst_trafo * vol_trafo).get_offset()); } v->set_offset(Vec3d::Zero()); } } } int object_idx = 0; for (ModelObject* o : model.objects) { int volume_idx = 0; for (ModelVolume* v : o->volumes) { if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) { v->source.input_file = filename; if (v->source.volume_idx == -1) v->source.volume_idx = volume_idx; if (v->source.object_idx == -1) v->source.object_idx = object_idx; } ++volume_idx; } ++object_idx; } const ConfigOptionStrings* filament_ids_opt = config.option("filament_settings_id"); int max_filament_id = filament_ids_opt ? filament_ids_opt->size() : std::numeric_limits::max(); for (ModelObject* mo : m_model->objects) { const ConfigOptionInt* extruder_opt = dynamic_cast(mo->config.option("extruder")); int extruder_id = 0; if (extruder_opt != nullptr) extruder_id = extruder_opt->getInt(); if (extruder_id == 0 || extruder_id > max_filament_id) mo->config.set_key_value("extruder", new ConfigOptionInt(1)); if (mo->volumes.size() == 1) { mo->volumes[0]->config.erase("extruder"); } else { for (ModelVolume* mv : mo->volumes) { const ConfigOptionInt* vol_extruder_opt = dynamic_cast(mv->config.option("extruder")); if (vol_extruder_opt == nullptr) continue; if (vol_extruder_opt->getInt() == 0) mv->config.erase("extruder"); else if (vol_extruder_opt->getInt() > max_filament_id) mv->config.set_key_value("extruder", new ConfigOptionInt(1)); } } } // // fixes the min z of the model if negative // model.adjust_min_z(); //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_LOADING_PLATES, m_plater_data size %1%, m_backup_path %2%\n")%m_plater_data.size() %m_backup_path; if (proFn) { proFn(IMPORT_STAGE_LOADING_PLATES, 0, 1, cb_cancel); if (cb_cancel) return false; } //BBS: load the plate info into plate_data_list std::map::iterator it = m_plater_data.begin(); plate_data_list.clear(); plate_data_list.reserve(m_plater_data.size()); for (unsigned int i = 0; i < m_plater_data.size(); i++) { PlateData* plate = new PlateData(); plate_data_list.push_back(plate); } while (it != m_plater_data.end()) { if (it->first > m_plater_data.size()) { add_error("invalid plate index"); return false; } plate_data_list[it->first-1]->locked = it->second->locked; plate_data_list[it->first-1]->plate_index = it->second->plate_index-1; plate_data_list[it->first-1]->plate_name = it->second->plate_name; plate_data_list[it->first-1]->obj_inst_map = it->second->obj_inst_map; plate_data_list[it->first-1]->gcode_file = (m_load_restore || it->second->gcode_file.empty()) ? it->second->gcode_file : m_backup_path + "/" + it->second->gcode_file; plate_data_list[it->first-1]->gcode_prediction = it->second->gcode_prediction; plate_data_list[it->first-1]->gcode_weight = it->second->gcode_weight; plate_data_list[it->first-1]->toolpath_outside = it->second->toolpath_outside; plate_data_list[it->first-1]->is_support_used = it->second->is_support_used; plate_data_list[it->first-1]->is_label_object_enabled = it->second->is_label_object_enabled; plate_data_list[it->first-1]->slice_filaments_info = it->second->slice_filaments_info; plate_data_list[it->first-1]->skipped_objects = it->second->skipped_objects; plate_data_list[it->first-1]->warnings = it->second->warnings; plate_data_list[it->first-1]->thumbnail_file = (m_load_restore || it->second->thumbnail_file.empty()) ? it->second->thumbnail_file : m_backup_path + "/" + it->second->thumbnail_file; //plate_data_list[it->first-1]->pattern_file = (m_load_restore || it->second->pattern_file.empty()) ? it->second->pattern_file : m_backup_path + "/" + it->second->pattern_file; plate_data_list[it->first-1]->no_light_thumbnail_file = (m_load_restore || it->second->no_light_thumbnail_file.empty()) ? it->second->no_light_thumbnail_file : m_backup_path + "/" + it->second->no_light_thumbnail_file; plate_data_list[it->first-1]->top_file = (m_load_restore || it->second->top_file.empty()) ? it->second->top_file : m_backup_path + "/" + it->second->top_file; plate_data_list[it->first-1]->pick_file = (m_load_restore || it->second->pick_file.empty()) ? it->second->pick_file : m_backup_path + "/" + it->second->pick_file; plate_data_list[it->first-1]->pattern_bbox_file = (m_load_restore || it->second->pattern_bbox_file.empty()) ? it->second->pattern_bbox_file : m_backup_path + "/" + it->second->pattern_bbox_file; plate_data_list[it->first-1]->config = it->second->config; current_plate_data = plate_data_list[it->first - 1]; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", plate %1%, thumbnail_file=%2%, no_light_thumbnail_file=%3%")%it->first %plate_data_list[it->first-1]->thumbnail_file %plate_data_list[it->first-1]->no_light_thumbnail_file; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_file=%1%, pick_thumbnail_file=%2%")%plate_data_list[it->first-1]->top_file %plate_data_list[it->first-1]->pick_file; it++; //update the arrange order std::map>::iterator map_it = current_plate_data->obj_inst_map.begin(); while (map_it != current_plate_data->obj_inst_map.end()) { int obj_index, obj_id = map_it->first, inst_index = map_it->second.first; IndexToPathMap::iterator index_iter = m_index_paths.find(obj_id); if (index_iter == m_index_paths.end()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format(", can not find object from plate's obj_map, id=%1%, skip this object")%obj_id; map_it++; continue; } Id temp_id = std::make_pair(index_iter->second, index_iter->first); IdToModelObjectMap::iterator object_item = m_objects.find(temp_id); if (object_item == m_objects.end()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format(", can not find object from plate's obj_map, ID <%1%, %2%>, skip this object")%index_iter->second %index_iter->first; map_it++; continue; } obj_index = object_item->second; if (obj_index >= m_model->objects.size()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format("invalid object id %1%\n")%obj_index; map_it++; continue; } ModelObject* obj = m_model->objects[obj_index]; if (inst_index >= obj->instances.size()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format("invalid instance id %1%\n")%inst_index; map_it++; continue; } ModelInstance* inst = obj->instances[inst_index]; inst->loaded_id = map_it->second.second; map_it++; } } if ((plate_id > 0) && (plate_id <= m_plater_data.size())) { //remove the no need objects std::vector delete_ids; for (int index = 0; index < m_model->objects.size(); index++) { ModelObject* obj = m_model->objects[index]; if (obj->volumes.size() == 0) { //remove this model objects delete_ids.push_back(index); } } for (int index = delete_ids.size() - 1; index >= 0; index--) m_model->delete_object(delete_ids[index]); } //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_FINISH\n"); if (proFn) { proFn(IMPORT_STAGE_FINISH, 0, 1, cb_cancel); if (cb_cancel) return false; } return true; } bool _BBS_3MF_Importer::_extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function extract, bool restore) { mz_uint num_entries = mz_zip_reader_get_num_files(&archive); mz_zip_archive_file_stat stat; std::string path2 = path; if (path2.front() == '/') path2 = path2.substr(1); // try utf8 encoding int index = mz_zip_reader_locate_file(&archive, path2.c_str(), nullptr, 0); if (index < 0) { // try native encoding std::string native_path = encode_path(path2.c_str()); index = mz_zip_reader_locate_file(&archive, native_path.c_str(), nullptr, 0); } if (index < 0) { // try unicode path extra std::string extra(1024, 0); for (mz_uint i = 0; i < archive.m_total_files; ++i) { size_t n = mz_zip_reader_get_extra(&archive, i, extra.data(), extra.size()); if (n > 0 && path2 == ZipUnicodePathExtraField::decode(extra.substr(0, n))) { index = i; break; } } } if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat)) { if (restore) { std::vector paths = {m_backup_path + path}; if (!m_origin_file.empty()) paths.push_back(m_origin_file); for (auto & path2 : paths) { bool result = false; if (boost::filesystem::exists(path2)) { mz_zip_archive archive; mz_zip_zero_struct(&archive); if (open_zip_reader(&archive, path2)) { result = _extract_from_archive(archive, path, extract); close_zip_reader(&archive); } } if (result) return result; } } char error_buf[1024]; ::snprintf(error_buf, 1024, "File %s not found from archive", path.c_str()); add_error(error_buf); return false; } try { if (!extract(archive, stat)) { return false; } } catch (const std::exception& e) { // ensure the zip archive is closed and rethrow the exception add_error(e.what()); return false; } return true; } bool _BBS_3MF_Importer::_extract_xml_from_archive(mz_zip_archive& archive, const std::string & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler) { return _extract_from_archive(archive, path, [this, start_handler, end_handler](mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { return _extract_xml_from_archive(archive, stat, start_handler, end_handler); }); } bool _BBS_3MF_Importer::_extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler) { if (stat.m_uncomp_size == 0) { add_error("Found invalid size"); return false; } _destroy_xml_parser(); m_xml_parser = XML_ParserCreate(nullptr); if (m_xml_parser == nullptr) { add_error("Unable to create parser"); return false; } XML_SetUserData(m_xml_parser, (void*)this); XML_SetElementHandler(m_xml_parser, start_handler, end_handler); XML_SetCharacterDataHandler(m_xml_parser, _BBS_3MF_Importer::_handle_xml_characters); void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size); if (parser_buffer == nullptr) { add_error("Unable to create buffer"); return false; } 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) { add_error("Error while reading config data to buffer"); return false; } if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) { char error_buf[1024]; ::snprintf(error_buf, 1024, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser)); add_error(error_buf); return false; } return true; } bool _BBS_3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size == 0) { add_error("Found invalid size"); return false; } _destroy_xml_parser(); m_xml_parser = XML_ParserCreate(nullptr); if (m_xml_parser == nullptr) { add_error("Unable to create parser"); return false; } XML_SetUserData(m_xml_parser, (void*)this); XML_SetElementHandler(m_xml_parser, _BBS_3MF_Importer::_handle_start_model_xml_element, _BBS_3MF_Importer::_handle_end_model_xml_element); XML_SetCharacterDataHandler(m_xml_parser, _BBS_3MF_Importer::_handle_xml_characters); struct CallbackData { XML_Parser& parser; _BBS_3MF_Importer& importer; const mz_zip_archive_file_stat& stat; CallbackData(XML_Parser& parser, _BBS_3MF_Importer& importer, const mz_zip_archive_file_stat& stat) : parser(parser), importer(importer), stat(stat) {} }; CallbackData data(m_xml_parser, *this, stat); mz_bool res = 0; try { mz_file_write_func callback = [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t { CallbackData* data = (CallbackData*)pOpaque; if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->importer.parse_error()) { char error_buf[1024]; ::snprintf(error_buf, 1024, "Error (%s) while parsing '%s' at line %d", data->importer.parse_error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); throw Slic3r::FileIOError(error_buf); } return n; }; void* opaque = &data; res = mz_zip_reader_extract_to_callback(&archive, stat.m_file_index, callback, opaque, 0); } catch (const version_error& e) { // rethrow the exception throw Slic3r::FileIOError(e.what()); } catch (std::exception& e) { add_error(e.what()); return false; } if (res == 0) { add_error("Error while extracting model data from zip archive"); return false; } return true; } void _BBS_3MF_Importer::_extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions) { if (stat.m_uncomp_size > 0) { std::string buffer((size_t) stat.m_uncomp_size, 0); mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void *) buffer.data(), (size_t) stat.m_uncomp_size, 0); if (res == 0) { add_error("Error while reading cut information data to buffer"); return; } std::istringstream iss(buffer); // wrap returned xml to istringstream pt::ptree objects_tree; pt::read_xml(iss, objects_tree); for (const auto &object : objects_tree.get_child("objects")) { pt::ptree object_tree = object.second; int obj_idx = object_tree.get(".id", -1); if (obj_idx <= 0) { add_error("Found invalid object id"); continue; } IdToCutObjectInfoMap::iterator object_item = m_cut_object_infos.find(obj_idx); if (object_item != m_cut_object_infos.end()) { add_error("Found duplicated cut_object_id"); continue; } CutObjectBase cut_id; std::vector connectors; for (const auto &obj_cut_info : object_tree) { if (obj_cut_info.first == "cut_id") { pt::ptree cut_id_tree = obj_cut_info.second; cut_id = CutObjectBase(ObjectID(cut_id_tree.get(".id")), cut_id_tree.get(".check_sum"), cut_id_tree.get(".connectors_cnt")); } if (obj_cut_info.first == "connectors") { pt::ptree cut_connectors_tree = obj_cut_info.second; for (const auto &cut_connector : cut_connectors_tree) { if (cut_connector.first != "connector") continue; pt::ptree connector_tree = cut_connector.second; CutObjectInfo::Connector connector = {connector_tree.get(".volume_id"), connector_tree.get(".type"), connector_tree.get(".radius", 0.f), connector_tree.get(".height", 0.f), connector_tree.get(".r_tolerance"), connector_tree.get(".h_tolerance")}; connectors.emplace_back(connector); } } } CutObjectInfo cut_info{cut_id, connectors}; m_cut_object_infos.insert({obj_idx, cut_info}); } } } void _BBS_3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, const std::string& archive_filename) { if (stat.m_uncomp_size > 0) { std::string buffer((size_t)stat.m_uncomp_size, 0); mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == 0) { add_error("Error while reading config data to buffer"); return; } ConfigBase::load_from_gcode_string_legacy(config, buffer.data(), config_substitutions); } } //BBS: extract project config from json files void _BBS_3MF_Importer::_extract_project_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model& model) { if (stat.m_uncomp_size > 0) { const std::string& temp_path = model.get_backup_path(); std::string dest_file = temp_path + std::string("/") + "_temp_3.config";; std::string dest_zip_file = encode_path(dest_file.c_str()); mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_file % stat.m_filename % res; if (res == 0) { add_error("Error while extract project config file to file"); return; } std::map key_values; std::string reason; int ret = config.load_from_json(dest_file, config_substitutions, true, key_values, reason); if (ret) { add_error("Error load config from json:"+reason); return; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", load project config file successfully from %1%\n") %dest_file; } } //BBS: extract project embedded presets void _BBS_3MF_Importer::_extract_project_embedded_presets_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, std::vector&project_presets, Model& model, Preset::Type type, bool use_json) { if (stat.m_uncomp_size > 0) { /*std::string src_file = decode_path(stat.m_filename); std::size_t found = src_file.find(METADATA_DIR); if (found != std::string::npos) src_file = src_file.substr(found + METADATA_STR_LEN); else return;*/ std::string dest_file = m_backup_path + std::string("/") + "_temp_2.config";; std::string dest_zip_file = encode_path(dest_file.c_str()); mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_file % stat.m_filename % res; if (res == 0) { add_error("Error while extract auxiliary file to file"); return; } //load presets DynamicPrintConfig config; //ConfigSubstitutions config_substitutions = config.load_from_ini(dest_file, Enable); std::map key_values; std::string reason; ConfigSubstitutions config_substitutions = use_json? config.load_from_json(dest_file, Enable, key_values, reason) : config.load_from_ini(dest_file, Enable); if (!reason.empty()) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", load project embedded config from %1% failed\n") % dest_file; //skip this file return; } ConfigOptionString* print_name; ConfigOptionStrings* filament_names; std::string preset_name; if (type == Preset::TYPE_PRINT) { print_name = dynamic_cast < ConfigOptionString* > (config.option("print_settings_id")); if (!print_name) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", can not found print_settings_id from %1%\n") % dest_file; //skip this file return; } preset_name = print_name->value; } else if (type == Preset::TYPE_FILAMENT) { filament_names = dynamic_cast < ConfigOptionStrings* > (config.option("filament_settings_id")); if (!filament_names) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", can not found filament_settings_id from %1%\n") % dest_file; //skip this file return; } preset_name = filament_names->values[0]; } else if (type == Preset::TYPE_PRINTER) { print_name = dynamic_cast < ConfigOptionString* > (config.option("printer_settings_id")); if (!print_name) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", can not found printer_settings_id from %1%\n") % dest_file; //skip this file return; } preset_name = print_name->value; } else { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", invalid type %1% from file %2%\n")% Preset::get_type_string(type) % dest_file; //skip this file return; } Preset *preset = new Preset(type, preset_name, false); preset->file = dest_file; preset->config = std::move(config); preset->loaded = true; preset->is_project_embedded = true; preset->is_external = true; preset->is_dirty = false; std::string version_str = key_values[BBL_JSON_KEY_VERSION]; boost::optional version = Semver::parse(version_str); if (version) { preset->version = *version; } else preset->version = this->m_bambuslicer_generator_version?*this->m_bambuslicer_generator_version: Semver(); /*for (int i = 0; i < config_substitutions.size(); i++) { //ConfigSubstitution config_substitution; //config_substitution.opt_def = optdef; //config_substitution.old_value = value; //config_substitution.new_value = ConfigOptionUniquePtr(opt->clone()); preset->loading_substitutions.emplace_back(std::move(config_substitutions[i])); }*/ if (!config_substitutions.empty()) { preset->loading_substitutions = new ConfigSubstitutions(); *(preset->loading_substitutions) = std::move(config_substitutions); } project_presets.push_back(preset); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", create one project embedded preset: %1% from %2%, type %3%\n") % preset_name % dest_file %Preset::get_type_string(type); } } void _BBS_3MF_Importer::_extract_auxiliary_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", stat.m_uncomp_size is %1%")%stat.m_uncomp_size; if (stat.m_uncomp_size > 0) { std::string dest_file; if (stat.m_is_utf8) { dest_file = stat.m_filename; } else { std::string extra(1024, 0); size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size()); dest_file = ZipUnicodePathExtraField::decode(extra.substr(0, n), stat.m_filename); } std::string temp_path = model.get_auxiliary_file_temp_path(); //aux directory from model boost::filesystem::path dir = boost::filesystem::path(temp_path); std::size_t found = dest_file.find(AUXILIARY_DIR); if (found != std::string::npos) dest_file = dest_file.substr(found + AUXILIARY_STR_LEN); else return; if (dest_file.find('/') != std::string::npos) { boost::filesystem::path src_path = boost::filesystem::path(dest_file); boost::filesystem::path parent_path = src_path.parent_path(); std::string temp_path = dir.string() + std::string("/") + parent_path.string(); boost::filesystem::path parent_full_path = boost::filesystem::path(temp_path); if (!boost::filesystem::exists(parent_full_path)) boost::filesystem::create_directories(parent_full_path); } dest_file = dir.string() + std::string("/") + dest_file; std::string dest_zip_file = encode_path(dest_file.c_str()); mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_file % stat.m_filename % res; if (res == 0) { add_error("Error while extract auxiliary file to file"); return; } } } void _BBS_3MF_Importer::_extract_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size > 0) { std::string src_file = decode_path(stat.m_filename); // BBS: use backup path //aux directory from model boost::filesystem::path dest_path = boost::filesystem::path(m_backup_path + "/" + src_file); std::string dest_zip_file = encode_path(dest_path.string().c_str()); mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_path % stat.m_filename % res; if (res == 0) { add_error("Error while extract file to temp directory"); return; } } return; } void _BBS_3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size > 0) { std::string buffer((size_t)stat.m_uncomp_size, 0); mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == 0) { add_error("Error while reading layer heights profile data to buffer"); return; } if (buffer.back() == '\n') buffer.pop_back(); std::vector objects; boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); for (const std::string& object : objects) { std::vector object_data; boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); if (object_data.size() != 2) { add_error("Error while reading object data"); continue; } std::vector object_data_id; boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); if (object_data_id.size() != 2) { add_error("Error while reading object id"); continue; } int object_id = std::atoi(object_data_id[1].c_str()); if (object_id == 0) { add_error("Found invalid object id"); continue; } IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id); if (object_item != m_layer_heights_profiles.end()) { add_error("Found duplicated layer heights profile"); continue; } std::vector object_data_profile; boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off); if (object_data_profile.size() <= 4 || object_data_profile.size() % 2 != 0) { add_error("Found invalid layer heights profile"); continue; } std::vector profile; profile.reserve(object_data_profile.size()); for (const std::string& value : object_data_profile) { profile.push_back((coordf_t)std::atof(value.c_str())); } m_layer_heights_profiles.insert({ object_id, profile }); } } } void _BBS_3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions) { if (stat.m_uncomp_size > 0) { std::string buffer((size_t)stat.m_uncomp_size, 0); mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == 0) { add_error("Error while reading layer config ranges data to buffer"); return; } std::istringstream iss(buffer); // wrap returned xml to istringstream pt::ptree objects_tree; pt::read_xml(iss, objects_tree); for (const auto& object : objects_tree.get_child("objects")) { pt::ptree object_tree = object.second; int obj_idx = object_tree.get(".id", -1); if (obj_idx <= 0) { add_error("Found invalid object id"); continue; } IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx); if (object_item != m_layer_config_ranges.end()) { add_error("Found duplicated layer config range"); continue; } t_layer_config_ranges config_ranges; for (const auto& range : object_tree) { if (range.first != "range") continue; pt::ptree range_tree = range.second; double min_z = range_tree.get(".min_z"); double max_z = range_tree.get(".max_z"); // get Z range information DynamicPrintConfig config; for (const auto& option : range_tree) { if (option.first != "option") continue; std::string opt_key = option.second.get(".opt_key"); std::string value = option.second.data(); config.set_deserialize(opt_key, value, config_substitutions); } config_ranges[{ min_z, max_z }].assign_config(std::move(config)); } if (!config_ranges.empty()) m_layer_config_ranges.insert({ obj_idx, std::move(config_ranges) }); } } } /* void _BBS_3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size > 0) { std::string buffer((size_t)stat.m_uncomp_size, 0); mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == 0) { add_error("Error while reading sla support points data to buffer"); return; } if (buffer.back() == '\n') buffer.pop_back(); std::vector objects; boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); // Info on format versioning - see 3mf.hpp int version = 0; std::string key("support_points_format_version="); if (!objects.empty() && objects[0].find(key) != std::string::npos) { objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string version = std::stoi(objects[0]); objects.erase(objects.begin()); // pop the header } for (const std::string& object : objects) { std::vector object_data; boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); if (object_data.size() != 2) { add_error("Error while reading object data"); continue; } std::vector object_data_id; boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); if (object_data_id.size() != 2) { add_error("Error while reading object id"); continue; } int object_id = std::atoi(object_data_id[1].c_str()); if (object_id == 0) { add_error("Found invalid object id"); continue; } IdToSlaSupportPointsMap::iterator object_item = m_sla_support_points.find(object_id); if (object_item != m_sla_support_points.end()) { add_error("Found duplicated SLA support points"); continue; } std::vector object_data_points; boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); std::vector sla_support_points; if (version == 0) { for (unsigned int i=0; i 0) { std::string buffer(size_t(stat.m_uncomp_size), 0); mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == 0) { add_error("Error while reading sla support points data to buffer"); return; } if (buffer.back() == '\n') buffer.pop_back(); std::vector objects; boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); // Info on format versioning - see 3mf.hpp int version = 0; std::string key("drain_holes_format_version="); if (!objects.empty() && objects[0].find(key) != std::string::npos) { objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string version = std::stoi(objects[0]); objects.erase(objects.begin()); // pop the header } for (const std::string& object : objects) { std::vector object_data; boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); if (object_data.size() != 2) { add_error("Error while reading object data"); continue; } std::vector object_data_id; boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); if (object_data_id.size() != 2) { add_error("Error while reading object id"); continue; } int object_id = std::atoi(object_data_id[1].c_str()); if (object_id == 0) { add_error("Found invalid object id"); continue; } IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id); if (object_item != m_sla_drain_holes.end()) { add_error("Found duplicated SLA drain holes"); continue; } std::vector object_data_points; boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); sla::DrainHoles sla_drain_holes; if (version == 1) { for (unsigned int i=0; i 0) { std::string buffer((size_t)stat.m_uncomp_size, 0); mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == 0) { add_error("Error while reading custom Gcodes per height data to buffer"); return; } std::istringstream iss(buffer); // wrap returned xml to istringstream pt::ptree main_tree; pt::read_xml(iss, main_tree); if (main_tree.front().first != "custom_gcodes_per_layer") return; auto extract_code = [this](int plate_id, pt::ptree code_tree) { for (const auto& code : code_tree) { if (code.first == "mode") { pt::ptree tree = code.second; std::string mode = tree.get(".value"); m_model->plates_custom_gcodes[plate_id - 1].mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder : mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle : CustomGCode::Mode::MultiExtruder; } if (code.first == "layer") { pt::ptree tree = code.second; double print_z = tree.get(".top_z"); int extruder = tree.get(".extruder"); std::string color = tree.get(".color"); CustomGCode::Type type; std::string extra; pt::ptree attr_tree = tree.find("")->second; if (attr_tree.find("type") == attr_tree.not_found()) { // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer // read old data ... std::string gcode = tree.get(".gcode"); // ... and interpret them to the new data type = gcode == "M600" ? CustomGCode::ColorChange : gcode == "M601" ? CustomGCode::PausePrint : gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom; extra = type == CustomGCode::PausePrint ? color : type == CustomGCode::Custom ? gcode : ""; } else { type = static_cast(tree.get(".type")); extra = tree.get(".extra"); } m_model->plates_custom_gcodes[plate_id - 1].gcodes.push_back(CustomGCode::Item{ print_z, type, extruder, color, extra }); } } }; m_model->plates_custom_gcodes.clear(); bool has_plate_info = false; for (const auto& element : main_tree.front().second) { if (element.first == "plate") { has_plate_info = true; int plate_id = -1; pt::ptree code_tree = element.second; for (const auto& code : code_tree) { if (code.first == "plate_info") { plate_id = code.second.get(".id"); } } if (plate_id == -1) continue; extract_code(plate_id, code_tree); } } if (!has_plate_info) { int plate_id = 1; pt::ptree code_tree = main_tree.front().second; extract_code(plate_id, code_tree); } } } void _BBS_3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes) { if (m_xml_parser == nullptr) return; bool res = true; unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); if (::strcmp(MODEL_TAG, name) == 0) res = _handle_start_model(attributes, num_attributes); else if (::strcmp(RESOURCES_TAG, name) == 0) res = _handle_start_resources(attributes, num_attributes); else if (::strcmp(OBJECT_TAG, name) == 0) res = _handle_start_object(attributes, num_attributes); else if (::strcmp(COLOR_GROUP_TAG, name) == 0) res = _handle_start_color_group(attributes, num_attributes); else if (::strcmp(COLOR_TAG, name) == 0) res = _handle_start_color(attributes, num_attributes); else if (::strcmp(MESH_TAG, name) == 0) res = _handle_start_mesh(attributes, num_attributes); else if (::strcmp(VERTICES_TAG, name) == 0) res = _handle_start_vertices(attributes, num_attributes); else if (::strcmp(VERTEX_TAG, name) == 0) res = _handle_start_vertex(attributes, num_attributes); else if (::strcmp(TRIANGLES_TAG, name) == 0) res = _handle_start_triangles(attributes, num_attributes); else if (::strcmp(TRIANGLE_TAG, name) == 0) res = _handle_start_triangle(attributes, num_attributes); else if (::strcmp(COMPONENTS_TAG, name) == 0) res = _handle_start_components(attributes, num_attributes); else if (::strcmp(COMPONENT_TAG, name) == 0) res = _handle_start_component(attributes, num_attributes); else if (::strcmp(BUILD_TAG, name) == 0) res = _handle_start_build(attributes, num_attributes); else if (::strcmp(ITEM_TAG, name) == 0) res = _handle_start_item(attributes, num_attributes); else if (::strcmp(METADATA_TAG, name) == 0) res = _handle_start_metadata(attributes, num_attributes); if (!res) _stop_xml_parser(); } void _BBS_3MF_Importer::_handle_end_model_xml_element(const char* name) { if (m_xml_parser == nullptr) return; bool res = true; if (::strcmp(MODEL_TAG, name) == 0) res = _handle_end_model(); else if (::strcmp(RESOURCES_TAG, name) == 0) res = _handle_end_resources(); else if (::strcmp(OBJECT_TAG, name) == 0) res = _handle_end_object(); else if (::strcmp(COLOR_GROUP_TAG, name) == 0) res = _handle_end_color_group(); else if (::strcmp(COLOR_TAG, name) == 0) res = _handle_end_color(); else if (::strcmp(MESH_TAG, name) == 0) res = _handle_end_mesh(); else if (::strcmp(VERTICES_TAG, name) == 0) res = _handle_end_vertices(); else if (::strcmp(VERTEX_TAG, name) == 0) res = _handle_end_vertex(); else if (::strcmp(TRIANGLES_TAG, name) == 0) res = _handle_end_triangles(); else if (::strcmp(TRIANGLE_TAG, name) == 0) res = _handle_end_triangle(); else if (::strcmp(COMPONENTS_TAG, name) == 0) res = _handle_end_components(); else if (::strcmp(COMPONENT_TAG, name) == 0) res = _handle_end_component(); else if (::strcmp(BUILD_TAG, name) == 0) res = _handle_end_build(); else if (::strcmp(ITEM_TAG, name) == 0) res = _handle_end_item(); else if (::strcmp(METADATA_TAG, name) == 0) res = _handle_end_metadata(); if (!res) _stop_xml_parser(); } void _BBS_3MF_Importer::_handle_xml_characters(const XML_Char* s, int len) { m_curr_characters.append(s, len); } void _BBS_3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes) { if (m_xml_parser == nullptr) return; bool res = true; unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); if (::strcmp(CONFIG_TAG, name) == 0) res = _handle_start_config(attributes, num_attributes); else if (::strcmp(OBJECT_TAG, name) == 0) res = _handle_start_config_object(attributes, num_attributes); else if (::strcmp(VOLUME_TAG, name) == 0) res = _handle_start_config_volume(attributes, num_attributes); else if (::strcmp(PART_TAG, name) == 0) res = _handle_start_config_volume(attributes, num_attributes); else if (::strcmp(MESH_STAT_TAG, name) == 0) res = _handle_start_config_volume_mesh(attributes, num_attributes); else if (::strcmp(METADATA_TAG, name) == 0) res = _handle_start_config_metadata(attributes, num_attributes); else if (::strcmp(PLATE_TAG, name) == 0) res = _handle_start_config_plater(attributes, num_attributes); else if (::strcmp(INSTANCE_TAG, name) == 0) res = _handle_start_config_plater_instance(attributes, num_attributes); else if (::strcmp(FILAMENT_TAG, name) == 0) res = _handle_start_config_filament(attributes, num_attributes); else if (::strcmp(SLICE_WARNING_TAG, name) == 0) res = _handle_start_config_warning(attributes, num_attributes); else if (::strcmp(ASSEMBLE_TAG, name) == 0) res = _handle_start_assemble(attributes, num_attributes); else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0) res = _handle_start_assemble_item(attributes, num_attributes); else if (::strcmp(TEXT_INFO_TAG, name) == 0) res = _handle_start_text_info_item(attributes, num_attributes); if (!res) _stop_xml_parser(); } void _BBS_3MF_Importer::_handle_end_config_xml_element(const char* name) { if (m_xml_parser == nullptr) return; bool res = true; if (::strcmp(CONFIG_TAG, name) == 0) res = _handle_end_config(); else if (::strcmp(OBJECT_TAG, name) == 0) res = _handle_end_config_object(); else if (::strcmp(VOLUME_TAG, name) == 0) res = _handle_end_config_volume(); else if (::strcmp(PART_TAG, name) == 0) res = _handle_end_config_volume(); else if (::strcmp(MESH_STAT_TAG, name) == 0) res = _handle_end_config_volume_mesh(); else if (::strcmp(METADATA_TAG, name) == 0) res = _handle_end_config_metadata(); else if (::strcmp(PLATE_TAG, name) == 0) res = _handle_end_config_plater(); else if (::strcmp(FILAMENT_TAG, name) == 0) res = _handle_end_config_filament(); else if (::strcmp(INSTANCE_TAG, name) == 0) res = _handle_end_config_plater_instance(); else if (::strcmp(ASSEMBLE_TAG, name) == 0) res = _handle_end_assemble(); else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0) res = _handle_end_assemble_item(); if (!res) _stop_xml_parser(); } bool _BBS_3MF_Importer::_handle_start_model(const char** attributes, unsigned int num_attributes) { m_unit_factor = bbs_get_unit_factor(bbs_get_attribute_value_string(attributes, num_attributes, UNIT_ATTR)); return true; } bool _BBS_3MF_Importer::_handle_end_model() { // BBS: Production Extension if (!m_sub_model_path.empty()) return true; // deletes all non-built or non-instanced objects for (const IdToModelObjectMap::value_type& object : m_objects) { if (object.second >= int(m_model->objects.size())) { add_error("Unable to find object"); return false; } ModelObject *model_object = m_model->objects[object.second]; if (model_object != nullptr && model_object->instances.size() == 0) m_model->delete_object(model_object); } //construct the index maps for (const IdToCurrentObjectMap::value_type& object : m_current_objects) { m_index_paths.insert({ object.first.second, object.first.first}); } if (!m_is_bbl_3mf) { // if the 3mf was not produced by BambuStudio and there is only one object, // set the object name to match the filename if (m_model->objects.size() == 1) m_model->objects.front()->name = m_name; } // applies instances' matrices for (Instance& instance : m_instances) { if (instance.instance != nullptr && instance.instance->get_object() != nullptr) // apply the transform to the instance _apply_transform(*instance.instance, instance.transform); } return true; } bool _BBS_3MF_Importer::_handle_start_resources(const char** attributes, unsigned int num_attributes) { // do nothing return true; } bool _BBS_3MF_Importer::_handle_end_resources() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_object(const char** attributes, unsigned int num_attributes) { // reset current object data if (m_curr_object) { delete m_curr_object; m_curr_object = nullptr; } std::string object_type = bbs_get_attribute_value_string(attributes, num_attributes, TYPE_ATTR); if (bbs_is_valid_object_type(object_type)) { if (!m_curr_object) { m_curr_object = new CurrentObject(); // create new object (it may be removed later if no instances are generated from it) /*m_curr_object->model_object_idx = (int)m_model->objects.size(); m_curr_object.object = m_model->add_object(); if (m_curr_object.object == nullptr) { add_error("Unable to create object"); return false; }*/ } m_curr_object->id = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR); m_curr_object->name = bbs_get_attribute_value_string(attributes, num_attributes, NAME_ATTR); m_curr_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_ATTR); if (m_curr_object->uuid.empty()) { m_curr_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_LOWER_ATTR); } m_curr_object->pid = bbs_get_attribute_value_int(attributes, num_attributes, PID_ATTR); } return true; } bool _BBS_3MF_Importer::_handle_end_object() { if (!m_load_model) { delete m_curr_object; m_curr_object = nullptr; return true; } if (!m_curr_object || (m_curr_object->id == -1)) { add_error("Found invalid object"); return false; } else { if (m_is_bbl_3mf && boost::ends_with(m_curr_object->uuid, OBJECT_UUID_SUFFIX) && m_load_restore) { // Adjust backup object/volume id std::istringstream iss(m_curr_object->uuid); int backup_id; bool need_replace = false; if (iss >> std::hex >> backup_id) { need_replace = (m_curr_object->id != backup_id); m_curr_object->id = backup_id; } if (!m_curr_object->components.empty()) { Id first_id = m_curr_object->components.front().object_id; first_id.second = 0; IdToCurrentObjectMap::iterator current_object = m_current_objects.lower_bound(first_id); IdToCurrentObjectMap new_map; for (int index = 0; index < m_curr_object->components.size(); index++) { Component& component = m_curr_object->components[index]; Id new_id = component.object_id; new_id.second = (index + 1) << 16 | backup_id; if (current_object != m_current_objects.end() && (new_id.first.empty() || new_id.first == current_object->first.first)) { current_object->second.id = new_id.second; new_map.insert({new_id, std::move(current_object->second)}); current_object = m_current_objects.erase(current_object); } else { add_error("can not find object for component, id=" + std::to_string(component.object_id.second)); delete m_curr_object; m_curr_object = nullptr; return false; } component.object_id.second = new_id.second; } for (auto & obj : new_map) m_current_objects.insert({obj.first, std::move(obj.second)}); } } Id id = std::make_pair(m_sub_model_path, m_curr_object->id); if (m_current_objects.find(id) == m_current_objects.end()) { m_current_objects.insert({ id, std::move(*m_curr_object) }); delete m_curr_object; m_curr_object = nullptr; } else { add_error("Found object with duplicate id"); delete m_curr_object; m_curr_object = nullptr; return false; } } /*if (m_curr_object.object != nullptr) { if (m_curr_object.id != -1) { if (m_curr_object.geometry.empty()) { // no geometry defined // remove the object from the model m_model->delete_object(m_curr_object.object); if (m_curr_object.components.empty()) { // no components defined -> invalid object, delete it IdToModelObjectMap::iterator object_item = m_objects.find(id); if (object_item != m_objects.end()) m_objects.erase(object_item); IdToAliasesMap::iterator alias_item = m_objects_aliases.find(id); if (alias_item != m_objects_aliases.end()) m_objects_aliases.erase(alias_item); } else // adds components to aliases m_objects_aliases.insert({ id, m_curr_object.components }); } else { // geometry defined, store it for later use m_geometries.insert({ id, std::move(m_curr_object.geometry) }); // stores the object for later use if (m_objects.find(id) == m_objects.end()) { m_objects.insert({ id, m_curr_object.model_object_idx }); m_objects_aliases.insert({ id, { 1, Component(m_curr_object.id) } }); // aliases itself } else { add_error("Found object with duplicate id"); return false; } } } else { //sub objects } }*/ return true; } bool _BBS_3MF_Importer::_handle_start_color_group(const char **attributes, unsigned int num_attributes) { m_current_color_group = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR); return true; } bool _BBS_3MF_Importer::_handle_end_color_group() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_color(const char **attributes, unsigned int num_attributes) { std::string color = bbs_get_attribute_value_string(attributes, num_attributes, COLOR_ATTR); m_group_id_to_color[m_current_color_group] = color; return true; } bool _BBS_3MF_Importer::_handle_end_color() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_mesh(const char** attributes, unsigned int num_attributes) { // reset current geometry if (m_curr_object) m_curr_object->geometry.reset(); return true; } bool _BBS_3MF_Importer::_handle_end_mesh() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_vertices(const char** attributes, unsigned int num_attributes) { // reset current vertices if (m_curr_object) m_curr_object->geometry.vertices.clear(); return true; } bool _BBS_3MF_Importer::_handle_end_vertices() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_vertex(const char** attributes, unsigned int num_attributes) { // appends the vertex coordinates // missing values are set equal to ZERO if (m_curr_object) m_curr_object->geometry.vertices.emplace_back( m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, X_ATTR), m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Y_ATTR), m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Z_ATTR)); return true; } bool _BBS_3MF_Importer::_handle_end_vertex() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes) { // reset current triangles if (m_curr_object) m_curr_object->geometry.triangles.clear(); return true; } bool _BBS_3MF_Importer::_handle_end_triangles() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_triangle(const char** attributes, unsigned int num_attributes) { // we are ignoring the following attributes: // p1 // p2 // p3 // pid // see specifications // appends the triangle's vertices indices // missing values are set equal to ZERO if (m_curr_object) { m_curr_object->geometry.triangles.emplace_back( bbs_get_attribute_value_int(attributes, num_attributes, V1_ATTR), bbs_get_attribute_value_int(attributes, num_attributes, V2_ATTR), bbs_get_attribute_value_int(attributes, num_attributes, V3_ATTR)); m_curr_object->geometry.custom_supports.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); m_curr_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); m_curr_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); // BBS m_curr_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR)); } return true; } bool _BBS_3MF_Importer::_handle_end_triangle() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes) { // reset current components if (m_curr_object) m_curr_object->components.clear(); return true; } bool _BBS_3MF_Importer::_handle_end_components() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes) { std::string path = xml_unescape(bbs_get_attribute_value_string(attributes, num_attributes, PPATH_ATTR)); int object_id = bbs_get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); /*Id id = std::make_pair(m_sub_model_path, object_id); IdToModelObjectMap::iterator object_item = m_objects.find(id); if (object_item == m_objects.end()) { IdToAliasesMap::iterator alias_item = m_objects_aliases.find(id); if (alias_item == m_objects_aliases.end()) { add_error("Found component with invalid object id"); return false; } }*/ if (m_curr_object) { Id id = std::make_pair(m_sub_model_path.empty() ? path : m_sub_model_path, object_id); m_curr_object->components.emplace_back(id, transform); } return true; } bool _BBS_3MF_Importer::_handle_end_component() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_build(const char** attributes, unsigned int num_attributes) { // do nothing return true; } bool _BBS_3MF_Importer::_handle_end_build() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_item(const char** attributes, unsigned int num_attributes) { // we are ignoring the following attributes // thumbnail // partnumber // pid // pindex // see specifications int object_id = bbs_get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); std::string path = bbs_get_attribute_value_string(attributes, num_attributes, PPATH_ATTR); Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); int printable = bbs_get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); return !m_load_model || _create_object_instance(path, object_id, transform, printable, 1); } bool _BBS_3MF_Importer::_handle_end_item() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes) { m_curr_characters.clear(); std::string name = bbs_get_attribute_value_string(attributes, num_attributes, NAME_ATTR); if (!name.empty()) { m_curr_metadata_name = name; } return true; } inline static void check_painting_version(unsigned int loaded_version, unsigned int highest_supported_version, const std::string &error_msg) { if (loaded_version > highest_supported_version) throw version_error(error_msg); } bool _BBS_3MF_Importer::_handle_end_metadata() { if ((m_curr_metadata_name == BBS_3MF_VERSION)||(m_curr_metadata_name == BBS_3MF_VERSION1)) { //m_is_bbl_3mf = true; m_version = (unsigned int)atoi(m_curr_characters.c_str()); /*if (m_check_version && (m_version > VERSION_BBS_3MF_COMPATIBLE)) { // std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible.")); // throw version_error(msg.c_str()); const std::string msg = (boost::format(_(L("The selected 3mf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str(); throw version_error(msg); }*/ } else if (m_curr_metadata_name == BBL_APPLICATION_TAG) { // Generator application of the 3MF. // SLIC3R_APP_KEY - SLIC3R_VERSION if (boost::starts_with(m_curr_characters, "BambuStudio-")) { m_is_bbl_3mf = true; m_bambuslicer_generator_version = Semver::parse(m_curr_characters.substr(12)); } //TODO: currently use version 0, no need to load&&save this string /*} else if (m_curr_metadata_name == BBS_FDM_SUPPORTS_PAINTING_VERSION) { m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION, _(L("The selected 3MF contains FDM supports painted object using a newer version of BambuStudio and is not compatible."))); } else if (m_curr_metadata_name == BBS_SEAM_PAINTING_VERSION) { m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION, _(L("The selected 3MF contains seam painted object using a newer version of BambuStudio and is not compatible."))); } else if (m_curr_metadata_name == BBS_MM_PAINTING_VERSION) { m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION, _(L("The selected 3MF contains multi-material painted object using a newer version of BambuStudio and is not compatible.")));*/ } else if (m_curr_metadata_name == BBL_MODEL_ID_TAG) { m_model_id = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_MODEL_NAME_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found model name = " << m_curr_characters; model_info.model_name = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_ORIGIN_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found model name = " << m_curr_characters; model_info.origin = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_DESIGNER_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer = " << m_curr_characters; m_designer = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_DESIGNER_USER_ID_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer_user_id = " << m_curr_characters; m_designer_user_id = xml_unescape(m_curr_characters); }else if (m_curr_metadata_name == BBL_DESIGNER_MODEL_ID_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer_model_id = " << m_curr_characters; m_designer_id = xml_unescape(m_curr_characters); }else if (m_curr_metadata_name == BBL_DESIGN_ID_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found design_id = " << m_curr_characters; m_design_id = xml_unescape(m_curr_characters); }else if (m_curr_metadata_name == BBL_DESIGNER_COVER_FILE_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer_cover = " << m_curr_characters; model_info.cover_file = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_DESCRIPTION_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found description = " << m_curr_characters; model_info.description = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_LICENSE_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found license = " << m_curr_characters; model_info.license = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_COPYRIGHT_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found CopyRight = " << m_curr_characters; model_info.copyright = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_COPYRIGHT_NORMATIVE_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found Copyright = " << m_curr_characters; model_info.copyright = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_REGION_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found region = " << m_curr_characters; m_contry_code = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_PROFILE_TITLE_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_title = " << m_curr_characters; m_profile_title = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_PROFILE_COVER_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_cover = " << m_curr_characters; m_profile_cover = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_PROFILE_DESCRIPTION_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_description = " << m_curr_characters; m_Profile_description = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_PROFILE_USER_ID_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_user_id = " << m_curr_characters; m_profile_user_id = xml_unescape(m_curr_characters); }else if (m_curr_metadata_name == BBL_PROFILE_USER_NAME_TAG) { BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found profile_user_name = " << m_curr_characters; m_profile_user_name = xml_unescape(m_curr_characters); } else if (m_curr_metadata_name == BBL_CREATION_DATE_TAG) { ; } else if (m_curr_metadata_name == BBL_MODIFICATION_TAG) { ; } else { ; } if (!m_curr_metadata_name.empty()) { model_info.metadata_items[m_curr_metadata_name] = xml_unescape(m_curr_characters); } return true; } bool _BBS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) { static const unsigned int MAX_RECURSIONS = 10; // escape from circular aliasing if (recur_counter > MAX_RECURSIONS) { add_error("Too many recursions"); return false; } Id id{path, object_id}; IdToCurrentObjectMap::iterator it = m_current_objects.find(id); if (it == m_current_objects.end()) { add_error("can not find object id " + std::to_string(object_id) + " to builditem"); return false; } IdToModelObjectMap::iterator object_item = m_objects.find(id); if (object_item == m_objects.end()) { //add object CurrentObject& current_object = it->second; int object_index = (int)m_model->objects.size(); ModelObject* model_object = m_model->add_object(); if (model_object == nullptr) { add_error("Unable to create object for builditem, id " + std::to_string(object_id)); return false; } m_objects.insert({ id, object_index }); current_object.model_object_idx = object_index; current_object.object = model_object; ModelInstance* instance = m_model->objects[object_index]->add_instance(); if (instance == nullptr) { add_error("error when add object instance for id " + std::to_string(object_id)); return false; } instance->printable = printable; m_instances.emplace_back(instance, transform); if (m_is_bbl_3mf && boost::ends_with(current_object.uuid, OBJECT_UUID_SUFFIX)) { std::istringstream iss(current_object.uuid); int backup_id; if (iss >> std::hex >> backup_id) { m_model->set_object_backup_id(*model_object, backup_id); } } /*if (!current_object.geometry.empty()) { } else if (!current_object.components.empty()) { // recursively process nested components for (const Component& component : it->second) { if (!_create_object_instance(path, component.object_id, transform * component.transform, printable, recur_counter + 1)) return false; } } else { add_error("can not construct build items with invalid object, id " + std::to_string(object_id)); return false; }*/ } else { //add instance ModelInstance* instance = m_model->objects[object_item->second]->add_instance(); if (instance == nullptr) { add_error("error when add object instance for id " + std::to_string(object_id)); return false; } instance->printable = printable; m_instances.emplace_back(instance, transform); } /*if (it->second.size() == 1 && it->second[0].object_id == object_id) { // aliasing to itself IdToModelObjectMap::iterator object_item = m_objects.find(id); if (object_item == m_objects.end() || object_item->second == -1) { add_error("Found invalid object"); return false; } else { ModelInstance* instance = m_model->objects[object_item->second]->add_instance(); if (instance == nullptr) { add_error("Unable to add object instance"); return false; } instance->printable = printable; m_instances.emplace_back(instance, transform); } } else { // recursively process nested components for (const Component& component : it->second) { if (!_create_object_instance(path, component.object_id, transform * component.transform, printable, recur_counter + 1)) return false; } }*/ return true; } void _BBS_3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform) { Slic3r::Geometry::Transformation t(transform); // invalid scale value, return if (!t.get_scaling_factor().all()) return; instance.set_transformation(t); } bool _BBS_3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes) { // do nothing return true; } bool _BBS_3MF_Importer::_handle_end_config() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_config_object(const char** attributes, unsigned int num_attributes) { if (m_parsing_slice_info) return true; int object_id = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR); IdToMetadataMap::iterator object_item = m_objects_metadata.find(object_id); if (object_item != m_objects_metadata.end()) { add_error("Duplicated object id: " + std::to_string(object_id) + " in model_settings.config"); return false; } // Added because of github #3435, currently not used by PrusaSlicer // int instances_count_id = bbs_get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR); m_objects_metadata.insert({ object_id, ObjectMetadata() }); m_curr_config.object_id = object_id; return true; } bool _BBS_3MF_Importer::_handle_end_config_object() { m_curr_config.object_id = -1; return true; } bool _BBS_3MF_Importer::_handle_start_config_volume(const char** attributes, unsigned int num_attributes) { IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); if (object == m_objects_metadata.end()) { add_error("can not find object for part, id " + std::to_string(m_curr_config.object_id) ); return false; } m_curr_config.volume_id = (int)object->second.volumes.size(); unsigned int first_triangle_id = (unsigned int)bbs_get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR); unsigned int last_triangle_id = (unsigned int)bbs_get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR); //BBS: refine the part type logic std::string subtype_str = bbs_get_attribute_value_string(attributes, num_attributes, SUBTYPE_ATTR); ModelVolumeType type = ModelVolume::type_from_string(subtype_str); int subbject_id = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR); if (last_triangle_id > 0) object->second.volumes.emplace_back(first_triangle_id, last_triangle_id, type); else object->second.volumes.emplace_back(subbject_id, type); return true; } bool _BBS_3MF_Importer::_handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes) { IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); if (object == m_objects_metadata.end()) { add_error("can not find object for mesh_stats, id " + std::to_string(m_curr_config.object_id) ); return false; } if ((m_curr_config.volume_id == -1) || ((object->second.volumes.size() - 1) < m_curr_config.volume_id)) { add_error("can not find part for mesh_stats"); return false; } ObjectMetadata::VolumeMetadata& volume = object->second.volumes[m_curr_config.volume_id]; int edges_fixed = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_EDGES_FIXED ); int degenerate_facets = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_DEGENERATED_FACETS); int facets_removed = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_REMOVED ); int facets_reversed = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_RESERVED ); int backwards_edges = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_BACKWARDS_EDGES ); volume.mesh_stats = { edges_fixed, degenerate_facets, facets_removed, facets_reversed, backwards_edges }; return true; } bool _BBS_3MF_Importer::_handle_end_config_volume() { m_curr_config.volume_id = -1; return true; } bool _BBS_3MF_Importer::_handle_end_config_volume_mesh() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_config_metadata(const char** attributes, unsigned int num_attributes) { //std::string type = bbs_get_attribute_value_string(attributes, num_attributes, TYPE_ATTR); std::string key = bbs_get_attribute_value_string(attributes, num_attributes, KEY_ATTR); std::string value = bbs_get_attribute_value_string(attributes, num_attributes, VALUE_ATTR); if ((m_curr_plater == nullptr)&&!m_parsing_slice_info) { IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); if (object == m_objects_metadata.end()) { add_error("Cannot find object for metadata, id " + std::to_string(m_curr_config.object_id)); return false; } if (m_curr_config.volume_id == -1) object->second.metadata.emplace_back(key, value); else { if (size_t(m_curr_config.volume_id) < object->second.volumes.size()) object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value); } } else { //plater if (key == PLATERID_ATTR) { m_curr_plater->plate_index = atoi(value.c_str()); } else if (key == PLATER_NAME_ATTR) { m_curr_plater->plate_name = xml_unescape(value.c_str()); } else if (key == LOCK_ATTR) { std::istringstream(value) >> std::boolalpha >> m_curr_plater->locked; } else if (key == BED_TYPE_ATTR) { BedType bed_type = BedType::btPC; ConfigOptionEnum::from_string(value, bed_type); m_curr_plater->config.set_key_value("curr_bed_type", new ConfigOptionEnum(bed_type)); } else if (key == PRINT_SEQUENCE_ATTR) { PrintSequence print_sequence = PrintSequence::ByLayer; ConfigOptionEnum::from_string(value, print_sequence); m_curr_plater->config.set_key_value("print_sequence", new ConfigOptionEnum(print_sequence)); } else if (key == FIRST_LAYER_PRINT_SEQUENCE_ATTR) { auto get_vector_from_string = [](const std::string &str) -> std::vector { std::stringstream stream(str); int value; std::vector results; while (stream >> value) { results.push_back(value); } return results; }; m_curr_plater->config.set_key_value("first_layer_print_sequence", new ConfigOptionInts(get_vector_from_string(value))); } else if (key == OTHER_LAYERS_PRINT_SEQUENCE_ATTR) { auto get_vector_from_string = [](const std::string &str) -> std::vector { std::stringstream stream(str); int value; std::vector results; while (stream >> value) { results.push_back(value); } return results; }; m_curr_plater->config.set_key_value("other_layers_print_sequence", new ConfigOptionInts(get_vector_from_string(value))); } else if (key == OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR) { m_curr_plater->config.set_key_value("other_layers_print_sequence_nums", new ConfigOptionInt(stoi(value))); } else if (key == SPIRAL_VASE_MODE) { bool spiral_mode = false; std::istringstream(value) >> std::boolalpha >> spiral_mode; m_curr_plater->config.set_key_value("spiral_mode", new ConfigOptionBool(spiral_mode)); } else if (key == GCODE_FILE_ATTR) { m_curr_plater->gcode_file = value; } else if (key == THUMBNAIL_FILE_ATTR) { m_curr_plater->thumbnail_file = value; } else if (key == NO_LIGHT_THUMBNAIL_FILE_ATTR) { m_curr_plater->no_light_thumbnail_file = value; } else if (key == TOP_FILE_ATTR) { m_curr_plater->top_file = value; } else if (key == PICK_FILE_ATTR) { m_curr_plater->pick_file = value; } //else if (key == PATTERN_FILE_ATTR) //{ // m_curr_plater->pattern_file = value; //} else if (key == PATTERN_BBOX_FILE_ATTR) { m_curr_plater->pattern_bbox_file = value; } else if (key == INSTANCEID_ATTR) { m_curr_instance.instance_id = atoi(value.c_str()); } else if (key == IDENTIFYID_ATTR) { m_curr_instance.identify_id = atoi(value.c_str()); } else if (key == OBJECT_ID_ATTR) { m_curr_instance.object_id = atoi(value.c_str()); /*int obj_id = atoi(value.c_str()); m_curr_instance.object_id = -1; IndexToPathMap::iterator index_iter = m_index_paths.find(obj_id); if (index_iter == m_index_paths.end()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format(", can not find object for plate's item, id=%1%, skip this object")%obj_id; return true; } Id temp_id = std::make_pair(index_iter->second, index_iter->first); IdToModelObjectMap::iterator object_item = m_objects.find(temp_id); if (object_item == m_objects.end()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format(", can not find object for plate's item, ID <%1%, %2%>, skip this object")%index_iter->second %index_iter->first; return true; } m_curr_instance.object_id = object_item->second;*/ } else if (key == PLATE_IDX_ATTR) { int plate_index = atoi(value.c_str()); std::map::iterator it = m_plater_data.find(plate_index); if (it != m_plater_data.end()) m_curr_plater = it->second; } else if (key == SLICE_PREDICTION_ATTR) { if (m_curr_plater) m_curr_plater->gcode_prediction = value; } else if (key == SLICE_WEIGHT_ATTR) { if (m_curr_plater) m_curr_plater->gcode_weight = value; } else if (key == OUTSIDE_ATTR) { if (m_curr_plater) std::istringstream(value) >> std::boolalpha >> m_curr_plater->toolpath_outside; } else if (key == SUPPORT_USED_ATTR) { if (m_curr_plater) std::istringstream(value) >> std::boolalpha >> m_curr_plater->is_support_used; } else if (key == LABEL_OBJECT_ENABLED_ATTR) { if (m_curr_plater) std::istringstream(value) >> std::boolalpha >> m_curr_plater->is_label_object_enabled; } else if (key == PRINTER_MODEL_ID_ATTR) { if (m_curr_plater) m_curr_plater->printer_model_id = value; } else if (key == NOZZLE_DIAMETERS_ATTR) { if (m_curr_plater) m_curr_plater->nozzle_diameters = value; } } return true; } bool _BBS_3MF_Importer::_handle_end_config_metadata() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_config_filament(const char** attributes, unsigned int num_attributes) { if (m_curr_plater) { std::string id = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_ID_TAG); std::string type = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_TYPE_TAG); std::string color = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_COLOR_TAG); std::string used_m = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_M_TAG); std::string used_g = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_G_TAG); std::string filament_id = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_TRAY_INFO_ID_TAG); FilamentInfo filament_info; filament_info.id = atoi(id.c_str()) - 1; filament_info.type = type; filament_info.color = color; filament_info.used_m = atof(used_m.c_str()); filament_info.used_g = atof(used_g.c_str()); filament_info.filament_id = filament_id; m_curr_plater->slice_filaments_info.push_back(filament_info); } return true; } bool _BBS_3MF_Importer::_handle_end_config_filament() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_config_warning(const char** attributes, unsigned int num_attributes) { if (m_curr_plater) { std::string msg = bbs_get_attribute_value_string(attributes, num_attributes, WARNING_MSG_TAG); std::string lvl_str = bbs_get_attribute_value_string(attributes, num_attributes, "level"); GCodeProcessorResult::SliceWarning sw; sw.msg = msg; try { sw.level = atoi(lvl_str.c_str()); } catch(...) { }; m_curr_plater->warnings.push_back(sw); } return true; } bool _BBS_3MF_Importer::_handle_end_config_warning() { // do nothing return true; } bool _BBS_3MF_Importer::_handle_start_config_plater(const char** attributes, unsigned int num_attributes) { if (!m_parsing_slice_info) { m_curr_plater = new PlateData(); } return true; } bool _BBS_3MF_Importer::_handle_end_config_plater() { if (!m_curr_plater) { add_error("_handle_end_config_plater: don't find plate created before"); return false; } m_plater_data.emplace(m_curr_plater->plate_index, m_curr_plater); m_curr_plater = nullptr; return true; } bool _BBS_3MF_Importer::_handle_start_config_plater_instance(const char** attributes, unsigned int num_attributes) { if (!m_curr_plater) { add_error("_handle_start_config_plater_instance: don't find plate created before"); return false; } //do nothing return true; } bool _BBS_3MF_Importer::_handle_end_config_plater_instance() { if (!m_curr_plater) { add_error("_handle_end_config_plater_instance: don't find plate created before"); return false; } if ((m_curr_instance.object_id == -1) || (m_curr_instance.instance_id == -1)) { //add_error("invalid object id/instance id"); //skip this instance m_curr_instance.object_id = m_curr_instance.instance_id = -1; m_curr_instance.identify_id = 0; return true; } m_curr_plater->obj_inst_map.emplace(m_curr_instance.object_id, std::make_pair(m_curr_instance.instance_id, m_curr_instance.identify_id)); m_curr_instance.object_id = m_curr_instance.instance_id = -1; m_curr_instance.identify_id = 0; return true; } bool _BBS_3MF_Importer::_handle_start_assemble(const char** attributes, unsigned int num_attributes) { return true; } bool _BBS_3MF_Importer::_handle_end_assemble() { //do nothing return true; } bool _BBS_3MF_Importer::_handle_start_assemble_item(const char** attributes, unsigned int num_attributes) { if (!m_load_model) return true; int object_id = bbs_get_attribute_value_int(attributes, num_attributes, OBJECT_ID_ATTR); int instance_id = bbs_get_attribute_value_int(attributes, num_attributes, INSTANCEID_ATTR); IndexToPathMap::iterator index_iter = m_index_paths.find(object_id); if (index_iter == m_index_paths.end()) { add_error("can not find object for assemble item, id= " + std::to_string(object_id)); return false; } Id temp_id = std::make_pair(index_iter->second, index_iter->first); IdToModelObjectMap::iterator object_item = m_objects.find(temp_id); if (object_item == m_objects.end()) { add_error("can not find object for assemble item, id= " + std::to_string(object_id)); return false; } object_id = object_item->second; Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); Vec3d ofs2ass = bbs_get_offset_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, OFFSET_ATTR)); if (object_id < m_model->objects.size()) { if (instance_id < m_model->objects[object_id]->instances.size()) { m_model->objects[object_id]->instances[instance_id]->set_assemble_from_transform(transform); m_model->objects[object_id]->instances[instance_id]->set_offset_to_assembly(ofs2ass); } } return true; } bool _BBS_3MF_Importer::_handle_end_assemble_item() { return true; } bool _BBS_3MF_Importer::_handle_start_text_info_item(const char **attributes, unsigned int num_attributes) { IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); if (object == m_objects_metadata.end()) { add_error("can not find object for text_info, id " + std::to_string(m_curr_config.object_id)); return false; } if ((m_curr_config.volume_id == -1) || ((object->second.volumes.size() - 1) < m_curr_config.volume_id)) { add_error("can not find part for text_info"); return false; } ObjectMetadata::VolumeMetadata &volume = object->second.volumes[m_curr_config.volume_id]; TextInfo text_info; text_info.m_text = xml_unescape(bbs_get_attribute_value_string(attributes, num_attributes, TEXT_ATTR)); text_info.m_font_name = bbs_get_attribute_value_string(attributes, num_attributes, FONT_NAME_ATTR); text_info.m_curr_font_idx = bbs_get_attribute_value_int(attributes, num_attributes, FONT_INDEX_ATTR); text_info.m_font_size = bbs_get_attribute_value_float(attributes, num_attributes, FONT_SIZE_ATTR); text_info.m_thickness = bbs_get_attribute_value_float(attributes, num_attributes, THICKNESS_ATTR); text_info.m_embeded_depth = bbs_get_attribute_value_float(attributes, num_attributes, EMBEDED_DEPTH_ATTR); text_info.m_rotate_angle = bbs_get_attribute_value_float(attributes, num_attributes, ROTATE_ANGLE_ATTR); text_info.m_text_gap = bbs_get_attribute_value_float(attributes, num_attributes, TEXT_GAP_ATTR); text_info.m_bold = bbs_get_attribute_value_int(attributes, num_attributes, BOLD_ATTR); text_info.m_italic = bbs_get_attribute_value_int(attributes, num_attributes, ITALIC_ATTR); text_info.m_is_surface_text = bbs_get_attribute_value_int(attributes, num_attributes, SURFACE_TEXT_ATTR); text_info.m_keep_horizontal = bbs_get_attribute_value_int(attributes, num_attributes, KEEP_HORIZONTAL_ATTR); text_info.m_rr.mesh_id = bbs_get_attribute_value_int(attributes, num_attributes, HIT_MESH_ATTR); std::string hit_pos = bbs_get_attribute_value_string(attributes, num_attributes, HIT_POSITION_ATTR); if (!hit_pos.empty()) text_info.m_rr.hit = get_vec3_from_string(hit_pos); std::string hit_normal = bbs_get_attribute_value_string(attributes, num_attributes, HIT_NORMAL_ATTR); if (!hit_normal.empty()) text_info.m_rr.normal = get_vec3_from_string(hit_normal); volume.text_info = text_info; return true; } bool _BBS_3MF_Importer::_handle_end_text_info_item() { return true; } void XMLCALL _BBS_3MF_Importer::_handle_start_relationships_element(void* userData, const char* name, const char** attributes) { _BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData; if (importer != nullptr) importer->_handle_start_relationships_element(name, attributes); } void XMLCALL _BBS_3MF_Importer::_handle_end_relationships_element(void* userData, const char* name) { _BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData; if (importer != nullptr) importer->_handle_end_relationships_element(name); } void _BBS_3MF_Importer::_handle_start_relationships_element(const char* name, const char** attributes) { if (m_xml_parser == nullptr) return; bool res = true; unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); if (::strcmp(RELATIONSHIP_TAG, name) == 0) res = _handle_start_relationship(attributes, num_attributes); m_curr_characters.clear(); if (!res) _stop_xml_parser(); } void _BBS_3MF_Importer::_handle_end_relationships_element(const char* name) { if (m_xml_parser == nullptr) return; bool res = true; if (!res) _stop_xml_parser(); } bool _BBS_3MF_Importer::_handle_start_relationship(const char** attributes, unsigned int num_attributes) { std::string path = bbs_get_attribute_value_string(attributes, num_attributes, TARGET_ATTR); std::string type = bbs_get_attribute_value_string(attributes, num_attributes, RELS_TYPE_ATTR); if (boost::starts_with(type, "http://schemas.microsoft.com/3dmanufacturing/") && boost::ends_with(type, "3dmodel")) { if (m_start_part_path.empty()) m_start_part_path = path; else m_sub_model_paths.push_back(path); } else if (boost::starts_with(type, "http://schemas.openxmlformats.org/") && boost::ends_with(type, "thumbnail")) { if (boost::algorithm::ends_with(path, ".png")) m_thumbnail_path = path; } else if (boost::starts_with(type, "http://schemas.bambulab.com/") && boost::ends_with(type, "cover-thumbnail-middle")) { m_thumbnail_middle = path; } else if (boost::starts_with(type, "http://schemas.bambulab.com/") && boost::ends_with(type, "cover-thumbnail-small")) { m_thumbnail_small = path; } return true; } void _BBS_3MF_Importer::_generate_current_object_list(std::vector &sub_objects, Id object_id, IdToCurrentObjectMap ¤t_objects) { std::list> id_list; id_list.push_back(std::make_pair(Component(object_id, Transform3d::Identity()), Transform3d::Identity())); while (!id_list.empty()) { auto current_item = id_list.front(); Component current_id = current_item.first; id_list.pop_front(); IdToCurrentObjectMap::iterator current_object = current_objects.find(current_id.object_id); if (current_object != current_objects.end()) { //found one if (!current_object->second.components.empty()) { for (const Component &comp : current_object->second.components) { id_list.push_back(std::pair(comp, current_item.second * comp.transform)); } } else if (!(current_object->second.geometry.empty())) { //CurrentObject* ptr = &(current_objects[current_id]); //CurrentObject* ptr2 = &(current_object->second); sub_objects.push_back({ current_object->first, current_item.second}); } } } } bool _BBS_3MF_Importer::_generate_volumes_new(ModelObject& object, const std::vector &sub_objects, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions) { if (!object.volumes.empty()) { add_error("object already built with parts"); return false; } //unsigned int geo_tri_count = (unsigned int)geometry.triangles.size(); unsigned int renamed_volumes_count = 0; for (unsigned int index = 0; index < sub_objects.size(); index++) { //find the volume metadata firstly Component sub_comp = sub_objects[index]; Id object_id = sub_comp.object_id; IdToCurrentObjectMap::iterator current_object = m_current_objects.find(object_id); if (current_object == m_current_objects.end()) { add_error("sub_objects can not be found, id=" + std::to_string(object_id.second)); return false; } CurrentObject* sub_object = &(current_object->second); const ObjectMetadata::VolumeMetadata* volume_data = nullptr; ObjectMetadata::VolumeMetadata default_volume_data(sub_object->id); if (index < volumes.size() && volumes[index].subobject_id == sub_object->id) volume_data = &volumes[index]; else for (const ObjectMetadata::VolumeMetadata& volume_iter : volumes) { if (volume_iter.subobject_id == sub_object->id) { volume_data = &volume_iter; break; } } Transform3d volume_matrix_to_object = Transform3d::Identity(); bool has_transform = false; int shared_mesh_id = object_id.second; if (volume_data) { int found_count = 0; // extract the volume transformation from the volume's metadata, if present for (const Metadata& metadata : volume_data->metadata) { if (metadata.key == MATRIX_KEY) { volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); found_count++; } else if (metadata.key == MESH_SHARED_KEY){ //add the shared mesh logic shared_mesh_id = ::atoi(metadata.value.c_str()); found_count++; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": line %1%, shared_mesh_id %2%")%__LINE__%shared_mesh_id; } if (found_count >= 2) break; } } else { //create a volume_data volume_data = &default_volume_data; } ModelVolume* volume = nullptr; ModelVolume *shared_volume = nullptr; BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": line %1%, subobject_id %2%, shared_mesh_id %3%")%__LINE__ %sub_object->id %shared_mesh_id; if (shared_mesh_id != -1) { std::map::iterator iter = m_shared_meshes.find(shared_mesh_id); if (iter != m_shared_meshes.end()) { shared_volume = iter->second; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": line %1%, found shared mesh, id %2%, mesh %3%")%__LINE__%shared_mesh_id%shared_volume; } } else { //for some cases, object point to this shared mesh already loaded, treat that one as the root std::map::iterator iter = m_shared_meshes.find(sub_object->id); if (iter != m_shared_meshes.end()) { shared_volume = iter->second; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": line %1%, already loaded copy-share mesh before, id %2%, mesh %3%")%__LINE__%sub_object->id%shared_volume; } } const size_t triangles_count = sub_object->geometry.triangles.size(); if (triangles_count == 0) { add_error("found no trianges in the object " + std::to_string(sub_object->id)); return false; } if (!shared_volume){ // splits volume out of imported geometry indexed_triangle_set its; its.indices.assign(sub_object->geometry.triangles.begin(), sub_object->geometry.triangles.end()); //const size_t triangles_count = its.indices.size(); //if (triangles_count == 0) { // add_error("found no trianges in the object " + std::to_string(sub_object->id)); // return false; //} for (const Vec3i& face : its.indices) { for (const int tri_id : face) { if (tri_id < 0 || tri_id >= int(sub_object->geometry.vertices.size())) { add_error("invalid vertex id in object " + std::to_string(sub_object->id)); return false; } } } its.vertices.assign(sub_object->geometry.vertices.begin(), sub_object->geometry.vertices.end()); // BBS for (const std::string prop_str : sub_object->geometry.face_properties) { FaceProperty face_prop; face_prop.from_string(prop_str); its.properties.push_back(face_prop); } TriangleMesh triangle_mesh(std::move(its), volume_data->mesh_stats); // BBS: no need to multiply the instance matrix into the volume //if (!m_is_bbl_3mf) { // // if the 3mf was not produced by BambuStudio and there is only one instance, // // bake the transformation into the geometry to allow the reload from disk command // // to work properly // if (object.instances.size() == 1) { // triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false); // object.instances.front()->set_transformation(Slic3r::Geometry::Transformation()); // //FIXME do the mesh fixing? // } //} if (triangle_mesh.volume() < 0) triangle_mesh.flip_triangles(); volume = object.add_volume(std::move(triangle_mesh)); if (shared_mesh_id != -1) //for some cases the shared mesh is in other plate and not loaded in cli slicing //we need to use the first one in the same plate instead m_shared_meshes[shared_mesh_id] = volume; else m_shared_meshes[sub_object->id] = volume; } else { //create volume to use shared mesh volume = object.add_volume_with_shared_mesh(*shared_volume); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": line %1%, create volume using shared_mesh %2%")%__LINE__%shared_volume; } // stores the volume matrix taken from the metadata, if present if (has_transform) volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); volume->calculate_convex_hull(); //set transform from 3mf Slic3r::Geometry::Transformation comp_transformatino(sub_comp.transform); volume->set_transformation(comp_transformatino * volume->get_transformation()); if (shared_volume) { const TriangleMesh& trangle_mesh = volume->mesh(); Vec3d shift = trangle_mesh.get_init_shift(); if (!shift.isApprox(Vec3d::Zero())) volume->translate(shift); } // recreate custom supports, seam and mmu segmentation from previously loaded attribute { volume->supported_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count); volume->mmu_segmentation_facets.reserve(triangles_count); for (size_t i=0; igeometry.custom_supports.size()); assert(i < sub_object->geometry.custom_seam.size()); assert(i < sub_object->geometry.mmu_segmentation.size()); if (! sub_object->geometry.custom_supports[i].empty()) volume->supported_facets.set_triangle_from_string(i, sub_object->geometry.custom_supports[i]); if (! sub_object->geometry.custom_seam[i].empty()) volume->seam_facets.set_triangle_from_string(i, sub_object->geometry.custom_seam[i]); if (! sub_object->geometry.mmu_segmentation[i].empty()) volume->mmu_segmentation_facets.set_triangle_from_string(i, sub_object->geometry.mmu_segmentation[i]); } volume->supported_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit(); volume->mmu_segmentation_facets.shrink_to_fit(); volume->mmu_segmentation_facets.touch(); } volume->set_type(volume_data->part_type); if (!volume_data->text_info.m_text.empty()) volume->set_text_info(volume_data->text_info); // apply the remaining volume's metadata for (const Metadata& metadata : volume_data->metadata) { if (metadata.key == NAME_KEY) volume->name = metadata.value; //else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) // volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); //for old format else if ((metadata.key == VOLUME_TYPE_KEY) || (metadata.key == PART_TYPE_KEY)) volume->set_type(ModelVolume::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) volume->source.object_idx = ::atoi(metadata.value.c_str()); else if (metadata.key == SOURCE_VOLUME_ID_KEY) volume->source.volume_idx = ::atoi(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_X_KEY) volume->source.mesh_offset(0) = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Y_KEY) volume->source.mesh_offset(1) = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Z_KEY) volume->source.mesh_offset(2) = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_IN_INCHES) volume->source.is_converted_from_inches = metadata.value == "1"; else if (metadata.key == SOURCE_IN_METERS) volume->source.is_converted_from_meters = metadata.value == "1"; else if ((metadata.key == MATRIX_KEY) || (metadata.key == MESH_SHARED_KEY)) continue; else volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } // this may happen for 3mf saved by 3rd part softwares if (volume->name.empty()) { volume->name = object.name; if (renamed_volumes_count > 0) volume->name += "_" + std::to_string(renamed_volumes_count + 1); ++renamed_volumes_count; } } return true; } bool _BBS_3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions) { if (!object.volumes.empty()) { add_error("Found invalid volumes count"); return false; } unsigned int geo_tri_count = (unsigned int)geometry.triangles.size(); unsigned int renamed_volumes_count = 0; for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) { add_error("Found invalid triangle id"); return false; } Transform3d volume_matrix_to_object = Transform3d::Identity(); bool has_transform = false; // extract the volume transformation from the volume's metadata, if present for (const Metadata& metadata : volume_data.metadata) { if (metadata.key == MATRIX_KEY) { volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); break; } } // splits volume out of imported geometry indexed_triangle_set its; its.indices.assign(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1); const size_t triangles_count = its.indices.size(); if (triangles_count == 0) { add_error("An empty triangle mesh found"); return false; } { int min_id = its.indices.front()[0]; int max_id = min_id; for (const Vec3i& face : its.indices) { for (const int tri_id : face) { if (tri_id < 0 || tri_id >= int(geometry.vertices.size())) { add_error("Found invalid vertex id"); return false; } min_id = std::min(min_id, tri_id); max_id = std::max(max_id, tri_id); } } its.vertices.assign(geometry.vertices.begin() + min_id, geometry.vertices.begin() + max_id + 1); // BBS for (const std::string prop_str : geometry.face_properties) { FaceProperty face_prop; face_prop.from_string(prop_str); its.properties.push_back(face_prop); } // rebase indices to the current vertices list for (Vec3i& face : its.indices) for (int& tri_id : face) tri_id -= min_id; } TriangleMesh triangle_mesh(std::move(its), volume_data.mesh_stats); if (!m_is_bbl_3mf) { // if the 3mf was not produced by BambuStudio and there is only one instance, // bake the transformation into the geometry to allow the reload from disk command // to work properly if (object.instances.size() == 1) { triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false); object.instances.front()->set_transformation(Slic3r::Geometry::Transformation()); //FIXME do the mesh fixing? } } if (triangle_mesh.volume() < 0) triangle_mesh.flip_triangles(); ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); // stores the volume matrix taken from the metadata, if present if (has_transform) volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); volume->calculate_convex_hull(); // recreate custom supports, seam and mmu segmentation from previously loaded attribute volume->supported_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count); volume->mmu_segmentation_facets.reserve(triangles_count); for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); if (! geometry.custom_seam[index].empty()) volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); if (! geometry.mmu_segmentation[index].empty()) volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); } volume->supported_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit(); volume->mmu_segmentation_facets.shrink_to_fit(); volume->set_type(volume_data.part_type); // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { if (metadata.key == NAME_KEY) volume->name = metadata.value; //else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) // volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); //for old format else if ((metadata.key == VOLUME_TYPE_KEY) || (metadata.key == PART_TYPE_KEY)) volume->set_type(ModelVolume::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) volume->source.object_idx = ::atoi(metadata.value.c_str()); else if (metadata.key == SOURCE_VOLUME_ID_KEY) volume->source.volume_idx = ::atoi(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_X_KEY) volume->source.mesh_offset(0) = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Y_KEY) volume->source.mesh_offset(1) = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_OFFSET_Z_KEY) volume->source.mesh_offset(2) = ::atof(metadata.value.c_str()); else if (metadata.key == SOURCE_IN_INCHES) volume->source.is_converted_from_inches = metadata.value == "1"; else if (metadata.key == SOURCE_IN_METERS) volume->source.is_converted_from_meters = metadata.value == "1"; else volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } // this may happen for 3mf saved by 3rd part softwares if (volume->name.empty()) { volume->name = object.name; if (renamed_volumes_count > 0) volume->name += "_" + std::to_string(renamed_volumes_count + 1); ++renamed_volumes_count; } } return true; } void XMLCALL _BBS_3MF_Importer::_handle_start_model_xml_element(void* userData, const char* name, const char** attributes) { _BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData; if (importer != nullptr) importer->_handle_start_model_xml_element(name, attributes); } void XMLCALL _BBS_3MF_Importer::_handle_end_model_xml_element(void* userData, const char* name) { _BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData; if (importer != nullptr) importer->_handle_end_model_xml_element(name); } void XMLCALL _BBS_3MF_Importer::_handle_xml_characters(void* userData, const XML_Char* s, int len) { _BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData; if (importer != nullptr) importer->_handle_xml_characters(s, len); } void XMLCALL _BBS_3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes) { _BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData; if (importer != nullptr) importer->_handle_start_config_xml_element(name, attributes); } void XMLCALL _BBS_3MF_Importer::_handle_end_config_xml_element(void* userData, const char* name) { _BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData; if (importer != nullptr) importer->_handle_end_config_xml_element(name); } /* functions of ObjectImporter */ bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_model(const char** attributes, unsigned int num_attributes) { object_unit_factor = bbs_get_unit_factor(bbs_get_attribute_value_string(attributes, num_attributes, UNIT_ATTR)); return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_model() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_resources(const char** attributes, unsigned int num_attributes) { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_resources() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_object(const char** attributes, unsigned int num_attributes) { // reset current object data if (current_object) { delete current_object; current_object = nullptr; } std::string object_type = bbs_get_attribute_value_string(attributes, num_attributes, TYPE_ATTR); if (bbs_is_valid_object_type(object_type)) { if (!current_object) { current_object = new CurrentObject(); } current_object->id = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR); current_object->name = bbs_get_attribute_value_string(attributes, num_attributes, NAME_ATTR); current_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_ATTR); if (current_object->uuid.empty()) { current_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_LOWER_ATTR); } current_object->pid = bbs_get_attribute_value_int(attributes, num_attributes, PID_ATTR); } return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_object() { if (!current_object || (current_object->id == -1)) { top_importer->add_error("Found invalid object for "+ object_path); return false; } else { if (is_bbl_3mf && boost::ends_with(current_object->uuid, OBJECT_UUID_SUFFIX) && top_importer->m_load_restore) { std::istringstream iss(current_object->uuid); int backup_id; bool need_replace = false; if (iss >> std::hex >> backup_id) { need_replace = (current_object->id != backup_id); current_object->id = backup_id; } //if (need_replace) { for (int index = 0; index < current_object->components.size(); index++) { int temp_id = (index + 1) << 16 | backup_id; Component& component = current_object->components[index]; std::string new_path = component.object_id.first; Id new_id = std::make_pair(new_path, temp_id); IdToCurrentObjectMap::iterator object_it = object_list.find(component.object_id); if (object_it != object_list.end()) { CurrentObject new_object; new_object.geometry = std::move(object_it->second.geometry); new_object.id = temp_id; new_object.model_object_idx = object_it->second.model_object_idx; new_object.name = object_it->second.name; new_object.uuid = object_it->second.uuid; object_list.erase(object_it); object_list.insert({ new_id, std::move(new_object) }); } else { top_importer->add_error("can not find object for component, id=" + std::to_string(component.object_id.second)); delete current_object; current_object = nullptr; return false; } component.object_id.second = temp_id; } } } Id id = std::make_pair(object_path, current_object->id); if (object_list.find(id) == object_list.end()) { object_list.insert({ id, std::move(*current_object) }); delete current_object; current_object = nullptr; } else { top_importer->add_error("Found object with duplicate id for "+object_path); delete current_object; current_object = nullptr; return false; } } return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_color_group(const char **attributes, unsigned int num_attributes) { object_current_color_group = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR); return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_color_group() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_color(const char **attributes, unsigned int num_attributes) { std::string color = bbs_get_attribute_value_string(attributes, num_attributes, COLOR_ATTR); object_group_id_to_color[object_current_color_group] = color; return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_color() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_mesh(const char** attributes, unsigned int num_attributes) { // reset current geometry if (current_object) current_object->geometry.reset(); return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_mesh() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_vertices(const char** attributes, unsigned int num_attributes) { // reset current vertices if (current_object) current_object->geometry.vertices.clear(); return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_vertices() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_vertex(const char** attributes, unsigned int num_attributes) { // appends the vertex coordinates // missing values are set equal to ZERO if (current_object) current_object->geometry.vertices.emplace_back( object_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, X_ATTR), object_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Y_ATTR), object_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Z_ATTR)); return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_vertex() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_triangles(const char** attributes, unsigned int num_attributes) { // reset current triangles if (current_object) current_object->geometry.triangles.clear(); return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_triangles() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_triangle(const char** attributes, unsigned int num_attributes) { // we are ignoring the following attributes: // p1 // p2 // p3 // pid // see specifications // appends the triangle's vertices indices // missing values are set equal to ZERO if (current_object) { current_object->geometry.triangles.emplace_back( bbs_get_attribute_value_int(attributes, num_attributes, V1_ATTR), bbs_get_attribute_value_int(attributes, num_attributes, V2_ATTR), bbs_get_attribute_value_int(attributes, num_attributes, V3_ATTR)); current_object->geometry.custom_supports.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); current_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); current_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); // BBS current_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR)); } return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_triangle() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_components(const char** attributes, unsigned int num_attributes) { // reset current components if (current_object) current_object->components.clear(); return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_components() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_component(const char** attributes, unsigned int num_attributes) { int object_id = bbs_get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); /*Id id = std::make_pair(m_sub_model_path, object_id); IdToModelObjectMap::iterator object_item = m_objects.find(id); if (object_item == m_objects.end()) { IdToAliasesMap::iterator alias_item = m_objects_aliases.find(id); if (alias_item == m_objects_aliases.end()) { add_error("Found component with invalid object id"); return false; } }*/ if (current_object) { Id id = std::make_pair(object_path, object_id); current_object->components.emplace_back(id, transform); } return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_component() { // do nothing return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_start_metadata(const char** attributes, unsigned int num_attributes) { obj_curr_metadata_name.clear(); std::string name = bbs_get_attribute_value_string(attributes, num_attributes, NAME_ATTR); if (!name.empty()) { obj_curr_metadata_name = name; } return true; } bool _BBS_3MF_Importer::ObjectImporter::_handle_object_end_metadata() { if ((obj_curr_metadata_name == BBS_3MF_VERSION)||(obj_curr_metadata_name == BBS_3MF_VERSION1)) { is_bbl_3mf = true; } return true; } void _BBS_3MF_Importer::ObjectImporter::_handle_object_start_model_xml_element(const char* name, const char** attributes) { if (object_xml_parser == nullptr) return; bool res = true; unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(object_xml_parser); if (::strcmp(MODEL_TAG, name) == 0) res = _handle_object_start_model(attributes, num_attributes); else if (::strcmp(RESOURCES_TAG, name) == 0) res = _handle_object_start_resources(attributes, num_attributes); else if (::strcmp(OBJECT_TAG, name) == 0) res = _handle_object_start_object(attributes, num_attributes); else if (::strcmp(COLOR_GROUP_TAG, name) == 0) res = _handle_object_start_color_group(attributes, num_attributes); else if (::strcmp(COLOR_TAG, name) == 0) res = _handle_object_start_color(attributes, num_attributes); else if (::strcmp(MESH_TAG, name) == 0) res = _handle_object_start_mesh(attributes, num_attributes); else if (::strcmp(VERTICES_TAG, name) == 0) res = _handle_object_start_vertices(attributes, num_attributes); else if (::strcmp(VERTEX_TAG, name) == 0) res = _handle_object_start_vertex(attributes, num_attributes); else if (::strcmp(TRIANGLES_TAG, name) == 0) res = _handle_object_start_triangles(attributes, num_attributes); else if (::strcmp(TRIANGLE_TAG, name) == 0) res = _handle_object_start_triangle(attributes, num_attributes); else if (::strcmp(COMPONENTS_TAG, name) == 0) res = _handle_object_start_components(attributes, num_attributes); else if (::strcmp(COMPONENT_TAG, name) == 0) res = _handle_object_start_component(attributes, num_attributes); else if (::strcmp(METADATA_TAG, name) == 0) res = _handle_object_start_metadata(attributes, num_attributes); if (!res) _stop_object_xml_parser(); } void _BBS_3MF_Importer::ObjectImporter::_handle_object_end_model_xml_element(const char* name) { if (object_xml_parser == nullptr) return; bool res = true; if (::strcmp(MODEL_TAG, name) == 0) res = _handle_object_end_model(); else if (::strcmp(RESOURCES_TAG, name) == 0) res = _handle_object_end_resources(); else if (::strcmp(OBJECT_TAG, name) == 0) res = _handle_object_end_object(); else if (::strcmp(COLOR_GROUP_TAG, name) == 0) res = _handle_object_end_color_group(); else if (::strcmp(COLOR_TAG, name) == 0) res = _handle_object_end_color(); else if (::strcmp(MESH_TAG, name) == 0) res = _handle_object_end_mesh(); else if (::strcmp(VERTICES_TAG, name) == 0) res = _handle_object_end_vertices(); else if (::strcmp(VERTEX_TAG, name) == 0) res = _handle_object_end_vertex(); else if (::strcmp(TRIANGLES_TAG, name) == 0) res = _handle_object_end_triangles(); else if (::strcmp(TRIANGLE_TAG, name) == 0) res = _handle_object_end_triangle(); else if (::strcmp(COMPONENTS_TAG, name) == 0) res = _handle_object_end_components(); else if (::strcmp(COMPONENT_TAG, name) == 0) res = _handle_object_end_component(); else if (::strcmp(METADATA_TAG, name) == 0) res = _handle_object_end_metadata(); if (!res) _stop_object_xml_parser(); } void _BBS_3MF_Importer::ObjectImporter::_handle_object_xml_characters(const XML_Char* s, int len) { obj_curr_characters.append(s, len); } void XMLCALL _BBS_3MF_Importer::ObjectImporter::_handle_object_start_model_xml_element(void* userData, const char* name, const char** attributes) { ObjectImporter* importer = (ObjectImporter*)userData; if (importer != nullptr) importer->_handle_object_start_model_xml_element(name, attributes); } void XMLCALL _BBS_3MF_Importer::ObjectImporter::_handle_object_end_model_xml_element(void* userData, const char* name) { ObjectImporter* importer = (ObjectImporter*)userData; if (importer != nullptr) importer->_handle_object_end_model_xml_element(name); } void XMLCALL _BBS_3MF_Importer::ObjectImporter::_handle_object_xml_characters(void* userData, const XML_Char* s, int len) { ObjectImporter* importer = (ObjectImporter*)userData; if (importer != nullptr) importer->_handle_object_xml_characters(s, len); } bool _BBS_3MF_Importer::ObjectImporter::_extract_object_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size == 0) { top_importer->add_error("Found invalid size for "+object_path); return false; } object_xml_parser = XML_ParserCreate(nullptr); if (object_xml_parser == nullptr) { top_importer->add_error("Unable to create parser for "+object_path); return false; } XML_SetUserData(object_xml_parser, (void*)this); XML_SetElementHandler(object_xml_parser, _BBS_3MF_Importer::ObjectImporter::_handle_object_start_model_xml_element, _BBS_3MF_Importer::ObjectImporter::_handle_object_end_model_xml_element); XML_SetCharacterDataHandler(object_xml_parser, _BBS_3MF_Importer::ObjectImporter::_handle_object_xml_characters); struct CallbackData { XML_Parser& parser; _BBS_3MF_Importer::ObjectImporter& importer; const mz_zip_archive_file_stat& stat; CallbackData(XML_Parser& parser, _BBS_3MF_Importer::ObjectImporter& importer, const mz_zip_archive_file_stat& stat) : parser(parser), importer(importer), stat(stat) {} }; CallbackData data(object_xml_parser, *this, stat); mz_bool res = 0; try { mz_file_write_func callback = [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t { CallbackData* data = (CallbackData*)pOpaque; if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->importer.object_parse_error()) { char error_buf[1024]; ::snprintf(error_buf, 1024, "Error (%s) while parsing '%s' at line %d", data->importer.object_parse_error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); throw Slic3r::FileIOError(error_buf); } return n; }; void* opaque = &data; res = mz_zip_reader_extract_to_callback(&archive, stat.m_file_index, callback, opaque, 0); } catch (const version_error& e) { // rethrow the exception std::string error_message = std::string(e.what()) + " for " + object_path; throw Slic3r::FileIOError(error_message); } catch (std::exception& e) { std::string error_message = std::string(e.what()) + " for " + object_path; top_importer->add_error(error_message); return false; } if (res == 0) { top_importer->add_error("Error while extracting model data from zip archive for "+object_path); return false; } return true; } class _BBS_3MF_Exporter : public _BBS_3MF_Base { struct BuildItem { std::string path; unsigned int id; Transform3d transform; bool printable; BuildItem(std::string const & path, unsigned int id, const Transform3d& transform, const bool printable) : path(path) , id(id) , transform(transform) , printable(printable) { } }; //BBS: change volume to seperate objects /*struct Offsets { unsigned int first_vertex_id; unsigned int first_triangle_id; unsigned int last_triangle_id; Offsets(unsigned int first_vertex_id) : first_vertex_id(first_vertex_id) , first_triangle_id(-1) , last_triangle_id(-1) { } };*/ //typedef std::map VolumeToOffsetsMap; typedef std::map VolumeToObjectIDMap; struct ObjectData { ModelObject const * object; int backup_id; int object_id = 0; std::string sub_path; bool share_mesh = false; VolumeToObjectIDMap volumes_objectID; }; typedef std::vector BuildItemsList; typedef std::map ObjectToObjectDataMap; bool m_fullpath_sources{ true }; bool m_zip64 { true }; bool m_production_ext { false }; // save with Production Extention bool m_skip_static{ false }; // not save mesh and other big static contents bool m_from_backup_save{ false }; // the object save is from backup store bool m_split_model { false }; // save object per file with Production Extention bool m_save_gcode { false }; // whether to save gcode for normal save bool m_skip_model { false }; // skip model when exporting .gcode.3mf bool m_skip_auxiliary { false }; // skip normal axuiliary files bool m_use_loaded_id { false }; // whether to use loaded id for identify_id bool m_share_mesh { false }; // whether to share mesh between objects std::string m_thumbnail_middle = PRINTER_THUMBNAIL_MIDDLE_FILE; std::string m_thumbnail_small = PRINTER_THUMBNAIL_SMALL_FILE; std::map> m_shared_meshes; std::map> m_volume_paths; public: //BBS: add plate data related logic // add backup logic //bool save_model_to_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, const DynamicPrintConfig* config, bool fullpath_sources, const std::vector& thumbnail_data, bool zip64, bool skip_static, Export3mfProgressFn proFn = nullptr, bool silence = false); bool save_model_to_file(StoreParams& store_params); // add backup logic bool save_object_mesh(const std::string& temp_path, ModelObject const & object, int obj_id); private: //BBS: add plate data related logic bool _save_model_to_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, const DynamicPrintConfig* config, const std::vector& thumbnail_data, const std::vector& no_light_thumbnail_data, const std::vector& top_thumbnail_data, const std::vector& pick_thumbnail_data, Export3mfProgressFn proFn, const std::vector& calibration_data, const std::vector& id_bboxes, BBLProject* project = nullptr, int export_plate_idx = -1); bool _add_file_to_archive(mz_zip_archive& archive, const std::string & path_in_zip, const std::string & file_path); bool _add_content_types_file_to_archive(mz_zip_archive& archive); bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, const char* local_path, int index, bool generate_small_thumbnail = false); bool _add_calibration_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, int index); bool _add_bbox_file_to_archive(mz_zip_archive& archive, const PlateBBoxData& id_bboxes, int index); bool _add_relationships_file_to_archive(mz_zip_archive & archive, std::string const & from = {}, std::vector const &targets = {}, std::vector const &types = {}, PackingTemporaryData data = PackingTemporaryData(), int export_plate_idx = -1) const; bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, ObjectToObjectDataMap& objects_data, Export3mfProgressFn proFn = nullptr, BBLProject* project = nullptr) const; bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, ObjectData const &object_data) const; void _add_object_components_to_stream(std::stringstream &stream, ObjectData const &object_data) const; //BBS: change volume to seperate objects bool _add_mesh_to_object_stream(std::function const &flush, ObjectData const &object_data) const; bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const; bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); //BBS: add project config file logic for json format bool _add_project_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, Model& model); //BBS: add project embedded preset files bool _add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector project_presets); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, int export_plate_idx = -1, bool save_gcode = true, bool use_loaded_id = false); bool _add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model); bool _add_slice_info_config_file_to_archive(mz_zip_archive &archive, const Model &model, PlateDataPtrs &plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config); bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr); bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); bool _add_auxiliary_dir_to_archive(mz_zip_archive &archive, const std::string &aux_dir, PackingTemporaryData &data); static int convert_instance_id_to_resource_id(const Model& model, int obj_id, int instance_id) { int resource_id = 1; for (int i = 0; i < obj_id; ++i) { resource_id += model.objects[i]->volumes.size() + 1; } resource_id += model.objects[obj_id]->volumes.size(); return resource_id; } }; bool _BBS_3MF_Exporter::save_model_to_file(StoreParams& store_params) { clear_errors(); m_fullpath_sources = store_params.strategy & SaveStrategy::FullPathSources; m_zip64 = store_params.strategy & SaveStrategy::Zip64; m_production_ext = store_params.strategy & SaveStrategy::ProductionExt; m_skip_static = store_params.strategy & SaveStrategy::SkipStatic; m_split_model = store_params.strategy & SaveStrategy::SplitModel; m_save_gcode = store_params.strategy & SaveStrategy::WithGcode; m_skip_model = store_params.strategy & SaveStrategy::SkipModel; m_skip_auxiliary = store_params.strategy & SaveStrategy::SkipAuxiliary; m_share_mesh = store_params.strategy & SaveStrategy::ShareMesh; m_from_backup_save = store_params.strategy & SaveStrategy::Backup; m_use_loaded_id = store_params.strategy & SaveStrategy::UseLoadedId; if (auto info = store_params.model->model_info) { if (auto iter = info->metadata_items.find("Thumbnail_Small"); iter != info->metadata_items.end()) m_thumbnail_small = iter->second; if (auto iter = info->metadata_items.find("Thumbnail_Middle"); iter != info->metadata_items.end()) m_thumbnail_middle = iter->second; } boost::system::error_code ec; std::string filename = std::string(store_params.path); boost::filesystem::remove(filename + ".tmp", ec); bool result = _save_model_to_file(filename + ".tmp", *store_params.model, store_params.plate_data_list, store_params.project_presets, store_params.config, store_params.thumbnail_data, store_params.no_light_thumbnail_data, store_params.top_thumbnail_data, store_params.pick_thumbnail_data, store_params.proFn, store_params.calibration_thumbnail_data, store_params.id_bboxes, store_params.project, store_params.export_plate_idx); if (result) { boost::filesystem::rename(filename + ".tmp", filename, ec); if (ec) { add_error("Failed to rename file: " + ec.message()); boost::filesystem::remove(filename + ".tmp", ec); return false; } if (!(store_params.strategy & SaveStrategy::Silence)) boost::filesystem::save_string_file(store_params.model->get_backup_path() + "/origin.txt", filename); } return result; } // backup mesh-only bool _BBS_3MF_Exporter::save_object_mesh(const std::string& temp_path, ModelObject const & object, int obj_id) { m_production_ext = true; m_from_backup_save = true; Model const & model = *object.get_model(); mz_zip_archive archive; mz_zip_zero_struct(&archive); auto filename = boost::format("3D/Objects/object_%d.model") % obj_id; std::string filepath = temp_path + "/" + filename.str(); std::string filepath_tmp = filepath + ".tmp"; boost::system::error_code ec; boost::filesystem::remove(filepath_tmp, ec); if (!open_zip_writer(&archive, filepath_tmp)) { add_error("Unable to open the file"+filepath_tmp); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to open the file\n"); return false; } struct close_lock { mz_zip_archive & archive; std::string const * filename; void close() { close_zip_writer(&archive); filename = nullptr; } ~close_lock() { if (filename) { close_zip_writer(&archive); boost::system::error_code ec; boost::filesystem::remove(*filename, ec); } } } lock{archive, &filepath_tmp}; ObjectToObjectDataMap objects_data; auto & volumes_objectID = objects_data.insert({&object, {&object, obj_id}}).first->second.volumes_objectID; unsigned int volume_count = 0; for (ModelVolume *volume : object.volumes) { if (volume == nullptr) continue; volumes_objectID.insert({volume, (++volume_count << 16 | obj_id)}); } _add_model_file_to_archive(filename.str(), archive, model, objects_data); mz_zip_writer_finalize_archive(&archive); lock.close(); boost::filesystem::rename(filepath_tmp, filepath, ec); return true; } //BBS: add plate data related logic bool _BBS_3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector& project_presets, const DynamicPrintConfig* config, const std::vector& thumbnail_data, const std::vector& no_light_thumbnail_data, const std::vector& top_thumbnail_data, const std::vector& pick_thumbnail_data, Export3mfProgressFn proFn, const std::vector& calibration_data, const std::vector& id_bboxes, BBLProject* project, int export_plate_idx) { PackingTemporaryData temp_data; mz_zip_archive archive; mz_zip_zero_struct(&archive); bool cb_cancel = false; //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",before open zip writer, m_skip_static %1%, m_save_gcode %2%, m_use_loaded_id %3%")%m_skip_static %m_save_gcode %m_use_loaded_id; if (proFn) { proFn(EXPORT_STAGE_OPEN_3MF, 0, 1, cb_cancel); if (cb_cancel) return false; } if (!open_zip_writer(&archive, filename)) { add_error("Unable to open the file"+filename); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to open the file\n"); return false; } struct close_lock { mz_zip_archive & archive; std::string const * filename; ~close_lock() { close_zip_writer(&archive); if (filename) { boost::system::error_code ec; boost::filesystem::remove(*filename, ec); } } } lock{ archive, &filename}; //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add _add_content_types_file_to_archive\n"); if (proFn) { proFn(EXPORT_STAGE_CONTENT_TYPES, 0, 1, cb_cancel); if (cb_cancel) return false; } // Adds content types file ("[Content_Types].xml";). // The content of this file is the same for each BambuStudio 3mf. if (!_add_content_types_file_to_archive(archive)) { return false; } //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(",before add thumbnails, count %1%") % thumbnail_data.size(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",top&&pick thumbnails, count %1%")%top_thumbnail_data.size(); //BBS: add thumbnail for each plate if (!m_skip_static) { std::vector thumbnail_status(plate_data_list.size(), false); std::vector no_light_thumbnail_status(plate_data_list.size(), false); std::vector top_thumbnail_status(plate_data_list.size(), false); std::vector pick_thumbnail_status(plate_data_list.size(), false); if ((thumbnail_data.size() > 0)&&(thumbnail_data.size() > plate_data_list.size())) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", thumbnail_data size %1% > plate count %2%") % thumbnail_data.size() %plate_data_list.size(); return false; } if ((no_light_thumbnail_data.size() > 0) && (no_light_thumbnail_data.size() > plate_data_list.size())) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", no_light_thumbnail_data size %1% > plate count %2%") % no_light_thumbnail_data.size() % plate_data_list.size(); return false; } if ((top_thumbnail_data.size() > 0)&&(top_thumbnail_data.size() > plate_data_list.size())) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_data size %1% > plate count %2%") % top_thumbnail_data.size() %plate_data_list.size(); return false; } if ((pick_thumbnail_data.size() > 0)&&(pick_thumbnail_data.size() > plate_data_list.size())) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", pick_thumbnail_data size %1% > plate count %2%") % pick_thumbnail_data.size() %plate_data_list.size(); return false; } if (top_thumbnail_data.size() != pick_thumbnail_data.size()) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", top_thumbnail_data size %1% != pick_thumbnail_data size %2%") % top_thumbnail_data.size() %pick_thumbnail_data.size(); return false; } if (proFn) { proFn(EXPORT_STAGE_ADD_THUMBNAILS, 0, plate_data_list.size(), cb_cancel); if (cb_cancel) return false; } for (unsigned int index = 0; index < thumbnail_data.size(); index++) { if (thumbnail_data[index]->is_valid()) { if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data[index], "Metadata/plate", index, true)) { return false; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",add thumbnail %1%'s data into 3mf")%(index+1); thumbnail_status[index] = true; } } for (unsigned int index = 0; index < no_light_thumbnail_data.size(); index++) { if (no_light_thumbnail_data[index]->is_valid()) { if (!_add_thumbnail_file_to_archive(archive, *no_light_thumbnail_data[index], "Metadata/plate_no_light", index)) { return false; } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(",add no light thumbnail %1%'s data into 3mf") % (index + 1); no_light_thumbnail_status[index] = true; } } // Adds the file Metadata/top_i.png and Metadata/pick_i.png for (unsigned int index = 0; index < top_thumbnail_data.size(); index++) { if (top_thumbnail_data[index]->is_valid()) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",add top thumbnail %1%'s data into 3mf")%(index+1); if (!_add_thumbnail_file_to_archive(archive, *top_thumbnail_data[index], "Metadata/top", index)) { return false; } top_thumbnail_status[index] = true; } if (pick_thumbnail_data[index]->is_valid()) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",add pick thumbnail %1%'s data into 3mf")%(index+1); if (!_add_thumbnail_file_to_archive(archive, *pick_thumbnail_data[index], "Metadata/pick", index)) { return false; } pick_thumbnail_status[index] = true; } } for (int i = 0; i < plate_data_list.size(); i++) { PlateData *plate_data = plate_data_list[i]; if (!thumbnail_status[i] && !plate_data->thumbnail_file.empty() && (boost::filesystem::exists(plate_data->thumbnail_file))){ std::string dst_in_3mf = (boost::format("Metadata/plate_%1%.png") % (i + 1)).str(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add thumbnail %1% from file %2%") % (i+1) %plate_data->thumbnail_file; if (!_add_file_to_archive(archive, dst_in_3mf, plate_data->thumbnail_file)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", add thumbnail %1% from file %2% failed\n") % (i+1) %plate_data->thumbnail_file; return false; } } if (!no_light_thumbnail_status[i] && !plate_data->no_light_thumbnail_file.empty() && (boost::filesystem::exists(plate_data->no_light_thumbnail_file))){ std::string dst_in_3mf = (boost::format("Metadata/plate_no_light_%1%.png") % (i + 1)).str(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add no light thumbnail %1% from file %2%") % (i+1) %plate_data->no_light_thumbnail_file; if (!_add_file_to_archive(archive, dst_in_3mf, plate_data->no_light_thumbnail_file)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", add no light thumbnail %1% from file %2% failed\n") % (i+1) %plate_data->no_light_thumbnail_file; return false; } } if (!top_thumbnail_status[i] && !plate_data->top_file.empty() && (boost::filesystem::exists(plate_data->top_file))){ std::string dst_in_3mf = (boost::format("Metadata/top_%1%.png") % (i + 1)).str(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add top thumbnail %1% from file %2%") % (i+1) %plate_data->top_file; if (!_add_file_to_archive(archive, dst_in_3mf, plate_data->top_file)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", add top thumbnail %1% failed") % (i+1); return false; } top_thumbnail_status[i] = true; } if (!pick_thumbnail_status[i] && !plate_data->pick_file.empty() && (boost::filesystem::exists(plate_data->pick_file))){ std::string dst_in_3mf = (boost::format("Metadata/pick_%1%.png") % (i + 1)).str(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add pick thumbnail %1% from file %2%") % (i+1) %plate_data->pick_file; if (!_add_file_to_archive(archive, dst_in_3mf, plate_data->pick_file)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", add pick thumbnail %1% failed") % (i+1); return false; } pick_thumbnail_status[i] = true; } } if (proFn) { proFn(EXPORT_STAGE_ADD_THUMBNAILS, plate_data_list.size(), plate_data_list.size(), cb_cancel); if (cb_cancel) return false; } } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",before add calibration thumbnails, count %1%\n")%calibration_data.size(); //BBS add calibration thumbnail for each plate if (!m_skip_static && calibration_data.size() > 0) { // Adds the file Metadata/calibration_p[X].png. for (unsigned int index = 0; index < calibration_data.size(); index++) { if (proFn) { proFn(EXPORT_STAGE_ADD_THUMBNAILS, index, calibration_data.size(), cb_cancel); if (cb_cancel) return false; } if (calibration_data[index]->is_valid()) { if (!_add_calibration_file_to_archive(archive, *calibration_data[index], index)) { close_zip_writer(&archive); return false; } } } } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",before add calibration boundingbox, count %1%\n")%id_bboxes.size(); if (!m_skip_static && id_bboxes.size() > 0) { // Adds the file Metadata/calibration_p[X].png. for (unsigned int index = 0; index < id_bboxes.size(); index++) { // BBS: save bounding box to json if (id_bboxes[index]->is_valid()) { if (!_add_bbox_file_to_archive(archive, *id_bboxes[index], index)) { close_zip_writer(&archive); return false; } } } } //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add models\n"); if (proFn) { proFn(EXPORT_STAGE_ADD_MODELS, 0, 1, cb_cancel); if (cb_cancel) return false; } // Adds model file ("3D/3dmodel.model"). // This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes. ObjectToObjectDataMap objects_data; //if (!m_skip_model) { if (!_add_model_file_to_archive(filename, archive, model, objects_data, proFn, project)) { 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. // The index differes from the index of an object ID of an object instance of a 3MF file! if (!_add_layer_height_profile_file_to_archive(archive, model)) { close_zip_writer(&archive); return false; } // BBS progress point /*BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format("export 3mf EXPORT_STAGE_ADD_LAYER_RANGE\n"); if (proFn) { proFn(EXPORT_STAGE_ADD_LAYER_RANGE, 0, 1, cb_cancel); if (cb_cancel) return false; }*/ // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. // The index differes from the index of an object ID of an object instance of a 3MF file! if (!_add_layer_config_ranges_file_to_archive(archive, model)) { close_zip_writer(&archive); return false; } // BBS progress point /*BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format("export 3mf EXPORT_STAGE_ADD_SUPPORT\n"); if (proFn) { proFn(EXPORT_STAGE_ADD_SUPPORT, 0, 1, cb_cancel); if (cb_cancel) return false; } // Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt"). // All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. // The index differes from the index of an object ID of an object instance of a 3MF file! if (!_add_sla_support_points_file_to_archive(archive, model)) { return false; } if (!_add_sla_drain_holes_file_to_archive(archive, model)) { return false; }*/ // BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", before add custom gcodes\n"); if (proFn) { proFn(EXPORT_STAGE_ADD_CUSTOM_GCODE, 0, 1, cb_cancel); if (cb_cancel) return false; } // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"). // All custom gcode per height of whole Model are stored here if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model, config)) { return false; } // BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", before add project_settings\n"); if (proFn) { proFn(EXPORT_STAGE_ADD_PRINT_CONFIG, 0, 1, cb_cancel); if (cb_cancel) return false; } // Adds slic3r print config file ("Metadata/Slic3r_PE.config"). // This file contains the content of FullPrintConfing / SLAFullPrintConfig. if (config != nullptr) { // BBS: change to json format // if (!_add_print_config_file_to_archive(archive, *config)) { if (!_add_project_config_file_to_archive(archive, *config, model)) { return false; } } // BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", before add project embedded settings\n"); if (proFn) { proFn(EXPORT_STAGE_ADD_CONFIG_FILE, 0, 1, cb_cancel); if (cb_cancel) return false; } // BBS: add project config if (project_presets.size() > 0) { // BBS: add project embedded preset files _add_project_embedded_presets_to_archive(archive, model, project_presets); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", finished add project embedded settings, size %1%\n")%project_presets.size(); if (proFn) { proFn(EXPORT_STAGE_ADD_PROJECT_CONFIG, 0, 1, cb_cancel); if (cb_cancel) return false; } } } // add plate_N.gcode.md5 to file if (!m_skip_static && m_save_gcode) { for (int i = 0; i < plate_data_list.size(); i++) { PlateData *plate_data = plate_data_list[i]; if (!plate_data->gcode_file.empty() && plate_data->is_sliced_valid && boost::filesystem::exists(plate_data->gcode_file)) { unsigned char digest[16]; MD5_CTX ctx; MD5_Init(&ctx); auto src_gcode_file = plate_data->gcode_file; boost::filesystem::ifstream ifs(src_gcode_file, std::ios::binary); std::string buf(64 * 1024, 0); const std::size_t & size = boost::filesystem::file_size(src_gcode_file); std::size_t left_size = size; while (ifs) { ifs.read(buf.data(), buf.size()); int read_bytes = ifs.gcount(); MD5_Update(&ctx, (unsigned char *) buf.data(), read_bytes); } MD5_Final(digest, &ctx); char md5_str[33]; for (int j = 0; j < 16; j++) { sprintf(&md5_str[j * 2], "%02X", (unsigned int) digest[j]); } plate_data->gcode_file_md5 = std::string(md5_str); std::string target_file = (boost::format("Metadata/plate_%1%.gcode.md5") % (plate_data->plate_index + 1)).str(); if (!mz_zip_writer_add_mem(&archive, target_file.c_str(), (const void *) plate_data->gcode_file_md5.c_str(), plate_data->gcode_file_md5.length(), MZ_DEFAULT_COMPRESSION)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", store gcode md5 to 3mf's %1%, length %2%, failed\n") %target_file %plate_data->gcode_file_md5.length(); return false; } } } } // Adds gcode files ("Metadata/plate_1.gcode, plate_2.gcode, ...) // Before _add_model_config_file_to_archive, because we modify plate_data //if (!m_skip_static && !_add_gcode_file_to_archive(archive, model, plate_data_list, proFn)) { if (!m_skip_static && m_save_gcode && !_add_gcode_file_to_archive(archive, model, plate_data_list, proFn)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_gcode_file_to_archive failed\n"); return false; } // Adds slic3r model config file ("Metadata/Slic3r_PE_model.config"). // This file contains all the attributes of all ModelObjects and their ModelVolumes (names, parameter overrides). // As there is just a single Indexed Triangle Set data stored per ModelObject, offsets of volumes into their respective Indexed Triangle Set data // is stored here as well. if (!_add_model_config_file_to_archive(archive, model, plate_data_list, objects_data, export_plate_idx, m_save_gcode, m_use_loaded_id)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_model_config_file_to_archive failed\n"); return false; } if (!_add_cut_information_file_to_archive(archive, model)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_cut_information_file_to_archive failed\n"); return false; } //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add sliced info to 3mf\n"); if (proFn) { proFn(EXPORT_STAGE_ADD_SLICE_INFO, 0, 1, cb_cancel); if (cb_cancel) return false; } // Adds sliced info of plate file ("Metadata/slice_info.config") // This file contains all sliced info of all plates if (!_add_slice_info_config_file_to_archive(archive, model, plate_data_list, objects_data, *config)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_slice_info_config_file_to_archive failed\n"); return false; } //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add auxiliary dir to 3mf\n"); if (proFn) { proFn(EXPORT_STAGE_ADD_AUXILIARIES, 0, 1, cb_cancel); if (cb_cancel) return false; } if (!m_skip_static && !_add_auxiliary_dir_to_archive(archive, model.get_auxiliary_file_temp_path(), temp_data)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", _add_auxiliary_dir_to_archive failed\n"); return false; } //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add relation file to 3mf\n"); if (proFn) { proFn(EXPORT_STAGE_ADD_RELATIONS, 0, 1, cb_cancel); if (cb_cancel) return false; } // Adds relationships file ("_rels/.rels"). // The content of this file is the same for each BambuStudio 3mf. // The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA. if (!_add_relationships_file_to_archive(archive, {}, {}, {}, temp_data, export_plate_idx)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", _add_relationships_file_to_archive failed\n"); return false; } if (!mz_zip_writer_finalize_archive(&archive)) { add_error("Unable to finalize the archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to finalize the archive\n"); return false; } //BBS progress point BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", finished exporting 3mf\n"); if (proFn) { proFn(EXPORT_STAGE_FINISH, 0, 1, cb_cancel); if (cb_cancel) return false; } lock.filename = nullptr; return true; } bool _BBS_3MF_Exporter::_add_file_to_archive(mz_zip_archive& archive, const std::string& path_in_zip, const std::string& src_file_path) { static std::string const nocomp_exts[] = {".png", ".jpg", ".mp4", ".jpeg", ".zip", ".3mf"}; auto end = nocomp_exts + sizeof(nocomp_exts) / sizeof(nocomp_exts[0]); bool nocomp = std::find_if(nocomp_exts, end, [&path_in_zip](auto & ext) { return boost::algorithm::ends_with(path_in_zip, ext); }) != end; #if WRITE_ZIP_LANGUAGE_ENCODING bool result = mz_zip_writer_add_file(&archive, path_in_zip.c_str(), encode_path(src_file_path.c_str()).c_str(), NULL, 0, nocomp ? MZ_NO_COMPRESSION : MZ_DEFAULT_LEVEL); #else std::string native_path = encode_path(path_in_zip.c_str()); std::string extra = ZipUnicodePathExtraField::encode(path_in_zip, native_path); bool result = mz_zip_writer_add_file_ex(&archive, native_path.c_str(), encode_path(src_file_path.c_str()).c_str(), NULL, 0, nocomp ? MZ_ZIP_FLAG_ASCII_FILENAME : MZ_DEFAULT_COMPRESSION, extra.c_str(), extra.length(), extra.c_str(), extra.length()); #endif if (!result) { add_error("Unable to add file " + src_file_path + " to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", Unable to add file %1% to archive %2%\n") % src_file_path % path_in_zip; } else { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add file %1% to archive %2%\n") % src_file_path % path_in_zip; } return result; } bool _BBS_3MF_Exporter::_add_content_types_file_to_archive(mz_zip_archive& archive) { std::stringstream stream; stream << "\n"; stream << "\n"; stream << " \n"; stream << " \n"; stream << " \n"; stream << " \n"; stream << ""; std::string out = stream.str(); if (!mz_zip_writer_add_mem(&archive, CONTENT_TYPES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add content types file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add content types file to archive\n"); return false; } return true; } bool _BBS_3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, const char* local_path, int index, bool generate_small_thumbnail) { bool res = false; size_t png_size = 0; void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_COMPRESSION, 1); if (png_data != nullptr) { std::string thumbnail_name = (boost::format("%1%_%2%.png")%local_path % (index + 1)).str(); res = mz_zip_writer_add_mem(&archive, thumbnail_name.c_str(), (const void*)png_data, png_size, MZ_NO_COMPRESSION); mz_free(png_data); } if (!res) { add_error("Unable to add thumbnail file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add thumbnail file to archive\n"); } if (generate_small_thumbnail && thumbnail_data.is_valid()) { //generate small size of thumbnail std::vector small_pixels; small_pixels.resize(PLATE_THUMBNAIL_SMALL_WIDTH * PLATE_THUMBNAIL_SMALL_HEIGHT * 4); /* step width and step height */ int sw = thumbnail_data.width / PLATE_THUMBNAIL_SMALL_WIDTH; int sh = thumbnail_data.height / PLATE_THUMBNAIL_SMALL_HEIGHT; int clampped_width = sw * PLATE_THUMBNAIL_SMALL_WIDTH; int clampped_height = sh * PLATE_THUMBNAIL_SMALL_HEIGHT; for (int i = 0; i < clampped_height; i += sh) { for (int j = 0; j < clampped_width; j += sw) { int r = 0, g = 0, b = 0, a = 0; for (int m = 0; m < sh; m++) { for (int n = 0; n < sw; n++) { r += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 0]; g += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 1]; b += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 2]; a += (int)thumbnail_data.pixels[4 * ((i + m) * thumbnail_data.width + j + n) + 3]; } } r = std::clamp(0, r / sw / sh, 255); g = std::clamp(0, g / sw / sh, 255); b = std::clamp(0, b / sw / sh, 255); a = std::clamp(0, a / sw / sh, 255); small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 0] = (unsigned char)r; small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 1] = (unsigned char)g; small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 2] = (unsigned char)b; small_pixels[4 * (i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh) + 3] = (unsigned char)a; //memcpy((void*)&small_pixels[4*(i / sw * PLATE_THUMBNAIL_SMALL_WIDTH + j / sh)], thumbnail_data.pixels.data() + 4*(i * thumbnail_data.width + j), 4); } } size_t small_png_size = 0; void* small_png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)small_pixels.data(), PLATE_THUMBNAIL_SMALL_WIDTH, PLATE_THUMBNAIL_SMALL_HEIGHT, 4, &small_png_size, MZ_DEFAULT_COMPRESSION, 1); if (png_data != nullptr) { std::string thumbnail_name = (boost::format("%1%_%2%_small.png") % local_path % (index + 1)).str(); res = mz_zip_writer_add_mem(&archive, thumbnail_name.c_str(), (const void*)small_png_data, small_png_size, MZ_NO_COMPRESSION); mz_free(small_png_data); } if (!res) { add_error("Unable to add small thumbnail file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add small thumbnail file to archive\n"); } } return res; } bool _BBS_3MF_Exporter::_add_calibration_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, int index) { bool res = false; /*size_t png_size = 0; void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_COMPRESSION, 1); if (png_data != nullptr) { std::string thumbnail_name = (boost::format(PATTERN_FILE_FORMAT) % (index + 1)).str(); res = mz_zip_writer_add_mem(&archive, thumbnail_name.c_str(), (const void*)png_data, png_size, MZ_NO_COMPRESSION); mz_free(png_data); } if (!res) { add_error("Unable to add thumbnail file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add thumbnail file to archive\n"); }*/ return res; } bool _BBS_3MF_Exporter::_add_bbox_file_to_archive(mz_zip_archive& archive, const PlateBBoxData& id_bboxes, int index) { bool res = false; nlohmann::json j; id_bboxes.to_json(j); std::string out = j.dump(); std::string json_file_name = (boost::format(PATTERN_CONFIG_FILE_FORMAT) % (index + 1)).str(); if (!mz_zip_writer_add_mem(&archive, json_file_name.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add json file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add json file to archive\n"); return false; } return true; } bool _BBS_3MF_Exporter::_add_relationships_file_to_archive( mz_zip_archive &archive, std::string const &from, std::vector const &targets, std::vector const &types, PackingTemporaryData data, int export_plate_idx) const { std::stringstream stream; stream << "\n"; stream << "\n"; if (from.empty()) { stream << " \n"; if (export_plate_idx < 0) { //use cover image if have if (data._3mf_thumbnail.empty()) { stream << " \n"; } else { stream << " \n"; } if (data._3mf_printer_thumbnail_middle.empty()) { stream << " \n"; } else { stream << " \n"; } if (data._3mf_printer_thumbnail_small.empty()) { stream << "\n"; } else { stream << " \n"; } } else { //always use plate thumbnails std::string thumbnail_file_str = (boost::format("Metadata/plate_%1%.png") % (export_plate_idx + 1)).str(); stream << " \n"; thumbnail_file_str = (boost::format("Metadata/plate_%1%.png") % (export_plate_idx + 1)).str(); stream << " \n"; thumbnail_file_str = (boost::format("Metadata/plate_%1%_small.png") % (export_plate_idx + 1)).str(); stream << " \n"; } } else if (targets.empty()) { return false; } else { int i = 0; for (auto & path : targets) { for (auto & type : types) stream << " \n"; } } stream << ""; std::string out = stream.str(); if (!mz_zip_writer_add_mem(&archive, from.empty() ? RELATIONSHIPS_FILE.c_str() : from.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add relationships file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add relationships file to archive\n"); return false; } return true; } static void reset_stream(std::stringstream &stream) { stream.str(""); stream.clear(); // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 // Conversion of a floating-point value to text and back is exact as long as at least max_digits10 were used (9 for float, 17 for double). // It is guaranteed to produce the same floating-point value, even though the intermediate text representation is not exact. // The default value of std::stream precision is 6 digits only! stream << std::setprecision(std::numeric_limits::max_digits10); } /* * BBS: Production Extension (SplitModel) * save sub model if objects_data is not empty * not collect build items in sub model */ bool _BBS_3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, ObjectToObjectDataMap& objects_data, Export3mfProgressFn proFn, BBLProject* project) const { bool sub_model = !objects_data.empty(); bool write_object = sub_model || !m_split_model; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", filename %1%, m_split_model %2%, sub_model %3%")%filename % m_split_model % sub_model; #if WRITE_ZIP_LANGUAGE_ENCODING auto & zip_filename = filename; #else std::string zip_filename = encode_path(filename.c_str()); std::string extra = sub_model ? ZipUnicodePathExtraField::encode(filename, zip_filename) : ""; #endif mz_zip_writer_staged_context context; if (!mz_zip_writer_add_staged_open(&archive, &context, sub_model ? zip_filename.c_str() : MODEL_FILE.c_str(), m_zip64 ? // Maximum expected and allowed 3MF file size is 16GiB. // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records. (uint64_t(1) << 30) * 16 : // Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see // GH issue #6193. (uint64_t(1) << 32) - 1, #if WRITE_ZIP_LANGUAGE_ENCODING nullptr, nullptr, 0, MZ_DEFAULT_LEVEL, nullptr, 0, nullptr, 0)) { #else nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, extra.c_str(), extra.length(), extra.c_str(), extra.length())) { #endif add_error("Unable to add model file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add model file to archive\n"); return false; } { std::stringstream stream; reset_stream(stream); stream << "\n"; stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:BambuStudio=\"http://schemas.bambulab.com/package/2021\""; if (m_production_ext) stream << " xmlns:p=\"http://schemas.microsoft.com/3dmanufacturing/production/2015/06\" requiredextensions=\"p\""; stream << ">\n"; std::string origin; std::string name; std::string user_name; std::string user_id; std::string design_cover; std::string license; std::string description; std::string copyright; std::string rating; std::string model_id; std::string region_code; if (model.design_info) { user_name = model.design_info->Designer; user_id = model.design_info->DesignerUserId; BOOST_LOG_TRIVIAL(trace) << "design_info, save_3mf found designer = " << user_name; BOOST_LOG_TRIVIAL(trace) << "design_info, save_3mf found designer_user_id = " << user_id; } if (project) { model_id = project->project_model_id; region_code = project->project_country_code; } if (model.model_info) { design_cover = model.model_info->cover_file; license = model.model_info->license; description = model.model_info->description; copyright = model.model_info->copyright; name = model.model_info->model_name; origin = model.model_info->origin; BOOST_LOG_TRIVIAL(trace) << "design_info, save_3mf found designer_cover = " << design_cover; } // remember to use metadata_item_map to store metadata info std::map metadata_item_map; if (!sub_model) { // update metadat_items if (model.model_info && model.model_info.get()) { metadata_item_map = model.model_info.get()->metadata_items; } metadata_item_map[BBL_MODEL_NAME_TAG] = xml_escape(name); metadata_item_map[BBL_ORIGIN_TAG] = xml_escape(origin); metadata_item_map[BBL_DESIGNER_TAG] = xml_escape(user_name); metadata_item_map[BBL_DESIGNER_USER_ID_TAG] = user_id; metadata_item_map[BBL_DESIGNER_COVER_FILE_TAG] = xml_escape(design_cover); metadata_item_map[BBL_DESCRIPTION_TAG] = xml_escape(description); metadata_item_map[BBL_COPYRIGHT_NORMATIVE_TAG] = xml_escape(copyright); metadata_item_map[BBL_LICENSE_TAG] = xml_escape(license); /* save model info */ if (!model_id.empty()) { metadata_item_map[BBL_MODEL_ID_TAG] = model_id; metadata_item_map[BBL_REGION_TAG] = region_code; } std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc()); // keep only the date part of the string date = date.substr(0, 10); metadata_item_map[BBL_CREATION_DATE_TAG] = date; metadata_item_map[BBL_MODIFICATION_TAG] = date; metadata_item_map[BBL_APPLICATION_TAG] = (boost::format("%1%-%2%") % SLIC3R_APP_KEY % SLIC3R_VERSION).str(); } metadata_item_map[BBS_3MF_VERSION] = std::to_string(VERSION_BBS_3MF); if (!model.mk_name.empty()) { metadata_item_map[BBL_MAKERLAB_TAG] = xml_escape(model.mk_name); BOOST_LOG_TRIVIAL(info) << "saved mk_name " << model.mk_name; } if (!model.mk_version.empty()) { metadata_item_map[BBL_MAKERLAB_VERSION_TAG] = xml_escape(model.mk_version); BOOST_LOG_TRIVIAL(info) << "saved mk_version " << model.mk_version; } if (!model.md_name.empty()) { for (unsigned int i = 0; i < model.md_name.size(); i++) { BOOST_LOG_TRIVIAL(info) << boost::format("saved metadata_name %1%, metadata_value %2%") %model.md_name[i] %model.md_value[i]; metadata_item_map[model.md_name[i]] = xml_escape(model.md_value[i]); } } // store metadata info for (auto item : metadata_item_map) { BOOST_LOG_TRIVIAL(info) << "bbs_3mf: save key= " << item.first << ", value = " << item.second; stream << " <" << METADATA_TAG << " name=\"" << item.first << "\">" << xml_escape(item.second) << "\n"; } stream << " <" << RESOURCES_TAG << ">\n"; std::string buf = stream.str(); if (! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) { add_error("Unable to add model file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add model file to archive\n"); return false; } } // Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects). BuildItemsList build_items; // The object_id here is a one based identifier of the first instance of a ModelObject in the 3MF file, where // all the object instances of all ModelObjects are stored and indexed in a 1 based linear fashion. // Therefore the list of object_ids here may not be continuous. unsigned int object_id = 1; unsigned int object_index = 0; bool cb_cancel = false; std::vector object_paths; // if (!m_skip_model) { for (ModelObject* obj : model.objects) { if (sub_model && obj != objects_data.begin()->second.object) continue; if (proFn) { proFn(EXPORT_STAGE_ADD_MODELS, object_index++, model.objects.size(), cb_cancel); if (cb_cancel) return false; } if (obj == nullptr) continue; // Index of an object in the 3MF file corresponding to the 1st instance of a ModelObject. ObjectToObjectDataMap::iterator object_it = objects_data.begin(); if (!sub_model) { // For backup, use backup id as object id int backup_id = const_cast(model).get_object_backup_id(*obj); if (m_from_backup_save) object_id = backup_id; object_it = objects_data.insert({obj, {obj, backup_id} }).first; auto & object_data = object_it->second; if (m_split_model) { auto filename = boost::format("3D/Objects/object_%d.model") % backup_id; object_data.sub_path = "/" + filename.str(); object_paths.push_back(filename.str()); } auto &volumes_objectID = object_data.volumes_objectID; unsigned int volume_id = object_id, volume_count = 0; for (ModelVolume *volume : obj->volumes) { if (volume == nullptr) continue; volume_count++; if (m_share_mesh) { auto iter = m_shared_meshes.find(volume->mesh_ptr()); if (iter != m_shared_meshes.end()) { const ModelVolume* shared_volume = iter->second.second; if ((shared_volume->supported_facets.equals(volume->supported_facets)) && (shared_volume->seam_facets.equals(volume->seam_facets)) && (shared_volume->mmu_segmentation_facets.equals(volume->mmu_segmentation_facets))) { auto data = iter->second.first; const_cast<_BBS_3MF_Exporter *>(this)->m_volume_paths.insert({volume, {data->sub_path, data->volumes_objectID.find(iter->second.second)->second}}); volumes_objectID.insert({volume, 0}); object_data.share_mesh = true; continue; } } const_cast<_BBS_3MF_Exporter *>(this)->m_shared_meshes.insert({volume->mesh_ptr(), {&object_data, volume}}); } if (m_from_backup_save) volume_id = (volume_count << 16 | backup_id); volumes_objectID.insert({volume, volume_id}); volume_id++; } if (!m_from_backup_save) object_id = volume_id; object_data.object_id = object_id; } if (m_skip_model) continue; if (write_object) { // Store geometry of all ModelVolumes contained in a single ModelObject into a single 3MF indexed triangle set object. // object_it->second.volumes_objectID will contain the offsets of the ModelVolumes in that single indexed triangle set. // object_id will be increased to point to the 1st instance of the next ModelObject. if (!_add_object_to_model_stream(context, object_it->second)) { add_error("Unable to add object to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add object to archive\n"); return false; } } if (sub_model) break; unsigned int count = 0; for (const ModelInstance* instance : obj->instances) { Transform3d t = instance->get_matrix(); // instance_id is just a 1 indexed index in build_items. //assert(m_skip_static || curr_id == build_items.size() + 1); build_items.emplace_back("", object_it->second.object_id, t, instance->printable); count++; } if (!m_from_backup_save) object_id++; } // } { std::stringstream stream; reset_stream(stream); if (!m_skip_model && !sub_model) { for (auto object : model.objects) { auto &data = objects_data[object]; _add_object_components_to_stream(stream, data); } } stream << " \n"; // Store the transformations of all the ModelInstances of all ModelObjects, indexed in a linear fashion. if (!_add_build_to_model_stream(stream, build_items)) { add_error("Unable to add build to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add build to archive\n"); return false; } stream << "\n"; std::string buf = stream.str(); if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) || ! mz_zip_writer_add_staged_finish(&context)) { add_error("Unable to add model file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add model file to archive\n"); return false; } } if (m_skip_model || write_object) return true; // write model rels _add_relationships_file_to_archive(archive, MODEL_RELS_FILE, object_paths, {"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"}); if (!m_from_backup_save) { boost::mutex mutex; tbb::parallel_for(tbb::blocked_range(0, objects_data.size(), 1), [this, &mutex, &model, objects = model.objects, &objects_data, &object_paths, main = &archive, project](const tbb::blocked_range& range) { for (size_t i = range.begin(); i < range.end(); ++i) { auto iter = objects_data.find(objects[i]); ObjectToObjectDataMap objects_data2; objects_data2.insert(*iter); auto & object = *iter->second.object; mz_zip_archive archive; mz_zip_zero_struct(&archive); mz_zip_writer_init_heap(&archive, 0, 1024 * 1024); CNumericLocalesSetter locales_setter; _add_model_file_to_archive(object_paths[i], archive, model, objects_data2, nullptr, project); iter->second = objects_data2.begin()->second; void *ppBuf; size_t pSize; mz_zip_writer_finalize_heap_archive(&archive, &ppBuf, &pSize); mz_zip_writer_end(&archive); mz_zip_zero_struct(&archive); mz_zip_reader_init_mem(&archive, ppBuf, pSize, 0); { boost::unique_lock l(mutex); mz_zip_writer_add_from_zip_reader(main, &archive, 0); } mz_zip_reader_end(&archive); } }); } return true; } bool _BBS_3MF_Exporter::_add_object_to_model_stream(mz_zip_writer_staged_context &context, ObjectData const &object_data) const { // backup: make _add_mesh_to_object_stream() reusable auto flush = [this, &context](std::string & buf, bool force = false) { if ((force && !buf.empty()) || buf.size() >= 65536 * 16) { if (!mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) { add_error("Error during writing or compression"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Error during writing or compression\n"); return false; } buf.clear(); } return true; }; if (!_add_mesh_to_object_stream(flush, object_data)) { add_error("Unable to add mesh to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add mesh to archive\n"); return false; } // Move all components to main model //_add_object_components_to_stream(stream, object_data); return true; } void _BBS_3MF_Exporter::_add_object_components_to_stream(std::stringstream &stream, ObjectData const &object_data) const { auto & object = *object_data.object; stream << " <" << OBJECT_TAG << " id=\"" << object_data.object_id; if (m_production_ext) stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap{(boost::uint32_t)object_data.backup_id} << (object_data.share_mesh ? OBJECT_UUID_SUFFIX2 : OBJECT_UUID_SUFFIX); stream << "\" type=\"model\">\n"; stream << " <" << COMPONENTS_TAG << ">\n"; for (unsigned int index = 0; index < object.volumes.size(); index ++) { ModelVolume *volume = object.volumes[index]; unsigned int volume_id = object_data.volumes_objectID.find(volume)->second; auto * ppath = &object_data.sub_path; auto iter = m_volume_paths.find(volume); if (iter != m_volume_paths.end()) { ppath = &iter->second.first; volume_id = iter->second.second; } //add the transform of the volume if (ppath->empty()) stream << " <" << COMPONENT_TAG << " objectid=\"" << volume_id; else stream << " <" << COMPONENT_TAG << " p:path=\"" << xml_escape(*ppath) << "\" objectid=\"" << volume_id; // << "\"/>\n"; if (m_production_ext) stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap{(boost::uint32_t) (index + (object_data.backup_id << 16))} << COMPONENT_UUID_SUFFIX; const Transform3d &transf = volume->get_matrix(); stream << "\" " << TRANSFORM_ATTR << "=\""; for (unsigned c = 0; c < 4; ++c) { for (unsigned r = 0; r < 3; ++r) { stream << transf(r, c); if (r != 2 || c != 3) stream << " "; } } stream << "\"/>\n"; } stream << " \n"; stream << " \n"; } #if EXPORT_3MF_USE_SPIRIT_KARMA_FP template struct coordinate_policy_fixed : boost::spirit::karma::real_policies { static int floatfield(Num n) { return fmtflags::fixed; } // Number of decimal digits to maintain float accuracy when storing into a text file and parsing back. static unsigned precision(Num /* n */) { return std::numeric_limits::max_digits10 + 1; } // No trailing zeros, thus for fmtflags::fixed usually much less than max_digits10 decimal numbers will be produced. static bool trailing_zeros(Num /* n */) { return false; } }; template struct coordinate_policy_scientific : coordinate_policy_fixed { static int floatfield(Num n) { return fmtflags::scientific; } }; // Define a new generator type based on the new coordinate policy. using coordinate_type_fixed = boost::spirit::karma::real_generator>; using coordinate_type_scientific = boost::spirit::karma::real_generator>; #endif // EXPORT_3MF_USE_SPIRIT_KARMA_FP //BBS: change volume to seperate objects bool _BBS_3MF_Exporter::_add_mesh_to_object_stream(std::function const &flush, ObjectData const &object_data) const { std::string output_buffer; #if 0 auto flush = [this, &output_buffer, &context](bool force = false) { if ((force && ! output_buffer.empty()) || output_buffer.size() >= 65536 * 16) { if (! mz_zip_writer_add_staged_data(&context, output_buffer.data(), output_buffer.size())) { add_error("Error during writing or compression"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Error during writing or compression\n"); return false; } output_buffer.clear(); } return true; }; #endif /*output_buffer += " <"; output_buffer += MESH_TAG; output_buffer += ">\n <"; output_buffer += VERTICES_TAG; output_buffer += ">\n";*/ auto format_coordinate = [](float f, char *buf) -> char* { assert(is_decimal_separator_point()); #if EXPORT_3MF_USE_SPIRIT_KARMA_FP // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, // https://github.com/boostorg/spirit/pull/586 // where the exported string is one digit shorter than it should be to guarantee lossless round trip. // The code is left here for the ocasion boost guys improve. coordinate_type_fixed const coordinate_fixed = coordinate_type_fixed(); coordinate_type_scientific const coordinate_scientific = coordinate_type_scientific(); // Format "f" in a fixed format. char *ptr = buf; boost::spirit::karma::generate(ptr, coordinate_fixed, f); // Format "f" in a scientific format. char *ptr2 = ptr; boost::spirit::karma::generate(ptr2, coordinate_scientific, f); // Return end of the shorter string. auto len2 = ptr2 - ptr; if (ptr - buf > len2) { // Move the shorter scientific form to the front. memcpy(buf, ptr, len2); ptr = buf + len2; } // Return pointer to the end. return ptr; #else // Round-trippable float, shortest possible. return buf + sprintf(buf, "%.9g", f); #endif }; auto const & object = *object_data.object; char buf[256]; unsigned int vertices_count = 0; //unsigned int triangles_count = 0; for (unsigned int index = 0; index < object.volumes.size(); index++) { ModelVolume *volume = object.volumes[index]; if (volume == nullptr) continue; int volume_id = object_data.volumes_objectID.find(volume)->second; if (m_share_mesh && volume_id == 0) continue; //if (!volume->mesh().stats().repaired()) // throw Slic3r::FileIOError("store_3mf() requires repair()"); const indexed_triangle_set &its = volume->mesh().its; if (its.vertices.empty()) { add_error("Found invalid mesh"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Found invalid mesh\n"); return false; } std::string type = (volume->type() == ModelVolumeType::MODEL_PART)?"model":"other"; output_buffer += " <"; output_buffer += OBJECT_TAG; output_buffer += " id=\""; output_buffer += std::to_string(volume_id); if (m_production_ext) { std::stringstream stream; reset_stream(stream); stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap{(boost::uint32_t) (index + (object_data.backup_id << 16))} << SUB_OBJECT_UUID_SUFFIX; //output_buffer += "\" "; //output_buffer += PUUID_ATTR; //output_buffer += "=\""; //output_buffer += std::to_string(hex_wrap{(boost::uint32_t)backup_id}); //output_buffer += OBJECT_UUID_SUFFIX; output_buffer += stream.str(); } output_buffer += "\" type=\""; output_buffer += type; output_buffer += "\">\n"; output_buffer += " <"; output_buffer += MESH_TAG; output_buffer += ">\n <"; output_buffer += VERTICES_TAG; output_buffer += ">\n"; vertices_count += (int)its.vertices.size(); for (size_t i = 0; i < its.vertices.size(); ++i) { //don't save the volume's matrix into vertex data //add the shared mesh logic //Vec3f v = (matrix * its.vertices[i].cast()).cast(); Vec3f v = its.vertices[i]; char* ptr = buf; boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << VERTEX_TAG << " x=\""); ptr = format_coordinate(v.x(), ptr); boost::spirit::karma::generate(ptr, "\" y=\""); ptr = format_coordinate(v.y(), ptr); boost::spirit::karma::generate(ptr, "\" z=\""); ptr = format_coordinate(v.z(), ptr); boost::spirit::karma::generate(ptr, "\"/>\n"); *ptr = '\0'; output_buffer += buf; if (!flush(output_buffer, false)) return false; } //} output_buffer += " \n <"; output_buffer += TRIANGLES_TAG; output_buffer += ">\n"; //for (ModelVolume* volume : object.volumes) { // if (volume == nullptr) // continue; //BBS: as we stored matrix seperately, not multiplied into vertex //we don't need to consider this left hand case specially //bool is_left_handed = volume->is_left_handed(); bool is_left_handed = false; //VolumeToOffsetsMap::iterator volume_it = volumes_objectID.find(volume); //assert(volume_it != volumes_objectID.end()); //const indexed_triangle_set &its = volume->mesh().its; // updates triangle offsets //unsigned int first_triangle_id = triangles_count; //triangles_count += (int)its.indices.size(); //unsigned int last_triangle_id = triangles_count - 1; for (int i = 0; i < int(its.indices.size()); ++ i) { { const Vec3i &idx = its.indices[i]; char *ptr = buf; boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << " v1=\"" << boost::spirit::int_ << "\" v2=\"" << boost::spirit::int_ << "\" v3=\"" << boost::spirit::int_ << "\"", idx[is_left_handed ? 2 : 0], idx[1], idx[is_left_handed ? 0 : 2]); *ptr = '\0'; output_buffer += buf; } std::string custom_supports_data_string = volume->supported_facets.get_triangle_as_string(i); if (! custom_supports_data_string.empty()) { output_buffer += " "; output_buffer += CUSTOM_SUPPORTS_ATTR; output_buffer += "=\""; output_buffer += custom_supports_data_string; output_buffer += "\""; } std::string custom_seam_data_string = volume->seam_facets.get_triangle_as_string(i); if (! custom_seam_data_string.empty()) { output_buffer += " "; output_buffer += CUSTOM_SEAM_ATTR; output_buffer += "=\""; output_buffer += custom_seam_data_string; output_buffer += "\""; } std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i); if (! mmu_painting_data_string.empty()) { output_buffer += " "; output_buffer += MMU_SEGMENTATION_ATTR; output_buffer += "=\""; output_buffer += mmu_painting_data_string; output_buffer += "\""; } // BBS if (i < its.properties.size()) { std::string prop_str = its.properties[i].to_string(); if (!prop_str.empty()) { output_buffer += " "; output_buffer += FACE_PROPERTY_ATTR; output_buffer += "=\""; output_buffer += prop_str; output_buffer += "\""; } } output_buffer += "/>\n"; if (! flush(output_buffer, false)) return false; } output_buffer += " \n \n"; output_buffer += " \n"; } // Force flush. return flush(output_buffer, true); } bool _BBS_3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const { // This happens for empty projects if (build_items.size() == 0) { stream << " <" << BUILD_TAG << "/>\n"; return true; } stream << " <" << BUILD_TAG; if (m_production_ext) stream << " " << PUUID_ATTR << "=\"" << BUILD_UUID << "\""; stream << ">\n"; for (const BuildItem& item : build_items) { stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "=\"" << item.id; if (m_production_ext) stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap{item.id} << BUILD_UUID_SUFFIX; if (!item.path.empty()) stream << "\" " << PPATH_ATTR << "=\"" << xml_escape(item.path); stream << "\" " << TRANSFORM_ATTR << "=\""; for (unsigned c = 0; c < 4; ++c) { for (unsigned r = 0; r < 3; ++r) { stream << item.transform(r, c); if (r != 2 || c != 3) stream << " "; } } stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n"; } stream << " \n"; return true; } bool _BBS_3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) { assert(is_decimal_separator_point()); std::string out = ""; char buffer[1024]; unsigned int count = 0; for (const ModelObject* object : model.objects) { ++count; const std::vector& layer_height_profile = object->layer_height_profile.get(); if (layer_height_profile.size() >= 4 && layer_height_profile.size() % 2 == 0) { snprintf(buffer, 1024, "object_id=%d|", count); out += buffer; // Store the layer height profile as a single semicolon separated list. for (size_t i = 0; i < layer_height_profile.size(); ++i) { snprintf(buffer, 1024, (i == 0) ? "%f" : ";%f", layer_height_profile[i]); out += buffer; } out += "\n"; } } if (!out.empty()) { if (!mz_zip_writer_add_mem(&archive, BBS_LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add layer heights profile file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add layer heights profile file to archive\n"); return false; } } return true; } bool _BBS_3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) { std::string out = ""; pt::ptree tree; unsigned int object_cnt = 0; for (const ModelObject* object : model.objects) { object_cnt++; const t_layer_config_ranges& ranges = object->layer_config_ranges; if (!ranges.empty()) { pt::ptree& obj_tree = tree.add("objects.object",""); obj_tree.put(".id", object_cnt); // Store the layer config ranges. for (const auto& range : ranges) { pt::ptree& range_tree = obj_tree.add("range", ""); // store minX and maxZ range_tree.put(".min_z", range.first.first); range_tree.put(".max_z", range.first.second); // store range configuration const ModelConfig& config = range.second; for (const std::string& opt_key : config.keys()) { pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); opt_tree.put(".opt_key", opt_key); } } } } if (!tree.empty()) { std::ostringstream oss; pt::write_xml(oss, tree); out = oss.str(); // Post processing("beautification") of the output string for a better preview boost::replace_all(out, ">\n \n \n ", ">\n "); boost::replace_all(out, ">", ">\n "); // OR just boost::replace_all(out, "><", ">\n<"); } if (!out.empty()) { if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add layer heights profile file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add layer heights profile file to archive\n"); return false; } } return true; } /* bool _BBS_3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) { assert(is_decimal_separator_point()); std::string out = ""; char buffer[1024]; unsigned int count = 0; for (const ModelObject* object : model.objects) { ++count; const std::vector& sla_support_points = object->sla_support_points; if (!sla_support_points.empty()) { sprintf(buffer, "object_id=%d|", count); out += buffer; // Store the layer height profile as a single space separated list. for (size_t i = 0; i < sla_support_points.size(); ++i) { sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island); out += buffer; } out += "\n"; } } if (!out.empty()) { // Adds version header at the beginning: //out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out; if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add sla support points file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add sla support points file to archive\n"); return false; } } return true; } bool _BBS_3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model) { assert(is_decimal_separator_point()); const char *const fmt = "object_id=%d|"; std::string out; unsigned int count = 0; for (const ModelObject* object : model.objects) { ++count; sla::DrainHoles drain_holes = object->sla_drain_holes; // The holes were placed 1mm above the mesh in the first implementation. // This was a bad idea and the reference point was changed in 2.3 so // to be on the mesh exactly. The elevated position is still saved // in 3MFs for compatibility reasons. for (sla::DrainHole& hole : drain_holes) { hole.pos -= hole.normal.normalized(); hole.height += 1.f; } if (!drain_holes.empty()) { out += string_printf(fmt, count); // Store the layer height profile as a single space separated list. for (size_t i = 0; i < drain_holes.size(); ++i) out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"), drain_holes[i].pos(0), drain_holes[i].pos(1), drain_holes[i].pos(2), drain_holes[i].normal(0), drain_holes[i].normal(1), drain_holes[i].normal(2), drain_holes[i].radius, drain_holes[i].height); out += "\n"; } } if (!out.empty()) { // Adds version header at the beginning: //out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out; if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION))) { add_error("Unable to add sla support points file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add sla support points file to archive\n"); return false; } } return true; }*/ bool _BBS_3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) { assert(is_decimal_separator_point()); char buffer[1024]; snprintf(buffer, 1024, "; %s\n\n", header_slic3r_generated().c_str()); std::string out = buffer; for (const std::string &key : config.keys()) if (key != "compatible_printers") out += "; " + key + " = " + config.opt_serialize(key) + "\n"; if (!out.empty()) { if (!mz_zip_writer_add_mem(&archive, BBS_PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add print config file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add print config file to archive\n"); return false; } } return true; } //BBS: add project config file logic for new json format bool _BBS_3MF_Exporter::_add_project_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, Model& model) { const std::string& temp_path = model.get_backup_path(); std::string temp_file = temp_path + std::string("/") + "_temp_1.config"; config.save_to_json(temp_file, std::string("project_settings"), std::string("project"), std::string(SLIC3R_VERSION)); return _add_file_to_archive(archive, BBS_PROJECT_CONFIG_FILE, temp_file); } //BBS: add project embedded preset files bool _BBS_3MF_Exporter::_add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector project_presets) { char buffer[1024]; snprintf(buffer, 1024, "; %s\n\n", header_slic3r_generated().c_str()); std::string out = buffer; int print_count = 0, filament_count = 0, printer_count = 0; const std::string& temp_path = model.get_backup_path(); for (int i = 0; i < project_presets.size(); i++) { Preset* preset = project_presets[i]; if (preset) { preset->file = temp_path + std::string("/") + "_temp_1.config"; DynamicPrintConfig& config = preset->config; //config.save(preset->file); config.save_to_json(preset->file, preset->name, std::string("project"), preset->version.to_string()); std::string dest_file; if (preset->type == Preset::TYPE_PRINT) { dest_file = (boost::format(EMBEDDED_PRINT_FILE_FORMAT) % (print_count + 1)).str(); print_count++; } else if (preset->type == Preset::TYPE_FILAMENT) { dest_file = (boost::format(EMBEDDED_FILAMENT_FILE_FORMAT) % (filament_count + 1)).str(); filament_count++; } else if (preset->type == Preset::TYPE_PRINTER) { dest_file = (boost::format(EMBEDDED_PRINTER_FILE_FORMAT) % (printer_count + 1)).str(); printer_count++; } else continue; _add_file_to_archive(archive, dest_file, preset->file); } } return true; } void _add_text_info_to_archive(std::stringstream& stream, const TextInfo& text_info) { stream << " <" << TEXT_INFO_TAG << " "; stream << TEXT_ATTR << "=\"" << xml_escape(text_info.m_text) << "\" "; stream << FONT_NAME_ATTR << "=\"" << text_info.m_font_name << "\" "; stream << FONT_INDEX_ATTR << "=\"" << text_info.m_curr_font_idx << "\" "; stream << FONT_SIZE_ATTR << "=\"" << text_info.m_font_size << "\" "; stream << THICKNESS_ATTR << "=\"" << text_info.m_thickness << "\" "; stream << EMBEDED_DEPTH_ATTR << "=\"" << text_info.m_embeded_depth << "\" "; stream << ROTATE_ANGLE_ATTR << "=\"" << text_info.m_rotate_angle << "\" "; stream << TEXT_GAP_ATTR << "=\"" << text_info.m_text_gap << "\" "; stream << BOLD_ATTR << "=\"" << (text_info.m_bold ? 1 : 0) << "\" "; stream << ITALIC_ATTR << "=\"" << (text_info.m_italic ? 1 : 0) << "\" "; stream << SURFACE_TEXT_ATTR << "=\"" << (text_info.m_is_surface_text ? 1 : 0) << "\" "; stream << KEEP_HORIZONTAL_ATTR << "=\"" << (text_info.m_keep_horizontal ? 1 : 0) << "\" "; stream << HIT_MESH_ATTR << "=\"" << text_info.m_rr.mesh_id << "\" "; stream << HIT_POSITION_ATTR << "=\""; add_vec3(stream, text_info.m_rr.hit); stream << "\" "; stream << HIT_NORMAL_ATTR << "=\""; add_vec3(stream, text_info.m_rr.normal); stream << "\" "; stream << "/>\n"; } bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, int export_plate_idx, bool save_gcode, bool use_loaded_id) { std::stringstream stream; // Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back // when loaded as accurately as possible. stream << std::setprecision(std::numeric_limits::max_digits10); stream << "\n"; stream << "<" << CONFIG_TAG << ">\n"; if (!m_skip_model) for (const ObjectToObjectDataMap::value_type& obj_metadata : objects_data) { auto object_data = obj_metadata.second; const ModelObject *obj = object_data.object; if (obj != nullptr) { // Output of instances count added because of github #3435, currently not used by PrusaSlicer //stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\" " << INSTANCESCOUNT_ATTR << "=\"" << obj->instances.size() << "\">\n"; stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << object_data.object_id << "\">\n"; // stores object's name if (!obj->name.empty()) stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n"; //BBS: store object's module name if (!obj->module_name.empty()) stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"module\" " << VALUE_ATTR << "=\"" << xml_escape(obj->module_name) << "\"/>\n"; // stores object's config data for (const std::string& key : obj->config.keys()) { stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n"; } for (const ModelVolume* volume : obj_metadata.second.object->volumes) { if (volume != nullptr) { const VolumeToObjectIDMap& objectIDs = obj_metadata.second.volumes_objectID; VolumeToObjectIDMap::const_iterator it = objectIDs.find(volume); if (it != objectIDs.end()) { // stores volume's offsets stream << " <" << PART_TAG << " "; //stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" "; //stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\" "; int volume_id = it->second; if (m_share_mesh && volume_id == 0) volume_id = m_volume_paths.find(volume)->second.second; stream << ID_ATTR << "=\"" << volume_id << "\" "; stream << SUBTYPE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\">\n"; //stream << " <" << PART_TAG << " " << ID_ATTR << "=\"" << it->second << "\" " << SUBTYPE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\">\n"; // stores volume's name if (!volume->name.empty()) stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; //stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; // stores volume's modifier field (legacy, to support old slicers) /*if (volume->is_modifier()) stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << PART_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; // stores volume's type (overrides the modifier field above) stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << PART_TYPE << "\" " << KEY_ATTR << "=\"" << PART_TYPE_KEY << "\" " << VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";*/ // stores volume's local matrix stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) { stream << matrix(r, c); if (r != 3 || c != 3) stream << " "; } } stream << "\"/>\n"; // stores volume's source data { std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); //std::string prefix = std::string(" <") + METADATA_TAG + " " + KEY_ATTR + "=\""; std::string prefix = std::string(" <") + METADATA_TAG + " " + KEY_ATTR + "=\""; if (! volume->source.input_file.empty()) { stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; } assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); if (volume->source.is_converted_from_inches) stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n"; else if (volume->source.is_converted_from_meters) stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n"; } // stores volume's config data for (const std::string& key : volume->config.keys()) { stream << " <" << METADATA_TAG << " "<< KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; } const TextInfo &text_info = volume->get_text_info(); if (!text_info.m_text.empty()) _add_text_info_to_archive(stream, text_info); // stores mesh's statistics const RepairedMeshErrors& stats = volume->mesh().stats().repaired_errors; stream << " <" << MESH_STAT_TAG << " "; stream << MESH_STAT_EDGES_FIXED << "=\"" << stats.edges_fixed << "\" "; stream << MESH_STAT_DEGENERATED_FACETS << "=\"" << stats.degenerate_facets << "\" "; stream << MESH_STAT_FACETS_REMOVED << "=\"" << stats.facets_removed << "\" "; stream << MESH_STAT_FACETS_RESERVED << "=\"" << stats.facets_reversed << "\" "; stream << MESH_STAT_BACKWARDS_EDGES << "=\"" << stats.backwards_edges << "\"/>\n"; stream << " \n"; } } } stream << " \n"; } } //BBS: store plate related logic std::vector gcode_paths; for (unsigned int i = 0; i < (unsigned int)plate_data_list.size(); ++i) { PlateData* plate_data = plate_data_list[i]; int instance_size = plate_data->objects_and_instances.size(); if (plate_data != nullptr) { stream << " <" << PLATE_TAG << ">\n"; //plate index stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PLATERID_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->plate_index + 1 << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PLATER_NAME_ATTR << "\" " << VALUE_ATTR << "=\"" << xml_escape(plate_data->plate_name.c_str()) << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << LOCK_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->locked<< "\"/>\n"; ConfigOption* bed_type_opt = plate_data->config.option("curr_bed_type"); t_config_enum_names bed_type_names = ConfigOptionEnum::get_enum_names(); if (bed_type_opt != nullptr && bed_type_names.size() > bed_type_opt->getInt()) stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << BED_TYPE_ATTR << "\" " << VALUE_ATTR << "=\"" << bed_type_names[bed_type_opt->getInt()] << "\"/>\n"; ConfigOption* print_sequence_opt = plate_data->config.option("print_sequence"); t_config_enum_names print_sequence_names = ConfigOptionEnum::get_enum_names(); if (print_sequence_opt != nullptr && print_sequence_names.size() > print_sequence_opt->getInt()) stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PRINT_SEQUENCE_ATTR << "\" " << VALUE_ATTR << "=\"" << print_sequence_names[print_sequence_opt->getInt()] << "\"/>\n"; ConfigOptionInts *first_layer_print_sequence_opt = plate_data->config.option("first_layer_print_sequence"); if (first_layer_print_sequence_opt != nullptr) { stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << FIRST_LAYER_PRINT_SEQUENCE_ATTR << "\" " << VALUE_ATTR << "=\""; const std::vector& values = first_layer_print_sequence_opt->values; for (int i = 0; i < values.size(); ++i) { stream << values[i]; if (i != (values.size() - 1)) stream << " "; } stream << "\"/>\n"; } ConfigOptionInts *other_layers_print_sequence_opt = plate_data->config.option("other_layers_print_sequence"); if (other_layers_print_sequence_opt != nullptr) { stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OTHER_LAYERS_PRINT_SEQUENCE_ATTR << "\" " << VALUE_ATTR << "=\""; const std::vector &values = other_layers_print_sequence_opt->values; for (int i = 0; i < values.size(); ++i) { stream << values[i]; if (i != (values.size() - 1)) stream << " "; } stream << "\"/>\n"; } const ConfigOptionInt *sequence_nums_opt = dynamic_cast(plate_data->config.option("other_layers_print_sequence_nums")); if (sequence_nums_opt != nullptr) { stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OTHER_LAYERS_PRINT_SEQUENCE_NUMS_ATTR << "\" " << VALUE_ATTR << "=\"" << sequence_nums_opt->getInt() << "\"/>\n"; } ConfigOption* spiral_mode_opt = plate_data->config.option("spiral_mode"); if (spiral_mode_opt) stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SPIRAL_VASE_MODE << "\" " << VALUE_ATTR << "=\"" << spiral_mode_opt->getBool() << "\"/>\n"; if (save_gcode) stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << GCODE_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << xml_escape(plate_data->gcode_file) << "\"/>\n"; if (!plate_data->gcode_file.empty()) { gcode_paths.push_back(plate_data->gcode_file); } if (plate_data->plate_thumbnail.is_valid()) { std::string thumbnail_file_in_3mf = (boost::format(THUMBNAIL_FILE_FORMAT) % (plate_data->plate_index + 1)).str(); stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << THUMBNAIL_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << thumbnail_file_in_3mf << "\"/>\n"; } else if (!plate_data->thumbnail_file.empty() && (boost::filesystem::exists(plate_data->thumbnail_file))){ std::string thumbnail_file_in_3mf = (boost::format(THUMBNAIL_FILE_FORMAT) % (plate_data->plate_index + 1)).str(); stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << THUMBNAIL_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << thumbnail_file_in_3mf << "\"/>\n"; } if (!plate_data->no_light_thumbnail_file.empty()){ std::string no_light_thumbnail_file_in_3mf = (boost::format(NO_LIGHT_THUMBNAIL_FILE_FORMAT) % (plate_data->plate_index + 1)).str(); stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NO_LIGHT_THUMBNAIL_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << no_light_thumbnail_file_in_3mf << "\"/>\n"; } if (!plate_data->top_file.empty()) { std::string top_file_in_3mf = (boost::format(TOP_FILE_FORMAT) % (plate_data->plate_index + 1)).str(); stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TOP_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << top_file_in_3mf << "\"/>\n"; } if (!plate_data->pick_file.empty()) { std::string pick_file_in_3mf = (boost::format(PICK_FILE_FORMAT) % (plate_data->plate_index + 1)).str(); stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PICK_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << pick_file_in_3mf << "\"/>\n"; } /*if (!plate_data->pattern_file.empty()) { std::string pattern_file_in_3mf = (boost::format(PATTERN_FILE_FORMAT) % (plate_data->plate_index + 1)).str(); stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PATTERN_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << pattern_file_in_3mf << "\"/>\n"; }*/ if (!plate_data->pattern_bbox_file.empty()) { std::string pattern_bbox_file_in_3mf = (boost::format(PATTERN_CONFIG_FILE_FORMAT) % (plate_data->plate_index + 1)).str(); stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PATTERN_BBOX_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << pattern_bbox_file_in_3mf << "\"/>\n"; } if (!m_skip_model && instance_size > 0) { for (unsigned int j = 0; j < instance_size; ++j) { stream << " <" << INSTANCE_TAG << ">\n"; int obj_id = plate_data->objects_and_instances[j].first; int inst_id = plate_data->objects_and_instances[j].second; int identify_id = 0; ModelObject* obj = NULL; ModelInstance* inst = NULL; if (obj_id >= model.objects.size()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format("invalid object id %1%\n")%obj_id; } else obj = model.objects[obj_id]; if (obj && (inst_id >= obj->instances.size())) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format("invalid instance id %1%\n")%inst_id; } else if (obj){ inst = obj->instances[inst_id]; if (use_loaded_id && (inst->loaded_id > 0)) { identify_id = inst->loaded_id; if (identify_id & 0xFF000000) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", identify_id %1%, too big, limit the high bits to 0\n") % identify_id; identify_id = identify_id & 0x00FFFFFF; } } else identify_id = inst->id().id; } obj_id = objects_data.find(obj)->second.object_id; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OBJECT_ID_ATTR << "\" " << VALUE_ATTR << "=\"" << obj_id << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << INSTANCEID_ATTR << "\" " << VALUE_ATTR << "=\"" << inst_id << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << IDENTIFYID_ATTR << "\" " << VALUE_ATTR << "=\"" << identify_id << "\"/>\n"; stream << " \n"; } } stream << " \n"; } } // write model rels if (save_gcode) _add_relationships_file_to_archive(archive, BBS_MODEL_CONFIG_RELS_FILE, gcode_paths, {"http://schemas.bambulab.com/package/2021/gcode"}, Slic3r::PackingTemporaryData(), export_plate_idx); if (!m_skip_model) { //BBS: store assemble related info stream << " <" << ASSEMBLE_TAG << ">\n"; for (const ObjectToObjectDataMap::value_type& obj_metadata : objects_data) { auto object_data = obj_metadata.second; const ModelObject* obj = object_data.object; if (obj != nullptr) { for (int instance_idx = 0; instance_idx < obj->instances.size(); ++instance_idx) { if (obj->instances[instance_idx]->is_assemble_initialized()) { stream << " <" << ASSEMBLE_ITEM_TAG << " " << OBJECT_ID_ATTR << "=\"" << object_data.object_id << "\" "; stream << INSTANCEID_ATTR << "=\"" << instance_idx << "\" " << TRANSFORM_ATTR << "=\""; for (unsigned c = 0; c < 4; ++c) { for (unsigned r = 0; r < 3; ++r) { const Transform3d assemble_trans = obj->instances[instance_idx]->get_assemble_transformation().get_matrix(); stream << assemble_trans(r, c); if (r != 2 || c != 3) stream << " "; } } stream << "\" "; stream << OFFSET_ATTR << "=\""; const Vec3d ofs2ass = obj->instances[instance_idx]->get_offset_to_assembly(); stream << ofs2ass(0) << " " << ofs2ass(1) << " " << ofs2ass(2); stream << "\" />\n"; } } } } stream << " \n"; } stream << "\n"; std::string out = stream.str(); if (!mz_zip_writer_add_mem(&archive, BBS_MODEL_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add model config file to archive\n"); add_error("Unable to add model config file to archive"); return false; } return true; } bool _BBS_3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model) { std::string out = ""; pt::ptree tree; unsigned int object_cnt = 0; for (const ModelObject *object : model.objects) { object_cnt++; pt::ptree &obj_tree = tree.add("objects.object", ""); obj_tree.put(".id", object_cnt); // Store info for cut_id pt::ptree &cut_id_tree = obj_tree.add("cut_id", ""); // store cut_id atributes cut_id_tree.put(".id", object->cut_id.id().id); cut_id_tree.put(".check_sum", object->cut_id.check_sum()); cut_id_tree.put(".connectors_cnt", object->cut_id.connectors_cnt()); int volume_idx = -1; for (const ModelVolume *volume : object->volumes) { ++volume_idx; if (volume->is_cut_connector()) { pt::ptree &connectors_tree = obj_tree.add("connectors.connector", ""); connectors_tree.put(".volume_id", volume_idx); connectors_tree.put(".type", int(volume->cut_info.connector_type)); connectors_tree.put(".radius", volume->cut_info.radius); connectors_tree.put(".height", volume->cut_info.height); connectors_tree.put(".r_tolerance", volume->cut_info.radius_tolerance); connectors_tree.put(".h_tolerance", volume->cut_info.height_tolerance); } } } if (!tree.empty()) { std::ostringstream oss; pt::write_xml(oss, tree); out = oss.str(); // Post processing("beautification") of the output string for a better preview boost::replace_all(out, ">\n \n ", ">\n "); boost::replace_all(out, ">\n ", ">\n "); boost::replace_all(out, ">\n ", ">\n "); boost::replace_all(out, ">", ">\n "); // OR just boost::replace_all(out, "><", ">\n<"); } if (!out.empty()) { if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void *) out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add cut information file to archive"); return false; } } return true; } bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config) { std::stringstream stream; // Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back // when loaded as accurately as possible. stream << std::setprecision(std::numeric_limits::max_digits10); stream << std::setiosflags(std::ios::fixed) << std::setprecision(2); stream << "\n"; stream << "<" << CONFIG_TAG << ">\n"; // save slice header for debug stream << " <" << SLICE_HEADER_TAG << ">\n"; stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Type" << "\" " << VALUE_ATTR << "=\"" << "slicer" << "\"/>\n"; stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Version" << "\" " << VALUE_ATTR << "=\"" << convert_to_full_version(SLIC3R_VERSION) << "\"/>\n"; stream << " \n"; for (unsigned int i = 0; i < (unsigned int)plate_data_list.size(); ++i) { PlateData* plate_data = plate_data_list[i]; //int instance_size = plate_data->objects_and_instances.size(); if (plate_data != nullptr && plate_data->is_sliced_valid) { stream << " <" << PLATE_TAG << ">\n"; //plate index stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PLATE_IDX_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->plate_index + 1 << "\"/>\n"; int timelapse_type = int(config.opt_enum("timelapse_type")); for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) { if (it->msg == NOT_GENERATE_TIMELAPSE) { timelapse_type = -1; break; } } stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PRINTER_MODEL_ID_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->printer_model_id << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NOZZLE_DIAMETERS_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->nozzle_diameters << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TIMELAPSE_TYPE_ATTR << "\" " << VALUE_ATTR << "=\"" << timelapse_type << "\"/>\n"; //stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TIMELAPSE_ERROR_CODE_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->timelapse_warning_code << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SLICE_PREDICTION_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->get_gcode_prediction_str() << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SLICE_WEIGHT_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->get_gcode_weight_str() << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OUTSIDE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->toolpath_outside << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SUPPORT_USED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_support_used << "\"/>\n"; stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << LABEL_OBJECT_ENABLED_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->is_label_object_enabled << "\"/>\n"; for (auto it = plate_data->objects_and_instances.begin(); it != plate_data->objects_and_instances.end(); it++) { int obj_id = it->first; int inst_id = it->second; int identify_id = 0; ModelObject* obj = NULL; ModelInstance* inst = NULL; if (obj_id >= model.objects.size()) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format("invalid object id %1%\n")%obj_id; continue; } obj = model.objects[obj_id]; if (obj && (inst_id >= obj->instances.size())) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__ << boost::format("invalid instance id %1%\n")%inst_id; continue; } inst = obj->instances[inst_id]; if (!inst->printable) continue; if (m_use_loaded_id && (inst->loaded_id > 0)) { identify_id = inst->loaded_id; if (identify_id & 0xFF000000) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", identify_id %1%, too big, limit the high bits to 0\n") % identify_id; identify_id = identify_id & 0x00FFFFFF; } } else identify_id = inst->id().id; bool skipped = std::find(plate_data->skipped_objects.begin(), plate_data->skipped_objects.end(), identify_id) != plate_data->skipped_objects.end(); stream << " <" << OBJECT_TAG << " " << IDENTIFYID_ATTR << "=\"" << std::to_string(identify_id) << "\" " << NAME_ATTR << "=\"" << xml_escape(obj->name) << "\" " << SKIPPED_ATTR << "=\"" << (skipped ? "true" : "false") << "\" />\n"; } for (auto it = plate_data->slice_filaments_info.begin(); it != plate_data->slice_filaments_info.end(); it++) { stream << " <" << FILAMENT_TAG << " " << FILAMENT_ID_TAG << "=\"" << std::to_string(it->id + 1) << "\" " << FILAMENT_TRAY_INFO_ID_TAG <<"=\""<< it->filament_id <<"\" " << FILAMENT_TYPE_TAG << "=\"" << it->type << "\" " << FILAMENT_COLOR_TAG << "=\"" << it->color << "\" " << FILAMENT_USED_M_TAG << "=\"" << it->used_m << "\" " << FILAMENT_USED_G_TAG << "=\"" << it->used_g << "\" />\n"; } for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) { stream << " <" << SLICE_WARNING_TAG << " msg=\"" << it->msg << "\" level=\"" << std::to_string(it->level) << "\" error_code =\"" << it->error_code << "\" />\n"; } stream << " \n"; } } stream << "\n"; std::string out = stream.str(); if (!mz_zip_writer_add_mem(&archive, SLICE_INFO_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add model config file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", store slice-info to 3mf, length %1%, failed\n") % out.length(); return false; } return true; } bool _BBS_3MF_Exporter::_add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn) { bool result = true; bool cb_cancel = false; PlateDataPtrs plate_data_list2; for (unsigned int i = 0; i < (unsigned int)plate_data_list.size(); ++i) { if (proFn) { proFn(EXPORT_STAGE_ADD_GCODE, i, plate_data_list.size(), cb_cancel); if (cb_cancel) return false; } PlateData* plate_data = plate_data_list[i]; if (!plate_data->gcode_file.empty() && plate_data->is_sliced_valid && boost::filesystem::exists(plate_data->gcode_file)) { plate_data_list2.push_back(plate_data); } } boost::mutex mutex; tbb::parallel_for(tbb::blocked_range(0, plate_data_list2.size(), 1), [this, &plate_data_list2, &root_archive = archive, &mutex, &result](const tbb::blocked_range& range) { for (int i = range.begin(); i < range.end(); ++i) { PlateData* plate_data = plate_data_list2[i]; auto src_gcode_file = plate_data->gcode_file; std::string gcode_in_3mf = (boost::format(GCODE_FILE_FORMAT) % (plate_data->plate_index + 1)).str(); plate_data->gcode_file = gcode_in_3mf; mz_zip_archive archive; mz_zip_writer_staged_context context; mz_zip_zero_struct(&archive); mz_zip_writer_init_heap(&archive, 0, 1024 * 1024); { mz_zip_writer_add_staged_open(&archive, &context, gcode_in_3mf.c_str(), m_zip64 ? (uint64_t(1) << 30) * 16 : (uint64_t(1) << 32) - 1, nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0); boost::filesystem::path src_gcode_path(src_gcode_file); if (!boost::filesystem::exists(src_gcode_path)) { BOOST_LOG_TRIVIAL(error) << "Gcode is missing, filename = " << src_gcode_file; result = false; } boost::filesystem::ifstream ifs(src_gcode_file, std::ios::binary); std::string buf(64 * 1024, 0); while (ifs) { ifs.read(buf.data(), buf.size()); mz_zip_writer_add_staged_data(&context, buf.data(), ifs.gcount()); } mz_zip_writer_add_staged_finish(&context); } void *ppBuf; size_t pSize; mz_zip_writer_finalize_heap_archive(&archive, &ppBuf, &pSize); mz_zip_writer_end(&archive); mz_zip_zero_struct(&archive); mz_zip_reader_init_mem(&archive, ppBuf, pSize, 0); { boost::unique_lock l(mutex); mz_zip_writer_add_from_zip_reader(&root_archive, &archive, 0); } mz_zip_reader_end(&archive); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", store %1% to 3mf %2%\n") % src_gcode_file % gcode_in_3mf; } }); return result; } bool _BBS_3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config) { //BBS: add plate tree related logic std::string out = ""; bool has_custom_gcode = false; pt::ptree tree; pt::ptree& main_tree = tree.add("custom_gcodes_per_layer", ""); for (auto custom_gcodes : model.plates_custom_gcodes) { has_custom_gcode = true; pt::ptree& plate_tree = main_tree.add("plate", ""); pt::ptree& plate_idx_tree = plate_tree.add("plate_info", ""); plate_idx_tree.put(".id", custom_gcodes.first + 1); // store data of custom_gcode_per_print_z for (const CustomGCode::Item& code : custom_gcodes.second.gcodes) { pt::ptree& code_tree = plate_tree.add("layer", ""); code_tree.put(".top_z", code.print_z); code_tree.put(".type", static_cast(code.type)); code_tree.put(".extruder", code.extruder); code_tree.put(".color", code.color); code_tree.put(".extra", code.extra); //BBS std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : code.type == CustomGCode::PausePrint ? config->opt_string("machine_pause_gcode") : code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; code_tree.put(".gcode", gcode); } pt::ptree& mode_tree = plate_tree.add("mode", ""); // store mode of a custom_gcode_per_print_z mode_tree.put(".value", custom_gcodes.second.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : custom_gcodes.second.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : CustomGCode::MultiExtruderMode); } if (has_custom_gcode) { std::ostringstream oss; boost::property_tree::write_xml(oss, tree); out = oss.str(); // Post processing("beautification") of the output string boost::replace_all(out, "><", ">\n<"); } if (!out.empty()) { if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { add_error("Unable to add custom Gcodes per print_z file to archive"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add custom Gcodes per print_z file to archive\n"); return false; } } return true; } bool _BBS_3MF_Exporter::_add_auxiliary_dir_to_archive(mz_zip_archive &archive, const std::string &aux_dir, PackingTemporaryData &data) { bool result = true; if (aux_dir.empty()) { //no accessory directories return result; } boost::filesystem::path dir = boost::filesystem::path(aux_dir); if (!boost::filesystem::exists(dir)) { //no accessory directories return result; } static std::string const nocomp_exts[] = {".png", ".jpg", ".mp4", ".jpeg"}; std::deque directories({dir}); int root_dir_len = dir.string().length() + 1; //boost file access while (!directories.empty()) { boost::system::error_code ec; boost::filesystem::directory_iterator iterator(directories.front(), ec); directories.pop_front(); if (ec) continue; for (; iterator != end(iterator); iterator.increment(ec)) { if (ec) break; auto dir_entry = *iterator; std::string src_file; std::string dst_in_3mf; if (boost::filesystem::is_directory(dir_entry.path(), ec)) { directories.push_back(dir_entry.path()); continue; } if (boost::filesystem::is_regular_file(dir_entry.path(), ec) && !m_skip_auxiliary) { src_file = dir_entry.path().string(); dst_in_3mf = dir_entry.path().string(); dst_in_3mf.replace(0, root_dir_len, AUXILIARY_DIR); std::replace(dst_in_3mf.begin(), dst_in_3mf.end(), '\\', '/'); if (_3MF_COVER_FILE.compare(1, _3MF_COVER_FILE.length() - 1, dst_in_3mf) == 0) { data._3mf_thumbnail = dst_in_3mf; } else if (m_thumbnail_small.compare(1, m_thumbnail_small.length() - 1, dst_in_3mf) == 0) { data._3mf_printer_thumbnail_small = dst_in_3mf; if (m_thumbnail_middle == m_thumbnail_small) data._3mf_printer_thumbnail_middle = dst_in_3mf; } else if (m_thumbnail_middle.compare(1, m_thumbnail_middle.length() - 1, dst_in_3mf) == 0) { data._3mf_printer_thumbnail_middle = dst_in_3mf; } result &= _add_file_to_archive(archive, dst_in_3mf, src_file); } } } return result; } // Perform conversions based on the config values available. //FIXME provide a version of PrusaSlicer that stored the project file (3MF). static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config) { if (! config.has("brim_object_gap")) { if (auto *opt_elephant_foot = config.option("elefant_foot_compensation", false); opt_elephant_foot) { // Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation. auto *opt_brim_separation = config.option("brim_object_gap", true); opt_brim_separation->value = opt_elephant_foot->value; } } } // backup backgroud thread to dispatch tasks and coperate with ui thread class _BBS_Backup_Manager { public: static _BBS_Backup_Manager& get() { static _BBS_Backup_Manager m; return m; } void set_post_callback(std::function c) { boost::lock_guard lock(m_mutex); m_post_callback = c; } void run_ui_tasks() { std::deque tasks; { boost::lock_guard lock(m_mutex); std::swap(tasks, m_ui_tasks); } for (auto& t : tasks) { process_ui_task(t); } } void push_object_gaurd(ModelObject& object) { m_gaurd_objects.push_back(std::make_pair(&object, 0)); } void pop_object_gaurd() { auto object = m_gaurd_objects.back(); m_gaurd_objects.pop_back(); if (object.second) add_object_mesh(*object.first); } void add_object_mesh(ModelObject& object) { for (auto& g : m_gaurd_objects) { if (g.first == &object) { ++g.second; return; } } // clone object auto model = object.get_model(); auto o = m_temp_model.add_object(object); int backup_id = model->get_object_backup_id(object); push_task({ AddObject, (size_t) backup_id, object.get_model()->get_backup_path(), o, 1 }); } void remove_object_mesh(ModelObject& object) { push_task({ RemoveObject, object.id().id, object.get_model()->get_backup_path() }); } void backup_soon() { boost::lock_guard lock(m_mutex); m_other_changes_backup = true; m_tasks.push_back({ Backup, 0, std::string(), nullptr, ++m_task_seq }); m_cond.notify_all(); } void remove_backup(Model& model, bool removeAll) { BOOST_LOG_TRIVIAL(info) << "remove_backup " << model.get_backup_path() << ", " << removeAll; std::deque canceled_tasks; boost::unique_lock lock(m_mutex); if (removeAll && model.is_need_backup()) { // running task may not be canceled for (auto & t : m_ui_tasks) canceled_tasks.push_back(t); for (auto & t : m_tasks) canceled_tasks.push_back(t); m_ui_tasks.clear(); m_tasks.clear(); } m_tasks.push_back({ RemoveBackup, model.id().id, model.get_backup_path(), nullptr, removeAll }); ++m_task_seq; if (model.is_need_backup()) { m_other_changes = false; m_other_changes_backup = false; } m_cond.notify_all(); lock.unlock(); for (auto& t : canceled_tasks) { process_ui_task(t, true); } } void set_interval(long n) { boost::lock_guard lock(m_mutex); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " entry, and last interval is: " << m_interval; m_next_backup -= boost::posix_time::seconds(m_interval); m_interval = n; m_next_backup += boost::posix_time::seconds(m_interval); m_cond.notify_all(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " exit, and new interval is: " << m_interval; } void put_other_changes() { BOOST_LOG_TRIVIAL(info) << "put_other_changes"; m_other_changes = true; m_other_changes_backup = true; } void clear_other_changes(bool backup) { if (backup) m_other_changes_backup = false; else m_other_changes = false; } bool has_other_changes(bool backup) { return backup ? m_other_changes_backup : m_other_changes; } private: enum TaskType { None, Backup, // this task is working as response in ui thread AddObject, RemoveObject, RemoveBackup, Exit }; struct Task { TaskType type; size_t id = 0; std::string path; ModelObject* object = nullptr; union { size_t delay = 0; // delay sequence, only last task is delayed size_t sequence; bool removeAll; }; friend bool operator==(Task const& l, Task const& r) { return l.type == r.type && l.id == r.id; } std::string to_string() const { constexpr char const *type_names[] = {"None", "Backup", "AddObject", "RemoveObject", "RemoveBackup", "Exit"}; std::ostringstream os; os << "{ type:" << type_names[type] << ", id:" << id << ", path:" << path << ", object:" << (object ? object->id().id : 0) << ", extra:" << delay << "}"; return os.str(); } }; struct timer { timer(char const * msg) : msg(msg), start(boost::posix_time::microsec_clock::universal_time()) { } ~timer() { #ifdef __WIN32__ auto end = boost::posix_time::microsec_clock::universal_time(); int duration = (int)(end - start).total_milliseconds(); char buf[20]; OutputDebugStringA(msg); OutputDebugStringA(": "); OutputDebugStringA(itoa(duration, buf, 10)); OutputDebugStringA("\n"); #endif } char const* msg; boost::posix_time::ptime start; }; private: _BBS_Backup_Manager() { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inital and interval = " << m_interval; m_next_backup = boost::get_system_time() + boost::posix_time::seconds(m_interval); boost::unique_lock lock(m_mutex); m_thread = std::move(boost::thread(boost::ref(*this))); } ~_BBS_Backup_Manager() { push_task({Exit}); m_thread.join(); } void push_task(Task const & t) { boost::unique_lock lock(m_mutex); if (t.delay && !m_tasks.empty() && m_tasks.back() == t) { auto t2 = m_tasks.back(); m_tasks.back() = t; m_tasks.back().delay = t2.delay + 1; m_cond.notify_all(); lock.unlock(); process_ui_task(t2); } else { m_tasks.push_back(t); ++m_task_seq; m_cond.notify_all(); } } void process_ui_task(Task& t, bool canceled = false) { switch (t.type) { case Backup: { if (canceled) break; std::function callback; boost::unique_lock lock(m_mutex); if (m_task_seq != t.sequence) { if (find(m_tasks.begin(), m_tasks.end(), Task{ Backup }) == m_tasks.end()) { t.sequence = ++m_task_seq; // may has pending tasks, retry later m_tasks.push_back(t); m_cond.notify_all(); } break; } callback = m_post_callback; lock.unlock(); { timer t("backup cost"); try { if (callback) callback(1); } catch (...) {} } m_other_changes_backup = false; break; } case AddObject: m_temp_model.delete_object(t.object); break; case RemoveBackup: if (t.removeAll) { try { boost::filesystem::remove(t.path + "/lock.txt"); boost::filesystem::remove_all(t.path); BOOST_LOG_TRIVIAL(info) << "process_ui_task: remove all of backup path " << t.path; } catch (std::exception &ex) { BOOST_LOG_TRIVIAL(error) << "process_ui_task: failed to remove backup path" << t.path << ": " << ex.what(); } } break; } } void process_task(Task& t) { switch (t.type) { case Backup: // do it in response break; case AddObject: { BOOST_LOG_TRIVIAL(info) << "process_task" << t.to_string(); { CNumericLocalesSetter locales_setter; _BBS_3MF_Exporter e; e.save_object_mesh(t.path, *t.object, (int) t.id); // response to delete cloned object } break; } case RemoveObject: { BOOST_LOG_TRIVIAL(info) << "process_task" << t.to_string(); boost::system::error_code ec; boost::filesystem::remove(t.path + "/mesh_" + boost::lexical_cast(t.id) + ".xml", ec); t.type = None; break; } case RemoveBackup: { BOOST_LOG_TRIVIAL(info) << "process_task" << t.to_string(); try { boost::system::error_code ec; boost::filesystem::remove(t.path + "/.3mf", ec); // We Saved with SplitModel now, so we can safe delete these sub models. boost::filesystem::remove_all(t.path + "/3D/Objects"); boost::filesystem::create_directory(t.path + "/3D/Objects"); } catch (...) {} } } } public: void operator()() { boost::unique_lock lock(m_mutex); while (true) { while (m_tasks.empty()) { if (m_interval > 0) m_cond.timed_wait(lock, m_next_backup); else m_cond.wait(lock); if (m_interval > 0 && boost::get_system_time() > m_next_backup) { m_tasks.push_back({ Backup, 0, std::string(), nullptr, ++m_task_seq }); m_next_backup += boost::posix_time::seconds(m_interval); // Maybe wakeup from power sleep if (m_next_backup < boost::get_system_time()) m_next_backup = boost::get_system_time() + boost::posix_time::seconds(m_interval); } } Task t = m_tasks.front(); if (t.type == Exit) break; if (t.object && t.delay) { if (!delay_task(t, lock)) continue; } m_tasks.pop_front(); auto callback = m_post_callback; lock.unlock(); process_task(t); lock.lock(); if (t.type > None) { m_ui_tasks.push_back(t); if (m_ui_tasks.size() == 1 && callback) callback(0); } } } bool delay_task(Task& t, boost::unique_lock & lock) { // delay last task for 3 seconds after last modify auto now = boost::get_system_time(); auto delay_expire = now + boost::posix_time::seconds(10); // must not delay over this time auto wait = now + boost::posix_time::seconds(3); while (true) { m_cond.timed_wait(lock, wait); // Only delay when it's the only-one task if (m_tasks.size() != 1 || m_tasks.front().delay == t.delay) break; t.delay = m_tasks.front().delay; now = boost::get_system_time(); if (now >= delay_expire) break; wait = now + boost::posix_time::seconds(3); if (wait > delay_expire) wait = delay_expire; }; // task maybe canceled if (m_tasks.empty()) return false; t = m_tasks.front(); return true; } private: boost::mutex m_mutex; boost::condition_variable m_cond; std::deque m_tasks; std::deque m_ui_tasks; size_t m_task_seq = 0; // param 0: should call run_ui_tasks // param 1: should backup current project std::function m_post_callback; long m_interval = 1 * 60; boost::system_time m_next_backup; Model m_temp_model; // visit only in main thread bool m_other_changes = false; // visit only in main thread bool m_other_changes_backup = false; // visit only in main thread std::vector> m_gaurd_objects; boost::thread m_thread; }; //BBS: add plate data list related logic bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, PlateDataPtrs* plate_data_list, std::vector* project_presets, bool* is_bbl_3mf, Semver* file_version, Import3mfProgressFn proFn, LoadStrategy strategy, BBLProject *project, int plate_id) { if (path == nullptr || config == nullptr || model == nullptr) return false; // All import should use "C" locales for number formatting. CNumericLocalesSetter locales_setter; _BBS_3MF_Importer importer; bool res = importer.load_model_from_file(path, *model, *plate_data_list, *project_presets, *config, *config_substitutions, strategy, *is_bbl_3mf, *file_version, proFn, project, plate_id); importer.log_errors(); //BBS: remove legacy project logic currently //handle_legacy_project_loaded(importer.version(), *config); return res; } std::string bbs_3mf_get_thumbnail(const char *path) { _BBS_3MF_Importer importer; std::string data; bool res = importer.get_thumbnail(path, data); if (!res) importer.log_errors(); return data; } bool load_gcode_3mf_from_stream(std::istream &data, DynamicPrintConfig *config, Model *model, PlateDataPtrs *plate_data_list, Semver *file_version) { CNumericLocalesSetter locales_setter; _BBS_3MF_Importer importer; bool res = importer.load_gcode_3mf_from_stream(data, *model, *plate_data_list, *config, *file_version); importer.log_errors(); return res; } bool store_bbs_3mf(StoreParams& store_params) { // All export should use "C" locales for number formatting. CNumericLocalesSetter locales_setter; if (store_params.path == nullptr || store_params.model == nullptr) return false; _BBS_3MF_Exporter exporter; bool res = exporter.save_model_to_file(store_params); if (!res) exporter.log_errors(); return res; } //BBS: release plate data list void release_PlateData_list(PlateDataPtrs& plate_data_list) { //clear for (unsigned int i = 0; i < plate_data_list.size(); i++) { delete plate_data_list[i]; } plate_data_list.clear(); return; } // backup interface void save_object_mesh(ModelObject& object) { if (!object.get_model() || !object.get_model()->is_need_backup()) return; if (object.volumes.empty() || object.instances.empty()) return; _BBS_Backup_Manager::get().add_object_mesh(object); } void delete_object_mesh(ModelObject& object) { // not really remove // _BBS_Backup_Manager::get().remove_object_mesh(object); } void backup_soon() { _BBS_Backup_Manager::get().backup_soon(); } void remove_backup(Model& model, bool removeAll) { _BBS_Backup_Manager::get().remove_backup(model, removeAll); } void set_backup_interval(long interval) { _BBS_Backup_Manager::get().set_interval(interval); } void set_backup_callback(std::function callback) { _BBS_Backup_Manager::get().set_post_callback(callback); } void run_backup_ui_tasks() { _BBS_Backup_Manager::get().run_ui_tasks(); } bool has_restore_data(std::string & path, std::string& origin) { if (path.empty()) { origin = ""; return false; } if (boost::filesystem::exists(path + "/lock.txt")) { std::string pid; boost::filesystem::load_string_file(path + "/lock.txt", pid); try { if (get_process_name(boost::lexical_cast(pid)) == get_process_name(0)) { origin = ""; return false; } } catch (...) { return false; } } std::string file3mf = path + "/.3mf"; if (!boost::filesystem::exists(file3mf)) return false; try { if (boost::filesystem::exists(path + "/origin.txt")) boost::filesystem::load_string_file(path + "/origin.txt", origin); } catch (...) { } path = file3mf; return true; } void put_other_changes() { _BBS_Backup_Manager::get().put_other_changes(); } void clear_other_changes(bool backup) { _BBS_Backup_Manager::get().clear_other_changes(backup); } bool has_other_changes(bool backup) { return _BBS_Backup_Manager::get().has_other_changes(backup); } SaveObjectGaurd::SaveObjectGaurd(ModelObject& object) { _BBS_Backup_Manager::get().push_object_gaurd(object); } SaveObjectGaurd::~SaveObjectGaurd() { _BBS_Backup_Manager::get().pop_object_gaurd(); } } // namespace Slic3r