BambuStudio/src/libslic3r/Format/bbs_3mf.cpp

6413 lines
290 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* COLOR_GROUP_TAG = "m:colorgroup";
static constexpr const char* COLOR_TAG = "m:color";
static constexpr const char* OBJECT_TAG = "object";
static constexpr const char* MESH_TAG = "mesh";
static constexpr const char* MESH_STAT_TAG = "mesh_stat";
static constexpr const char* VERTICES_TAG = "vertices";
static constexpr const char* VERTEX_TAG = "vertex";
static constexpr const char* TRIANGLES_TAG = "triangles";
static constexpr const char* TRIANGLE_TAG = "triangle";
static constexpr const char* COMPONENTS_TAG = "components";
static constexpr const char* COMPONENT_TAG = "component";
static constexpr const char* BUILD_TAG = "build";
static constexpr const char* ITEM_TAG = "item";
static constexpr const char* METADATA_TAG = "metadata";
static constexpr const char* FILAMENT_TAG = "filament";
static constexpr const char* SLICE_WARNING_TAG = "warning";
static constexpr const char* WARNING_MSG_TAG = "msg";
static constexpr const char *FILAMENT_ID_TAG = "id";
static constexpr const char* FILAMENT_TYPE_TAG = "type";
static constexpr const char *FILAMENT_COLOR_TAG = "color";
static constexpr const char *FILAMENT_USED_M_TAG = "used_m";
static constexpr const char *FILAMENT_USED_G_TAG = "used_g";
static constexpr const char* 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* PID_ATTR = "pid";
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* COLOR_ATTR = "color";
static constexpr const char* TYPE_ATTR = "type";
static constexpr const char* ID_ATTR = "id";
static constexpr const char* X_ATTR = "x";
static constexpr const char* Y_ATTR = "y";
static constexpr const char* Z_ATTR = "z";
static constexpr const char* V1_ATTR = "v1";
static constexpr const char* V2_ATTR = "v2";
static constexpr const char* V3_ATTR = "v3";
static constexpr const char* OBJECTID_ATTR = "objectid";
static constexpr const char* TRANSFORM_ATTR = "transform";
// BBS
static constexpr const char* OFFSET_ATTR = "offset";
static constexpr const char* PRINTABLE_ATTR = "printable";
static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "paint_supports";
static constexpr const char* CUSTOM_SEAM_ATTR = "paint_seam";
static constexpr const char* MMU_SEGMENTATION_ATTR = "paint_color";
// BBS
static constexpr const char* FACE_PROPERTY_ATTR = "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_SHARED_KEY = "mesh_shared";
static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed";
static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets";
static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed";
static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed";
static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges";
const unsigned int BBS_VALID_OBJECT_TYPES_COUNT = 2;
const char* BBS_VALID_OBJECT_TYPES[] =
{
"model",
"other"
};
const char* BBS_INVALID_OBJECT_TYPES[] =
{
"solidsupport",
"support",
"surface"
};
template <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);
}
/* only for test
GCodeProcessorResult::SliceWarnings sw;
sw.msg = BED_TEMP_TOO_HIGH_THAN_FILAMENT;
sw.level = 1;
result->warnings.push_back(sw);
*/
warnings = result->warnings;
}
//! macro used to mark string used at localization,
//! return same string
#define L(s) (s)
#define _(s) Slic3r::I18N::translate(s)
// Base class with error messages management
class _BBS_3MF_Base
{
mutable boost::mutex mutex;
mutable std::vector<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;
int pid{-1};
//bool is_model_object;
CurrentObject() { reset(); }
void reset() {
id = -1;
model_object_idx = -1;
geometry.reset();
object = nullptr;
components.clear();
//BBS: sub object id
uuid.clear();
name.clear();
}
};
struct CurrentConfig
{
int object_id {-1};
int volume_id {-1};
};
struct CurrentInstance
{
int object_id;
int instance_id;
};
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;
std::map<int, ModelVolume*> m_shared_meshes;
//BBS: plater related structures
bool m_is_bbl_3mf { false };
bool m_parsing_slice_info { false };
PlateDataMaps m_plater_data;
PlateData* m_curr_plater;
CurrentInstance m_curr_instance;
int m_current_color_group{-1};
std::map<int, std::string> m_group_id_to_color;
public:
_BBS_3MF_Importer();
~_BBS_3MF_Importer();
//BBS: add plate data related logic
// add backup & restore logic
bool load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<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_color_group(const char **attributes, unsigned int num_attributes);
bool _handle_end_color_group();
bool _handle_start_color(const char **attributes, unsigned int num_attributes);
bool _handle_end_color();
bool _handle_start_mesh(const char** attributes, unsigned int num_attributes);
bool _handle_end_mesh();
bool _handle_start_vertices(const char** attributes, unsigned int num_attributes);
bool _handle_end_vertices();
bool _handle_start_vertex(const char** attributes, unsigned int num_attributes);
bool _handle_end_vertex();
bool _handle_start_triangles(const char** attributes, unsigned int num_attributes);
bool _handle_end_triangles();
bool _handle_start_triangle(const char** attributes, unsigned int num_attributes);
bool _handle_end_triangle();
bool _handle_start_components(const char** attributes, unsigned int num_attributes);
bool _handle_end_components();
bool _handle_start_component(const char** attributes, unsigned int num_attributes);
bool _handle_end_component();
bool _handle_start_build(const char** attributes, unsigned int num_attributes);
bool _handle_end_build();
bool _handle_start_item(const char** attributes, unsigned int num_attributes);
bool _handle_end_item();
bool _handle_start_metadata(const char** attributes, unsigned int num_attributes);
bool _handle_end_metadata();
bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
void _apply_transform(ModelInstance& instance, const Transform3d& transform);
bool _handle_start_config(const char** attributes, unsigned int num_attributes);
bool _handle_end_config();
bool _handle_start_config_object(const char** attributes, unsigned int num_attributes);
bool _handle_end_config_object();
bool _handle_start_config_volume(const char** attributes, unsigned int num_attributes);
bool _handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes);
bool _handle_end_config_volume();
bool _handle_end_config_volume_mesh();
bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes);
bool _handle_end_config_metadata();
bool _handle_start_config_filament(const char** attributes, unsigned int num_attributes);
bool _handle_end_config_filament();
bool _handle_start_config_warning(const char** attributes, unsigned int num_attributes);
bool _handle_end_config_warning();
//BBS: add plater config parse functions
bool _handle_start_config_plater(const char** attributes, unsigned int num_attributes);
bool _handle_end_config_plater();
bool _handle_start_config_plater_instance(const char** attributes, unsigned int num_attributes);
bool _handle_end_config_plater_instance();
bool _handle_start_assemble(const char** attributes, unsigned int num_attributes);
bool _handle_end_assemble();
bool _handle_start_assemble_item(const char** attributes, unsigned int num_attributes);
bool _handle_end_assemble_item();
// 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<Component> &sub_objects, Id object_id, IdToCurrentObjectMap current_objects);
bool _generate_volumes_new(ModelObject& object, const std::vector<Component> &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<Component> object_id_list;
_generate_current_object_list(object_id_list, object.first, m_current_objects);
ObjectMetadata::VolumeMetadataList volumes;
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
for (int k = 0; k < object_id_list.size(); k++)
{
Id object_id = object_id_list[k].object_id;
volumes.emplace_back(object_id.second);
}
// select as volumes
volumes_ptr = &volumes;
// for each instance after the 1st, create a new model object containing only that instance
// and copy into it the geometry
while (model_object->instances.size() > 1) {
ModelObject* new_model_object = m_model->add_object(*model_object);
new_model_object->clear_instances();
new_model_object->add_instance(*model_object->instances.back());
model_object->delete_last_instance();
if (!_generate_volumes_new(*new_model_object, object_id_list, *volumes_ptr, config_substitutions))
return false;
}
}
}
}
std::map<int, int> color_group_id_to_extruder_id_map;
std::map<std::string, int> color_to_extruder_id_map;
int extruder_id = 0;
for (auto group_iter = m_group_id_to_color.begin(); group_iter != m_group_id_to_color.end(); ++group_iter) {
auto color_iter = color_to_extruder_id_map.find(group_iter->second);
if (color_iter == color_to_extruder_id_map.end()) {
++extruder_id;
color_to_extruder_id_map[group_iter->second] = extruder_id;
color_group_id_to_extruder_id_map[group_iter->first] = extruder_id;
} else {
color_group_id_to_extruder_id_map[group_iter->first] = color_iter->second;
}
}
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<Component> object_id_list;
_generate_current_object_list(object_id_list, object.first, m_current_objects);
ObjectMetadata::VolumeMetadataList volumes;
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second);
if (obj_metadata != m_objects_metadata.end()) {
// config data has been found, this model was saved using slic3r pe
// apply object's name and config data
for (const Metadata& metadata : obj_metadata->second.metadata) {
if (metadata.key == "name")
model_object->name = metadata.value;
//BBS: add module name
else if (metadata.key == "module")
model_object->module_name = metadata.value;
else
model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
// select object's detected volumes
volumes_ptr = &obj_metadata->second.volumes;
}
else {
// config data not found, this model was not saved using slic3r pe
// add the entire geometry as the single volume to generate
//volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1);
for (int k = 0; k < object_id_list.size(); k++)
{
Id object_id = object_id_list[k].object_id;
volumes.emplace_back(object_id.second);
}
IdToCurrentObjectMap::const_iterator current_object = m_current_objects.find(object.first);
if (current_object != m_current_objects.end()) {
// get name
model_object->name = current_object->second.name;
// get color
auto extruder_itor = color_group_id_to_extruder_id_map.find(current_object->second.pid);
if (extruder_itor != color_group_id_to_extruder_id_map.end()) {
model_object->config.set_key_value("extruder", new ConfigOptionInt(extruder_itor->second));
}
}
// select as volumes
volumes_ptr = &volumes;
}
if (!_generate_volumes_new(*model_object, object_id_list, *volumes_ptr, config_substitutions))
return false;
}
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]->warnings = it->second->warnings;
plate_data_list[it->first-1]->thumbnail_file = (m_load_restore || it->second->thumbnail_file.empty()) ? it->second->thumbnail_file : m_backup_path + "/" + it->second->thumbnail_file;
plate_data_list[it->first-1]->pattern_file = (m_load_restore || it->second->pattern_file.empty()) ? it->second->pattern_file : m_backup_path + "/" + it->second->pattern_file;
plate_data_list[it->first-1]->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(COLOR_GROUP_TAG, name) == 0)
res = _handle_start_color_group(attributes, num_attributes);
else if (::strcmp(COLOR_TAG, name) == 0)
res = _handle_start_color(attributes, num_attributes);
else if (::strcmp(MESH_TAG, name) == 0)
res = _handle_start_mesh(attributes, num_attributes);
else if (::strcmp(VERTICES_TAG, name) == 0)
res = _handle_start_vertices(attributes, num_attributes);
else if (::strcmp(VERTEX_TAG, name) == 0)
res = _handle_start_vertex(attributes, num_attributes);
else if (::strcmp(TRIANGLES_TAG, name) == 0)
res = _handle_start_triangles(attributes, num_attributes);
else if (::strcmp(TRIANGLE_TAG, name) == 0)
res = _handle_start_triangle(attributes, num_attributes);
else if (::strcmp(COMPONENTS_TAG, name) == 0)
res = _handle_start_components(attributes, num_attributes);
else if (::strcmp(COMPONENT_TAG, name) == 0)
res = _handle_start_component(attributes, num_attributes);
else if (::strcmp(BUILD_TAG, name) == 0)
res = _handle_start_build(attributes, num_attributes);
else if (::strcmp(ITEM_TAG, name) == 0)
res = _handle_start_item(attributes, num_attributes);
else if (::strcmp(METADATA_TAG, name) == 0)
res = _handle_start_metadata(attributes, num_attributes);
if (!res)
_stop_xml_parser();
}
void _BBS_3MF_Importer::_handle_end_model_xml_element(const char* name)
{
if (m_xml_parser == nullptr)
return;
bool res = true;
if (::strcmp(MODEL_TAG, name) == 0)
res = _handle_end_model();
else if (::strcmp(RESOURCES_TAG, name) == 0)
res = _handle_end_resources();
else if (::strcmp(OBJECT_TAG, name) == 0)
res = _handle_end_object();
else if (::strcmp(COLOR_GROUP_TAG, name) == 0)
res = _handle_end_color_group();
else if (::strcmp(COLOR_TAG, name) == 0)
res = _handle_end_color();
else if (::strcmp(MESH_TAG, name) == 0)
res = _handle_end_mesh();
else if (::strcmp(VERTICES_TAG, name) == 0)
res = _handle_end_vertices();
else if (::strcmp(VERTEX_TAG, name) == 0)
res = _handle_end_vertex();
else if (::strcmp(TRIANGLES_TAG, name) == 0)
res = _handle_end_triangles();
else if (::strcmp(TRIANGLE_TAG, name) == 0)
res = _handle_end_triangle();
else if (::strcmp(COMPONENTS_TAG, name) == 0)
res = _handle_end_components();
else if (::strcmp(COMPONENT_TAG, name) == 0)
res = _handle_end_component();
else if (::strcmp(BUILD_TAG, name) == 0)
res = _handle_end_build();
else if (::strcmp(ITEM_TAG, name) == 0)
res = _handle_end_item();
else if (::strcmp(METADATA_TAG, name) == 0)
res = _handle_end_metadata();
if (!res)
_stop_xml_parser();
}
void _BBS_3MF_Importer::_handle_xml_characters(const XML_Char* s, int len)
{
m_curr_characters.append(s, len);
}
void _BBS_3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes)
{
if (m_xml_parser == nullptr)
return;
bool res = true;
unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser);
if (::strcmp(CONFIG_TAG, name) == 0)
res = _handle_start_config(attributes, num_attributes);
else if (::strcmp(OBJECT_TAG, name) == 0)
res = _handle_start_config_object(attributes, num_attributes);
else if (::strcmp(VOLUME_TAG, name) == 0)
res = _handle_start_config_volume(attributes, num_attributes);
else if (::strcmp(PART_TAG, name) == 0)
res = _handle_start_config_volume(attributes, num_attributes);
else if (::strcmp(MESH_STAT_TAG, name) == 0)
res = _handle_start_config_volume_mesh(attributes, num_attributes);
else if (::strcmp(METADATA_TAG, name) == 0)
res = _handle_start_config_metadata(attributes, num_attributes);
else if (::strcmp(PLATE_TAG, name) == 0)
res = _handle_start_config_plater(attributes, num_attributes);
else if (::strcmp(INSTANCE_TAG, name) == 0)
res = _handle_start_config_plater_instance(attributes, num_attributes);
else if (::strcmp(FILAMENT_TAG, name) == 0)
res = _handle_start_config_filament(attributes, num_attributes);
else if (::strcmp(SLICE_WARNING_TAG, name) == 0)
res = _handle_start_config_warning(attributes, num_attributes);
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
res = _handle_start_assemble(attributes, num_attributes);
else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0)
res = _handle_start_assemble_item(attributes, num_attributes);
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);
m_curr_object->pid = bbs_get_attribute_value_int(attributes, num_attributes, PID_ATTR);
}
return true;
}
bool _BBS_3MF_Importer::_handle_end_object()
{
if (!m_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_color_group(const char **attributes, unsigned int num_attributes)
{
m_current_color_group = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR);
return true;
}
bool _BBS_3MF_Importer::_handle_end_color_group()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_color(const char **attributes, unsigned int num_attributes)
{
std::string color = bbs_get_attribute_value_string(attributes, num_attributes, COLOR_ATTR);
m_group_id_to_color[m_current_color_group] = color;
return true;
}
bool _BBS_3MF_Importer::_handle_end_color()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_mesh(const char** attributes, unsigned int num_attributes)
{
// reset current geometry
if (m_curr_object)
m_curr_object->geometry.reset();
return true;
}
bool _BBS_3MF_Importer::_handle_end_mesh()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_vertices(const char** attributes, unsigned int num_attributes)
{
// reset current vertices
if (m_curr_object)
m_curr_object->geometry.vertices.clear();
return true;
}
bool _BBS_3MF_Importer::_handle_end_vertices()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_vertex(const char** attributes, unsigned int num_attributes)
{
// appends the vertex coordinates
// missing values are set equal to ZERO
if (m_curr_object)
m_curr_object->geometry.vertices.emplace_back(
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, X_ATTR),
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Y_ATTR),
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Z_ATTR));
return true;
}
bool _BBS_3MF_Importer::_handle_end_vertex()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes)
{
// reset current triangles
if (m_curr_object)
m_curr_object->geometry.triangles.clear();
return true;
}
bool _BBS_3MF_Importer::_handle_end_triangles()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_triangle(const char** attributes, unsigned int num_attributes)
{
// we are ignoring the following attributes:
// p1
// p2
// p3
// pid
// see specifications
// appends the triangle's vertices indices
// missing values are set equal to ZERO
if (m_curr_object) {
m_curr_object->geometry.triangles.emplace_back(
bbs_get_attribute_value_int(attributes, num_attributes, V1_ATTR),
bbs_get_attribute_value_int(attributes, num_attributes, V2_ATTR),
bbs_get_attribute_value_int(attributes, num_attributes, V3_ATTR));
m_curr_object->geometry.custom_supports.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
m_curr_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
m_curr_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
// BBS
m_curr_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
}
return true;
}
bool _BBS_3MF_Importer::_handle_end_triangle()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes)
{
// reset current components
if (m_curr_object)
m_curr_object->components.clear();
return true;
}
bool _BBS_3MF_Importer::_handle_end_components()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
{
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 = xml_unescape(m_curr_characters);
} else if (m_curr_metadata_name == BBL_MODEL_NAME_TAG) {
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found model name = " << m_curr_characters;
model_info.model_name = xml_unescape(m_curr_characters);
} else if (m_curr_metadata_name == BBL_DESIGNER_TAG) {
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer = " << m_curr_characters;
m_designer = xml_unescape(m_curr_characters);
} else if (m_curr_metadata_name == BBL_DESIGNER_USER_ID_TAG) {
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer_user_id = " << m_curr_characters;
m_designer_user_id = xml_unescape(m_curr_characters);
} else if (m_curr_metadata_name == BBL_DESIGNER_COVER_FILE_TAG) {
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer_cover = " << m_curr_characters;
model_info.cover_file = xml_unescape(m_curr_characters);
} else if (m_curr_metadata_name == BBL_DESCRIPTION_TAG) {
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found description = " << m_curr_characters;
model_info.description = xml_unescape(m_curr_characters);
} else if (m_curr_metadata_name == BBL_LICENSE_TAG) {
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found license = " << m_curr_characters;
model_info.license = xml_unescape(m_curr_characters);
} else if (m_curr_metadata_name == BBL_COPYRIGHT_TAG) {
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found copyright = " << m_curr_characters;
model_info.copyright = xml_unescape(m_curr_characters);
} else if (m_curr_metadata_name == BBL_REGION_TAG) {
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found region = " << m_curr_characters;
m_contry_code = xml_unescape(m_curr_characters);
}
return true;
}
bool _BBS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
{
static const unsigned int MAX_RECURSIONS = 10;
// escape from circular aliasing
if (recur_counter > MAX_RECURSIONS) {
add_error("Too many recursions");
return false;
}
Id id{path, object_id};
IdToCurrentObjectMap::iterator it = m_current_objects.find(id);
if (it == m_current_objects.end()) {
add_error("can not find object id " + std::to_string(object_id) + " to builditem");
return false;
}
IdToModelObjectMap::iterator object_item = m_objects.find(id);
if (object_item == m_objects.end()) {
//add object
CurrentObject& current_object = it->second;
int object_index = (int)m_model->objects.size();
ModelObject* model_object = m_model->add_object();
if (model_object == nullptr) {
add_error("Unable to create object for builditem, id " + std::to_string(object_id));
return false;
}
m_objects.insert({ id, object_index });
current_object.model_object_idx = object_index;
current_object.object = model_object;
ModelInstance* instance = m_model->objects[object_index]->add_instance();
if (instance == nullptr) {
add_error("error when add object instance for id " + std::to_string(object_id));
return false;
}
instance->printable = printable;
m_instances.emplace_back(instance, transform);
if (m_is_bbl_3mf && boost::ends_with(current_object.uuid, OBJECT_UUID_SUFFIX)) {
std::istringstream iss(current_object.uuid);
int backup_id;
if (iss >> std::hex >> backup_id) {
m_model->set_object_backup_id(*model_object, backup_id);
}
}
/*if (!current_object.geometry.empty()) {
}
else if (!current_object.components.empty()) {
// recursively process nested components
for (const Component& component : it->second) {
if (!_create_object_instance(path, component.object_id, transform * component.transform, printable, recur_counter + 1))
return false;
}
}
else {
add_error("can not construct build items with invalid object, id " + std::to_string(object_id));
return false;
}*/
}
else {
//add instance
ModelInstance* instance = m_model->objects[object_item->second]->add_instance();
if (instance == nullptr) {
add_error("error when add object instance for id " + std::to_string(object_id));
return false;
}
instance->printable = printable;
m_instances.emplace_back(instance, transform);
}
/*if (it->second.size() == 1 && it->second[0].object_id == object_id) {
// aliasing to itself
IdToModelObjectMap::iterator object_item = m_objects.find(id);
if (object_item == m_objects.end() || object_item->second == -1) {
add_error("Found invalid object");
return false;
}
else {
ModelInstance* instance = m_model->objects[object_item->second]->add_instance();
if (instance == nullptr) {
add_error("Unable to add object instance");
return false;
}
instance->printable = printable;
m_instances.emplace_back(instance, transform);
}
}
else {
// recursively process nested components
for (const Component& component : it->second) {
if (!_create_object_instance(path, component.object_id, transform * component.transform, printable, recur_counter + 1))
return false;
}
}*/
return true;
}
void _BBS_3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform)
{
Slic3r::Geometry::Transformation t(transform);
// invalid scale value, return
if (!t.get_scaling_factor().all())
return;
instance.set_transformation(t);
}
bool _BBS_3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes)
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_end_config()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_config_object(const char** attributes, unsigned int num_attributes)
{
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_warning(const char** attributes, unsigned int num_attributes)
{
if (m_curr_plater) {
std::string msg = bbs_get_attribute_value_string(attributes, num_attributes, WARNING_MSG_TAG);
std::string lvl_str = bbs_get_attribute_value_string(attributes, num_attributes, "level");
GCodeProcessorResult::SliceWarnings sw;
sw.msg = msg;
try {
sw.level = atoi(lvl_str.c_str());
}
catch(...) {
};
m_curr_plater->warnings.push_back(sw);
}
return true;
}
bool _BBS_3MF_Importer::_handle_end_config_warning()
{
// do nothing
return true;
}
bool _BBS_3MF_Importer::_handle_start_config_plater(const char** attributes, unsigned int num_attributes)
{
if (!m_parsing_slice_info) {
m_curr_plater = new PlateData();
}
return true;
}
bool _BBS_3MF_Importer::_handle_end_config_plater()
{
if (!m_curr_plater)
{
add_error("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<Component> &sub_objects, Id object_id, IdToCurrentObjectMap current_objects)
{
std::list<Component> id_list;
id_list.push_back({ object_id, Transform3d::Identity() });
while (!id_list.empty())
{
Component current_id = id_list.front();
id_list.pop_front();
IdToCurrentObjectMap::iterator current_object = current_objects.find(current_id.object_id);
if (current_object != current_objects.end()) {
//found one
if (!current_object->second.components.empty()) {
for (const Component& comp: current_object->second.components)
{
id_list.push_back(comp);
}
}
else if (!(current_object->second.geometry.empty())) {
//CurrentObject* ptr = &(current_objects[current_id]);
//CurrentObject* ptr2 = &(current_object->second);
sub_objects.push_back({ current_object->first, current_id.transform });
}
}
}
}
bool _BBS_3MF_Importer::_generate_volumes_new(ModelObject& object, const std::vector<Component> &sub_objects, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions)
{
if (!object.volumes.empty()) {
add_error("object already built with parts");
return false;
}
//unsigned int geo_tri_count = (unsigned int)geometry.triangles.size();
unsigned int renamed_volumes_count = 0;
for (unsigned int index = 0; index < sub_objects.size(); index++)
{
//find the volume metadata firstly
Component sub_comp = sub_objects[index];
Id object_id = sub_comp.object_id;
IdToCurrentObjectMap::iterator current_object = m_current_objects.find(object_id);
if (current_object == m_current_objects.end()) {
add_error("sub_objects can not be found, id=" + std::to_string(object_id.second));
return false;
}
CurrentObject* sub_object = &(current_object->second);
const ObjectMetadata::VolumeMetadata* volume_data = nullptr;
ObjectMetadata::VolumeMetadata default_volume_data(sub_object->id);
for (const ObjectMetadata::VolumeMetadata& volume_iter : volumes) {
if (volume_iter.subobject_id == sub_object->id) {
volume_data = &volume_iter;
break;
}
}
Transform3d volume_matrix_to_object = Transform3d::Identity();
bool has_transform = false;
int shared_mesh_id = -1;
if (volume_data)
{
int found_count = 0;
// extract the volume transformation from the volume's metadata, if present
for (const Metadata& metadata : volume_data->metadata) {
if (metadata.key == MATRIX_KEY) {
volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value);
has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10);
found_count++;
}
else if (metadata.key == MESH_SHARED_KEY){
//add the shared mesh logic
shared_mesh_id = ::atoi(metadata.value.c_str());
found_count++;
}
if (found_count >= 2)
break;
}
}
else {
//create a volume_data
volume_data = &default_volume_data;
}
ModelVolume* volume = nullptr;
ModelVolume *shared_volume = nullptr;
if (shared_mesh_id != -1) {
std::map<int, ModelVolume*>::iterator iter = m_shared_meshes.find(shared_mesh_id);
if (iter != m_shared_meshes.end()) {
shared_volume = iter->second;
}
}
const size_t triangles_count = sub_object->geometry.triangles.size();
if (triangles_count == 0) {
add_error("found no trianges in the object " + std::to_string(sub_object->id));
return false;
}
if (!shared_volume){
// splits volume out of imported geometry
indexed_triangle_set its;
its.indices.assign(sub_object->geometry.triangles.begin(), sub_object->geometry.triangles.end());
//const size_t triangles_count = its.indices.size();
//if (triangles_count == 0) {
// add_error("found no trianges in the object " + std::to_string(sub_object->id));
// return false;
//}
for (const Vec3i& face : its.indices) {
for (const int tri_id : face) {
if (tri_id < 0 || tri_id >= int(sub_object->geometry.vertices.size())) {
add_error("invalid vertex id in object " + std::to_string(sub_object->id));
return false;
}
}
}
its.vertices.assign(sub_object->geometry.vertices.begin(), sub_object->geometry.vertices.end());
// BBS
for (const std::string prop_str : sub_object->geometry.face_properties) {
FaceProperty face_prop;
face_prop.from_string(prop_str);
its.properties.push_back(face_prop);
}
TriangleMesh triangle_mesh(std::move(its), volume_data->mesh_stats);
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();
volume = object.add_volume(std::move(triangle_mesh));
m_shared_meshes[sub_object->id] = volume;
}
else {
//create volume to use shared mesh
volume = object.add_volume_with_shared_mesh(*shared_volume);
}
// stores the volume matrix taken from the metadata, if present
if (has_transform)
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
volume->calculate_convex_hull();
//set transform from 3mf
Slic3r::Geometry::Transformation comp_transformatino(sub_comp.transform);
volume->set_transformation(volume->get_transformation() * comp_transformatino);
if (shared_volume) {
const TriangleMesh& trangle_mesh = volume->mesh();
Vec3d shift = trangle_mesh.get_init_shift();
if (!shift.isApprox(Vec3d::Zero()))
volume->translate(shift);
}
// recreate custom supports, seam and mmu segmentation from previously loaded attribute
{
volume->supported_facets.reserve(triangles_count);
volume->seam_facets.reserve(triangles_count);
volume->mmu_segmentation_facets.reserve(triangles_count);
for (size_t i=0; 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) || (metadata.key == MESH_SHARED_KEY))
continue;
else
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
}
// this may happen for 3mf saved by 3rd part softwares
if (volume->name.empty()) {
volume->name = object.name;
if (renamed_volumes_count > 0)
volume->name += "_" + std::to_string(renamed_volumes_count + 1);
++renamed_volumes_count;
}
}
return true;
}
bool _BBS_3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions)
{
if (!object.volumes.empty()) {
add_error("Found invalid volumes count");
return false;
}
unsigned int geo_tri_count = (unsigned int)geometry.triangles.size();
unsigned int renamed_volumes_count = 0;
for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) {
if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) {
add_error("Found invalid triangle id");
return false;
}
Transform3d volume_matrix_to_object = Transform3d::Identity();
bool has_transform = false;
// extract the volume transformation from the volume's metadata, if present
for (const Metadata& metadata : volume_data.metadata) {
if (metadata.key == MATRIX_KEY) {
volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value);
has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10);
break;
}
}
// splits volume out of imported geometry
indexed_triangle_set its;
its.indices.assign(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1);
const size_t triangles_count = its.indices.size();
if (triangles_count == 0) {
add_error("An empty triangle mesh found");
return false;
}
{
int min_id = its.indices.front()[0];
int max_id = min_id;
for (const Vec3i& face : its.indices) {
for (const int tri_id : face) {
if (tri_id < 0 || tri_id >= int(geometry.vertices.size())) {
add_error("Found invalid vertex id");
return false;
}
min_id = std::min(min_id, tri_id);
max_id = std::max(max_id, tri_id);
}
}
its.vertices.assign(geometry.vertices.begin() + min_id, geometry.vertices.begin() + max_id + 1);
// BBS
for (const std::string prop_str : geometry.face_properties) {
FaceProperty face_prop;
face_prop.from_string(prop_str);
its.properties.push_back(face_prop);
}
// rebase indices to the current vertices list
for (Vec3i& face : its.indices)
for (int& tri_id : face)
tri_id -= min_id;
}
TriangleMesh triangle_mesh(std::move(its), volume_data.mesh_stats);
if (m_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
bool m_skip_auxiliary { false }; // skip normal axuiliary files
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 save_gcode = true);
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;
m_skip_auxiliary = store_params.strategy & SaveStrategy::SkipAuxiliary;
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();
std::string filepath_tmp = filepath + ".tmp";
boost::system::error_code ec;
boost::filesystem::remove(filepath_tmp, ec);
if (!open_zip_writer(&archive, filepath_tmp)) {
add_error("Unable to open the file");
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to open the file\n");
return false;
}
struct close_lock
{
mz_zip_archive & archive;
std::string const * filename;
void close() {
close_zip_writer(&archive);
filename = nullptr;
}
~close_lock() {
if (filename) {
close_zip_writer(&archive);
boost::filesystem::remove(*filename);
}
}
} lock{archive, &filepath_tmp};
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);
lock.close();
boost::filesystem::rename(filepath_tmp, filepath, ec);
return true;
}
//BBS: add plate data related logic
bool _BBS_3MF_Exporter::_save_model_to_file(const std::string& filename,
Model& model,
PlateDataPtrs& plate_data_list,
std::vector<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, m_save_gcode)) {
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;
}
if (!sub_model) {
stream << " <" << METADATA_TAG << " name=\"" << BBL_MODEL_NAME_TAG << "\">" << xml_escape(name) << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESIGNER_TAG << "\">" << xml_escape(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 << "\">" << xml_escape(design_cover) << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESCRIPTION_TAG << "\">" << xml_escape(description) << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_COPYRIGHT_TAG << "\">" << xml_escape(copyright) << "</" << METADATA_TAG << ">\n";
stream << " <" << METADATA_TAG << " name=\"" << BBL_LICENSE_TAG << "\">" << xml_escape(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";
//add the transform of the volume
ModelVolume* volume = object.volumes[index - 1];
const Transform3d& transf = volume->get_matrix();
stream << "\" " << TRANSFORM_ATTR << "=\"";
for (unsigned c = 0; c < 4; ++c) {
for (unsigned r = 0; r < 3; ++r) {
stream << transf(r, c);
if (r != 2 || c != 3)
stream << " ";
}
}
stream << "\"/>\n";
}
}
else {
for (unsigned int index = object_id; index < volume_start_id; index ++) {
stream << " <" << COMPONENT_TAG << " objectid=\"" << index; // << "\"/>\n";
//add the transform of the volume
ModelVolume* volume = object.volumes[index - object_id];
const Transform3d& transf = volume->get_matrix();
stream << "\" " << TRANSFORM_ATTR << "=\"";
for (unsigned c = 0; c < 4; ++c) {
for (unsigned r = 0; r < 3; ++r) {
stream << transf(r, c);
if (r != 2 || c != 3)
stream << " ";
}
}
stream << "\"/>\n";
}
}
}
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) {
//don't save the volume's matrix into vertex data
//add the shared mesh logic
//Vec3f v = (matrix * its.vertices[i].cast<double>()).cast<float>();
Vec3f v = its.vertices[i];
char* ptr = buf;
boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << VERTEX_TAG << " x=\"");
ptr = format_coordinate(v.x(), ptr);
boost::spirit::karma::generate(ptr, "\" y=\"");
ptr = format_coordinate(v.y(), ptr);
boost::spirit::karma::generate(ptr, "\" z=\"");
ptr = format_coordinate(v.z(), ptr);
boost::spirit::karma::generate(ptr, "\"/>\n");
*ptr = '\0';
output_buffer += buf;
if (!flush(output_buffer, false))
return false;
}
//}
output_buffer += " </";
output_buffer += VERTICES_TAG;
output_buffer += ">\n <";
output_buffer += TRIANGLES_TAG;
output_buffer += ">\n";
//for (ModelVolume* volume : object.volumes) {
// if (volume == nullptr)
// continue;
//BBS: as we stored matrix seperately, not multiplied into vertex
//we don't need to consider this left hand case specially
//bool is_left_handed = volume->is_left_handed();
bool is_left_handed = false;
//VolumeToOffsetsMap::iterator volume_it = volumes_objectID.find(volume);
//assert(volume_it != volumes_objectID.end());
//const indexed_triangle_set &its = volume->mesh().its;
// updates triangle offsets
//unsigned int first_triangle_id = triangles_count;
//triangles_count += (int)its.indices.size();
//unsigned int last_triangle_id = triangles_count - 1;
for (int i = 0; i < int(its.indices.size()); ++ i) {
{
const Vec3i &idx = its.indices[i];
char *ptr = buf;
boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG <<
" v1=\"" << boost::spirit::int_ <<
"\" v2=\"" << boost::spirit::int_ <<
"\" v3=\"" << boost::spirit::int_ << "\"",
idx[is_left_handed ? 2 : 0] + 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, bool save_gcode)
{
std::stringstream stream;
std::map<const TriangleMesh*, int> shared_meshes;
// 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";
}
//add the shared mesh logic
const TriangleMesh* current_mesh = volume->mesh_ptr();
std::map<const TriangleMesh*,int>::iterator mesh_iter;
mesh_iter = shared_meshes.find(current_mesh);
if (mesh_iter != shared_meshes.end()) {
stream << " <" << METADATA_TAG << " "<< KEY_ATTR << "=\"" << MESH_SHARED_KEY << "\" " << VALUE_ATTR << "=\"" << mesh_iter->second << "\"/>\n";
}
else {
shared_meshes[current_mesh] = it->second;
}
// 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";
if (save_gcode)
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << GCODE_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << xml_escape(plate_data->gcode_file) << "\"/>\n";
if (!plate_data->gcode_file.empty()) {
gcode_paths.push_back(plate_data->gcode_file);
}
if (plate_data->plate_thumbnail.is_valid()) {
std::string thumbnail_file_in_3mf = (boost::format(THUMBNAIL_FILE_FORMAT) % (plate_data->plate_index + 1)).str();
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << THUMBNAIL_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << thumbnail_file_in_3mf << "\"/>\n";
}
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
if (save_gcode)
_add_relationships_file_to_archive(archive, BBS_MODEL_CONFIG_RELS_FILE, gcode_paths, {"http://schemas.bambulab.com/package/2021/gcode"}, Slic3r::PackingTemporaryData(), export_plate_idx);
//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";
}
for (auto it = plate_data->warnings.begin(); it != plate_data->warnings.end(); it++) {
stream << " <" << SLICE_WARNING_TAG << " " << "msg=\"" << it->msg << "\" " << "level=\"" << std::to_string(it->level) << "\" />\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()) && !m_skip_auxiliary)
{
src_file = dir_entry.path().string();
dst_in_3mf = dir_entry.path().string();
dst_in_3mf.replace(0, root_dir_len, AUXILIARY_DIR);
std::replace(dst_in_3mf.begin(), dst_in_3mf.end(), '\\', '/');
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_next_backup = boost::get_system_time() + boost::posix_time::seconds(m_interval);
boost::unique_lock lock(m_mutex);
m_thread = std::move(boost::thread(boost::ref(*this)));
}
~_BBS_Backup_Manager() {
push_task({Exit});
m_thread.join();
}
void push_task(Task const & t) {
boost::unique_lock lock(m_mutex);
if (t.delay && !m_tasks.empty() && m_tasks.back() == t) {
auto t2 = m_tasks.back();
m_tasks.back() = t;
m_tasks.back().delay = t2.delay + 1;
m_cond.notify_all();
lock.unlock();
process_ui_task(t2);
}
else {
m_tasks.push_back(t);
++m_task_seq;
m_cond.notify_all();
}
}
void process_ui_task(Task& t, bool canceled = false) {
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