Files
OrcaSlicer-KX/src/libslic3r/Format/bbs_3mf.cpp
lane.wei af0b3ddb28 Update the codes to 01.01.00.10 for the formal release
1. first formal version of macos
2. add the bambu networking plugin install logic
3. auto compute the wipe volume when filament change
4. add the logic of wiping into support
5. refine the GUI layout and icons, improve the gui apperance in lots of
   small places
6. serveral improve to support
7. support AMS auto-mapping
8. disable lots of unstable features: such as params table, media file download, HMS
9. fix serveral kinds of bugs
10. update the document of building
11. ...
2022-07-22 20:35:34 +08:00

6173 lines
279 KiB
C++

#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 <limits>
#include <stdexcept>
#include <iomanip>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/qi_int.hpp>
#include <boost/log/trivial.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/foreach.hpp>
#include <openssl/md5.h>
namespace pt = boost::property_tree;
#include <tbb/parallel_reduce.h>
#include <expat.h>
#include <Eigen/Dense>
#include "miniz_extension.hpp"
#include "nlohmann/json.hpp"
#include <fast_float/fast_float.h>
// 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_DESIGNER_TAG = "Designer";
const std::string BBL_DESIGNER_USER_ID_TAG = "DesignerUserId";
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_LICENSE_TAG = "License";
const std::string BBL_REGION_TAG = "Region";
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 THUMBNAILS_DIR = ".thumbnails";
const std::string PRINTER_THUMBNAIL_SMALL_FILE = "thumbnail_small.png";
const std::string PRINTER_THUMBNAIL_MIDDLE_FILE = "thumbnail_middle.png";
const std::string _3MF_COVER_FILE = "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 LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_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 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* 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 *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* 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";
// BBS: encrypt
static constexpr const char* RELATIONSHIP_TAG = "Relationship";
static constexpr const char* PUUID_ATTR = "p:uuid";
static constexpr const char* PPATH_ATTR = "p:path";
static constexpr const char* OBJECT_UUID_SUFFIX = "-41cb-4c03-9d28-80fed5dfa1dc";
static constexpr const char* BUILD_UUID = "d8eb061-b1ec-4553-aec9-835e5b724bb4";
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* 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 = "bbs: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* GCODE_FILE_ATTR = "gcode_file";
static constexpr const char* THUMBNAIL_FILE_ATTR = "thumbnail_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* PLATERID_ATTR = "plater_id";
static constexpr const char* PLATE_IDX_ATTR = "index";
static constexpr const char* SLICE_PREDICTION_ATTR = "prediction";
static constexpr const char* SLICE_WEIGHT_ATTR = "weight";
static constexpr const char* OUTSIDE_ATTR = "outside";
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_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 <typename T>
struct hex_wrap
{
T t;
};
namespace std {
template <class _Elem, class _Traits, class _Arg>
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;
}
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<std::string> 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<std::string> 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<float> m_filament_diameters = result->filament_diameters;
std::vector<float> 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<double, double> 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.volumes_per_extruder.begin(); it != ps.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_m = used_filament_m;
info.used_g = used_filament_g;
slice_filaments_info.push_back(info);
}
}
//! 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<std::string> 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<std::string, int> 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<Component> ComponentsList;
struct Geometry
{
std::vector<Vec3f> vertices;
std::vector<Vec3i> triangles;
std::vector<std::string> custom_supports;
std::vector<std::string> custom_seam;
std::vector<std::string> mmu_segmentation;
// BBS
std::vector<std::string> 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;
//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;
};
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<Metadata> 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;
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<VolumeMetadata> VolumeMetadataList;
MetadataList metadata;
VolumeMetadataList volumes;
};
// Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects.
//typedef std::pair<std::string, int> Id; // BBS: encrypt
typedef std::map<Id, CurrentObject> IdToCurrentObjectMap;
typedef std::map<int, std::string> IndexToPathMap;
typedef std::map<Id, int> IdToModelObjectMap;
//typedef std::map<Id, ComponentsList> IdToAliasesMap;
typedef std::vector<Instance> InstancesList;
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
//typedef std::map<Id, Geometry> IdToGeometryMap;
/*typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;*/
// Version of the 3mf file
unsigned int m_version;
bool m_check_version;
bool m_load_aux;
bool m_load_config;
// backup & restore
bool m_load_restore;
std::string m_backup_path;
std::string m_origin_file;
// Semantic version of Bambu Studio, that generated this 3MF.
boost::optional<Semver> 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_user_id;
std::string m_designer_cover;
ModelInfo model_info;
BBLProject project_info;
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;
/*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::vector<std::string> m_sub_model_paths;
//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;
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<Preset*>& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool& is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, BBLProject *project = nullptr);
bool get_thumbnail(const std::string &filename, std::string &data);
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<Preset*>& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Import3mfProgressFn proFn = nullptr,
BBLProject* project = nullptr);
bool _extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function<bool (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)>, 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_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<Preset*>&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_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();
//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();
// 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<Id> &sub_objects, Id object_id, IdToCurrentObjectMap current_objects);
bool _generate_volumes_new(ModelObject& object, const std::vector<Id> &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<int, PlateData*>::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<Preset*>& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool& is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn, BBLProject *project)
{
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_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;
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<std::string>(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);
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");
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_path.empty()) {
return _extract_from_archive(archive, m_thumbnail_path, [&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);
});
}
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<Preset*>& project_presets,
DynamicPrintConfig& config,
ConfigSubstitutionContext& config_substitutions,
Import3mfProgressFn proFn,
BBLProject *project)
{
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\n");
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");
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;
for (auto path : m_sub_model_paths) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_READ_FILES\n");
if (proFn) {
proFn(IMPORT_STAGE_READ_FILES, ++index, 3 + m_sub_model_paths.size(), cb_cancel);
if (cb_cancel)
return false;
}
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();
}
// 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<ModelDesignInfo>();
m_model->design_info->DesignerUserId = m_designer_user_id;
m_model->design_info->Designer = m_designer;
}
m_model->model_info = std::make_shared<ModelInfo>();
m_model->model_info->load(model_info);
//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 file %1%\n")%name;
//BBS: disable adaptive layer height related file in 3MF
/* if (boost::algorithm::iequals(name, 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);
}
//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);
}
}
}
lock.close();
if (m_version == 0) {
// if the 3mf was not produced by BambuStudio and there is more than one instance,
// split the object in as many objects as instances
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<Id> 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];
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;
}
}
}
}
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;
}
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<Id> 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];
volumes.emplace_back(object_id.second);
}
// select as volumes
volumes_ptr = &volumes;
}
if (!_generate_volumes_new(*model_object, object_id_list, *volumes_ptr, config_substitutions))
return false;
}
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<ConfigOptionStrings>("filament_settings_id");
int max_filament_id = filament_ids_opt ? filament_ids_opt->size() : std::numeric_limits<int>::max();
for (ModelObject* mo : m_model->objects) {
const ConfigOptionInt* extruder_opt = dynamic_cast<const ConfigOptionInt*>(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<const ConfigOptionInt*>(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%\n")%m_plater_data.size();
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<int, PlateData*>::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]->objects_and_instances = it->second->objects_and_instances;
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]->slice_filaments_info = it->second->slice_filaments_info;
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]->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;
it++;
}
//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<bool (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)> 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<std::string> 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];
::sprintf(error_buf, "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];
::sprintf(error_buf, "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];
::sprintf(error_buf, "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_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<std::string, std::string> 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<Preset*>&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<std::string, std::string> 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<Semver> 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)
{
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<std::string> objects;
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
for (const std::string& object : objects) {
std::vector<std::string> 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<std::string> 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<std::string> 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<coordf_t> 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<int>("<xmlattr>.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<double>("<xmlattr>.min_z");
double max_z = range_tree.get<double>("<xmlattr>.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<std::string>("<xmlattr>.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<std::string> 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<std::string> 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<std::string> 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<std::string> object_data_points;
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
std::vector<sla::SupportPoint> sla_support_points;
if (version == 0) {
for (unsigned int i=0; i<object_data_points.size(); i+=3)
sla_support_points.emplace_back(float(std::atof(object_data_points[i+0].c_str())),
float(std::atof(object_data_points[i+1].c_str())),
float(std::atof(object_data_points[i+2].c_str())),
0.4f,
false);
}
if (version == 1) {
for (unsigned int i=0; i<object_data_points.size(); i+=5)
sla_support_points.emplace_back(float(std::atof(object_data_points[i+0].c_str())),
float(std::atof(object_data_points[i+1].c_str())),
float(std::atof(object_data_points[i+2].c_str())),
float(std::atof(object_data_points[i+3].c_str())),
//FIXME storing boolean as 0 / 1 and importing it as float.
std::abs(std::atof(object_data_points[i+4].c_str()) - 1.) < EPSILON);
}
if (!sla_support_points.empty())
m_sla_support_points.insert({ object_id, sla_support_points });
}
}
}
void _BBS_3MF_Importer::_extract_sla_drain_holes_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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<object_data_points.size(); i+=8)
sla_drain_holes.emplace_back(Vec3f{float(std::atof(object_data_points[i+0].c_str())),
float(std::atof(object_data_points[i+1].c_str())),
float(std::atof(object_data_points[i+2].c_str()))},
Vec3f{float(std::atof(object_data_points[i+3].c_str())),
float(std::atof(object_data_points[i+4].c_str())),
float(std::atof(object_data_points[i+5].c_str()))},
float(std::atof(object_data_points[i+6].c_str())),
float(std::atof(object_data_points[i+7].c_str())));
}
// The holes are saved elevated above the mesh and deeper (bad idea indeed).
// This is retained for compatibility.
// Place the hole to the mesh and make it shallower to compensate.
// The offset is 1 mm above the mesh.
for (sla::DrainHole& hole : sla_drain_holes) {
hole.pos += hole.normal.normalized();
hole.height -= 1.f;
}
if (!sla_drain_holes.empty())
m_sla_drain_holes.insert({ object_id, sla_drain_holes });
}
}
}*/
void _BBS_3MF_Importer::_extract_custom_gcode_per_print_z_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 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;
pt::ptree code_tree = main_tree.front().second;
m_model->custom_gcode_per_print_z.gcodes.clear();
for (const auto& code : code_tree) {
if (code.first == "mode") {
pt::ptree tree = code.second;
std::string mode = tree.get<std::string>("<xmlattr>.value");
m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle :
CustomGCode::Mode::MultiExtruder;
}
if (code.first != "layer")
continue;
pt::ptree tree = code.second;
double print_z = tree.get<double> ("<xmlattr>.top_z" );
int extruder = tree.get<int> ("<xmlattr>.extruder");
std::string color = tree.get<std::string> ("<xmlattr>.color" );
CustomGCode::Type type;
std::string extra;
pt::ptree attr_tree = tree.find("<xmlattr>")->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<std::string> ("<xmlattr>.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<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
extra = tree.get<std::string>("<xmlattr>.extra");
}
m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ;
}
}
}
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(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(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(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);
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_version == 0) {
// 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);
}
return true;
}
bool _BBS_3MF_Importer::_handle_end_object()
{
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) {
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 (need_replace)
{
for (int index = 0; index < m_curr_object->components.size(); index++)
{
int temp_id = (index + 1) << 16 | backup_id;
Component& component = m_curr_object->components[index];
std::string new_path = component.object_id.first;
Id new_id = std::make_pair(new_path, temp_id);
IdToCurrentObjectMap::iterator current_object = m_current_objects.find(component.object_id);
if (current_object != m_current_objects.end()) {
CurrentObject new_object;
new_object.geometry = std::move(current_object->second.geometry);
new_object.id = temp_id;
new_object.model_object_idx = current_object->second.model_object_idx;
new_object.name = current_object->second.name;
new_object.uuid = current_object->second.uuid;
m_current_objects.erase(current_object);
m_current_objects.insert({ new_id, std::move(new_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 = temp_id;
}
}
}
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_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)
{
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, 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 _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 == "Application") {
// Generator application of the 3MF.
// SLIC3R_APP_KEY - SLIC3R_VERSION
if (boost::starts_with(m_curr_characters, "BambuStudio-"))
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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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)
{
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 == LOCK_ATTR)
{
std::istringstream(value) >> std::boolalpha >> m_curr_plater->locked;
}
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 == 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 == OBJECT_ID_ATTR)
{
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<int, PlateData*>::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;
}
}
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);
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());
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_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("don't find plater 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("don't find plater created before");
return false;
}
//do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_end_config_plater_instance()
{
if (!m_curr_plater)
{
add_error("don't find plater 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;
return true;
}
m_curr_plater->objects_and_instances.emplace_back(m_curr_instance.object_id, m_curr_instance.instance_id);
m_curr_instance.object_id = m_curr_instance.instance_id = -1;
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)
{
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;
}
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")) {
m_thumbnail_path = path;
}
return true;
}
void _BBS_3MF_Importer::_generate_current_object_list(std::vector<Id> &sub_objects, Id object_id, IdToCurrentObjectMap current_objects)
{
std::list<Id> id_list;
id_list.push_back(object_id);
while (!id_list.empty())
{
Id current_id = id_list.front();
id_list.pop_front();
IdToCurrentObjectMap::iterator current_object = current_objects.find(current_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(comp.object_id);
}
}
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);
}
}
}
}
bool _BBS_3MF_Importer::_generate_volumes_new(ModelObject& object, const std::vector<Id> &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
Id object_id = sub_objects[index];
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);
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;
if (volume_data)
{
// 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;
}
}
}
else {
//create a volume_data
volume_data = &default_volume_data;
}
// 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);
if (m_version == 0) {
// 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
if (m_load_config) {
volume->supported_facets.reserve(triangles_count);
volume->seam_facets.reserve(triangles_count);
volume->mmu_segmentation_facets.reserve(triangles_count);
for (size_t i=0; i<triangles_count; ++i) {
assert(i < sub_object->geometry.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->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 if (metadata.key == MATRIX_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_version == 0) {
// 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; i<triangles_count; ++i) {
size_t index = volume_data.first_triangle_id + i;
assert(index < geometry.custom_supports.size());
assert(index < geometry.custom_seam.size());
assert(index < geometry.mmu_segmentation.size());
if (! geometry.custom_supports[index].empty())
volume->supported_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);
}
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<const ModelVolume*, Offsets> VolumeToOffsetsMap;
typedef std::map<const ModelVolume*, int> VolumeToObjectIDMap;
struct ObjectData
{
ModelObject const * object;
int backup_id;
VolumeToObjectIDMap volumes_objectID;
};
typedef std::vector<BuildItem> BuildItemsList;
typedef std::map<int, ObjectData> IdToObjectDataMap;
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
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<Preset*>& project_presets, const DynamicPrintConfig* config, bool fullpath_sources, const std::vector<ThumbnailData*>& 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<Preset*>& project_presets,
const DynamicPrintConfig* config,
const std::vector<ThumbnailData*>& thumbnail_data,
Export3mfProgressFn proFn,
const std::vector<ThumbnailData*>& calibration_data,
const std::vector<PlateBBoxData*>& 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, int index);
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<std::string> const &targets = {},
std::vector<std::string> 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, IdToObjectDataMap& objects_data, Export3mfProgressFn proFn = nullptr, BBLProject* project = nullptr) const;
bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int object_id, ModelObject const & object, unsigned int backup_id, VolumeToObjectIDMap& volumes_objectID) const;
//BBS: change volume to seperate objects
bool _add_mesh_to_object_stream(std::function<bool(std::string&, bool)> const& flush, ModelObject const & object, unsigned int backup_id, VolumeToObjectIDMap& volumes_objectID, unsigned int& obj_idx) 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<Preset*> project_presets);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const IdToObjectDataMap &objects_data, int export_plate_idx = -1);
bool _add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list);
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;
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.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 (!(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/%s_%d.model") % object.name % obj_id;
std::string filepath = temp_path + "/" + filename.str();
if (!open_zip_writer(&archive, filepath)) {
add_error("Unable to open the file");
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to open the file\n");
return false;
}
IdToObjectDataMap objects_data;
objects_data.insert({obj_id, {&object, obj_id}});
_add_model_file_to_archive(filename.str(), archive, model, objects_data);
mz_zip_writer_finalize_archive(&archive);
close_zip_writer(&archive);
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<Preset*>& project_presets,
const DynamicPrintConfig* config,
const std::vector<ThumbnailData*>& thumbnail_data,
Export3mfProgressFn proFn,
const std::vector<ThumbnailData*>& calibration_data,
const std::vector<PlateBBoxData*>& 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%\n")%m_skip_static %m_save_gcode;
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");
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::filesystem::remove(*filename);
}
} 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%\n") % thumbnail_data.size();
//BBS: add thumbnail for each plate
if (!m_skip_static && thumbnail_data.size() > 0) {
// Adds the file Metadata/thumbnail.png.
for (unsigned int index = 0; index < thumbnail_data.size(); index++)
{
if (proFn) {
proFn(EXPORT_STAGE_ADD_THUMBNAILS, index, thumbnail_data.size(), cb_cancel);
if (cb_cancel)
return false;
}
if (thumbnail_data[index]->is_valid())
{
if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data[index], index)) {
return false;
}
}
}
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",before add calibration data, 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);
boost::filesystem::remove(filename);
return false;
}
}
// 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);
boost::filesystem::remove(filename);
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.
IdToObjectDataMap 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!
// BBS: don't need to save layer_height_profile because we calculate when slicing every time.
/*
if (!_add_layer_height_profile_file_to_archive(archive, model)) {
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)) {
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)) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_model_config_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)) {
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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n";
stream << " <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n";
stream << " <Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/>\n";
stream << " <Default Extension=\"png\" ContentType=\"image/png\"/>\n";
stream << " <Default Extension=\"gcode\" ContentType=\"text/x.gcode\"/>\n";
stream << "</Types>";
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, 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("Metadata/plate_%1%.png") % (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_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<std::string> const &targets, std::vector<std::string> const &types, PackingTemporaryData data, int export_plate_idx) const
{
std::stringstream stream;
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n";
if (from.empty()) {
stream << " <Relationship Target=\"/" << MODEL_FILE << "\" Id=\"rel-1\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\"/>\n";
if (data._3mf_thumbnail.empty()) {
if (export_plate_idx < 0) {
stream << " <Relationship Target=\"/" << THUMBNAIL_FILE
<< "\" Id=\"rel-2\" Type=\"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail\"/>\n";
} else {
std::string thumbnail_file_str = (boost::format("Metadata/plate_%1%.png") % (export_plate_idx + 1)).str();
stream << " <Relationship Target=\"/" << thumbnail_file_str
<< "\" Id=\"rel-2\" Type=\"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail\"/>\n";
}
} else {
stream << " <Relationship Target=\"/" << data._3mf_thumbnail
<< "\" Id=\"rel-2\" Type=\"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail\"/>\n";
}
if (!data._3mf_printer_thumbnail_middle.empty()) {
stream << " <Relationship Target=\"/" << data._3mf_printer_thumbnail_middle
<< "\" Id=\"rel-4\" Type=\"http://schemas.bambulab.com/package/2021/cover-thumbnail-middle\"/>\n";
}
if (!data._3mf_printer_thumbnail_small.empty())
stream << " <Relationship Target=\"/" << data._3mf_printer_thumbnail_small
<< "\" Id=\"rel-5\" Type=\"http://schemas.bambulab.com/package/2021/cover-thumbnail-small\"/>\n";
}
else if (targets.empty()) {
return false;
}
else {
int i = 0;
for (auto & path : targets) {
for (auto & type : types)
stream << " <Relationship Target=\"/" << xml_escape(path) << "\" Id=\"rel-" << boost::to_string(++i) << "\" Type=\"" << type << "\"/>\n";
}
}
stream << "</Relationships>";
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<float>::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, IdToObjectDataMap& objects_data, Export3mfProgressFn proFn, BBLProject* project) const
{
bool sub_model = !objects_data.empty();
bool write_object = sub_model || !m_split_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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\"";
if (m_production_ext)
stream << " xmlns:p=\"http://schemas.microsoft.com/3dmanufacturing/production/2015/06\" requiredextensions=\"p\"";
stream << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBS_3MF_VERSION << "\">" << VERSION_BBS_3MF << "</" << METADATA_TAG << ">\n";
//TODO: currently use version 0, no need to load&&save this string
/*if (model.is_fdm_support_painted())
stream << " <" << METADATA_TAG << " name=\"" << BBS_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
if (model.is_seam_painted())
stream << " <" << METADATA_TAG << " name=\"" << BBS_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
if (model.is_mm_painted())
stream << " <" << METADATA_TAG << " name=\"" << BBS_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";*/
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 (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;
BOOST_LOG_TRIVIAL(trace) << "design_info, save_3mf found designer_cover = " << design_cover;
}
if (project) {
model_id = project->project_model_id;
region_code = project->project_country_code;
}
stream << " <" << METADATA_TAG << " name=\"" << BBL_MODEL_NAME_TAG << "\">" << name << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESIGNER_TAG << "\">" << user_name << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESIGNER_USER_ID_TAG << "\">" << user_id << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESIGNER_COVER_FILE_TAG << "\">" << design_cover << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESCRIPTION_TAG << "\">" << description << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_COPYRIGHT_TAG << "\">" << copyright << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_LICENSE_TAG << "\">" << license << "</" << METADATA_TAG << ">\n";
/* save model info */
if (!model_id.empty()) {
stream << " <" << METADATA_TAG << " name=\"" << BBL_MODEL_ID_TAG << "\">" << model_id << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_REGION_TAG << "\">" << region_code << "</" << METADATA_TAG << ">\n";
}
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);
stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "</" << METADATA_TAG << ">\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;
bool cb_cancel = false;
int obj_idx = 0;
std::vector<unsigned int> object_ids;
std::vector<std::string> 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, obj_idx, model.objects.size(), cb_cancel);
if (cb_cancel)
return false;
obj_idx++;
}
if (obj == nullptr)
continue;
// Index of an object in the 3MF file corresponding to the 1st instance of a ModelObject.
IdToObjectDataMap::iterator object_it = objects_data.begin();
if (!sub_model) {
// For backup, use backup id as object id
int backup_id = const_cast<Model&>(model).get_object_backup_id(*obj);
if (m_skip_static) object_id = backup_id;
object_it = objects_data.insert({ (int) object_id, {obj, backup_id} }).first;
}
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->first, *obj, object_it->second.backup_id, object_it->second.volumes_objectID)) {
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;
object_ids.push_back(object_id);
unsigned int curr_id;
if (m_skip_static)
curr_id = object_id;
else
curr_id = object_id + obj->volumes.size();
object_id = object_id + obj->volumes.size() + 1;
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);
auto filename = boost::format("3D/Objects/%s_%d.model") % obj->name % object_it->second.backup_id;
if (count == 0)
object_paths.push_back(filename.str());
build_items.emplace_back(m_split_model ? "/" + filename.str() : "", curr_id, t, instance->printable);
count++;
}
}
}
{
std::stringstream stream;
reset_stream(stream);
stream << " </" << RESOURCES_TAG << ">\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 << "</" << MODEL_TAG << ">\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_skip_static) {
for (ModelObject* obj : model.objects) {
if (obj == nullptr)
continue;
int object_id = obj->get_backup_id();
auto & volumes_objectID = objects_data.find(object_id)->second.volumes_objectID;
//unsigned int vertices_count = 0;
//unsigned int triangles_count = 0;
unsigned int volume_count = 0;
for (ModelVolume* volume : obj->volumes) {
if (volume == nullptr)
continue;
VolumeToObjectIDMap::iterator volume_it = volumes_objectID.insert({ volume, (object_id | ((volume_count+1)<<16)) }).first;
volume_count++;
//const indexed_triangle_set &its = volume->mesh().its;
//vertices_count += (int)its.vertices.size();
//volume_it->second.first_triangle_id = triangles_count;
//triangles_count += (int)its.indices.size();
//volume_it->second.last_triangle_id = triangles_count - 1;
}
}
return true;
}
{
boost::mutex mutex;
tbb::parallel_for(tbb::blocked_range<size_t>(0, objects_data.size(), 1), [this, &mutex, &model, &object_ids, &objects_data, &object_paths, main = &archive, project](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
auto iter = objects_data.find(object_ids[i]);
IdToObjectDataMap 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);
_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, unsigned int object_id, ModelObject const & object, unsigned int backup_id, VolumeToObjectIDMap& volumes_objectID) const
{
std::stringstream stream;
reset_stream(stream);
unsigned int id = 0;
unsigned int volume_start_id = object_id;
for (const ModelInstance* instance : object.instances) {
assert(instance != nullptr);
if (instance == nullptr)
continue;
//stream << " <" << OBJECT_TAG << " id=\"" << instance_id;
//if (m_production_ext)
// stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap<boost::uint32_t>{(boost::uint32_t)backup_id} << OBJECT_UUID_SUFFIX;
//stream << "\" type=\"model\">\n";
if (id == 0) {
std::string buf = stream.str();
reset_stream(stream);
// 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 ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) ||
! _add_mesh_to_object_stream(flush, object, backup_id, volumes_objectID, volume_start_id)) {
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;
}
}
stream << " <" << OBJECT_TAG << " id=\"" << volume_start_id+id;
if ((id == 0) && m_production_ext)
stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap<boost::uint32_t>{(boost::uint32_t)backup_id} << OBJECT_UUID_SUFFIX;
stream << "\" type=\"model\">\n";
stream << " <" << COMPONENTS_TAG << ">\n";
if (id == 0) {
if (m_from_backup_save) {
for (unsigned int index = 1; index <= object.volumes.size(); index ++) {
unsigned int ref_id = object_id | (index << 16);
stream << " <" << COMPONENT_TAG << " objectid=\"" << ref_id << "\"/>\n";
}
}
else {
for (unsigned int index = object_id; index < volume_start_id; index ++)
stream << " <" << COMPONENT_TAG << " objectid=\"" << index << "\"/>\n";
}
}
else {
stream << " <" << COMPONENT_TAG << " objectid=\"" << volume_start_id << "\"/>\n";
}
stream << " </" << COMPONENTS_TAG << ">\n";
stream << " </" << OBJECT_TAG << ">\n";
++id;
}
std::string buf = stream.str();
return buf.empty() || mz_zip_writer_add_staged_data(&context, buf.data(), buf.size());
}
#if EXPORT_3MF_USE_SPIRIT_KARMA_FP
template <typename Num>
struct coordinate_policy_fixed : boost::spirit::karma::real_policies<Num>
{
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<Num>::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 <typename Num>
struct coordinate_policy_scientific : coordinate_policy_fixed<Num>
{
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<float, coordinate_policy_fixed<float>>;
using coordinate_type_scientific = boost::spirit::karma::real_generator<float, coordinate_policy_scientific<float>>;
#endif // EXPORT_3MF_USE_SPIRIT_KARMA_FP
//BBS: change volume to seperate objects
bool _BBS_3MF_Exporter::_add_mesh_to_object_stream(std::function<bool(std::string &,bool)> const & flush, ModelObject const & object, unsigned int backup_id, VolumeToObjectIDMap& volumes_objectID, unsigned int& obj_idx) 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
};
char buf[256];
unsigned int vertices_count = 0;
//unsigned int triangles_count = 0;
unsigned int volume_idx, volume_count = 0;
for (ModelVolume* volume : object.volumes) {
if (volume == nullptr)
continue;
//if (!volume->mesh().stats().repaired())
// throw Slic3r::FileIOError("store_3mf() requires repair()");
unsigned int first_vertex_id = 0;
volume_count++;
if (m_from_backup_save)
volume_idx = (volume_count<<16 | obj_idx);
else {
volume_idx = obj_idx;
obj_idx++;
}
volumes_objectID.insert({ volume, volume_idx });
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_idx);
/*if (m_production_ext) {
std::stringstream stream;
reset_stream(stream);
stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap<boost::uint32_t>{(boost::uint32_t)backup_id} << OBJECT_UUID_SUFFIX;
//output_buffer += "\" ";
//output_buffer += PUUID_ATTR;
//output_buffer += "=\"";
//output_buffer += std::to_string(hex_wrap<boost::uint32_t>{(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();
const Transform3d& matrix = volume->get_matrix();
for (size_t i = 0; i < its.vertices.size(); ++i) {
Vec3f v = (matrix * its.vertices[i].cast<double>()).cast<float>();
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 += " </";
output_buffer += VERTICES_TAG;
output_buffer += ">\n <";
output_buffer += TRIANGLES_TAG;
output_buffer += ">\n";
//for (ModelVolume* volume : object.volumes) {
// if (volume == nullptr)
// continue;
bool is_left_handed = volume->is_left_handed();
//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] + first_vertex_id,
idx[1] + first_vertex_id,
idx[is_left_handed ? 0 : 2] + first_vertex_id);
*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 += " </";
output_buffer += TRIANGLES_TAG;
output_buffer += ">\n </";
output_buffer += MESH_TAG;
output_buffer += ">\n";
output_buffer += " </";
output_buffer += OBJECT_TAG;
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) {
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<boost::uint32_t>{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 << " </" << BUILD_TAG << ">\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<double>& layer_height_profile = object->layer_height_profile.get();
if (layer_height_profile.size() >= 4 && layer_height_profile.size() % 2 == 0) {
sprintf(buffer, "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) {
sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]);
out += buffer;
}
out += "\n";
}
}
if (!out.empty()) {
if (!mz_zip_writer_add_mem(&archive, 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("<xmlattr>.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("<xmlattr>.min_z", range.first.first);
range_tree.put("<xmlattr>.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("<xmlattr>.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, "><object", ">\n <object");
boost::replace_all(out, "><range", ">\n <range");
boost::replace_all(out, "><option", ">\n <option");
boost::replace_all(out, "></range>", ">\n </range>");
boost::replace_all(out, "></object>", ">\n </object>");
// 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::SupportPoint>& 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<const void*>(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];
sprintf(buffer, "; %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<Preset*> project_presets)
{
char buffer[1024];
sprintf(buffer, "; %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;
}
bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const IdToObjectDataMap &objects_data, int export_plate_idx)
{
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<double>::max_digits10);
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
stream << "<" << CONFIG_TAG << ">\n";
for (const IdToObjectDataMap::value_type& obj_metadata : objects_data) {
const ModelObject* obj = obj_metadata.second.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";
if (m_skip_static)
stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\">\n";
else
stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first + obj->volumes.size() << "\">\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 << "\" ";
stream << ID_ATTR << "=\"" << it->second << "\" ";
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";
}
// 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 << " </" << PART_TAG << ">\n";
}
}
}
stream << " </" << OBJECT_TAG << ">\n";
}
}
//BBS: store plate related logic
std::vector<std::string> 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 << "=\"" << LOCK_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->locked<< "\"/>\n";
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << GCODE_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << 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";
}
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 (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;
if (m_skip_static) {
obj_id = model.objects[obj_id]->get_backup_id();
} else {
//inst_id = convert_instance_id_to_resource_id(model, obj_id, inst_id);
obj_id = convert_instance_id_to_resource_id(model, obj_id, 0);
}
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 << " </" << INSTANCE_TAG << ">\n";
}
}
stream << " </" << PLATE_TAG << ">\n";
}
}
// write model rels
_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);
//BBS: store assemble related info
stream << " <" << ASSEMBLE_TAG << ">\n";
for (const IdToObjectDataMap::value_type& obj_metadata : objects_data) {
const ModelObject* obj = obj_metadata.second.object;
if (obj != nullptr) {
for (int instance_idx = 0; instance_idx < obj->instances.size(); ++instance_idx) {
if (obj->instances[instance_idx]->is_assemble_initialized()) {
if (m_skip_static)
stream << " <" << ASSEMBLE_ITEM_TAG << " " << OBJECT_ID_ATTR << "=\"" << obj_metadata.first << "\" ";
else
stream << " <" << ASSEMBLE_ITEM_TAG << " " << OBJECT_ID_ATTR << "=\"" << obj_metadata.first + obj->volumes.size() << "\" ";
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 << "=\"";
Vec3d ofs2ass = obj->instances[instance_idx]->get_offset_to_assembly();
stream << ofs2ass(0) << " " << ofs2ass(1) << " " << ofs2ass(2);
stream << "\" />\n";
}
}
}
}
stream << " </" << ASSEMBLE_TAG << ">\n";
stream << "</" << CONFIG_TAG << ">\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_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list)
{
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<double>::max_digits10);
stream << std::setiosflags(std::ios::fixed) << std::setprecision(2);
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\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 << " </" << SLICE_HEADER_TAG << ">\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";
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";
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_TYPE_TAG << "=\"" << it->type << "\" "
<< FILAMENT_COLOR_TAG << "=\"" << it->color << "\" "
<< FILAMENT_USED_M_TAG << "=\"" << it->used_m << "\" "
<< FILAMENT_USED_G_TAG << "=\"" << it->used_g << "\" />\n";
}
stream << " </" << PLATE_TAG << ">\n";
}
}
stream << "</" << CONFIG_TAG << ">\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<size_t>(0, plate_data_list2.size(), 1), [this, &plate_data_list2, &root_archive = archive, &mutex, &result](const tbb::blocked_range<size_t>& 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)
{
std::string out = "";
if (!model.custom_gcode_per_print_z.gcodes.empty()) {
pt::ptree tree;
pt::ptree& main_tree = tree.add("custom_gcodes_per_layer", "");
for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) {
pt::ptree& code_tree = main_tree.add("layer", "");
// store data of custom_gcode_per_print_z
code_tree.put("<xmlattr>.top_z" , code.print_z );
code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
code_tree.put("<xmlattr>.extruder" , code.extruder );
code_tree.put("<xmlattr>.color" , code.color );
code_tree.put("<xmlattr>.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("<xmlattr>.gcode" , gcode );
}
pt::ptree& mode_tree = main_tree.add("mode", "");
// store mode of a custom_gcode_per_print_z
mode_tree.put("<xmlattr>.value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode :
CustomGCode::MultiExtruderMode);
if (!tree.empty()) {
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<boost::filesystem::path> directories({dir});
int root_dir_len = dir.string().length() + 1;
//boost file access
while (!directories.empty()) {
boost::filesystem::directory_iterator iterator(directories.front());
directories.pop_front();
for (auto &dir_entry : iterator)
{
std::string src_file;
std::string dst_in_3mf;
if (boost::filesystem::is_directory(dir_entry.path()))
{
if (dir_entry.path().filename() == THUMBNAILS_DIR) {
boost::filesystem::directory_iterator iterator(dir_entry.path());
for (auto &file_entry : iterator) {
if (boost::filesystem::is_regular_file(file_entry.path())) {
// BBS generate thumbnails
// copy to /Metadata folder
src_file = file_entry.path().string();
/* save to .thumbnails */
dst_in_3mf = file_entry.path().string();
dst_in_3mf.replace(0, root_dir_len, AUXILIARY_DIR);
std::replace(dst_in_3mf.begin(), dst_in_3mf.end(), '\\', '/');
if (file_entry.path().filename() == _3MF_COVER_FILE) {
data._3mf_thumbnail = dst_in_3mf;
} else if (file_entry.path().filename() == PRINTER_THUMBNAIL_SMALL_FILE) {
data._3mf_printer_thumbnail_small = dst_in_3mf;
} else if (file_entry.path().filename() == PRINTER_THUMBNAIL_MIDDLE_FILE) {
data._3mf_printer_thumbnail_middle = dst_in_3mf;
}
result &= _add_file_to_archive(archive, dst_in_3mf, src_file);
}
}
}
else {
directories.push_back(dir_entry.path());
}
continue;
}
if (boost::filesystem::is_regular_file(dir_entry.path()))
{
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(), '\\', '/');
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<ConfigOptionFloat>("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<ConfigOptionFloat>("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<void(int)> c) {
boost::lock_guard lock(m_mutex);
m_post_callback = c;
}
void run_ui_tasks() {
std::deque<Task> 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<Task> 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);
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();
}
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() : m_thread(boost::ref(*this)) {
m_next_backup = boost::get_system_time() + boost::posix_time::seconds(m_interval);
}
~_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) {
BOOST_LOG_TRIVIAL(info) << "process_ui_task" << t.to_string();
switch (t.type) {
case Backup: {
if (canceled)
break;
std::function<void(int)> 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) {
BOOST_LOG_TRIVIAL(info) << "process_task" << t.to_string();
switch (t.type) {
case Backup:
// do it in response
break;
case AddObject: {
{
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::filesystem::remove(t.path + "/mesh_" + boost::lexical_cast<std::string>(t.id) + ".xml");
t.type = None;
break;
}
case RemoveBackup: {
try {
boost::filesystem::remove(t.path + "/.3mf");
// 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()) {
m_cond.timed_wait(lock, m_next_backup);
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<boost::mutex> & 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<Task> m_tasks;
std::deque<Task> m_ui_tasks;
size_t m_task_seq = 0;
// param 0: should call run_ui_tasks
// param 1: should backup current project
std::function<void(int)> 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<std::pair<ModelObject*, size_t>> 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<Preset*>* project_presets, bool* is_bbl_3mf, Semver* file_version, Import3mfProgressFn proFn, LoadStrategy strategy, BBLProject *project)
{
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);
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 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<void(int)> 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 = "<lock>";
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<int>(pid)) ==
get_process_name(0)) {
origin = "<lock>";
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