1. first formal version of macos 2. add the bambu networking plugin install logic 3. auto compute the wipe volume when filament change 4. add the logic of wiping into support 5. refine the GUI layout and icons, improve the gui apperance in lots of small places 6. serveral improve to support 7. support AMS auto-mapping 8. disable lots of unstable features: such as params table, media file download, HMS 9. fix serveral kinds of bugs 10. update the document of building 11. ...
6173 lines
279 KiB
C++
6173 lines
279 KiB
C++
#include "../libslic3r.h"
|
|
#include "../Exception.hpp"
|
|
#include "../Model.hpp"
|
|
#include "../Preset.hpp"
|
|
#include "../Utils.hpp"
|
|
#include "../LocalesUtils.hpp"
|
|
#include "../GCode.hpp"
|
|
#include "../Geometry.hpp"
|
|
#include "../GCode/ThumbnailData.hpp"
|
|
#include "../Semver.hpp"
|
|
#include "../Time.hpp"
|
|
|
|
#include "../I18N.hpp"
|
|
|
|
#include "bbs_3mf.hpp"
|
|
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <iomanip>
|
|
|
|
#include <boost/algorithm/string/classification.hpp>
|
|
#include <boost/algorithm/string/split.hpp>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
#include <boost/algorithm/string/replace.hpp>
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <boost/nowide/fstream.hpp>
|
|
#include <boost/nowide/cstdio.hpp>
|
|
#include <boost/spirit/include/karma.hpp>
|
|
#include <boost/spirit/include/qi_int.hpp>
|
|
#include <boost/log/trivial.hpp>
|
|
#include <boost/beast/core/detail/base64.hpp>
|
|
#include <boost/property_tree/ptree.hpp>
|
|
#include <boost/property_tree/xml_parser.hpp>
|
|
#include <boost/foreach.hpp>
|
|
#include <openssl/md5.h>
|
|
|
|
namespace pt = boost::property_tree;
|
|
|
|
#include <tbb/parallel_reduce.h>
|
|
|
|
#include <expat.h>
|
|
#include <Eigen/Dense>
|
|
#include "miniz_extension.hpp"
|
|
#include "nlohmann/json.hpp"
|
|
#include <fast_float/fast_float.h>
|
|
|
|
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
|
|
// https://github.com/boostorg/spirit/pull/586
|
|
// where the exported string is one digit shorter than it should be to guarantee lossless round trip.
|
|
// The code is left here for the ocasion boost guys improve.
|
|
#define EXPORT_3MF_USE_SPIRIT_KARMA_FP 0
|
|
|
|
#define WRITE_ZIP_LANGUAGE_ENCODING 1
|
|
|
|
// @see https://commons.apache.org/proper/commons-compress/apidocs/src-html/org/apache/commons/compress/archivers/zip/AbstractUnicodeExtraField.html
|
|
struct ZipUnicodePathExtraField
|
|
{
|
|
static std::string encode(std::string const& u8path, std::string const& path) {
|
|
std::string extra;
|
|
if (u8path != path) {
|
|
// 0x7075 - for Unicode filenames
|
|
extra.push_back('\x75');
|
|
extra.push_back('\x70');
|
|
boost::uint16_t len = 5 + u8path.length();
|
|
extra.push_back((char)(len & 0xff));
|
|
extra.push_back((char)(len >> 8));
|
|
auto crc = mz_crc32(0, (unsigned char *) path.c_str(), path.length());
|
|
extra.push_back('\x01'); // version 1
|
|
extra.append((char *)&crc, (char *)&crc + 4); // Little Endian
|
|
extra.append(u8path);
|
|
}
|
|
return extra;
|
|
}
|
|
static std::string decode(std::string const& extra, std::string const& path = {}) {
|
|
char const * p = extra.data();
|
|
char const * e = p + extra.length();
|
|
while (p + 4 < e) {
|
|
boost::uint16_t len = ((boost::uint16_t)p[2]) | ((boost::uint16_t)p[3] << 8);
|
|
if (p[0] == '\x75' && p[1] == '\x70' && len >= 5 && p + 4 + len < e && p[4] == '\x01') {
|
|
return std::string(p + 9, p + 4 + len);
|
|
}
|
|
else {
|
|
p += 4 + len;
|
|
}
|
|
}
|
|
return Slic3r::decode_path(path.c_str());
|
|
}
|
|
};
|
|
|
|
// VERSION NUMBERS
|
|
// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
|
|
// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files.
|
|
// 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, meshes transformed back to their coordinate system on loading.
|
|
// WARNING !! -> the version number has been rolled back to 1
|
|
// the next change should use 3
|
|
const unsigned int VERSION_BBS_3MF = 1;
|
|
// Allow loading version 2 file as well.
|
|
const unsigned int VERSION_BBS_3MF_COMPATIBLE = 2;
|
|
const char* BBS_3MF_VERSION1 = "bamboo_slicer:Version3mf"; // definition of the metadata name saved into .model file
|
|
const char* BBS_3MF_VERSION = "BambuStudio:3mfVersion"; //compatible with prusa currently
|
|
// Painting gizmos data version numbers
|
|
// 0 : initial version of fdm, seam, mm
|
|
const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 0;
|
|
const unsigned int SEAM_PAINTING_VERSION = 0;
|
|
const unsigned int MM_PAINTING_VERSION = 0;
|
|
|
|
const std::string BBS_FDM_SUPPORTS_PAINTING_VERSION = "BambuStudio:FdmSupportsPaintingVersion";
|
|
const std::string BBS_SEAM_PAINTING_VERSION = "BambuStudio:SeamPaintingVersion";
|
|
const std::string BBS_MM_PAINTING_VERSION = "BambuStudio:MmPaintingVersion";
|
|
const std::string BBL_MODEL_ID_TAG = "model_id";
|
|
const std::string BBL_MODEL_NAME_TAG = "Title";
|
|
const std::string BBL_DESIGNER_TAG = "Designer";
|
|
const std::string BBL_DESIGNER_USER_ID_TAG = "DesignerUserId";
|
|
const std::string BBL_DESIGNER_COVER_FILE_TAG = "DesignerCover";
|
|
const std::string BBL_DESCRIPTION_TAG = "Description";
|
|
const std::string BBL_COPYRIGHT_TAG = "CopyRight";
|
|
const std::string BBL_LICENSE_TAG = "License";
|
|
const std::string BBL_REGION_TAG = "Region";
|
|
|
|
const std::string MODEL_FOLDER = "3D/";
|
|
const std::string MODEL_EXTENSION = ".model";
|
|
const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA
|
|
const std::string MODEL_RELS_FILE = "3D/_rels/3dmodel.model.rels";
|
|
//BBS: add metadata_folder
|
|
const std::string METADATA_DIR = "Metadata/";
|
|
const std::string ACCESOR_DIR = "accesories/";
|
|
const std::string GCODE_EXTENSION = ".gcode";
|
|
const std::string THUMBNAIL_EXTENSION = ".png";
|
|
const std::string CALIBRATION_INFO_EXTENSION = ".json";
|
|
const std::string CONTENT_TYPES_FILE = "[Content_Types].xml";
|
|
const std::string RELATIONSHIPS_FILE = "_rels/.rels";
|
|
const std::string THUMBNAIL_FILE = "Metadata/plate_1.png";
|
|
const std::string THUMBNAIL_FOR_PRINTER_FILE = "Metadata/bbl_thumbnail.png";
|
|
const std::string THUMBNAILS_DIR = ".thumbnails";
|
|
const std::string PRINTER_THUMBNAIL_SMALL_FILE = "thumbnail_small.png";
|
|
const std::string PRINTER_THUMBNAIL_MIDDLE_FILE = "thumbnail_middle.png";
|
|
const std::string _3MF_COVER_FILE = "thumbnail_3mf.png";
|
|
//const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config";
|
|
//const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
|
|
const std::string BBS_PRINT_CONFIG_FILE = "Metadata/print_profile.config";
|
|
const std::string BBS_PROJECT_CONFIG_FILE = "Metadata/project_settings.config";
|
|
const std::string BBS_MODEL_CONFIG_FILE = "Metadata/model_settings.config";
|
|
const std::string BBS_MODEL_CONFIG_RELS_FILE = "Metadata/_rels/model_settings.config.rels";
|
|
const std::string SLICE_INFO_CONFIG_FILE = "Metadata/slice_info.config";
|
|
/*const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
|
|
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml";
|
|
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
|
|
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";*/
|
|
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/custom_gcode_per_layer.xml";
|
|
const std::string AUXILIARY_DIR = "Auxiliaries/";
|
|
const std::string PROJECT_EMBEDDED_PRINT_PRESETS_FILE = "Metadata/print_setting_";
|
|
const std::string PROJECT_EMBEDDED_SLICE_PRESETS_FILE = "Metadata/process_settings_";
|
|
const std::string PROJECT_EMBEDDED_FILAMENT_PRESETS_FILE = "Metadata/filament_settings_";
|
|
const std::string PROJECT_EMBEDDED_PRINTER_PRESETS_FILE = "Metadata/machine_settings_";
|
|
|
|
|
|
const unsigned int AUXILIARY_STR_LEN = 12;
|
|
const unsigned int METADATA_STR_LEN = 9;
|
|
|
|
|
|
static constexpr const char* MODEL_TAG = "model";
|
|
static constexpr const char* RESOURCES_TAG = "resources";
|
|
static constexpr const char* OBJECT_TAG = "object";
|
|
static constexpr const char* MESH_TAG = "mesh";
|
|
static constexpr const char* MESH_STAT_TAG = "mesh_stat";
|
|
static constexpr const char* VERTICES_TAG = "vertices";
|
|
static constexpr const char* VERTEX_TAG = "vertex";
|
|
static constexpr const char* TRIANGLES_TAG = "triangles";
|
|
static constexpr const char* TRIANGLE_TAG = "triangle";
|
|
static constexpr const char* COMPONENTS_TAG = "components";
|
|
static constexpr const char* COMPONENT_TAG = "component";
|
|
static constexpr const char* BUILD_TAG = "build";
|
|
static constexpr const char* ITEM_TAG = "item";
|
|
static constexpr const char* METADATA_TAG = "metadata";
|
|
static constexpr const char* FILAMENT_TAG = "filament";
|
|
static constexpr const char *FILAMENT_ID_TAG = "id";
|
|
static constexpr const char* FILAMENT_TYPE_TAG = "type";
|
|
static constexpr const char *FILAMENT_COLOR_TAG = "color";
|
|
static constexpr const char *FILAMENT_USED_M_TAG = "used_m";
|
|
static constexpr const char *FILAMENT_USED_G_TAG = "used_g";
|
|
|
|
|
|
static constexpr const char* CONFIG_TAG = "config";
|
|
static constexpr const char* VOLUME_TAG = "volume";
|
|
static constexpr const char* PART_TAG = "part";
|
|
static constexpr const char* PLATE_TAG = "plate";
|
|
static constexpr const char* INSTANCE_TAG = "model_instance";
|
|
//BBS
|
|
static constexpr const char* ASSEMBLE_TAG = "assemble";
|
|
static constexpr const char* ASSEMBLE_ITEM_TAG = "assemble_item";
|
|
static constexpr const char* SLICE_HEADER_TAG = "header";
|
|
static constexpr const char* SLICE_HEADER_ITEM_TAG = "header_item";
|
|
|
|
// BBS: encrypt
|
|
static constexpr const char* RELATIONSHIP_TAG = "Relationship";
|
|
static constexpr const char* PUUID_ATTR = "p:uuid";
|
|
static constexpr const char* PPATH_ATTR = "p:path";
|
|
static constexpr const char* OBJECT_UUID_SUFFIX = "-41cb-4c03-9d28-80fed5dfa1dc";
|
|
static constexpr const char* BUILD_UUID = "d8eb061-b1ec-4553-aec9-835e5b724bb4";
|
|
static constexpr const char* BUILD_UUID_SUFFIX = "-b1ec-4553-aec9-835e5b724bb4";
|
|
static constexpr const char* TARGET_ATTR = "Target";
|
|
static constexpr const char* RELS_TYPE_ATTR = "Type";
|
|
|
|
static constexpr const char* UNIT_ATTR = "unit";
|
|
static constexpr const char* NAME_ATTR = "name";
|
|
static constexpr const char* TYPE_ATTR = "type";
|
|
static constexpr const char* ID_ATTR = "id";
|
|
static constexpr const char* X_ATTR = "x";
|
|
static constexpr const char* Y_ATTR = "y";
|
|
static constexpr const char* Z_ATTR = "z";
|
|
static constexpr const char* V1_ATTR = "v1";
|
|
static constexpr const char* V2_ATTR = "v2";
|
|
static constexpr const char* V3_ATTR = "v3";
|
|
static constexpr const char* OBJECTID_ATTR = "objectid";
|
|
static constexpr const char* TRANSFORM_ATTR = "transform";
|
|
// BBS
|
|
static constexpr const char* OFFSET_ATTR = "offset";
|
|
static constexpr const char* PRINTABLE_ATTR = "printable";
|
|
static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
|
|
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "paint_supports";
|
|
static constexpr const char* CUSTOM_SEAM_ATTR = "paint_seam";
|
|
static constexpr const char* MMU_SEGMENTATION_ATTR = "paint_color";
|
|
// BBS
|
|
static constexpr const char* FACE_PROPERTY_ATTR = "bbs:face_property";
|
|
|
|
static constexpr const char* KEY_ATTR = "key";
|
|
static constexpr const char* VALUE_ATTR = "value";
|
|
static constexpr const char* FIRST_TRIANGLE_ID_ATTR = "firstid";
|
|
static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid";
|
|
static constexpr const char* SUBTYPE_ATTR = "subtype";
|
|
static constexpr const char* LOCK_ATTR = "locked";
|
|
static constexpr const char* GCODE_FILE_ATTR = "gcode_file";
|
|
static constexpr const char* THUMBNAIL_FILE_ATTR = "thumbnail_file";
|
|
static constexpr const char* PATTERN_FILE_ATTR = "pattern_file";
|
|
static constexpr const char* PATTERN_BBOX_FILE_ATTR = "pattern_bbox_file";
|
|
static constexpr const char* OBJECT_ID_ATTR = "object_id";
|
|
static constexpr const char* INSTANCEID_ATTR = "instance_id";
|
|
static constexpr const char* PLATERID_ATTR = "plater_id";
|
|
static constexpr const char* PLATE_IDX_ATTR = "index";
|
|
static constexpr const char* SLICE_PREDICTION_ATTR = "prediction";
|
|
static constexpr const char* SLICE_WEIGHT_ATTR = "weight";
|
|
static constexpr const char* OUTSIDE_ATTR = "outside";
|
|
|
|
static constexpr const char* OBJECT_TYPE = "object";
|
|
static constexpr const char* VOLUME_TYPE = "volume";
|
|
static constexpr const char* PART_TYPE = "part";
|
|
|
|
static constexpr const char* NAME_KEY = "name";
|
|
static constexpr const char* VOLUME_TYPE_KEY = "volume_type";
|
|
static constexpr const char* PART_TYPE_KEY = "part_type";
|
|
static constexpr const char* MATRIX_KEY = "matrix";
|
|
static constexpr const char* SOURCE_FILE_KEY = "source_file";
|
|
static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id";
|
|
static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id";
|
|
static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x";
|
|
static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y";
|
|
static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z";
|
|
static constexpr const char* SOURCE_IN_INCHES = "source_in_inches";
|
|
static constexpr const char* SOURCE_IN_METERS = "source_in_meters";
|
|
|
|
static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed";
|
|
static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets";
|
|
static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed";
|
|
static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed";
|
|
static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges";
|
|
|
|
|
|
const unsigned int BBS_VALID_OBJECT_TYPES_COUNT = 2;
|
|
const char* BBS_VALID_OBJECT_TYPES[] =
|
|
{
|
|
"model",
|
|
"other"
|
|
};
|
|
|
|
const char* BBS_INVALID_OBJECT_TYPES[] =
|
|
{
|
|
"solidsupport",
|
|
"support",
|
|
"surface"
|
|
};
|
|
|
|
template <typename T>
|
|
struct hex_wrap
|
|
{
|
|
T t;
|
|
};
|
|
|
|
namespace std {
|
|
template <class _Elem, class _Traits, class _Arg>
|
|
basic_ostream<_Elem, _Traits>& operator<<(basic_ostream<_Elem, _Traits>& ostr,
|
|
const hex_wrap<_Arg>& wrap) { // insert by calling function with output stream and argument
|
|
auto of = ostr.fill('0');
|
|
ostr << setw(sizeof(_Arg) * 2) << std::hex << wrap.t;
|
|
ostr << std::dec << setw(0);
|
|
ostr.fill(of);
|
|
return ostr;
|
|
}
|
|
}
|
|
|
|
class version_error : public Slic3r::FileIOError
|
|
{
|
|
public:
|
|
version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {}
|
|
version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {}
|
|
};
|
|
|
|
const char* bbs_get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key)
|
|
{
|
|
if ((attributes == nullptr) || (attributes_size == 0) || (attributes_size % 2 != 0) || (attribute_key == nullptr))
|
|
return nullptr;
|
|
|
|
for (unsigned int a = 0; a < attributes_size; a += 2) {
|
|
if (::strcmp(attributes[a], attribute_key) == 0)
|
|
return attributes[a + 1];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::string bbs_get_attribute_value_string(const char** attributes, unsigned int attributes_size, const char* attribute_key)
|
|
{
|
|
const char* text = bbs_get_attribute_value_charptr(attributes, attributes_size, attribute_key);
|
|
return (text != nullptr) ? text : "";
|
|
}
|
|
|
|
float bbs_get_attribute_value_float(const char** attributes, unsigned int attributes_size, const char* attribute_key)
|
|
{
|
|
float value = 0.0f;
|
|
if (const char *text = bbs_get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr)
|
|
fast_float::from_chars(text, text + strlen(text), value);
|
|
return value;
|
|
}
|
|
|
|
int bbs_get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key)
|
|
{
|
|
int value = 0;
|
|
if (const char *text = bbs_get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr)
|
|
boost::spirit::qi::parse(text, text + strlen(text), boost::spirit::qi::int_, value);
|
|
return value;
|
|
}
|
|
|
|
bool bbs_get_attribute_value_bool(const char** attributes, unsigned int attributes_size, const char* attribute_key)
|
|
{
|
|
const char* text = bbs_get_attribute_value_charptr(attributes, attributes_size, attribute_key);
|
|
return (text != nullptr) ? (bool)::atoi(text) : true;
|
|
}
|
|
|
|
Slic3r::Transform3d bbs_get_transform_from_3mf_specs_string(const std::string& mat_str)
|
|
{
|
|
// check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md
|
|
// to see how matrices are stored inside 3mf according to specifications
|
|
Slic3r::Transform3d ret = Slic3r::Transform3d::Identity();
|
|
|
|
if (mat_str.empty())
|
|
// empty string means default identity matrix
|
|
return ret;
|
|
|
|
std::vector<std::string> mat_elements_str;
|
|
boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on);
|
|
|
|
unsigned int size = (unsigned int)mat_elements_str.size();
|
|
if (size != 12)
|
|
// invalid data, return identity matrix
|
|
return ret;
|
|
|
|
unsigned int i = 0;
|
|
// matrices are stored into 3mf files as 4x3
|
|
// we need to transpose them
|
|
for (unsigned int c = 0; c < 4; ++c) {
|
|
for (unsigned int r = 0; r < 3; ++r) {
|
|
ret(r, c) = ::atof(mat_elements_str[i++].c_str());
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Slic3r::Vec3d bbs_get_offset_from_3mf_specs_string(const std::string& vec_str)
|
|
{
|
|
Slic3r::Vec3d ofs2ass(0, 0, 0);
|
|
|
|
if (vec_str.empty())
|
|
// empty string means default zero offset
|
|
return ofs2ass;
|
|
|
|
std::vector<std::string> vec_elements_str;
|
|
boost::split(vec_elements_str, vec_str, boost::is_any_of(" "), boost::token_compress_on);
|
|
|
|
unsigned int size = (unsigned int)vec_elements_str.size();
|
|
if (size != 3)
|
|
// invalid data, return zero offset
|
|
return ofs2ass;
|
|
|
|
for (unsigned int i = 0; i < 3; i++) {
|
|
ofs2ass(i) = ::atof(vec_elements_str[i].c_str());
|
|
}
|
|
|
|
return ofs2ass;
|
|
}
|
|
|
|
float bbs_get_unit_factor(const std::string& unit)
|
|
{
|
|
const char* text = unit.c_str();
|
|
|
|
if (::strcmp(text, "micron") == 0)
|
|
return 0.001f;
|
|
else if (::strcmp(text, "centimeter") == 0)
|
|
return 10.0f;
|
|
else if (::strcmp(text, "inch") == 0)
|
|
return 25.4f;
|
|
else if (::strcmp(text, "foot") == 0)
|
|
return 304.8f;
|
|
else if (::strcmp(text, "meter") == 0)
|
|
return 1000.0f;
|
|
else
|
|
// default "millimeters" (see specification)
|
|
return 1.0f;
|
|
}
|
|
|
|
bool bbs_is_valid_object_type(const std::string& type)
|
|
{
|
|
// if the type is empty defaults to "model" (see specification)
|
|
if (type.empty())
|
|
return true;
|
|
|
|
for (unsigned int i = 0; i < BBS_VALID_OBJECT_TYPES_COUNT; ++i) {
|
|
if (::strcmp(type.c_str(), BBS_VALID_OBJECT_TYPES[i]) == 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
namespace Slic3r {
|
|
|
|
void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|
{
|
|
if (!result) return;
|
|
|
|
PrintEstimatedStatistics &ps = result->print_statistics;
|
|
std::vector<float> m_filament_diameters = result->filament_diameters;
|
|
std::vector<float> m_filament_densities = result->filament_densities;
|
|
auto get_used_filament_from_volume = [m_filament_diameters, m_filament_densities](double volume, int extruder_id) {
|
|
double koef = 0.001;
|
|
std::pair<double, double> ret = {koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])), volume * m_filament_densities[extruder_id] * 0.001};
|
|
return ret;
|
|
};
|
|
|
|
for (auto it = ps.volumes_per_extruder.begin(); it != ps.volumes_per_extruder.end(); it++) {
|
|
double volume = it->second;
|
|
auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, it->first);
|
|
FilamentInfo info;
|
|
info.id = it->first;
|
|
info.used_m = used_filament_m;
|
|
info.used_g = used_filament_g;
|
|
slice_filaments_info.push_back(info);
|
|
}
|
|
}
|
|
|
|
|
|
//! macro used to mark string used at localization,
|
|
//! return same string
|
|
#define L(s) (s)
|
|
#define _(s) Slic3r::I18N::translate(s)
|
|
|
|
// Base class with error messages management
|
|
class _BBS_3MF_Base
|
|
{
|
|
mutable boost::mutex mutex;
|
|
mutable std::vector<std::string> m_errors;
|
|
|
|
protected:
|
|
void add_error(const std::string& error) const { boost::unique_lock l(mutex); m_errors.push_back(error); }
|
|
void clear_errors() { m_errors.clear(); }
|
|
|
|
public:
|
|
void log_errors()
|
|
{
|
|
for (const std::string& error : m_errors)
|
|
BOOST_LOG_TRIVIAL(error) << error;
|
|
}
|
|
};
|
|
|
|
class _BBS_3MF_Importer : public _BBS_3MF_Base
|
|
{
|
|
typedef std::pair<std::string, int> Id; // BBS: encrypt
|
|
|
|
struct Component
|
|
{
|
|
Id object_id;
|
|
Transform3d transform;
|
|
|
|
explicit Component(Id object_id)
|
|
: object_id(object_id)
|
|
, transform(Transform3d::Identity())
|
|
{
|
|
}
|
|
|
|
Component(Id object_id, const Transform3d& transform)
|
|
: object_id(object_id)
|
|
, transform(transform)
|
|
{
|
|
}
|
|
};
|
|
|
|
typedef std::vector<Component> ComponentsList;
|
|
|
|
struct Geometry
|
|
{
|
|
std::vector<Vec3f> vertices;
|
|
std::vector<Vec3i> triangles;
|
|
std::vector<std::string> custom_supports;
|
|
std::vector<std::string> custom_seam;
|
|
std::vector<std::string> mmu_segmentation;
|
|
// BBS
|
|
std::vector<std::string> face_properties;
|
|
|
|
bool empty() { return vertices.empty() || triangles.empty(); }
|
|
|
|
// backup & restore
|
|
void swap(Geometry& o) {
|
|
std::swap(vertices, o.vertices);
|
|
std::swap(triangles, o.triangles);
|
|
std::swap(custom_supports, o.custom_supports);
|
|
std::swap(custom_seam, o.custom_seam);
|
|
}
|
|
|
|
void reset() {
|
|
vertices.clear();
|
|
triangles.clear();
|
|
custom_supports.clear();
|
|
custom_seam.clear();
|
|
mmu_segmentation.clear();
|
|
}
|
|
};
|
|
|
|
struct CurrentObject
|
|
{
|
|
// ID of the object inside the 3MF file, 1 based.
|
|
int id;
|
|
// Index of the ModelObject in its respective Model, zero based.
|
|
int model_object_idx;
|
|
Geometry geometry;
|
|
ModelObject* object;
|
|
ComponentsList components;
|
|
|
|
//BBS: sub object id
|
|
//int subobject_id;
|
|
std::string name;
|
|
std::string uuid;
|
|
//bool is_model_object;
|
|
|
|
CurrentObject() { reset(); }
|
|
|
|
void reset() {
|
|
id = -1;
|
|
model_object_idx = -1;
|
|
geometry.reset();
|
|
object = nullptr;
|
|
components.clear();
|
|
//BBS: sub object id
|
|
uuid.clear();
|
|
name.clear();
|
|
}
|
|
};
|
|
|
|
struct CurrentConfig
|
|
{
|
|
int object_id {-1};
|
|
int volume_id {-1};
|
|
};
|
|
|
|
struct CurrentInstance
|
|
{
|
|
int object_id;
|
|
int instance_id;
|
|
};
|
|
|
|
struct Instance
|
|
{
|
|
ModelInstance* instance;
|
|
Transform3d transform;
|
|
|
|
Instance(ModelInstance* instance, const Transform3d& transform)
|
|
: instance(instance)
|
|
, transform(transform)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct Metadata
|
|
{
|
|
std::string key;
|
|
std::string value;
|
|
|
|
Metadata(const std::string& key, const std::string& value)
|
|
: key(key)
|
|
, value(value)
|
|
{
|
|
}
|
|
};
|
|
|
|
typedef std::vector<Metadata> MetadataList;
|
|
|
|
struct ObjectMetadata
|
|
{
|
|
struct VolumeMetadata
|
|
{
|
|
//BBS: refine the part logic
|
|
unsigned int first_triangle_id;
|
|
unsigned int last_triangle_id;
|
|
int subobject_id;
|
|
MetadataList metadata;
|
|
RepairedMeshErrors mesh_stats;
|
|
ModelVolumeType part_type;
|
|
|
|
VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
|
: first_triangle_id(first_triangle_id)
|
|
, last_triangle_id(last_triangle_id)
|
|
, part_type(type)
|
|
, subobject_id(-1)
|
|
{
|
|
}
|
|
|
|
VolumeMetadata(int sub_id, ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
|
: subobject_id(sub_id)
|
|
, part_type(type)
|
|
, first_triangle_id(0)
|
|
, last_triangle_id(0)
|
|
{
|
|
}
|
|
};
|
|
|
|
typedef std::vector<VolumeMetadata> VolumeMetadataList;
|
|
|
|
MetadataList metadata;
|
|
VolumeMetadataList volumes;
|
|
};
|
|
|
|
// Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects.
|
|
//typedef std::pair<std::string, int> Id; // BBS: encrypt
|
|
typedef std::map<Id, CurrentObject> IdToCurrentObjectMap;
|
|
typedef std::map<int, std::string> IndexToPathMap;
|
|
typedef std::map<Id, int> IdToModelObjectMap;
|
|
//typedef std::map<Id, ComponentsList> IdToAliasesMap;
|
|
typedef std::vector<Instance> InstancesList;
|
|
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
|
|
//typedef std::map<Id, Geometry> IdToGeometryMap;
|
|
/*typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
|
|
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
|
|
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
|
|
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;*/
|
|
|
|
// Version of the 3mf file
|
|
unsigned int m_version;
|
|
bool m_check_version;
|
|
bool m_load_aux;
|
|
bool m_load_config;
|
|
// backup & restore
|
|
bool m_load_restore;
|
|
std::string m_backup_path;
|
|
std::string m_origin_file;
|
|
// Semantic version of Bambu Studio, that generated this 3MF.
|
|
boost::optional<Semver> m_bambuslicer_generator_version;
|
|
unsigned int m_fdm_supports_painting_version = 0;
|
|
unsigned int m_seam_painting_version = 0;
|
|
unsigned int m_mm_painting_version = 0;
|
|
std::string m_model_id;
|
|
std::string m_contry_code;
|
|
std::string m_designer;
|
|
std::string m_designer_user_id;
|
|
std::string m_designer_cover;
|
|
ModelInfo model_info;
|
|
BBLProject project_info;
|
|
|
|
XML_Parser m_xml_parser;
|
|
// Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state
|
|
// after returning from XML_Parse() function, thus we keep the error state here.
|
|
bool m_parse_error { false };
|
|
std::string m_parse_error_message;
|
|
Model* m_model;
|
|
float m_unit_factor;
|
|
CurrentObject* m_curr_object{nullptr};
|
|
IdToCurrentObjectMap m_current_objects;
|
|
IndexToPathMap m_index_paths;
|
|
IdToModelObjectMap m_objects;
|
|
//IdToAliasesMap m_objects_aliases;
|
|
InstancesList m_instances;
|
|
//IdToGeometryMap m_geometries;
|
|
//IdToGeometryMap m_orig_geometries; // backup & restore
|
|
CurrentConfig m_curr_config;
|
|
IdToMetadataMap m_objects_metadata;
|
|
/*IdToLayerHeightsProfileMap m_layer_heights_profiles;
|
|
IdToLayerConfigRangesMap m_layer_config_ranges;
|
|
IdToSlaSupportPointsMap m_sla_support_points;
|
|
IdToSlaDrainHolesMap m_sla_drain_holes;*/
|
|
std::string m_curr_metadata_name;
|
|
std::string m_curr_characters;
|
|
std::string m_name;
|
|
std::string m_sub_model_path;
|
|
|
|
std::string m_start_part_path;
|
|
std::string m_thumbnail_path;
|
|
std::vector<std::string> m_sub_model_paths;
|
|
|
|
//BBS: plater related structures
|
|
bool m_is_bbl_3mf { false };
|
|
bool m_parsing_slice_info { false };
|
|
PlateDataMaps m_plater_data;
|
|
PlateData* m_curr_plater;
|
|
CurrentInstance m_curr_instance;
|
|
|
|
public:
|
|
_BBS_3MF_Importer();
|
|
~_BBS_3MF_Importer();
|
|
|
|
//BBS: add plate data related logic
|
|
// add backup & restore logic
|
|
bool load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<Preset*>& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool& is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn = nullptr, BBLProject *project = nullptr);
|
|
bool get_thumbnail(const std::string &filename, std::string &data);
|
|
unsigned int version() const { return m_version; }
|
|
|
|
private:
|
|
void _destroy_xml_parser();
|
|
void _stop_xml_parser(const std::string& msg = std::string());
|
|
|
|
bool parse_error() const { return m_parse_error; }
|
|
const char* parse_error_message() const {
|
|
return m_parse_error ?
|
|
// The error was signalled by the user code, not the expat parser.
|
|
(m_parse_error_message.empty() ? "Invalid 3MF format" : m_parse_error_message.c_str()) :
|
|
// The error was signalled by the expat parser.
|
|
XML_ErrorString(XML_GetErrorCode(m_xml_parser));
|
|
}
|
|
|
|
//BBS: add plate data related logic
|
|
// add backup & restore logic
|
|
bool _load_model_from_file(std::string filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<Preset*>& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Import3mfProgressFn proFn = nullptr,
|
|
BBLProject* project = nullptr);
|
|
bool _extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function<bool (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)>, bool restore = false);
|
|
bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
|
|
bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
|
|
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
|
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
|
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
|
|
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
|
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
|
|
|
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
|
|
|
void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename);
|
|
//BBS: add project config file logic
|
|
void _extract_project_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, Model& model);
|
|
//BBS: extract project embedded presets
|
|
void _extract_project_embedded_presets_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, std::vector<Preset*>&project_presets, Model& model, Preset::Type type, bool use_json = true);
|
|
|
|
void _extract_auxiliary_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
|
|
void _extract_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
|
|
|
// handlers to parse the .model file
|
|
void _handle_start_model_xml_element(const char* name, const char** attributes);
|
|
void _handle_end_model_xml_element(const char* name);
|
|
void _handle_xml_characters(const XML_Char* s, int len);
|
|
|
|
// handlers to parse the MODEL_CONFIG_FILE file
|
|
void _handle_start_config_xml_element(const char* name, const char** attributes);
|
|
void _handle_end_config_xml_element(const char* name);
|
|
|
|
bool _handle_start_model(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_model();
|
|
|
|
bool _handle_start_resources(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_resources();
|
|
|
|
bool _handle_start_object(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_object();
|
|
|
|
bool _handle_start_mesh(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_mesh();
|
|
|
|
bool _handle_start_vertices(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_vertices();
|
|
|
|
bool _handle_start_vertex(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_vertex();
|
|
|
|
bool _handle_start_triangles(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_triangles();
|
|
|
|
bool _handle_start_triangle(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_triangle();
|
|
|
|
bool _handle_start_components(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_components();
|
|
|
|
bool _handle_start_component(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_component();
|
|
|
|
bool _handle_start_build(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_build();
|
|
|
|
bool _handle_start_item(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_item();
|
|
|
|
bool _handle_start_metadata(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_metadata();
|
|
|
|
bool _create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter);
|
|
|
|
void _apply_transform(ModelInstance& instance, const Transform3d& transform);
|
|
|
|
bool _handle_start_config(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_config();
|
|
|
|
bool _handle_start_config_object(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_config_object();
|
|
|
|
bool _handle_start_config_volume(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_config_volume();
|
|
bool _handle_end_config_volume_mesh();
|
|
|
|
bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_config_metadata();
|
|
|
|
bool _handle_start_config_filament(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_config_filament();
|
|
|
|
//BBS: add plater config parse functions
|
|
bool _handle_start_config_plater(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_config_plater();
|
|
|
|
bool _handle_start_config_plater_instance(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_config_plater_instance();
|
|
|
|
bool _handle_start_assemble(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_assemble();
|
|
|
|
bool _handle_start_assemble_item(const char** attributes, unsigned int num_attributes);
|
|
bool _handle_end_assemble_item();
|
|
|
|
// BBS: callbacks to parse the .rels file
|
|
static void XMLCALL _handle_start_relationships_element(void* userData, const char* name, const char** attributes);
|
|
static void XMLCALL _handle_end_relationships_element(void* userData, const char* name);
|
|
|
|
void _handle_start_relationships_element(const char* name, const char** attributes);
|
|
void _handle_end_relationships_element(const char* name);
|
|
|
|
bool _handle_start_relationship(const char** attributes, unsigned int num_attributes);
|
|
|
|
void _generate_current_object_list(std::vector<Id> &sub_objects, Id object_id, IdToCurrentObjectMap current_objects);
|
|
bool _generate_volumes_new(ModelObject& object, const std::vector<Id> &sub_objects, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
|
|
bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions);
|
|
|
|
// callbacks to parse the .model file
|
|
static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes);
|
|
static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name);
|
|
static void XMLCALL _handle_xml_characters(void* userData, const XML_Char* s, int len);
|
|
|
|
// callbacks to parse the MODEL_CONFIG_FILE file
|
|
static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes);
|
|
static void XMLCALL _handle_end_config_xml_element(void* userData, const char* name);
|
|
};
|
|
|
|
_BBS_3MF_Importer::_BBS_3MF_Importer()
|
|
: m_version(0)
|
|
, m_check_version(false)
|
|
, m_xml_parser(nullptr)
|
|
, m_model(nullptr)
|
|
, m_unit_factor(1.0f)
|
|
, m_curr_metadata_name("")
|
|
, m_curr_characters("")
|
|
, m_name("")
|
|
, m_curr_plater(nullptr)
|
|
{
|
|
}
|
|
|
|
_BBS_3MF_Importer::~_BBS_3MF_Importer()
|
|
{
|
|
_destroy_xml_parser();
|
|
clear_errors();
|
|
|
|
if (m_curr_object) {
|
|
delete m_curr_object;
|
|
m_curr_object = nullptr;
|
|
}
|
|
m_current_objects.clear();
|
|
m_index_paths.clear();
|
|
m_objects.clear();
|
|
m_instances.clear();
|
|
m_objects_metadata.clear();
|
|
m_curr_metadata_name.clear();
|
|
m_curr_characters.clear();
|
|
|
|
std::map<int, PlateData*>::iterator it = m_plater_data.begin();
|
|
while (it != m_plater_data.end())
|
|
{
|
|
delete it->second;
|
|
it++;
|
|
}
|
|
m_plater_data.clear();
|
|
}
|
|
|
|
//BBS: add plate data related logic
|
|
// add backup & restore logic
|
|
bool _BBS_3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<Preset*>& project_presets, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, LoadStrategy strategy, bool& is_bbl_3mf, Semver& file_version, Import3mfProgressFn proFn, BBLProject *project)
|
|
{
|
|
m_version = 0;
|
|
m_fdm_supports_painting_version = 0;
|
|
m_seam_painting_version = 0;
|
|
m_mm_painting_version = 0;
|
|
m_check_version = strategy & LoadStrategy::CheckVersion;
|
|
//BBS: auxiliary data
|
|
m_load_aux = strategy & LoadStrategy::LoadAuxiliary;
|
|
m_load_restore = strategy & LoadStrategy::Restore;
|
|
m_load_config = strategy & LoadStrategy::LoadConfig;
|
|
m_model = &model;
|
|
m_unit_factor = 1.0f;
|
|
m_curr_object = nullptr;
|
|
m_current_objects.clear();
|
|
m_index_paths.clear();
|
|
m_objects.clear();
|
|
//m_objects_aliases.clear();
|
|
m_instances.clear();
|
|
//m_geometries.clear();
|
|
m_curr_config.object_id = -1;
|
|
m_curr_config.volume_id = -1;
|
|
m_objects_metadata.clear();
|
|
//m_layer_heights_profiles.clear();
|
|
//m_layer_config_ranges.clear();
|
|
//m_sla_support_points.clear();
|
|
m_curr_metadata_name.clear();
|
|
m_curr_characters.clear();
|
|
//BBS: plater data init
|
|
m_plater_data.clear();
|
|
m_curr_instance.object_id = -1;
|
|
m_curr_instance.instance_id = -1;
|
|
clear_errors();
|
|
|
|
// restore
|
|
if (m_load_restore) {
|
|
m_backup_path = filename.substr(0, filename.size() - 5);
|
|
model.set_backup_path(m_backup_path);
|
|
try {
|
|
if (boost::filesystem::exists(model.get_backup_path() + "/origin.txt"))
|
|
boost::filesystem::load_string_file(model.get_backup_path() + "/origin.txt", m_origin_file);
|
|
} catch (...) {}
|
|
boost::filesystem::save_string_file(
|
|
model.get_backup_path() + "/lock.txt",
|
|
boost::lexical_cast<std::string>(get_current_pid()));
|
|
}
|
|
else {
|
|
m_backup_path = model.get_backup_path();
|
|
}
|
|
bool result = _load_model_from_file(filename, model, plate_data_list, project_presets, config, config_substitutions, proFn, project);
|
|
is_bbl_3mf = m_is_bbl_3mf;
|
|
if (m_bambuslicer_generator_version)
|
|
file_version = *m_bambuslicer_generator_version;
|
|
// save for restore
|
|
if (result && m_load_aux && !m_load_restore) {
|
|
boost::filesystem::save_string_file(model.get_backup_path() + "/origin.txt", filename);
|
|
}
|
|
if (m_load_restore && !result) // not clear failed backup data for later analyze
|
|
model.set_backup_path("detach");
|
|
return result;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::get_thumbnail(const std::string &filename, std::string &data)
|
|
{
|
|
mz_zip_archive archive;
|
|
mz_zip_zero_struct(&archive);
|
|
|
|
struct close_lock
|
|
{
|
|
mz_zip_archive *archive;
|
|
void close()
|
|
{
|
|
if (archive) {
|
|
close_zip_reader(archive);
|
|
archive = nullptr;
|
|
}
|
|
}
|
|
~close_lock() { close(); }
|
|
} lock{&archive};
|
|
|
|
if (!open_zip_reader(&archive, filename)) {
|
|
add_error("Unable to open the file");
|
|
return false;
|
|
}
|
|
|
|
// BBS: load relationships
|
|
if (!_extract_xml_from_archive(archive, RELATIONSHIPS_FILE, _handle_start_relationships_element, _handle_end_relationships_element))
|
|
return false;
|
|
if (!m_thumbnail_path.empty()) {
|
|
return _extract_from_archive(archive, m_thumbnail_path, [&data](auto &archive, auto const &stat) -> bool {
|
|
data.resize(stat.m_uncomp_size);
|
|
return mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, data.data(), data.size(), 0);
|
|
});
|
|
}
|
|
return _extract_from_archive(archive, THUMBNAIL_FILE, [&data](auto &archive, auto const &stat) -> bool {
|
|
data.resize(stat.m_uncomp_size);
|
|
return mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, data.data(), data.size(), 0);
|
|
});
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_destroy_xml_parser()
|
|
{
|
|
if (m_xml_parser != nullptr) {
|
|
XML_ParserFree(m_xml_parser);
|
|
m_xml_parser = nullptr;
|
|
}
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_stop_xml_parser(const std::string &msg)
|
|
{
|
|
assert(! m_parse_error);
|
|
assert(m_parse_error_message.empty());
|
|
assert(m_xml_parser != nullptr);
|
|
m_parse_error = true;
|
|
m_parse_error_message = msg;
|
|
XML_StopParser(m_xml_parser, false);
|
|
}
|
|
|
|
//BBS: add plate data related logic
|
|
bool _BBS_3MF_Importer::_load_model_from_file(
|
|
std::string filename,
|
|
Model& model,
|
|
PlateDataPtrs& plate_data_list,
|
|
std::vector<Preset*>& project_presets,
|
|
DynamicPrintConfig& config,
|
|
ConfigSubstitutionContext& config_substitutions,
|
|
Import3mfProgressFn proFn,
|
|
BBLProject *project)
|
|
{
|
|
bool cb_cancel = false;
|
|
//BBS progress point
|
|
// prepare restore
|
|
if (m_load_restore) {
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_RESTORE\n");
|
|
if (proFn) {
|
|
proFn(IMPORT_STAGE_RESTORE, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_OPEN\n");
|
|
if (proFn) {
|
|
proFn(IMPORT_STAGE_OPEN, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
mz_zip_archive archive;
|
|
mz_zip_zero_struct(&archive);
|
|
|
|
struct close_lock
|
|
{
|
|
mz_zip_archive * archive;
|
|
void close() {
|
|
if (archive) {
|
|
close_zip_reader(archive);
|
|
archive = nullptr;
|
|
}
|
|
}
|
|
~close_lock() {
|
|
close();
|
|
}
|
|
} lock{ &archive };
|
|
|
|
if (!open_zip_reader(&archive, filename)) {
|
|
add_error("Unable to open the file");
|
|
return false;
|
|
}
|
|
|
|
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
|
|
|
mz_zip_archive_file_stat stat;
|
|
|
|
m_name = boost::filesystem::path(filename).stem().string();
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_READ_FILES\n");
|
|
if (proFn) {
|
|
proFn(IMPORT_STAGE_READ_FILES, 0, 3, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
// BBS: load relationships
|
|
if (!_extract_xml_from_archive(archive, RELATIONSHIPS_FILE, _handle_start_relationships_element, _handle_end_relationships_element))
|
|
return false;
|
|
if (m_start_part_path.empty())
|
|
return false;
|
|
// BBS: load sub models (Production Extension)
|
|
std::string sub_rels = m_start_part_path;
|
|
sub_rels.insert(boost::find_last(sub_rels, "/").end() - sub_rels.begin(), "_rels/");
|
|
sub_rels.append(".rels");
|
|
if (sub_rels.front() == '/') sub_rels = sub_rels.substr(1);
|
|
|
|
//check whether sub relation file is exist or not
|
|
int sub_index = mz_zip_reader_locate_file(&archive, sub_rels.c_str(), nullptr, 0);
|
|
if (sub_index == -1) {
|
|
//no submodule files found, use only one 3dmodel.model
|
|
}
|
|
else {
|
|
_extract_xml_from_archive(archive, sub_rels, _handle_start_relationships_element, _handle_end_relationships_element);
|
|
int index = 0;
|
|
|
|
for (auto path : m_sub_model_paths) {
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_READ_FILES\n");
|
|
if (proFn) {
|
|
proFn(IMPORT_STAGE_READ_FILES, ++index, 3 + m_sub_model_paths.size(), cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
m_sub_model_path = path;
|
|
if (!_extract_from_archive(archive, path, [this] (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) {
|
|
return _extract_model_from_archive(archive, stat);
|
|
}, m_load_restore)) {
|
|
add_error("Archive does not contain a valid model");
|
|
return false;
|
|
}
|
|
m_sub_model_path.clear();
|
|
}
|
|
// BBS: load root model
|
|
if (proFn) {
|
|
proFn(IMPORT_STAGE_READ_FILES, 2, 3, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//extract model files
|
|
if (!_extract_from_archive(archive, m_start_part_path, [this] (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) {
|
|
return _extract_model_from_archive(archive, stat);
|
|
})) {
|
|
add_error("Archive does not contain a valid model");
|
|
return false;
|
|
}
|
|
|
|
if (!m_designer.empty()) {
|
|
m_model->design_info = std::make_shared<ModelDesignInfo>();
|
|
m_model->design_info->DesignerUserId = m_designer_user_id;
|
|
m_model->design_info->Designer = m_designer;
|
|
}
|
|
|
|
m_model->model_info = std::make_shared<ModelInfo>();
|
|
m_model->model_info->load(model_info);
|
|
|
|
//got project id
|
|
if (project) {
|
|
project->project_model_id = m_model_id;
|
|
project->project_country_code = m_contry_code;
|
|
}
|
|
|
|
//BBS: version check
|
|
bool dont_load_config = !m_load_config;
|
|
if (m_bambuslicer_generator_version) {
|
|
Semver app_version = *(Semver::parse(SLIC3R_VERSION));
|
|
Semver file_version = *m_bambuslicer_generator_version;
|
|
if (file_version.maj() != app_version.maj())
|
|
dont_load_config = true;
|
|
}
|
|
else {
|
|
m_bambuslicer_generator_version = Semver::parse("0.0.0.0");
|
|
dont_load_config = true;
|
|
}
|
|
|
|
// we then loop again the entries to read other files stored in the archive
|
|
for (mz_uint i = 0; i < num_entries; ++i) {
|
|
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
|
|
|
|
//BBS progress point
|
|
if (proFn) {
|
|
proFn(IMPORT_STAGE_EXTRACT, i, num_entries, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
std::string name(stat.m_filename);
|
|
std::replace(name.begin(), name.end(), '\\', '/');
|
|
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("extract file %1%\n")%name;
|
|
|
|
//BBS: disable adaptive layer height related file in 3MF
|
|
/* if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) {
|
|
// extract slic3r layer heights profile file
|
|
_extract_layer_heights_profile_config_from_archive(archive, stat);
|
|
}
|
|
else
|
|
if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) {
|
|
// extract slic3r layer config ranges file
|
|
_extract_layer_config_ranges_from_archive(archive, stat, config_substitutions);
|
|
}*/
|
|
//BBS: disable SLA related files currently
|
|
/*else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) {
|
|
// extract sla support points file
|
|
_extract_sla_support_points_from_archive(archive, stat);
|
|
}
|
|
else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE)) {
|
|
// extract sla support points file
|
|
_extract_sla_drain_holes_from_archive(archive, stat);
|
|
}*/
|
|
//BBS: project setting file
|
|
//if (!dont_load_config && boost::algorithm::iequals(name, BBS_PRINT_CONFIG_FILE)) {
|
|
// extract slic3r print config file
|
|
// _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename);
|
|
//} else
|
|
if (!dont_load_config && boost::algorithm::iequals(name, BBS_PROJECT_CONFIG_FILE)) {
|
|
// extract slic3r print config file
|
|
_extract_project_config_from_archive(archive, stat, config, config_substitutions, model);
|
|
}
|
|
//BBS: project embedded presets
|
|
else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_PRINT_PRESETS_FILE)) {
|
|
// extract slic3r layer config ranges file
|
|
_extract_project_embedded_presets_from_archive(archive, stat, project_presets, model, Preset::TYPE_PRINT, false);
|
|
}
|
|
else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_SLICE_PRESETS_FILE)) {
|
|
// extract slic3r layer config ranges file
|
|
_extract_project_embedded_presets_from_archive(archive, stat, project_presets, model, Preset::TYPE_PRINT);
|
|
}
|
|
else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_FILAMENT_PRESETS_FILE)) {
|
|
// extract slic3r layer config ranges file
|
|
_extract_project_embedded_presets_from_archive(archive, stat, project_presets, model, Preset::TYPE_FILAMENT);
|
|
}
|
|
else if (!dont_load_config && boost::algorithm::istarts_with(name, PROJECT_EMBEDDED_PRINTER_PRESETS_FILE)) {
|
|
// extract slic3r layer config ranges file
|
|
_extract_project_embedded_presets_from_archive(archive, stat, project_presets, model, Preset::TYPE_PRINTER);
|
|
}
|
|
else if (!dont_load_config && boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) {
|
|
// extract slic3r layer config ranges file
|
|
_extract_custom_gcode_per_print_z_from_archive(archive, stat);
|
|
}
|
|
else if (boost::algorithm::iequals(name, BBS_MODEL_CONFIG_FILE)) {
|
|
// extract slic3r model config file
|
|
if (!_extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element)) {
|
|
add_error("Archive does not contain a valid model config");
|
|
return false;
|
|
}
|
|
}
|
|
else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) {
|
|
m_parsing_slice_info = true;
|
|
//extract slice info from archive
|
|
_extract_xml_from_archive(archive, stat, _handle_start_config_xml_element, _handle_end_config_xml_element);
|
|
m_parsing_slice_info = false;
|
|
}
|
|
else if (boost::algorithm::istarts_with(name, AUXILIARY_DIR)) {
|
|
// extract auxiliary directory to temp directory, do nothing for restore
|
|
if (m_load_aux && !m_load_restore)
|
|
_extract_auxiliary_file_from_archive(archive, stat, model);
|
|
}
|
|
else if (!dont_load_config && boost::algorithm::istarts_with(name, METADATA_DIR) && boost::algorithm::iends_with(name, GCODE_EXTENSION)) {
|
|
//load gcode files
|
|
_extract_file_from_archive(archive, stat);
|
|
}
|
|
else if (!dont_load_config && boost::algorithm::istarts_with(name, METADATA_DIR) && boost::algorithm::iends_with(name, THUMBNAIL_EXTENSION)) {
|
|
//BBS parsing pattern thumbnail and plate thumbnails
|
|
_extract_file_from_archive(archive, stat);
|
|
}
|
|
else if (!dont_load_config && boost::algorithm::istarts_with(name, METADATA_DIR) && boost::algorithm::iends_with(name, CALIBRATION_INFO_EXTENSION)) {
|
|
//BBS parsing pattern config files
|
|
_extract_file_from_archive(archive, stat);
|
|
}
|
|
}
|
|
}
|
|
|
|
lock.close();
|
|
|
|
if (m_version == 0) {
|
|
// if the 3mf was not produced by BambuStudio and there is more than one instance,
|
|
// split the object in as many objects as instances
|
|
for (const IdToModelObjectMap::value_type& object : m_objects) {
|
|
if (object.second >= int(m_model->objects.size())) {
|
|
add_error("3rd 3mf, invalid object, id: "+std::to_string(object.first.second));
|
|
return false;
|
|
}
|
|
ModelObject* model_object = m_model->objects[object.second];
|
|
if (model_object->instances.size() > 1) {
|
|
IdToCurrentObjectMap::const_iterator current_object = m_current_objects.find(object.first);
|
|
if (current_object == m_current_objects.end()) {
|
|
add_error("3rd 3mf, can not find object, id " + std::to_string(object.first.second));
|
|
return false;
|
|
}
|
|
std::vector<Id> object_id_list;
|
|
_generate_current_object_list(object_id_list, object.first, m_current_objects);
|
|
|
|
ObjectMetadata::VolumeMetadataList volumes;
|
|
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
|
|
|
|
for (int k = 0; k < object_id_list.size(); k++)
|
|
{
|
|
Id object_id = object_id_list[k];
|
|
volumes.emplace_back(object_id.second);
|
|
}
|
|
|
|
// select as volumes
|
|
volumes_ptr = &volumes;
|
|
|
|
// for each instance after the 1st, create a new model object containing only that instance
|
|
// and copy into it the geometry
|
|
while (model_object->instances.size() > 1) {
|
|
ModelObject* new_model_object = m_model->add_object(*model_object);
|
|
new_model_object->clear_instances();
|
|
new_model_object->add_instance(*model_object->instances.back());
|
|
model_object->delete_last_instance();
|
|
if (!_generate_volumes_new(*new_model_object, object_id_list, *volumes_ptr, config_substitutions))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const IdToModelObjectMap::value_type& object : m_objects) {
|
|
if (object.second >= int(m_model->objects.size())) {
|
|
add_error("invalid object, id: "+std::to_string(object.first.second));
|
|
return false;
|
|
}
|
|
ModelObject* model_object = m_model->objects[object.second];
|
|
/*IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first);
|
|
if (obj_geometry == m_geometries.end()) {
|
|
add_error("Unable to find object geometry");
|
|
return false;
|
|
}*/
|
|
|
|
// m_layer_heights_profiles are indexed by a 1 based model object index.
|
|
/*IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.second + 1);
|
|
if (obj_layer_heights_profile != m_layer_heights_profiles.end())
|
|
model_object->layer_height_profile.set(std::move(obj_layer_heights_profile->second));
|
|
|
|
// m_layer_config_ranges are indexed by a 1 based model object index.
|
|
IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1);
|
|
if (obj_layer_config_ranges != m_layer_config_ranges.end())
|
|
model_object->layer_config_ranges = std::move(obj_layer_config_ranges->second);
|
|
|
|
// m_sla_support_points are indexed by a 1 based model object index.
|
|
IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1);
|
|
if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) {
|
|
model_object->sla_support_points = std::move(obj_sla_support_points->second);
|
|
model_object->sla_points_status = sla::PointsStatus::UserModified;
|
|
}
|
|
|
|
IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1);
|
|
if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) {
|
|
model_object->sla_drain_holes = std::move(obj_drain_holes->second);
|
|
}*/
|
|
|
|
std::vector<Id> object_id_list;
|
|
_generate_current_object_list(object_id_list, object.first, m_current_objects);
|
|
|
|
ObjectMetadata::VolumeMetadataList volumes;
|
|
ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
|
|
|
|
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first.second);
|
|
if (obj_metadata != m_objects_metadata.end()) {
|
|
// config data has been found, this model was saved using slic3r pe
|
|
|
|
// apply object's name and config data
|
|
for (const Metadata& metadata : obj_metadata->second.metadata) {
|
|
if (metadata.key == "name")
|
|
model_object->name = metadata.value;
|
|
//BBS: add module name
|
|
else if (metadata.key == "module")
|
|
model_object->module_name = metadata.value;
|
|
else
|
|
model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
|
|
}
|
|
|
|
// select object's detected volumes
|
|
volumes_ptr = &obj_metadata->second.volumes;
|
|
}
|
|
else {
|
|
// config data not found, this model was not saved using slic3r pe
|
|
|
|
// add the entire geometry as the single volume to generate
|
|
//volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1);
|
|
for (int k = 0; k < object_id_list.size(); k++)
|
|
{
|
|
Id object_id = object_id_list[k];
|
|
volumes.emplace_back(object_id.second);
|
|
}
|
|
|
|
// select as volumes
|
|
volumes_ptr = &volumes;
|
|
}
|
|
|
|
if (!_generate_volumes_new(*model_object, object_id_list, *volumes_ptr, config_substitutions))
|
|
return false;
|
|
}
|
|
|
|
int object_idx = 0;
|
|
for (ModelObject* o : model.objects) {
|
|
int volume_idx = 0;
|
|
for (ModelVolume* v : o->volumes) {
|
|
if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) {
|
|
v->source.input_file = filename;
|
|
if (v->source.volume_idx == -1)
|
|
v->source.volume_idx = volume_idx;
|
|
if (v->source.object_idx == -1)
|
|
v->source.object_idx = object_idx;
|
|
}
|
|
++volume_idx;
|
|
}
|
|
++object_idx;
|
|
}
|
|
|
|
const ConfigOptionStrings* filament_ids_opt = config.option<ConfigOptionStrings>("filament_settings_id");
|
|
int max_filament_id = filament_ids_opt ? filament_ids_opt->size() : std::numeric_limits<int>::max();
|
|
for (ModelObject* mo : m_model->objects) {
|
|
const ConfigOptionInt* extruder_opt = dynamic_cast<const ConfigOptionInt*>(mo->config.option("extruder"));
|
|
int extruder_id = 0;
|
|
if (extruder_opt != nullptr)
|
|
extruder_id = extruder_opt->getInt();
|
|
|
|
if (extruder_id == 0 || extruder_id > max_filament_id)
|
|
mo->config.set_key_value("extruder", new ConfigOptionInt(1));
|
|
|
|
if (mo->volumes.size() == 1) {
|
|
mo->volumes[0]->config.erase("extruder");
|
|
}
|
|
else {
|
|
for (ModelVolume* mv : mo->volumes) {
|
|
const ConfigOptionInt* vol_extruder_opt = dynamic_cast<const ConfigOptionInt*>(mv->config.option("extruder"));
|
|
if (vol_extruder_opt == nullptr)
|
|
continue;
|
|
|
|
if (vol_extruder_opt->getInt() == 0)
|
|
mv->config.erase("extruder");
|
|
else if (vol_extruder_opt->getInt() > max_filament_id)
|
|
mv->config.set_key_value("extruder", new ConfigOptionInt(1));
|
|
}
|
|
}
|
|
}
|
|
|
|
// // fixes the min z of the model if negative
|
|
// model.adjust_min_z();
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_LOADING_PLATES, m_plater_data size %1%\n")%m_plater_data.size();
|
|
if (proFn) {
|
|
proFn(IMPORT_STAGE_LOADING_PLATES, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
//BBS: load the plate info into plate_data_list
|
|
std::map<int, PlateData*>::iterator it = m_plater_data.begin();
|
|
plate_data_list.clear();
|
|
plate_data_list.reserve(m_plater_data.size());
|
|
for (unsigned int i = 0; i < m_plater_data.size(); i++)
|
|
{
|
|
PlateData* plate = new PlateData();
|
|
plate_data_list.push_back(plate);
|
|
}
|
|
while (it != m_plater_data.end())
|
|
{
|
|
if (it->first > m_plater_data.size())
|
|
{
|
|
add_error("invalid plate index");
|
|
return false;
|
|
}
|
|
plate_data_list[it->first-1]->locked = it->second->locked;
|
|
plate_data_list[it->first-1]->plate_index = it->second->plate_index-1;
|
|
plate_data_list[it->first-1]->objects_and_instances = it->second->objects_and_instances;
|
|
plate_data_list[it->first-1]->gcode_file = (m_load_restore || it->second->gcode_file.empty()) ? it->second->gcode_file : m_backup_path + "/" + it->second->gcode_file;
|
|
plate_data_list[it->first-1]->gcode_prediction = it->second->gcode_prediction;
|
|
plate_data_list[it->first-1]->gcode_weight = it->second->gcode_weight;
|
|
plate_data_list[it->first-1]->toolpath_outside = it->second->toolpath_outside;
|
|
plate_data_list[it->first-1]->slice_filaments_info = it->second->slice_filaments_info;
|
|
plate_data_list[it->first-1]->thumbnail_file = (m_load_restore || it->second->thumbnail_file.empty()) ? it->second->thumbnail_file : m_backup_path + "/" + it->second->thumbnail_file;
|
|
plate_data_list[it->first-1]->pattern_file = (m_load_restore || it->second->pattern_file.empty()) ? it->second->pattern_file : m_backup_path + "/" + it->second->pattern_file;
|
|
plate_data_list[it->first-1]->pattern_bbox_file = (m_load_restore || it->second->pattern_bbox_file.empty()) ? it->second->pattern_bbox_file : m_backup_path + "/" + it->second->pattern_bbox_file;
|
|
it++;
|
|
}
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format("import 3mf IMPORT_STAGE_FINISH\n");
|
|
if (proFn) {
|
|
proFn(IMPORT_STAGE_FINISH, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_extract_from_archive(mz_zip_archive& archive, std::string const & path, std::function<bool (mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)> extract, bool restore)
|
|
{
|
|
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
|
mz_zip_archive_file_stat stat;
|
|
std::string path2 = path;
|
|
if (path2.front() == '/') path2 = path2.substr(1);
|
|
// try utf8 encoding
|
|
int index = mz_zip_reader_locate_file(&archive, path2.c_str(), nullptr, 0);
|
|
if (index < 0) {
|
|
// try native encoding
|
|
std::string native_path = encode_path(path2.c_str());
|
|
index = mz_zip_reader_locate_file(&archive, native_path.c_str(), nullptr, 0);
|
|
}
|
|
if (index < 0) {
|
|
// try unicode path extra
|
|
std::string extra(1024, 0);
|
|
for (mz_uint i = 0; i < archive.m_total_files; ++i) {
|
|
size_t n = mz_zip_reader_get_extra(&archive, i, extra.data(), extra.size());
|
|
if (n > 0 && path2 == ZipUnicodePathExtraField::decode(extra.substr(0, n))) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (index < 0 || !mz_zip_reader_file_stat(&archive, index, &stat)) {
|
|
if (restore) {
|
|
std::vector<std::string> paths = {m_backup_path + path};
|
|
if (!m_origin_file.empty()) paths.push_back(m_origin_file);
|
|
for (auto & path2 : paths) {
|
|
bool result = false;
|
|
if (boost::filesystem::exists(path2)) {
|
|
mz_zip_archive archive;
|
|
mz_zip_zero_struct(&archive);
|
|
if (open_zip_reader(&archive, path2)) {
|
|
result = _extract_from_archive(archive, path, extract);
|
|
close_zip_reader(&archive);
|
|
}
|
|
}
|
|
if (result) return result;
|
|
}
|
|
}
|
|
char error_buf[1024];
|
|
::sprintf(error_buf, "File %s not found from archive", path.c_str());
|
|
add_error(error_buf);
|
|
return false;
|
|
}
|
|
try
|
|
{
|
|
if (!extract(archive, stat)) {
|
|
return false;
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
// ensure the zip archive is closed and rethrow the exception
|
|
add_error(e.what());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_extract_xml_from_archive(mz_zip_archive& archive, const std::string & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler)
|
|
{
|
|
return _extract_from_archive(archive, path, [this, start_handler, end_handler](mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) {
|
|
return _extract_xml_from_archive(archive, stat, start_handler, end_handler);
|
|
});
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler)
|
|
{
|
|
if (stat.m_uncomp_size == 0) {
|
|
add_error("Found invalid size");
|
|
return false;
|
|
}
|
|
|
|
_destroy_xml_parser();
|
|
|
|
m_xml_parser = XML_ParserCreate(nullptr);
|
|
if (m_xml_parser == nullptr) {
|
|
add_error("Unable to create parser");
|
|
return false;
|
|
}
|
|
|
|
XML_SetUserData(m_xml_parser, (void*)this);
|
|
XML_SetElementHandler(m_xml_parser, start_handler, end_handler);
|
|
XML_SetCharacterDataHandler(m_xml_parser, _BBS_3MF_Importer::_handle_xml_characters);
|
|
|
|
void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size);
|
|
if (parser_buffer == nullptr) {
|
|
add_error("Unable to create buffer");
|
|
return false;
|
|
}
|
|
|
|
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, parser_buffer, (size_t)stat.m_uncomp_size, 0);
|
|
if (res == 0) {
|
|
add_error("Error while reading config data to buffer");
|
|
return false;
|
|
}
|
|
|
|
if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) {
|
|
char error_buf[1024];
|
|
::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser));
|
|
add_error(error_buf);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
|
{
|
|
if (stat.m_uncomp_size == 0) {
|
|
add_error("Found invalid size");
|
|
return false;
|
|
}
|
|
|
|
_destroy_xml_parser();
|
|
|
|
m_xml_parser = XML_ParserCreate(nullptr);
|
|
if (m_xml_parser == nullptr) {
|
|
add_error("Unable to create parser");
|
|
return false;
|
|
}
|
|
|
|
XML_SetUserData(m_xml_parser, (void*)this);
|
|
XML_SetElementHandler(m_xml_parser, _BBS_3MF_Importer::_handle_start_model_xml_element, _BBS_3MF_Importer::_handle_end_model_xml_element);
|
|
XML_SetCharacterDataHandler(m_xml_parser, _BBS_3MF_Importer::_handle_xml_characters);
|
|
|
|
struct CallbackData
|
|
{
|
|
XML_Parser& parser;
|
|
_BBS_3MF_Importer& importer;
|
|
const mz_zip_archive_file_stat& stat;
|
|
|
|
CallbackData(XML_Parser& parser, _BBS_3MF_Importer& importer, const mz_zip_archive_file_stat& stat) : parser(parser), importer(importer), stat(stat) {}
|
|
};
|
|
|
|
CallbackData data(m_xml_parser, *this, stat);
|
|
|
|
mz_bool res = 0;
|
|
|
|
try
|
|
{
|
|
mz_file_write_func callback = [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t {
|
|
CallbackData* data = (CallbackData*)pOpaque;
|
|
if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->importer.parse_error()) {
|
|
char error_buf[1024];
|
|
::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", data->importer.parse_error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser));
|
|
throw Slic3r::FileIOError(error_buf);
|
|
}
|
|
return n;
|
|
};
|
|
void* opaque = &data;
|
|
res = mz_zip_reader_extract_to_callback(&archive, stat.m_file_index, callback, opaque, 0);
|
|
}
|
|
catch (const version_error& e)
|
|
{
|
|
// rethrow the exception
|
|
throw Slic3r::FileIOError(e.what());
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
add_error(e.what());
|
|
return false;
|
|
}
|
|
|
|
if (res == 0) {
|
|
add_error("Error while extracting model data from zip archive");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, const std::string& archive_filename)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
|
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
|
if (res == 0) {
|
|
add_error("Error while reading config data to buffer");
|
|
return;
|
|
}
|
|
ConfigBase::load_from_gcode_string_legacy(config, buffer.data(), config_substitutions);
|
|
}
|
|
}
|
|
|
|
//BBS: extract project config from json files
|
|
void _BBS_3MF_Importer::_extract_project_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model& model)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
const std::string& temp_path = model.get_backup_path();
|
|
|
|
std::string dest_file = temp_path + std::string("/") + "_temp_3.config";;
|
|
std::string dest_zip_file = encode_path(dest_file.c_str());
|
|
mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0);
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_file % stat.m_filename % res;
|
|
if (res == 0) {
|
|
add_error("Error while extract project config file to file");
|
|
return;
|
|
}
|
|
std::map<std::string, std::string> key_values;
|
|
std::string reason;
|
|
int ret = config.load_from_json(dest_file, config_substitutions, true, key_values, reason);
|
|
if (ret) {
|
|
add_error("Error load config from json:"+reason);
|
|
return;
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", load project config file successfully from %1%\n") %dest_file;
|
|
}
|
|
}
|
|
|
|
//BBS: extract project embedded presets
|
|
void _BBS_3MF_Importer::_extract_project_embedded_presets_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, std::vector<Preset*>&project_presets, Model& model, Preset::Type type, bool use_json)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
/*std::string src_file = decode_path(stat.m_filename);
|
|
std::size_t found = src_file.find(METADATA_DIR);
|
|
if (found != std::string::npos)
|
|
src_file = src_file.substr(found + METADATA_STR_LEN);
|
|
else
|
|
return;*/
|
|
std::string dest_file = m_backup_path + std::string("/") + "_temp_2.config";;
|
|
std::string dest_zip_file = encode_path(dest_file.c_str());
|
|
mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0);
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_file % stat.m_filename % res;
|
|
if (res == 0) {
|
|
add_error("Error while extract auxiliary file to file");
|
|
return;
|
|
}
|
|
//load presets
|
|
DynamicPrintConfig config;
|
|
//ConfigSubstitutions config_substitutions = config.load_from_ini(dest_file, Enable);
|
|
std::map<std::string, std::string> key_values;
|
|
std::string reason;
|
|
ConfigSubstitutions config_substitutions = use_json? config.load_from_json(dest_file, Enable, key_values, reason) : config.load_from_ini(dest_file, Enable);
|
|
if (!reason.empty()) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", load project embedded config from %1% failed\n") % dest_file;
|
|
//skip this file
|
|
return;
|
|
}
|
|
ConfigOptionString* print_name;
|
|
ConfigOptionStrings* filament_names;
|
|
std::string preset_name;
|
|
if (type == Preset::TYPE_PRINT) {
|
|
print_name = dynamic_cast < ConfigOptionString* > (config.option("print_settings_id"));
|
|
if (!print_name) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", can not found print_settings_id from %1%\n") % dest_file;
|
|
//skip this file
|
|
return;
|
|
}
|
|
preset_name = print_name->value;
|
|
}
|
|
else if (type == Preset::TYPE_FILAMENT) {
|
|
filament_names = dynamic_cast < ConfigOptionStrings* > (config.option("filament_settings_id"));
|
|
if (!filament_names) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", can not found filament_settings_id from %1%\n") % dest_file;
|
|
//skip this file
|
|
return;
|
|
}
|
|
preset_name = filament_names->values[0];
|
|
}
|
|
else if (type == Preset::TYPE_PRINTER) {
|
|
print_name = dynamic_cast < ConfigOptionString* > (config.option("printer_settings_id"));
|
|
if (!print_name) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", can not found printer_settings_id from %1%\n") % dest_file;
|
|
//skip this file
|
|
return;
|
|
}
|
|
preset_name = print_name->value;
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", invalid type %1% from file %2%\n")% Preset::get_type_string(type) % dest_file;
|
|
//skip this file
|
|
return;
|
|
}
|
|
|
|
Preset *preset = new Preset(type, preset_name, false);
|
|
preset->file = dest_file;
|
|
preset->config = std::move(config);
|
|
preset->loaded = true;
|
|
preset->is_project_embedded = true;
|
|
preset->is_external = true;
|
|
preset->is_dirty = false;
|
|
|
|
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
|
|
boost::optional<Semver> version = Semver::parse(version_str);
|
|
if (version) {
|
|
preset->version = *version;
|
|
}
|
|
else
|
|
preset->version = this->m_bambuslicer_generator_version?*this->m_bambuslicer_generator_version: Semver();
|
|
/*for (int i = 0; i < config_substitutions.size(); i++)
|
|
{
|
|
//ConfigSubstitution config_substitution;
|
|
//config_substitution.opt_def = optdef;
|
|
//config_substitution.old_value = value;
|
|
//config_substitution.new_value = ConfigOptionUniquePtr(opt->clone());
|
|
preset->loading_substitutions.emplace_back(std::move(config_substitutions[i]));
|
|
}*/
|
|
if (!config_substitutions.empty()) {
|
|
preset->loading_substitutions = new ConfigSubstitutions();
|
|
*(preset->loading_substitutions) = std::move(config_substitutions);
|
|
}
|
|
|
|
project_presets.push_back(preset);
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", create one project embedded preset: %1% from %2%, type %3%\n") % preset_name % dest_file %Preset::get_type_string(type);
|
|
}
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_extract_auxiliary_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
std::string dest_file;
|
|
if (stat.m_is_utf8) {
|
|
dest_file = stat.m_filename;
|
|
} else {
|
|
std::string extra(1024, 0);
|
|
size_t n = mz_zip_reader_get_extra(&archive, stat.m_file_index, extra.data(), extra.size());
|
|
dest_file = ZipUnicodePathExtraField::decode(extra.substr(0, n), stat.m_filename);
|
|
}
|
|
std::string temp_path = model.get_auxiliary_file_temp_path();
|
|
//aux directory from model
|
|
boost::filesystem::path dir = boost::filesystem::path(temp_path);
|
|
std::size_t found = dest_file.find(AUXILIARY_DIR);
|
|
if (found != std::string::npos)
|
|
dest_file = dest_file.substr(found + AUXILIARY_STR_LEN);
|
|
else
|
|
return;
|
|
if (dest_file.find('/') != std::string::npos) {
|
|
boost::filesystem::path src_path = boost::filesystem::path(dest_file);
|
|
boost::filesystem::path parent_path = src_path.parent_path();
|
|
std::string temp_path = dir.string() + std::string("/") + parent_path.string();
|
|
boost::filesystem::path parent_full_path = boost::filesystem::path(temp_path);
|
|
if (!boost::filesystem::exists(parent_full_path))
|
|
boost::filesystem::create_directories(parent_full_path);
|
|
}
|
|
dest_file = dir.string() + std::string("/") + dest_file;
|
|
std::string dest_zip_file = encode_path(dest_file.c_str());
|
|
mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0);
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_file % stat.m_filename % res;
|
|
if (res == 0) {
|
|
add_error("Error while extract auxiliary file to file");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_extract_file_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
std::string src_file = decode_path(stat.m_filename);
|
|
// BBS: use backup path
|
|
//aux directory from model
|
|
boost::filesystem::path dest_path = boost::filesystem::path(m_backup_path + "/" + src_file);
|
|
std::string dest_zip_file = encode_path(dest_path.string().c_str());
|
|
mz_bool res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_zip_file.c_str(), 0);
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", extract %1% from 3mf %2%, ret %3%\n") % dest_path % stat.m_filename % res;
|
|
if (res == 0) {
|
|
add_error("Error while extract file to temp directory");
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*void _BBS_3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
|
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
|
if (res == 0) {
|
|
add_error("Error while reading layer heights profile data to buffer");
|
|
return;
|
|
}
|
|
|
|
if (buffer.back() == '\n')
|
|
buffer.pop_back();
|
|
|
|
std::vector<std::string> objects;
|
|
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
|
|
|
|
for (const std::string& object : objects) {
|
|
std::vector<std::string> object_data;
|
|
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
|
|
if (object_data.size() != 2) {
|
|
add_error("Error while reading object data");
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::string> object_data_id;
|
|
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
|
|
if (object_data_id.size() != 2) {
|
|
add_error("Error while reading object id");
|
|
continue;
|
|
}
|
|
|
|
int object_id = std::atoi(object_data_id[1].c_str());
|
|
if (object_id == 0) {
|
|
add_error("Found invalid object id");
|
|
continue;
|
|
}
|
|
|
|
IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id);
|
|
if (object_item != m_layer_heights_profiles.end()) {
|
|
add_error("Found duplicated layer heights profile");
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::string> object_data_profile;
|
|
boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off);
|
|
if (object_data_profile.size() <= 4 || object_data_profile.size() % 2 != 0) {
|
|
add_error("Found invalid layer heights profile");
|
|
continue;
|
|
}
|
|
|
|
std::vector<coordf_t> profile;
|
|
profile.reserve(object_data_profile.size());
|
|
|
|
for (const std::string& value : object_data_profile) {
|
|
profile.push_back((coordf_t)std::atof(value.c_str()));
|
|
}
|
|
|
|
m_layer_heights_profiles.insert({ object_id, profile });
|
|
}
|
|
}
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
|
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
|
if (res == 0) {
|
|
add_error("Error while reading layer config ranges data to buffer");
|
|
return;
|
|
}
|
|
|
|
std::istringstream iss(buffer); // wrap returned xml to istringstream
|
|
pt::ptree objects_tree;
|
|
pt::read_xml(iss, objects_tree);
|
|
|
|
for (const auto& object : objects_tree.get_child("objects")) {
|
|
pt::ptree object_tree = object.second;
|
|
int obj_idx = object_tree.get<int>("<xmlattr>.id", -1);
|
|
if (obj_idx <= 0) {
|
|
add_error("Found invalid object id");
|
|
continue;
|
|
}
|
|
|
|
IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx);
|
|
if (object_item != m_layer_config_ranges.end()) {
|
|
add_error("Found duplicated layer config range");
|
|
continue;
|
|
}
|
|
|
|
t_layer_config_ranges config_ranges;
|
|
|
|
for (const auto& range : object_tree) {
|
|
if (range.first != "range")
|
|
continue;
|
|
pt::ptree range_tree = range.second;
|
|
double min_z = range_tree.get<double>("<xmlattr>.min_z");
|
|
double max_z = range_tree.get<double>("<xmlattr>.max_z");
|
|
|
|
// get Z range information
|
|
DynamicPrintConfig config;
|
|
|
|
for (const auto& option : range_tree) {
|
|
if (option.first != "option")
|
|
continue;
|
|
std::string opt_key = option.second.get<std::string>("<xmlattr>.opt_key");
|
|
std::string value = option.second.data();
|
|
|
|
config.set_deserialize(opt_key, value, config_substitutions);
|
|
}
|
|
|
|
config_ranges[{ min_z, max_z }].assign_config(std::move(config));
|
|
}
|
|
|
|
if (!config_ranges.empty())
|
|
m_layer_config_ranges.insert({ obj_idx, std::move(config_ranges) });
|
|
}
|
|
}
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
|
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
|
if (res == 0) {
|
|
add_error("Error while reading sla support points data to buffer");
|
|
return;
|
|
}
|
|
|
|
if (buffer.back() == '\n')
|
|
buffer.pop_back();
|
|
|
|
std::vector<std::string> objects;
|
|
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
|
|
|
|
// Info on format versioning - see 3mf.hpp
|
|
int version = 0;
|
|
std::string key("support_points_format_version=");
|
|
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
|
|
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
|
|
version = std::stoi(objects[0]);
|
|
objects.erase(objects.begin()); // pop the header
|
|
}
|
|
|
|
for (const std::string& object : objects) {
|
|
std::vector<std::string> object_data;
|
|
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
|
|
|
|
if (object_data.size() != 2) {
|
|
add_error("Error while reading object data");
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::string> object_data_id;
|
|
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
|
|
if (object_data_id.size() != 2) {
|
|
add_error("Error while reading object id");
|
|
continue;
|
|
}
|
|
|
|
int object_id = std::atoi(object_data_id[1].c_str());
|
|
if (object_id == 0) {
|
|
add_error("Found invalid object id");
|
|
continue;
|
|
}
|
|
|
|
IdToSlaSupportPointsMap::iterator object_item = m_sla_support_points.find(object_id);
|
|
if (object_item != m_sla_support_points.end()) {
|
|
add_error("Found duplicated SLA support points");
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::string> object_data_points;
|
|
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
|
|
|
|
std::vector<sla::SupportPoint> sla_support_points;
|
|
|
|
if (version == 0) {
|
|
for (unsigned int i=0; i<object_data_points.size(); i+=3)
|
|
sla_support_points.emplace_back(float(std::atof(object_data_points[i+0].c_str())),
|
|
float(std::atof(object_data_points[i+1].c_str())),
|
|
float(std::atof(object_data_points[i+2].c_str())),
|
|
0.4f,
|
|
false);
|
|
}
|
|
if (version == 1) {
|
|
for (unsigned int i=0; i<object_data_points.size(); i+=5)
|
|
sla_support_points.emplace_back(float(std::atof(object_data_points[i+0].c_str())),
|
|
float(std::atof(object_data_points[i+1].c_str())),
|
|
float(std::atof(object_data_points[i+2].c_str())),
|
|
float(std::atof(object_data_points[i+3].c_str())),
|
|
//FIXME storing boolean as 0 / 1 and importing it as float.
|
|
std::abs(std::atof(object_data_points[i+4].c_str()) - 1.) < EPSILON);
|
|
}
|
|
|
|
if (!sla_support_points.empty())
|
|
m_sla_support_points.insert({ object_id, sla_support_points });
|
|
}
|
|
}
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
std::string buffer(size_t(stat.m_uncomp_size), 0);
|
|
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
|
if (res == 0) {
|
|
add_error("Error while reading sla support points data to buffer");
|
|
return;
|
|
}
|
|
|
|
if (buffer.back() == '\n')
|
|
buffer.pop_back();
|
|
|
|
std::vector<std::string> objects;
|
|
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
|
|
|
|
// Info on format versioning - see 3mf.hpp
|
|
int version = 0;
|
|
std::string key("drain_holes_format_version=");
|
|
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
|
|
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
|
|
version = std::stoi(objects[0]);
|
|
objects.erase(objects.begin()); // pop the header
|
|
}
|
|
|
|
for (const std::string& object : objects) {
|
|
std::vector<std::string> object_data;
|
|
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
|
|
|
|
if (object_data.size() != 2) {
|
|
add_error("Error while reading object data");
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::string> object_data_id;
|
|
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
|
|
if (object_data_id.size() != 2) {
|
|
add_error("Error while reading object id");
|
|
continue;
|
|
}
|
|
|
|
int object_id = std::atoi(object_data_id[1].c_str());
|
|
if (object_id == 0) {
|
|
add_error("Found invalid object id");
|
|
continue;
|
|
}
|
|
|
|
IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id);
|
|
if (object_item != m_sla_drain_holes.end()) {
|
|
add_error("Found duplicated SLA drain holes");
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::string> object_data_points;
|
|
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
|
|
|
|
sla::DrainHoles sla_drain_holes;
|
|
|
|
if (version == 1) {
|
|
for (unsigned int i=0; i<object_data_points.size(); i+=8)
|
|
sla_drain_holes.emplace_back(Vec3f{float(std::atof(object_data_points[i+0].c_str())),
|
|
float(std::atof(object_data_points[i+1].c_str())),
|
|
float(std::atof(object_data_points[i+2].c_str()))},
|
|
Vec3f{float(std::atof(object_data_points[i+3].c_str())),
|
|
float(std::atof(object_data_points[i+4].c_str())),
|
|
float(std::atof(object_data_points[i+5].c_str()))},
|
|
float(std::atof(object_data_points[i+6].c_str())),
|
|
float(std::atof(object_data_points[i+7].c_str())));
|
|
}
|
|
|
|
// The holes are saved elevated above the mesh and deeper (bad idea indeed).
|
|
// This is retained for compatibility.
|
|
// Place the hole to the mesh and make it shallower to compensate.
|
|
// The offset is 1 mm above the mesh.
|
|
for (sla::DrainHole& hole : sla_drain_holes) {
|
|
hole.pos += hole.normal.normalized();
|
|
hole.height -= 1.f;
|
|
}
|
|
|
|
if (!sla_drain_holes.empty())
|
|
m_sla_drain_holes.insert({ object_id, sla_drain_holes });
|
|
}
|
|
}
|
|
}*/
|
|
|
|
void _BBS_3MF_Importer::_extract_custom_gcode_per_print_z_from_archive(::mz_zip_archive &archive, const mz_zip_archive_file_stat &stat)
|
|
{
|
|
if (stat.m_uncomp_size > 0) {
|
|
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
|
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
|
if (res == 0) {
|
|
add_error("Error while reading custom Gcodes per height data to buffer");
|
|
return;
|
|
}
|
|
|
|
std::istringstream iss(buffer); // wrap returned xml to istringstream
|
|
pt::ptree main_tree;
|
|
pt::read_xml(iss, main_tree);
|
|
|
|
if (main_tree.front().first != "custom_gcodes_per_layer")
|
|
return;
|
|
pt::ptree code_tree = main_tree.front().second;
|
|
|
|
m_model->custom_gcode_per_print_z.gcodes.clear();
|
|
|
|
for (const auto& code : code_tree) {
|
|
if (code.first == "mode") {
|
|
pt::ptree tree = code.second;
|
|
std::string mode = tree.get<std::string>("<xmlattr>.value");
|
|
m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
|
|
mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle :
|
|
CustomGCode::Mode::MultiExtruder;
|
|
}
|
|
if (code.first != "layer")
|
|
continue;
|
|
|
|
pt::ptree tree = code.second;
|
|
double print_z = tree.get<double> ("<xmlattr>.top_z" );
|
|
int extruder = tree.get<int> ("<xmlattr>.extruder");
|
|
std::string color = tree.get<std::string> ("<xmlattr>.color" );
|
|
|
|
CustomGCode::Type type;
|
|
std::string extra;
|
|
pt::ptree attr_tree = tree.find("<xmlattr>")->second;
|
|
if (attr_tree.find("type") == attr_tree.not_found()) {
|
|
// It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
|
|
// read old data ...
|
|
std::string gcode = tree.get<std::string> ("<xmlattr>.gcode");
|
|
// ... and interpret them to the new data
|
|
type = gcode == "M600" ? CustomGCode::ColorChange :
|
|
gcode == "M601" ? CustomGCode::PausePrint :
|
|
gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom;
|
|
extra = type == CustomGCode::PausePrint ? color :
|
|
type == CustomGCode::Custom ? gcode : "";
|
|
}
|
|
else {
|
|
type = static_cast<CustomGCode::Type>(tree.get<int>("<xmlattr>.type"));
|
|
extra = tree.get<std::string>("<xmlattr>.extra");
|
|
}
|
|
m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ;
|
|
}
|
|
}
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes)
|
|
{
|
|
if (m_xml_parser == nullptr)
|
|
return;
|
|
|
|
bool res = true;
|
|
unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser);
|
|
|
|
if (::strcmp(MODEL_TAG, name) == 0)
|
|
res = _handle_start_model(attributes, num_attributes);
|
|
else if (::strcmp(RESOURCES_TAG, name) == 0)
|
|
res = _handle_start_resources(attributes, num_attributes);
|
|
else if (::strcmp(OBJECT_TAG, name) == 0)
|
|
res = _handle_start_object(attributes, num_attributes);
|
|
else if (::strcmp(MESH_TAG, name) == 0)
|
|
res = _handle_start_mesh(attributes, num_attributes);
|
|
else if (::strcmp(VERTICES_TAG, name) == 0)
|
|
res = _handle_start_vertices(attributes, num_attributes);
|
|
else if (::strcmp(VERTEX_TAG, name) == 0)
|
|
res = _handle_start_vertex(attributes, num_attributes);
|
|
else if (::strcmp(TRIANGLES_TAG, name) == 0)
|
|
res = _handle_start_triangles(attributes, num_attributes);
|
|
else if (::strcmp(TRIANGLE_TAG, name) == 0)
|
|
res = _handle_start_triangle(attributes, num_attributes);
|
|
else if (::strcmp(COMPONENTS_TAG, name) == 0)
|
|
res = _handle_start_components(attributes, num_attributes);
|
|
else if (::strcmp(COMPONENT_TAG, name) == 0)
|
|
res = _handle_start_component(attributes, num_attributes);
|
|
else if (::strcmp(BUILD_TAG, name) == 0)
|
|
res = _handle_start_build(attributes, num_attributes);
|
|
else if (::strcmp(ITEM_TAG, name) == 0)
|
|
res = _handle_start_item(attributes, num_attributes);
|
|
else if (::strcmp(METADATA_TAG, name) == 0)
|
|
res = _handle_start_metadata(attributes, num_attributes);
|
|
|
|
if (!res)
|
|
_stop_xml_parser();
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_handle_end_model_xml_element(const char* name)
|
|
{
|
|
if (m_xml_parser == nullptr)
|
|
return;
|
|
|
|
bool res = true;
|
|
|
|
if (::strcmp(MODEL_TAG, name) == 0)
|
|
res = _handle_end_model();
|
|
else if (::strcmp(RESOURCES_TAG, name) == 0)
|
|
res = _handle_end_resources();
|
|
else if (::strcmp(OBJECT_TAG, name) == 0)
|
|
res = _handle_end_object();
|
|
else if (::strcmp(MESH_TAG, name) == 0)
|
|
res = _handle_end_mesh();
|
|
else if (::strcmp(VERTICES_TAG, name) == 0)
|
|
res = _handle_end_vertices();
|
|
else if (::strcmp(VERTEX_TAG, name) == 0)
|
|
res = _handle_end_vertex();
|
|
else if (::strcmp(TRIANGLES_TAG, name) == 0)
|
|
res = _handle_end_triangles();
|
|
else if (::strcmp(TRIANGLE_TAG, name) == 0)
|
|
res = _handle_end_triangle();
|
|
else if (::strcmp(COMPONENTS_TAG, name) == 0)
|
|
res = _handle_end_components();
|
|
else if (::strcmp(COMPONENT_TAG, name) == 0)
|
|
res = _handle_end_component();
|
|
else if (::strcmp(BUILD_TAG, name) == 0)
|
|
res = _handle_end_build();
|
|
else if (::strcmp(ITEM_TAG, name) == 0)
|
|
res = _handle_end_item();
|
|
else if (::strcmp(METADATA_TAG, name) == 0)
|
|
res = _handle_end_metadata();
|
|
|
|
if (!res)
|
|
_stop_xml_parser();
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_handle_xml_characters(const XML_Char* s, int len)
|
|
{
|
|
m_curr_characters.append(s, len);
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes)
|
|
{
|
|
if (m_xml_parser == nullptr)
|
|
return;
|
|
|
|
bool res = true;
|
|
unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser);
|
|
|
|
if (::strcmp(CONFIG_TAG, name) == 0)
|
|
res = _handle_start_config(attributes, num_attributes);
|
|
else if (::strcmp(OBJECT_TAG, name) == 0)
|
|
res = _handle_start_config_object(attributes, num_attributes);
|
|
else if (::strcmp(VOLUME_TAG, name) == 0)
|
|
res = _handle_start_config_volume(attributes, num_attributes);
|
|
else if (::strcmp(PART_TAG, name) == 0)
|
|
res = _handle_start_config_volume(attributes, num_attributes);
|
|
else if (::strcmp(MESH_STAT_TAG, name) == 0)
|
|
res = _handle_start_config_volume_mesh(attributes, num_attributes);
|
|
else if (::strcmp(METADATA_TAG, name) == 0)
|
|
res = _handle_start_config_metadata(attributes, num_attributes);
|
|
else if (::strcmp(PLATE_TAG, name) == 0)
|
|
res = _handle_start_config_plater(attributes, num_attributes);
|
|
else if (::strcmp(INSTANCE_TAG, name) == 0)
|
|
res = _handle_start_config_plater_instance(attributes, num_attributes);
|
|
else if (::strcmp(FILAMENT_TAG, name) == 0)
|
|
res = _handle_start_config_filament(attributes, num_attributes);
|
|
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
|
|
res = _handle_start_assemble(attributes, num_attributes);
|
|
else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0)
|
|
res = _handle_start_assemble_item(attributes, num_attributes);
|
|
|
|
if (!res)
|
|
_stop_xml_parser();
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_handle_end_config_xml_element(const char* name)
|
|
{
|
|
if (m_xml_parser == nullptr)
|
|
return;
|
|
|
|
bool res = true;
|
|
|
|
if (::strcmp(CONFIG_TAG, name) == 0)
|
|
res = _handle_end_config();
|
|
else if (::strcmp(OBJECT_TAG, name) == 0)
|
|
res = _handle_end_config_object();
|
|
else if (::strcmp(VOLUME_TAG, name) == 0)
|
|
res = _handle_end_config_volume();
|
|
else if (::strcmp(PART_TAG, name) == 0)
|
|
res = _handle_end_config_volume();
|
|
else if (::strcmp(MESH_STAT_TAG, name) == 0)
|
|
res = _handle_end_config_volume_mesh();
|
|
else if (::strcmp(METADATA_TAG, name) == 0)
|
|
res = _handle_end_config_metadata();
|
|
else if (::strcmp(PLATE_TAG, name) == 0)
|
|
res = _handle_end_config_plater();
|
|
else if (::strcmp(FILAMENT_TAG, name) == 0)
|
|
res = _handle_end_config_filament();
|
|
else if (::strcmp(INSTANCE_TAG, name) == 0)
|
|
res = _handle_end_config_plater_instance();
|
|
else if (::strcmp(ASSEMBLE_TAG, name) == 0)
|
|
res = _handle_end_assemble();
|
|
else if (::strcmp(ASSEMBLE_ITEM_TAG, name) == 0)
|
|
res = _handle_end_assemble_item();
|
|
|
|
if (!res)
|
|
_stop_xml_parser();
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_model(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
m_unit_factor = bbs_get_unit_factor(bbs_get_attribute_value_string(attributes, num_attributes, UNIT_ATTR));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_model()
|
|
{
|
|
// BBS: Production Extension
|
|
if (!m_sub_model_path.empty())
|
|
return true;
|
|
|
|
// deletes all non-built or non-instanced objects
|
|
for (const IdToModelObjectMap::value_type& object : m_objects) {
|
|
if (object.second >= int(m_model->objects.size())) {
|
|
add_error("Unable to find object");
|
|
return false;
|
|
}
|
|
ModelObject *model_object = m_model->objects[object.second];
|
|
if (model_object != nullptr && model_object->instances.size() == 0)
|
|
m_model->delete_object(model_object);
|
|
}
|
|
|
|
//construct the index maps
|
|
for (const IdToCurrentObjectMap::value_type& object : m_current_objects) {
|
|
m_index_paths.insert({ object.first.second, object.first.first});
|
|
}
|
|
|
|
if (m_version == 0) {
|
|
// if the 3mf was not produced by BambuStudio and there is only one object,
|
|
// set the object name to match the filename
|
|
if (m_model->objects.size() == 1)
|
|
m_model->objects.front()->name = m_name;
|
|
}
|
|
|
|
// applies instances' matrices
|
|
for (Instance& instance : m_instances) {
|
|
if (instance.instance != nullptr && instance.instance->get_object() != nullptr)
|
|
// apply the transform to the instance
|
|
_apply_transform(*instance.instance, instance.transform);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_resources(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_resources()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_object(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// reset current object data
|
|
if (m_curr_object) {
|
|
delete m_curr_object;
|
|
m_curr_object = nullptr;
|
|
}
|
|
|
|
std::string object_type = bbs_get_attribute_value_string(attributes, num_attributes, TYPE_ATTR);
|
|
|
|
if (bbs_is_valid_object_type(object_type)) {
|
|
if (!m_curr_object) {
|
|
m_curr_object = new CurrentObject();
|
|
// create new object (it may be removed later if no instances are generated from it)
|
|
/*m_curr_object->model_object_idx = (int)m_model->objects.size();
|
|
m_curr_object.object = m_model->add_object();
|
|
if (m_curr_object.object == nullptr) {
|
|
add_error("Unable to create object");
|
|
return false;
|
|
}*/
|
|
}
|
|
|
|
m_curr_object->id = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR);
|
|
m_curr_object->name = bbs_get_attribute_value_string(attributes, num_attributes, NAME_ATTR);
|
|
|
|
m_curr_object->uuid = bbs_get_attribute_value_string(attributes, num_attributes, PUUID_ATTR);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_object()
|
|
{
|
|
if (!m_curr_object || (m_curr_object->id == -1)) {
|
|
add_error("Found invalid object");
|
|
return false;
|
|
}
|
|
else {
|
|
if (m_is_bbl_3mf && boost::ends_with(m_curr_object->uuid, OBJECT_UUID_SUFFIX) && m_load_restore) {
|
|
std::istringstream iss(m_curr_object->uuid);
|
|
int backup_id;
|
|
bool need_replace = false;
|
|
if (iss >> std::hex >> backup_id) {
|
|
need_replace = (m_curr_object->id != backup_id);
|
|
m_curr_object->id = backup_id;
|
|
}
|
|
//if (need_replace)
|
|
{
|
|
for (int index = 0; index < m_curr_object->components.size(); index++)
|
|
{
|
|
int temp_id = (index + 1) << 16 | backup_id;
|
|
Component& component = m_curr_object->components[index];
|
|
std::string new_path = component.object_id.first;
|
|
Id new_id = std::make_pair(new_path, temp_id);
|
|
IdToCurrentObjectMap::iterator current_object = m_current_objects.find(component.object_id);
|
|
if (current_object != m_current_objects.end()) {
|
|
CurrentObject new_object;
|
|
new_object.geometry = std::move(current_object->second.geometry);
|
|
new_object.id = temp_id;
|
|
new_object.model_object_idx = current_object->second.model_object_idx;
|
|
new_object.name = current_object->second.name;
|
|
new_object.uuid = current_object->second.uuid;
|
|
|
|
m_current_objects.erase(current_object);
|
|
m_current_objects.insert({ new_id, std::move(new_object) });
|
|
}
|
|
else {
|
|
add_error("can not find object for component, id=" + std::to_string(component.object_id.second));
|
|
delete m_curr_object;
|
|
m_curr_object = nullptr;
|
|
return false;
|
|
}
|
|
|
|
component.object_id.second = temp_id;
|
|
}
|
|
}
|
|
}
|
|
Id id = std::make_pair(m_sub_model_path, m_curr_object->id);
|
|
if (m_current_objects.find(id) == m_current_objects.end()) {
|
|
m_current_objects.insert({ id, std::move(*m_curr_object) });
|
|
delete m_curr_object;
|
|
m_curr_object = nullptr;
|
|
}
|
|
else {
|
|
add_error("Found object with duplicate id");
|
|
delete m_curr_object;
|
|
m_curr_object = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*if (m_curr_object.object != nullptr) {
|
|
if (m_curr_object.id != -1) {
|
|
if (m_curr_object.geometry.empty()) {
|
|
// no geometry defined
|
|
// remove the object from the model
|
|
m_model->delete_object(m_curr_object.object);
|
|
|
|
if (m_curr_object.components.empty()) {
|
|
// no components defined -> invalid object, delete it
|
|
IdToModelObjectMap::iterator object_item = m_objects.find(id);
|
|
if (object_item != m_objects.end())
|
|
m_objects.erase(object_item);
|
|
|
|
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(id);
|
|
if (alias_item != m_objects_aliases.end())
|
|
m_objects_aliases.erase(alias_item);
|
|
}
|
|
else
|
|
// adds components to aliases
|
|
m_objects_aliases.insert({ id, m_curr_object.components });
|
|
}
|
|
else {
|
|
// geometry defined, store it for later use
|
|
m_geometries.insert({ id, std::move(m_curr_object.geometry) });
|
|
|
|
// stores the object for later use
|
|
if (m_objects.find(id) == m_objects.end()) {
|
|
m_objects.insert({ id, m_curr_object.model_object_idx });
|
|
m_objects_aliases.insert({ id, { 1, Component(m_curr_object.id) } }); // aliases itself
|
|
}
|
|
else {
|
|
add_error("Found object with duplicate id");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
//sub objects
|
|
}
|
|
}*/
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_mesh(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// reset current geometry
|
|
if (m_curr_object)
|
|
m_curr_object->geometry.reset();
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_mesh()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_vertices(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// reset current vertices
|
|
if (m_curr_object)
|
|
m_curr_object->geometry.vertices.clear();
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_vertices()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_vertex(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// appends the vertex coordinates
|
|
// missing values are set equal to ZERO
|
|
if (m_curr_object)
|
|
m_curr_object->geometry.vertices.emplace_back(
|
|
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, X_ATTR),
|
|
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Y_ATTR),
|
|
m_unit_factor * bbs_get_attribute_value_float(attributes, num_attributes, Z_ATTR));
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_vertex()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// reset current triangles
|
|
if (m_curr_object)
|
|
m_curr_object->geometry.triangles.clear();
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_triangles()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_triangle(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// we are ignoring the following attributes:
|
|
// p1
|
|
// p2
|
|
// p3
|
|
// pid
|
|
// see specifications
|
|
|
|
// appends the triangle's vertices indices
|
|
// missing values are set equal to ZERO
|
|
if (m_curr_object) {
|
|
m_curr_object->geometry.triangles.emplace_back(
|
|
bbs_get_attribute_value_int(attributes, num_attributes, V1_ATTR),
|
|
bbs_get_attribute_value_int(attributes, num_attributes, V2_ATTR),
|
|
bbs_get_attribute_value_int(attributes, num_attributes, V3_ATTR));
|
|
|
|
m_curr_object->geometry.custom_supports.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
|
|
m_curr_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
|
|
m_curr_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
|
|
// BBS
|
|
m_curr_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_triangle()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// reset current components
|
|
if (m_curr_object)
|
|
m_curr_object->components.clear();
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_components()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
int object_id = bbs_get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
|
|
Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
|
|
|
/*Id id = std::make_pair(m_sub_model_path, object_id);
|
|
IdToModelObjectMap::iterator object_item = m_objects.find(id);
|
|
if (object_item == m_objects.end()) {
|
|
IdToAliasesMap::iterator alias_item = m_objects_aliases.find(id);
|
|
if (alias_item == m_objects_aliases.end()) {
|
|
add_error("Found component with invalid object id");
|
|
return false;
|
|
}
|
|
}*/
|
|
|
|
if (m_curr_object) {
|
|
Id id = std::make_pair(m_sub_model_path, object_id);
|
|
m_curr_object->components.emplace_back(id, transform);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_component()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_build(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_build()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_item(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// we are ignoring the following attributes
|
|
// thumbnail
|
|
// partnumber
|
|
// pid
|
|
// pindex
|
|
// see specifications
|
|
|
|
int object_id = bbs_get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
|
|
std::string path = bbs_get_attribute_value_string(attributes, num_attributes, PPATH_ATTR);
|
|
Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
|
int printable = bbs_get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR);
|
|
|
|
return _create_object_instance(path, object_id, transform, printable, 1);
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_item()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
m_curr_characters.clear();
|
|
|
|
std::string name = bbs_get_attribute_value_string(attributes, num_attributes, NAME_ATTR);
|
|
if (!name.empty()) {
|
|
m_curr_metadata_name = name;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline static void check_painting_version(unsigned int loaded_version, unsigned int highest_supported_version, const std::string &error_msg)
|
|
{
|
|
if (loaded_version > highest_supported_version)
|
|
throw version_error(error_msg);
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_metadata()
|
|
{
|
|
if ((m_curr_metadata_name == BBS_3MF_VERSION)||(m_curr_metadata_name == BBS_3MF_VERSION1)) {
|
|
m_is_bbl_3mf = true;
|
|
m_version = (unsigned int)atoi(m_curr_characters.c_str());
|
|
/*if (m_check_version && (m_version > VERSION_BBS_3MF_COMPATIBLE)) {
|
|
// std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
|
|
// throw version_error(msg.c_str());
|
|
const std::string msg = (boost::format(_(L("The selected 3mf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str();
|
|
throw version_error(msg);
|
|
}*/
|
|
} else if (m_curr_metadata_name == "Application") {
|
|
// Generator application of the 3MF.
|
|
// SLIC3R_APP_KEY - SLIC3R_VERSION
|
|
if (boost::starts_with(m_curr_characters, "BambuStudio-"))
|
|
m_bambuslicer_generator_version = Semver::parse(m_curr_characters.substr(12));
|
|
//TODO: currently use version 0, no need to load&&save this string
|
|
/*} else if (m_curr_metadata_name == BBS_FDM_SUPPORTS_PAINTING_VERSION) {
|
|
m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str());
|
|
check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION,
|
|
_(L("The selected 3MF contains FDM supports painted object using a newer version of BambuStudio and is not compatible.")));
|
|
} else if (m_curr_metadata_name == BBS_SEAM_PAINTING_VERSION) {
|
|
m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str());
|
|
check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION,
|
|
_(L("The selected 3MF contains seam painted object using a newer version of BambuStudio and is not compatible.")));
|
|
} else if (m_curr_metadata_name == BBS_MM_PAINTING_VERSION) {
|
|
m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str());
|
|
check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION,
|
|
_(L("The selected 3MF contains multi-material painted object using a newer version of BambuStudio and is not compatible.")));*/
|
|
} else if (m_curr_metadata_name == BBL_MODEL_ID_TAG) {
|
|
m_model_id = m_curr_characters;
|
|
} else if (m_curr_metadata_name == BBL_MODEL_NAME_TAG) {
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found model name = " << m_curr_characters;
|
|
model_info.model_name = m_curr_characters;
|
|
} else if (m_curr_metadata_name == BBL_DESIGNER_TAG) {
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer = " << m_curr_characters;
|
|
m_designer = m_curr_characters;
|
|
} else if (m_curr_metadata_name == BBL_DESIGNER_USER_ID_TAG) {
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer_user_id = " << m_curr_characters;
|
|
m_designer_user_id = m_curr_characters;
|
|
} else if (m_curr_metadata_name == BBL_DESIGNER_COVER_FILE_TAG) {
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found designer_cover = " << m_curr_characters;
|
|
model_info.cover_file = m_curr_characters;
|
|
} else if (m_curr_metadata_name == BBL_DESCRIPTION_TAG) {
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found description = " << m_curr_characters;
|
|
model_info.description = m_curr_characters;
|
|
} else if (m_curr_metadata_name == BBL_LICENSE_TAG) {
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found license = " << m_curr_characters;
|
|
model_info.license = m_curr_characters;
|
|
} else if (m_curr_metadata_name == BBL_COPYRIGHT_TAG) {
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found copyright = " << m_curr_characters;
|
|
model_info.copyright = m_curr_characters;
|
|
} else if (m_curr_metadata_name == BBL_REGION_TAG) {
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, load_3mf found region = " << m_curr_characters;
|
|
m_contry_code = m_curr_characters;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_create_object_instance(std::string const & path, int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter)
|
|
{
|
|
static const unsigned int MAX_RECURSIONS = 10;
|
|
|
|
// escape from circular aliasing
|
|
if (recur_counter > MAX_RECURSIONS) {
|
|
add_error("Too many recursions");
|
|
return false;
|
|
}
|
|
|
|
Id id{path, object_id};
|
|
IdToCurrentObjectMap::iterator it = m_current_objects.find(id);
|
|
if (it == m_current_objects.end()) {
|
|
add_error("can not find object id " + std::to_string(object_id) + " to builditem");
|
|
return false;
|
|
}
|
|
|
|
IdToModelObjectMap::iterator object_item = m_objects.find(id);
|
|
if (object_item == m_objects.end()) {
|
|
//add object
|
|
CurrentObject& current_object = it->second;
|
|
int object_index = (int)m_model->objects.size();
|
|
ModelObject* model_object = m_model->add_object();
|
|
if (model_object == nullptr) {
|
|
add_error("Unable to create object for builditem, id " + std::to_string(object_id));
|
|
return false;
|
|
}
|
|
m_objects.insert({ id, object_index });
|
|
current_object.model_object_idx = object_index;
|
|
current_object.object = model_object;
|
|
|
|
ModelInstance* instance = m_model->objects[object_index]->add_instance();
|
|
if (instance == nullptr) {
|
|
add_error("error when add object instance for id " + std::to_string(object_id));
|
|
return false;
|
|
}
|
|
instance->printable = printable;
|
|
|
|
m_instances.emplace_back(instance, transform);
|
|
|
|
if (m_is_bbl_3mf && boost::ends_with(current_object.uuid, OBJECT_UUID_SUFFIX)) {
|
|
std::istringstream iss(current_object.uuid);
|
|
int backup_id;
|
|
if (iss >> std::hex >> backup_id) {
|
|
m_model->set_object_backup_id(*model_object, backup_id);
|
|
}
|
|
}
|
|
/*if (!current_object.geometry.empty()) {
|
|
}
|
|
else if (!current_object.components.empty()) {
|
|
// recursively process nested components
|
|
for (const Component& component : it->second) {
|
|
if (!_create_object_instance(path, component.object_id, transform * component.transform, printable, recur_counter + 1))
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
add_error("can not construct build items with invalid object, id " + std::to_string(object_id));
|
|
return false;
|
|
}*/
|
|
}
|
|
else {
|
|
//add instance
|
|
ModelInstance* instance = m_model->objects[object_item->second]->add_instance();
|
|
if (instance == nullptr) {
|
|
add_error("error when add object instance for id " + std::to_string(object_id));
|
|
return false;
|
|
}
|
|
instance->printable = printable;
|
|
|
|
m_instances.emplace_back(instance, transform);
|
|
}
|
|
|
|
/*if (it->second.size() == 1 && it->second[0].object_id == object_id) {
|
|
// aliasing to itself
|
|
|
|
IdToModelObjectMap::iterator object_item = m_objects.find(id);
|
|
if (object_item == m_objects.end() || object_item->second == -1) {
|
|
add_error("Found invalid object");
|
|
return false;
|
|
}
|
|
else {
|
|
ModelInstance* instance = m_model->objects[object_item->second]->add_instance();
|
|
if (instance == nullptr) {
|
|
add_error("Unable to add object instance");
|
|
return false;
|
|
}
|
|
instance->printable = printable;
|
|
|
|
m_instances.emplace_back(instance, transform);
|
|
}
|
|
}
|
|
else {
|
|
// recursively process nested components
|
|
for (const Component& component : it->second) {
|
|
if (!_create_object_instance(path, component.object_id, transform * component.transform, printable, recur_counter + 1))
|
|
return false;
|
|
}
|
|
}*/
|
|
|
|
return true;
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform)
|
|
{
|
|
Slic3r::Geometry::Transformation t(transform);
|
|
// invalid scale value, return
|
|
if (!t.get_scaling_factor().all())
|
|
return;
|
|
|
|
instance.set_transformation(t);
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_config()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_config_object(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
int object_id = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR);
|
|
IdToMetadataMap::iterator object_item = m_objects_metadata.find(object_id);
|
|
if (object_item != m_objects_metadata.end()) {
|
|
add_error("Duplicated object id: " + std::to_string(object_id) + " in model_settings.config");
|
|
return false;
|
|
}
|
|
|
|
// Added because of github #3435, currently not used by PrusaSlicer
|
|
// int instances_count_id = bbs_get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR);
|
|
|
|
m_objects_metadata.insert({ object_id, ObjectMetadata() });
|
|
m_curr_config.object_id = object_id;
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_config_object()
|
|
{
|
|
m_curr_config.object_id = -1;
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_config_volume(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
|
|
if (object == m_objects_metadata.end()) {
|
|
add_error("can not find object for part, id " + std::to_string(m_curr_config.object_id) );
|
|
return false;
|
|
}
|
|
|
|
m_curr_config.volume_id = (int)object->second.volumes.size();
|
|
|
|
unsigned int first_triangle_id = (unsigned int)bbs_get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR);
|
|
unsigned int last_triangle_id = (unsigned int)bbs_get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR);
|
|
|
|
//BBS: refine the part type logic
|
|
std::string subtype_str = bbs_get_attribute_value_string(attributes, num_attributes, SUBTYPE_ATTR);
|
|
ModelVolumeType type = ModelVolume::type_from_string(subtype_str);
|
|
|
|
int subbject_id = bbs_get_attribute_value_int(attributes, num_attributes, ID_ATTR);
|
|
|
|
if (last_triangle_id > 0)
|
|
object->second.volumes.emplace_back(first_triangle_id, last_triangle_id, type);
|
|
else
|
|
object->second.volumes.emplace_back(subbject_id, type);
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
|
|
if (object == m_objects_metadata.end()) {
|
|
add_error("can not find object for mesh_stats, id " + std::to_string(m_curr_config.object_id) );
|
|
return false;
|
|
}
|
|
if ((m_curr_config.volume_id == -1) || ((object->second.volumes.size() - 1) < m_curr_config.volume_id)) {
|
|
add_error("can not find part for mesh_stats");
|
|
return false;
|
|
}
|
|
|
|
ObjectMetadata::VolumeMetadata& volume = object->second.volumes[m_curr_config.volume_id];
|
|
|
|
int edges_fixed = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_EDGES_FIXED );
|
|
int degenerate_facets = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_DEGENERATED_FACETS);
|
|
int facets_removed = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_REMOVED );
|
|
int facets_reversed = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_RESERVED );
|
|
int backwards_edges = bbs_get_attribute_value_int(attributes, num_attributes, MESH_STAT_BACKWARDS_EDGES );
|
|
|
|
volume.mesh_stats = { edges_fixed, degenerate_facets, facets_removed, facets_reversed, backwards_edges };
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_config_volume()
|
|
{
|
|
m_curr_config.volume_id = -1;
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_config_volume_mesh()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_config_metadata(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
//std::string type = bbs_get_attribute_value_string(attributes, num_attributes, TYPE_ATTR);
|
|
std::string key = bbs_get_attribute_value_string(attributes, num_attributes, KEY_ATTR);
|
|
std::string value = bbs_get_attribute_value_string(attributes, num_attributes, VALUE_ATTR);
|
|
|
|
if ((m_curr_plater == nullptr)&&!m_parsing_slice_info)
|
|
{
|
|
IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id);
|
|
if (object == m_objects_metadata.end()) {
|
|
add_error("Cannot find object for metadata, id " + std::to_string(m_curr_config.object_id));
|
|
return false;
|
|
}
|
|
if (m_curr_config.volume_id == -1)
|
|
object->second.metadata.emplace_back(key, value);
|
|
else {
|
|
if (size_t(m_curr_config.volume_id) < object->second.volumes.size())
|
|
object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//plater
|
|
if (key == PLATERID_ATTR)
|
|
{
|
|
m_curr_plater->plate_index = atoi(value.c_str());
|
|
}
|
|
else if (key == LOCK_ATTR)
|
|
{
|
|
std::istringstream(value) >> std::boolalpha >> m_curr_plater->locked;
|
|
}
|
|
else if (key == GCODE_FILE_ATTR)
|
|
{
|
|
m_curr_plater->gcode_file = value;
|
|
}
|
|
else if (key == THUMBNAIL_FILE_ATTR)
|
|
{
|
|
m_curr_plater->thumbnail_file = value;
|
|
}
|
|
else if (key == PATTERN_FILE_ATTR)
|
|
{
|
|
m_curr_plater->pattern_file = value;
|
|
}
|
|
else if (key == PATTERN_BBOX_FILE_ATTR)
|
|
{
|
|
m_curr_plater->pattern_bbox_file = value;
|
|
}
|
|
else if (key == INSTANCEID_ATTR)
|
|
{
|
|
m_curr_instance.instance_id = atoi(value.c_str());
|
|
}
|
|
else if (key == OBJECT_ID_ATTR)
|
|
{
|
|
int obj_id = atoi(value.c_str());
|
|
m_curr_instance.object_id = -1;
|
|
IndexToPathMap::iterator index_iter = m_index_paths.find(obj_id);
|
|
if (index_iter == m_index_paths.end()) {
|
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__
|
|
<< boost::format(", can not find object for plate's item, id=%1%, skip this object")%obj_id;
|
|
return true;
|
|
}
|
|
Id temp_id = std::make_pair(index_iter->second, index_iter->first);
|
|
IdToModelObjectMap::iterator object_item = m_objects.find(temp_id);
|
|
if (object_item == m_objects.end()) {
|
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ":" << __LINE__
|
|
<< boost::format(", can not find object for plate's item, ID <%1%, %2%>, skip this object")%index_iter->second %index_iter->first;
|
|
return true;
|
|
}
|
|
m_curr_instance.object_id = object_item->second;
|
|
}
|
|
else if (key == PLATE_IDX_ATTR)
|
|
{
|
|
int plate_index = atoi(value.c_str());
|
|
std::map<int, PlateData*>::iterator it = m_plater_data.find(plate_index);
|
|
if (it != m_plater_data.end())
|
|
m_curr_plater = it->second;
|
|
}
|
|
else if (key == SLICE_PREDICTION_ATTR)
|
|
{
|
|
if (m_curr_plater)
|
|
m_curr_plater->gcode_prediction = value;
|
|
}
|
|
else if (key == SLICE_WEIGHT_ATTR)
|
|
{
|
|
if (m_curr_plater)
|
|
m_curr_plater->gcode_weight = value;
|
|
}
|
|
else if (key == OUTSIDE_ATTR)
|
|
{
|
|
if (m_curr_plater)
|
|
std::istringstream(value) >> std::boolalpha >> m_curr_plater->toolpath_outside;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_config_metadata()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_config_filament(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
if (m_curr_plater) {
|
|
std::string id = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_ID_TAG);
|
|
std::string type = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_TYPE_TAG);
|
|
std::string color = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_COLOR_TAG);
|
|
std::string used_m = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_M_TAG);
|
|
std::string used_g = bbs_get_attribute_value_string(attributes, num_attributes, FILAMENT_USED_G_TAG);
|
|
|
|
FilamentInfo filament_info;
|
|
filament_info.id = atoi(id.c_str()) - 1;
|
|
filament_info.type = type;
|
|
filament_info.color = color;
|
|
filament_info.used_m = atof(used_m.c_str());
|
|
filament_info.used_g = atof(used_g.c_str());
|
|
m_curr_plater->slice_filaments_info.push_back(filament_info);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_config_filament()
|
|
{
|
|
// do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_config_plater(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
if (!m_parsing_slice_info) {
|
|
m_curr_plater = new PlateData();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_config_plater()
|
|
{
|
|
if (!m_curr_plater)
|
|
{
|
|
add_error("don't find plater created before");
|
|
return false;
|
|
}
|
|
m_plater_data.emplace(m_curr_plater->plate_index, m_curr_plater);
|
|
m_curr_plater = nullptr;
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_config_plater_instance(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
if (!m_curr_plater)
|
|
{
|
|
add_error("don't find plater created before");
|
|
return false;
|
|
}
|
|
|
|
//do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_config_plater_instance()
|
|
{
|
|
if (!m_curr_plater)
|
|
{
|
|
add_error("don't find plater created before");
|
|
return false;
|
|
}
|
|
if ((m_curr_instance.object_id == -1) || (m_curr_instance.instance_id == -1))
|
|
{
|
|
//add_error("invalid object id/instance id");
|
|
//skip this instance
|
|
m_curr_instance.object_id = m_curr_instance.instance_id = -1;
|
|
return true;
|
|
}
|
|
|
|
m_curr_plater->objects_and_instances.emplace_back(m_curr_instance.object_id, m_curr_instance.instance_id);
|
|
m_curr_instance.object_id = m_curr_instance.instance_id = -1;
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_assemble(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_assemble()
|
|
{
|
|
//do nothing
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_assemble_item(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
int object_id = bbs_get_attribute_value_int(attributes, num_attributes, OBJECT_ID_ATTR);
|
|
int instance_id = bbs_get_attribute_value_int(attributes, num_attributes, INSTANCEID_ATTR);
|
|
|
|
IndexToPathMap::iterator index_iter = m_index_paths.find(object_id);
|
|
if (index_iter == m_index_paths.end()) {
|
|
add_error("can not find object for assemble item, id= " + std::to_string(object_id));
|
|
return false;
|
|
}
|
|
Id temp_id = std::make_pair(index_iter->second, index_iter->first);
|
|
IdToModelObjectMap::iterator object_item = m_objects.find(temp_id);
|
|
if (object_item == m_objects.end()) {
|
|
add_error("can not find object for assemble item, id= " + std::to_string(object_id));
|
|
return false;
|
|
}
|
|
object_id = object_item->second;
|
|
|
|
Transform3d transform = bbs_get_transform_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
|
|
Vec3d ofs2ass = bbs_get_offset_from_3mf_specs_string(bbs_get_attribute_value_string(attributes, num_attributes, OFFSET_ATTR));
|
|
if (object_id < m_model->objects.size()) {
|
|
if (instance_id < m_model->objects[object_id]->instances.size()) {
|
|
m_model->objects[object_id]->instances[instance_id]->set_assemble_from_transform(transform);
|
|
m_model->objects[object_id]->instances[instance_id]->set_offset_to_assembly(ofs2ass);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_end_assemble_item()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void XMLCALL _BBS_3MF_Importer::_handle_start_relationships_element(void* userData, const char* name, const char** attributes)
|
|
{
|
|
_BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData;
|
|
if (importer != nullptr)
|
|
importer->_handle_start_relationships_element(name, attributes);
|
|
}
|
|
|
|
void XMLCALL _BBS_3MF_Importer::_handle_end_relationships_element(void* userData, const char* name)
|
|
{
|
|
_BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData;
|
|
if (importer != nullptr)
|
|
importer->_handle_end_relationships_element(name);
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_handle_start_relationships_element(const char* name, const char** attributes)
|
|
{
|
|
if (m_xml_parser == nullptr)
|
|
return;
|
|
|
|
bool res = true;
|
|
unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser);
|
|
|
|
if (::strcmp(RELATIONSHIP_TAG, name) == 0)
|
|
res = _handle_start_relationship(attributes, num_attributes);
|
|
|
|
m_curr_characters.clear();
|
|
if (!res)
|
|
_stop_xml_parser();
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_handle_end_relationships_element(const char* name)
|
|
{
|
|
if (m_xml_parser == nullptr)
|
|
return;
|
|
|
|
bool res = true;
|
|
|
|
if (!res)
|
|
_stop_xml_parser();
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_handle_start_relationship(const char** attributes, unsigned int num_attributes)
|
|
{
|
|
std::string path = bbs_get_attribute_value_string(attributes, num_attributes, TARGET_ATTR);
|
|
std::string type = bbs_get_attribute_value_string(attributes, num_attributes, RELS_TYPE_ATTR);
|
|
if (boost::starts_with(type, "http://schemas.microsoft.com/3dmanufacturing/") && boost::ends_with(type, "3dmodel")) {
|
|
if (m_start_part_path.empty()) m_start_part_path = path;
|
|
else m_sub_model_paths.push_back(path);
|
|
} else if (boost::starts_with(type, "http://schemas.openxmlformats.org/") && boost::ends_with(type, "thumbnail")) {
|
|
m_thumbnail_path = path;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void _BBS_3MF_Importer::_generate_current_object_list(std::vector<Id> &sub_objects, Id object_id, IdToCurrentObjectMap current_objects)
|
|
{
|
|
std::list<Id> id_list;
|
|
id_list.push_back(object_id);
|
|
|
|
while (!id_list.empty())
|
|
{
|
|
Id current_id = id_list.front();
|
|
id_list.pop_front();
|
|
IdToCurrentObjectMap::iterator current_object = current_objects.find(current_id);
|
|
if (current_object != current_objects.end()) {
|
|
//found one
|
|
if (!current_object->second.components.empty()) {
|
|
for (const Component& comp: current_object->second.components)
|
|
{
|
|
id_list.push_back(comp.object_id);
|
|
}
|
|
}
|
|
else if (!(current_object->second.geometry.empty())) {
|
|
//CurrentObject* ptr = &(current_objects[current_id]);
|
|
//CurrentObject* ptr2 = &(current_object->second);
|
|
sub_objects.push_back(current_object->first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_generate_volumes_new(ModelObject& object, const std::vector<Id> &sub_objects, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions)
|
|
{
|
|
if (!object.volumes.empty()) {
|
|
add_error("object already built with parts");
|
|
return false;
|
|
}
|
|
|
|
//unsigned int geo_tri_count = (unsigned int)geometry.triangles.size();
|
|
unsigned int renamed_volumes_count = 0;
|
|
|
|
for (unsigned int index = 0; index < sub_objects.size(); index++)
|
|
{
|
|
//find the volume metadata firstly
|
|
Id object_id = sub_objects[index];
|
|
IdToCurrentObjectMap::iterator current_object = m_current_objects.find(object_id);
|
|
if (current_object == m_current_objects.end()) {
|
|
add_error("sub_objects can not be found, id=" + std::to_string(object_id.second));
|
|
return false;
|
|
}
|
|
CurrentObject* sub_object = &(current_object->second);
|
|
|
|
const ObjectMetadata::VolumeMetadata* volume_data = nullptr;
|
|
ObjectMetadata::VolumeMetadata default_volume_data(sub_object->id);
|
|
for (const ObjectMetadata::VolumeMetadata& volume_iter : volumes) {
|
|
if (volume_iter.subobject_id == sub_object->id) {
|
|
volume_data = &volume_iter;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Transform3d volume_matrix_to_object = Transform3d::Identity();
|
|
bool has_transform = false;
|
|
if (volume_data)
|
|
{
|
|
// extract the volume transformation from the volume's metadata, if present
|
|
for (const Metadata& metadata : volume_data->metadata) {
|
|
if (metadata.key == MATRIX_KEY) {
|
|
volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value);
|
|
has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
//create a volume_data
|
|
volume_data = &default_volume_data;
|
|
}
|
|
// splits volume out of imported geometry
|
|
indexed_triangle_set its;
|
|
its.indices.assign(sub_object->geometry.triangles.begin(), sub_object->geometry.triangles.end());
|
|
const size_t triangles_count = its.indices.size();
|
|
if (triangles_count == 0) {
|
|
add_error("found no trianges in the object " + std::to_string(sub_object->id));
|
|
return false;
|
|
}
|
|
for (const Vec3i& face : its.indices) {
|
|
for (const int tri_id : face) {
|
|
if (tri_id < 0 || tri_id >= int(sub_object->geometry.vertices.size())) {
|
|
add_error("invalid vertex id in object " + std::to_string(sub_object->id));
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
its.vertices.assign(sub_object->geometry.vertices.begin(), sub_object->geometry.vertices.end());
|
|
|
|
// BBS
|
|
for (const std::string prop_str : sub_object->geometry.face_properties) {
|
|
FaceProperty face_prop;
|
|
face_prop.from_string(prop_str);
|
|
its.properties.push_back(face_prop);
|
|
}
|
|
|
|
TriangleMesh triangle_mesh(std::move(its), volume_data->mesh_stats);
|
|
|
|
if (m_version == 0) {
|
|
// if the 3mf was not produced by BambuStudio and there is only one instance,
|
|
// bake the transformation into the geometry to allow the reload from disk command
|
|
// to work properly
|
|
if (object.instances.size() == 1) {
|
|
triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false);
|
|
object.instances.front()->set_transformation(Slic3r::Geometry::Transformation());
|
|
//FIXME do the mesh fixing?
|
|
}
|
|
}
|
|
if (triangle_mesh.volume() < 0)
|
|
triangle_mesh.flip_triangles();
|
|
|
|
ModelVolume* volume = object.add_volume(std::move(triangle_mesh));
|
|
// stores the volume matrix taken from the metadata, if present
|
|
if (has_transform)
|
|
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
|
|
volume->calculate_convex_hull();
|
|
|
|
// recreate custom supports, seam and mmu segmentation from previously loaded attribute
|
|
if (m_load_config) {
|
|
volume->supported_facets.reserve(triangles_count);
|
|
volume->seam_facets.reserve(triangles_count);
|
|
volume->mmu_segmentation_facets.reserve(triangles_count);
|
|
for (size_t i=0; i<triangles_count; ++i) {
|
|
assert(i < sub_object->geometry.custom_supports.size());
|
|
assert(i < sub_object->geometry.custom_seam.size());
|
|
assert(i < sub_object->geometry.mmu_segmentation.size());
|
|
if (! sub_object->geometry.custom_supports[i].empty())
|
|
volume->supported_facets.set_triangle_from_string(i, sub_object->geometry.custom_supports[i]);
|
|
if (! sub_object->geometry.custom_seam[i].empty())
|
|
volume->seam_facets.set_triangle_from_string(i, sub_object->geometry.custom_seam[i]);
|
|
if (! sub_object->geometry.mmu_segmentation[i].empty())
|
|
volume->mmu_segmentation_facets.set_triangle_from_string(i, sub_object->geometry.mmu_segmentation[i]);
|
|
}
|
|
volume->supported_facets.shrink_to_fit();
|
|
volume->seam_facets.shrink_to_fit();
|
|
volume->mmu_segmentation_facets.shrink_to_fit();
|
|
}
|
|
|
|
volume->set_type(volume_data->part_type);
|
|
|
|
// apply the remaining volume's metadata
|
|
for (const Metadata& metadata : volume_data->metadata) {
|
|
if (metadata.key == NAME_KEY)
|
|
volume->name = metadata.value;
|
|
//else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
|
|
// volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
|
|
//for old format
|
|
else if ((metadata.key == VOLUME_TYPE_KEY) || (metadata.key == PART_TYPE_KEY))
|
|
volume->set_type(ModelVolume::type_from_string(metadata.value));
|
|
else if (metadata.key == SOURCE_FILE_KEY)
|
|
volume->source.input_file = metadata.value;
|
|
else if (metadata.key == SOURCE_OBJECT_ID_KEY)
|
|
volume->source.object_idx = ::atoi(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_VOLUME_ID_KEY)
|
|
volume->source.volume_idx = ::atoi(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_OFFSET_X_KEY)
|
|
volume->source.mesh_offset(0) = ::atof(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_OFFSET_Y_KEY)
|
|
volume->source.mesh_offset(1) = ::atof(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_OFFSET_Z_KEY)
|
|
volume->source.mesh_offset(2) = ::atof(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_IN_INCHES)
|
|
volume->source.is_converted_from_inches = metadata.value == "1";
|
|
else if (metadata.key == SOURCE_IN_METERS)
|
|
volume->source.is_converted_from_meters = metadata.value == "1";
|
|
else if (metadata.key == MATRIX_KEY)
|
|
continue;
|
|
else
|
|
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
|
|
}
|
|
|
|
// this may happen for 3mf saved by 3rd part softwares
|
|
if (volume->name.empty()) {
|
|
volume->name = object.name;
|
|
if (renamed_volumes_count > 0)
|
|
volume->name += "_" + std::to_string(renamed_volumes_count + 1);
|
|
++renamed_volumes_count;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions)
|
|
{
|
|
if (!object.volumes.empty()) {
|
|
add_error("Found invalid volumes count");
|
|
return false;
|
|
}
|
|
|
|
unsigned int geo_tri_count = (unsigned int)geometry.triangles.size();
|
|
unsigned int renamed_volumes_count = 0;
|
|
|
|
for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) {
|
|
if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) {
|
|
add_error("Found invalid triangle id");
|
|
return false;
|
|
}
|
|
|
|
Transform3d volume_matrix_to_object = Transform3d::Identity();
|
|
bool has_transform = false;
|
|
// extract the volume transformation from the volume's metadata, if present
|
|
for (const Metadata& metadata : volume_data.metadata) {
|
|
if (metadata.key == MATRIX_KEY) {
|
|
volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value);
|
|
has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// splits volume out of imported geometry
|
|
indexed_triangle_set its;
|
|
its.indices.assign(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1);
|
|
const size_t triangles_count = its.indices.size();
|
|
if (triangles_count == 0) {
|
|
add_error("An empty triangle mesh found");
|
|
return false;
|
|
}
|
|
|
|
{
|
|
int min_id = its.indices.front()[0];
|
|
int max_id = min_id;
|
|
for (const Vec3i& face : its.indices) {
|
|
for (const int tri_id : face) {
|
|
if (tri_id < 0 || tri_id >= int(geometry.vertices.size())) {
|
|
add_error("Found invalid vertex id");
|
|
return false;
|
|
}
|
|
min_id = std::min(min_id, tri_id);
|
|
max_id = std::max(max_id, tri_id);
|
|
}
|
|
}
|
|
its.vertices.assign(geometry.vertices.begin() + min_id, geometry.vertices.begin() + max_id + 1);
|
|
|
|
// BBS
|
|
for (const std::string prop_str : geometry.face_properties) {
|
|
FaceProperty face_prop;
|
|
face_prop.from_string(prop_str);
|
|
its.properties.push_back(face_prop);
|
|
}
|
|
|
|
// rebase indices to the current vertices list
|
|
for (Vec3i& face : its.indices)
|
|
for (int& tri_id : face)
|
|
tri_id -= min_id;
|
|
}
|
|
|
|
TriangleMesh triangle_mesh(std::move(its), volume_data.mesh_stats);
|
|
|
|
if (m_version == 0) {
|
|
// if the 3mf was not produced by BambuStudio and there is only one instance,
|
|
// bake the transformation into the geometry to allow the reload from disk command
|
|
// to work properly
|
|
if (object.instances.size() == 1) {
|
|
triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false);
|
|
object.instances.front()->set_transformation(Slic3r::Geometry::Transformation());
|
|
//FIXME do the mesh fixing?
|
|
}
|
|
}
|
|
if (triangle_mesh.volume() < 0)
|
|
triangle_mesh.flip_triangles();
|
|
|
|
ModelVolume* volume = object.add_volume(std::move(triangle_mesh));
|
|
// stores the volume matrix taken from the metadata, if present
|
|
if (has_transform)
|
|
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
|
|
volume->calculate_convex_hull();
|
|
|
|
// recreate custom supports, seam and mmu segmentation from previously loaded attribute
|
|
volume->supported_facets.reserve(triangles_count);
|
|
volume->seam_facets.reserve(triangles_count);
|
|
volume->mmu_segmentation_facets.reserve(triangles_count);
|
|
for (size_t i=0; i<triangles_count; ++i) {
|
|
size_t index = volume_data.first_triangle_id + i;
|
|
assert(index < geometry.custom_supports.size());
|
|
assert(index < geometry.custom_seam.size());
|
|
assert(index < geometry.mmu_segmentation.size());
|
|
if (! geometry.custom_supports[index].empty())
|
|
volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
|
|
if (! geometry.custom_seam[index].empty())
|
|
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
|
|
if (! geometry.mmu_segmentation[index].empty())
|
|
volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]);
|
|
}
|
|
volume->supported_facets.shrink_to_fit();
|
|
volume->seam_facets.shrink_to_fit();
|
|
volume->mmu_segmentation_facets.shrink_to_fit();
|
|
|
|
volume->set_type(volume_data.part_type);
|
|
|
|
// apply the remaining volume's metadata
|
|
for (const Metadata& metadata : volume_data.metadata) {
|
|
if (metadata.key == NAME_KEY)
|
|
volume->name = metadata.value;
|
|
//else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
|
|
// volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
|
|
//for old format
|
|
else if ((metadata.key == VOLUME_TYPE_KEY) || (metadata.key == PART_TYPE_KEY))
|
|
volume->set_type(ModelVolume::type_from_string(metadata.value));
|
|
else if (metadata.key == SOURCE_FILE_KEY)
|
|
volume->source.input_file = metadata.value;
|
|
else if (metadata.key == SOURCE_OBJECT_ID_KEY)
|
|
volume->source.object_idx = ::atoi(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_VOLUME_ID_KEY)
|
|
volume->source.volume_idx = ::atoi(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_OFFSET_X_KEY)
|
|
volume->source.mesh_offset(0) = ::atof(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_OFFSET_Y_KEY)
|
|
volume->source.mesh_offset(1) = ::atof(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_OFFSET_Z_KEY)
|
|
volume->source.mesh_offset(2) = ::atof(metadata.value.c_str());
|
|
else if (metadata.key == SOURCE_IN_INCHES)
|
|
volume->source.is_converted_from_inches = metadata.value == "1";
|
|
else if (metadata.key == SOURCE_IN_METERS)
|
|
volume->source.is_converted_from_meters = metadata.value == "1";
|
|
else
|
|
volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
|
|
}
|
|
|
|
// this may happen for 3mf saved by 3rd part softwares
|
|
if (volume->name.empty()) {
|
|
volume->name = object.name;
|
|
if (renamed_volumes_count > 0)
|
|
volume->name += "_" + std::to_string(renamed_volumes_count + 1);
|
|
++renamed_volumes_count;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void XMLCALL _BBS_3MF_Importer::_handle_start_model_xml_element(void* userData, const char* name, const char** attributes)
|
|
{
|
|
_BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData;
|
|
if (importer != nullptr)
|
|
importer->_handle_start_model_xml_element(name, attributes);
|
|
}
|
|
|
|
void XMLCALL _BBS_3MF_Importer::_handle_end_model_xml_element(void* userData, const char* name)
|
|
{
|
|
_BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData;
|
|
if (importer != nullptr)
|
|
importer->_handle_end_model_xml_element(name);
|
|
}
|
|
|
|
void XMLCALL _BBS_3MF_Importer::_handle_xml_characters(void* userData, const XML_Char* s, int len)
|
|
{
|
|
_BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData;
|
|
if (importer != nullptr)
|
|
importer->_handle_xml_characters(s, len);
|
|
}
|
|
|
|
void XMLCALL _BBS_3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes)
|
|
{
|
|
_BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData;
|
|
if (importer != nullptr)
|
|
importer->_handle_start_config_xml_element(name, attributes);
|
|
}
|
|
|
|
void XMLCALL _BBS_3MF_Importer::_handle_end_config_xml_element(void* userData, const char* name)
|
|
{
|
|
_BBS_3MF_Importer* importer = (_BBS_3MF_Importer*)userData;
|
|
if (importer != nullptr)
|
|
importer->_handle_end_config_xml_element(name);
|
|
}
|
|
|
|
|
|
class _BBS_3MF_Exporter : public _BBS_3MF_Base
|
|
{
|
|
struct BuildItem
|
|
{
|
|
std::string path;
|
|
unsigned int id;
|
|
Transform3d transform;
|
|
bool printable;
|
|
|
|
BuildItem(std::string const & path, unsigned int id, const Transform3d& transform, const bool printable)
|
|
: path(path)
|
|
, id(id)
|
|
, transform(transform)
|
|
, printable(printable)
|
|
{
|
|
}
|
|
};
|
|
|
|
//BBS: change volume to seperate objects
|
|
/*struct Offsets
|
|
{
|
|
unsigned int first_vertex_id;
|
|
unsigned int first_triangle_id;
|
|
unsigned int last_triangle_id;
|
|
|
|
Offsets(unsigned int first_vertex_id)
|
|
: first_vertex_id(first_vertex_id)
|
|
, first_triangle_id(-1)
|
|
, last_triangle_id(-1)
|
|
{
|
|
}
|
|
};*/
|
|
|
|
//typedef std::map<const ModelVolume*, Offsets> VolumeToOffsetsMap;
|
|
typedef std::map<const ModelVolume*, int> VolumeToObjectIDMap;
|
|
|
|
struct ObjectData
|
|
{
|
|
ModelObject const * object;
|
|
int backup_id;
|
|
VolumeToObjectIDMap volumes_objectID;
|
|
};
|
|
|
|
typedef std::vector<BuildItem> BuildItemsList;
|
|
typedef std::map<int, ObjectData> IdToObjectDataMap;
|
|
|
|
bool m_fullpath_sources{ true };
|
|
bool m_zip64 { true };
|
|
bool m_production_ext { false }; // save with Production Extention
|
|
bool m_skip_static{ false }; // not save mesh and other big static contents
|
|
bool m_from_backup_save{ false }; // the object save is from backup store
|
|
bool m_split_model { false }; // save object per file with Production Extention
|
|
bool m_save_gcode { false }; // whether to save gcode for normal save
|
|
bool m_skip_model { false }; // skip model when exporting .gcode.3mf
|
|
|
|
public:
|
|
//BBS: add plate data related logic
|
|
|
|
// add backup logic
|
|
//bool save_model_to_file(const std::string& filename, Model& model, PlateDataPtrs& plate_data_list, std::vector<Preset*>& project_presets, const DynamicPrintConfig* config, bool fullpath_sources, const std::vector<ThumbnailData*>& thumbnail_data, bool zip64, bool skip_static, Export3mfProgressFn proFn = nullptr, bool silence = false);
|
|
|
|
bool save_model_to_file(StoreParams& store_params);
|
|
// add backup logic
|
|
bool save_object_mesh(const std::string& temp_path, ModelObject const & object, int obj_id);
|
|
|
|
private:
|
|
//BBS: add plate data related logic
|
|
bool _save_model_to_file(const std::string& filename,
|
|
Model& model, PlateDataPtrs& plate_data_list,
|
|
std::vector<Preset*>& project_presets,
|
|
const DynamicPrintConfig* config,
|
|
const std::vector<ThumbnailData*>& thumbnail_data,
|
|
Export3mfProgressFn proFn,
|
|
const std::vector<ThumbnailData*>& calibration_data,
|
|
const std::vector<PlateBBoxData*>& id_bboxes,
|
|
BBLProject* project = nullptr,
|
|
int export_plate_idx = -1);
|
|
|
|
bool _add_file_to_archive(mz_zip_archive& archive, const std::string & path_in_zip, const std::string & file_path);
|
|
|
|
bool _add_content_types_file_to_archive(mz_zip_archive& archive);
|
|
|
|
bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, int index);
|
|
bool _add_calibration_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, int index);
|
|
bool _add_bbox_file_to_archive(mz_zip_archive& archive, const PlateBBoxData& id_bboxes, int index);
|
|
bool _add_relationships_file_to_archive(mz_zip_archive & archive,
|
|
std::string const & from = {},
|
|
std::vector<std::string> const &targets = {},
|
|
std::vector<std::string> const &types = {},
|
|
PackingTemporaryData data = PackingTemporaryData(),
|
|
int export_plate_idx = -1) const;
|
|
bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data, Export3mfProgressFn proFn = nullptr, BBLProject* project = nullptr) const;
|
|
bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int object_id, ModelObject const & object, unsigned int backup_id, VolumeToObjectIDMap& volumes_objectID) const;
|
|
//BBS: change volume to seperate objects
|
|
bool _add_mesh_to_object_stream(std::function<bool(std::string&, bool)> const& flush, ModelObject const & object, unsigned int backup_id, VolumeToObjectIDMap& volumes_objectID, unsigned int& obj_idx) const;
|
|
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const;
|
|
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
|
|
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
|
|
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
|
|
bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model);
|
|
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
|
|
//BBS: add project config file logic for json format
|
|
bool _add_project_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, Model& model);
|
|
//BBS: add project embedded preset files
|
|
bool _add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector<Preset*> project_presets);
|
|
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const IdToObjectDataMap &objects_data, int export_plate_idx = -1);
|
|
bool _add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list);
|
|
bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr);
|
|
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
|
|
bool _add_auxiliary_dir_to_archive(mz_zip_archive &archive, const std::string &aux_dir, PackingTemporaryData &data);
|
|
|
|
static int convert_instance_id_to_resource_id(const Model& model, int obj_id, int instance_id)
|
|
{
|
|
int resource_id = 1;
|
|
|
|
for (int i = 0; i < obj_id; ++i)
|
|
{
|
|
resource_id += model.objects[i]->volumes.size() + 1;
|
|
}
|
|
|
|
resource_id += model.objects[obj_id]->volumes.size();
|
|
|
|
return resource_id;
|
|
}
|
|
};
|
|
|
|
bool _BBS_3MF_Exporter::save_model_to_file(StoreParams& store_params)
|
|
{
|
|
clear_errors();
|
|
m_fullpath_sources = store_params.strategy & SaveStrategy::FullPathSources;
|
|
m_zip64 = store_params.strategy & SaveStrategy::Zip64;
|
|
m_production_ext = store_params.strategy & SaveStrategy::ProductionExt;
|
|
|
|
m_skip_static = store_params.strategy & SaveStrategy::SkipStatic;
|
|
m_split_model = store_params.strategy & SaveStrategy::SplitModel;
|
|
m_save_gcode = store_params.strategy & SaveStrategy::WithGcode;
|
|
m_skip_model = store_params.strategy & SaveStrategy::SkipModel;
|
|
|
|
boost::system::error_code ec;
|
|
std::string filename = std::string(store_params.path);
|
|
boost::filesystem::remove(filename + ".tmp", ec);
|
|
|
|
bool result = _save_model_to_file(filename + ".tmp", *store_params.model, store_params.plate_data_list, store_params.project_presets, store_params.config,
|
|
store_params.thumbnail_data, store_params.proFn, store_params.calibration_thumbnail_data, store_params.id_bboxes, store_params.project, store_params.export_plate_idx);
|
|
if (result) {
|
|
boost::filesystem::rename(filename + ".tmp", filename, ec);
|
|
if (!(store_params.strategy & SaveStrategy::Silence))
|
|
boost::filesystem::save_string_file(store_params.model->get_backup_path() + "/origin.txt", filename);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// backup mesh-only
|
|
bool _BBS_3MF_Exporter::save_object_mesh(const std::string& temp_path, ModelObject const & object, int obj_id)
|
|
{
|
|
m_production_ext = true;
|
|
m_from_backup_save = true;
|
|
Model const & model = *object.get_model();
|
|
|
|
mz_zip_archive archive;
|
|
mz_zip_zero_struct(&archive);
|
|
|
|
auto filename = boost::format("3D/Objects/%s_%d.model") % object.name % obj_id;
|
|
std::string filepath = temp_path + "/" + filename.str();
|
|
if (!open_zip_writer(&archive, filepath)) {
|
|
add_error("Unable to open the file");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to open the file\n");
|
|
return false;
|
|
}
|
|
|
|
IdToObjectDataMap objects_data;
|
|
objects_data.insert({obj_id, {&object, obj_id}});
|
|
_add_model_file_to_archive(filename.str(), archive, model, objects_data);
|
|
|
|
mz_zip_writer_finalize_archive(&archive);
|
|
close_zip_writer(&archive);
|
|
return true;
|
|
}
|
|
|
|
//BBS: add plate data related logic
|
|
bool _BBS_3MF_Exporter::_save_model_to_file(const std::string& filename,
|
|
Model& model,
|
|
PlateDataPtrs& plate_data_list,
|
|
std::vector<Preset*>& project_presets,
|
|
const DynamicPrintConfig* config,
|
|
const std::vector<ThumbnailData*>& thumbnail_data,
|
|
Export3mfProgressFn proFn,
|
|
const std::vector<ThumbnailData*>& calibration_data,
|
|
const std::vector<PlateBBoxData*>& id_bboxes,
|
|
BBLProject* project,
|
|
int export_plate_idx)
|
|
{
|
|
PackingTemporaryData temp_data;
|
|
|
|
mz_zip_archive archive;
|
|
mz_zip_zero_struct(&archive);
|
|
|
|
bool cb_cancel = false;
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",before open zip writer, m_skip_static %1%, m_save_gcode %2%\n")%m_skip_static %m_save_gcode;
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_OPEN_3MF, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
if (!open_zip_writer(&archive, filename)) {
|
|
add_error("Unable to open the file");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to open the file\n");
|
|
return false;
|
|
}
|
|
|
|
struct close_lock
|
|
{
|
|
mz_zip_archive & archive;
|
|
std::string const * filename;
|
|
~close_lock() {
|
|
close_zip_writer(&archive);
|
|
if (filename)
|
|
boost::filesystem::remove(*filename);
|
|
}
|
|
} lock{ archive, &filename};
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add _add_content_types_file_to_archive\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_CONTENT_TYPES, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
// Adds content types file ("[Content_Types].xml";).
|
|
// The content of this file is the same for each BambuStudio 3mf.
|
|
if (!_add_content_types_file_to_archive(archive)) {
|
|
return false;
|
|
}
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(",before add thumbnails, count %1%\n") % thumbnail_data.size();
|
|
|
|
//BBS: add thumbnail for each plate
|
|
if (!m_skip_static && thumbnail_data.size() > 0) {
|
|
// Adds the file Metadata/thumbnail.png.
|
|
for (unsigned int index = 0; index < thumbnail_data.size(); index++)
|
|
{
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_THUMBNAILS, index, thumbnail_data.size(), cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
if (thumbnail_data[index]->is_valid())
|
|
{
|
|
if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data[index], index)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(",before add calibration data, count %1%\n")%calibration_data.size();
|
|
//BBS add calibration thumbnail for each plate
|
|
if (!m_skip_static && calibration_data.size() > 0) {
|
|
// Adds the file Metadata/calibration_p[X].png.
|
|
for (unsigned int index = 0; index < calibration_data.size(); index++)
|
|
{
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_THUMBNAILS, index, calibration_data.size(), cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
if (calibration_data[index]->is_valid())
|
|
{
|
|
if (!_add_calibration_file_to_archive(archive, *calibration_data[index], index)) {
|
|
close_zip_writer(&archive);
|
|
boost::filesystem::remove(filename);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// BBS: save bounding box to json
|
|
if (id_bboxes[index]->is_valid()) {
|
|
if (!_add_bbox_file_to_archive(archive, *id_bboxes[index], index)) {
|
|
close_zip_writer(&archive);
|
|
boost::filesystem::remove(filename);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add models\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_MODELS, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
// Adds model file ("3D/3dmodel.model").
|
|
// This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes.
|
|
IdToObjectDataMap objects_data;
|
|
//if (!m_skip_model)
|
|
{
|
|
if (!_add_model_file_to_archive(filename, archive, model, objects_data, proFn, project)) { return false; }
|
|
|
|
// Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt").
|
|
// All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
|
|
// The index differes from the index of an object ID of an object instance of a 3MF file!
|
|
// BBS: don't need to save layer_height_profile because we calculate when slicing every time.
|
|
/*
|
|
if (!_add_layer_height_profile_file_to_archive(archive, model)) {
|
|
return false;
|
|
}*/
|
|
|
|
// BBS progress point
|
|
/*BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format("export 3mf EXPORT_STAGE_ADD_LAYER_RANGE\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_LAYER_RANGE, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
// Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt").
|
|
// All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
|
|
// The index differes from the index of an object ID of an object instance of a 3MF file!
|
|
if (!_add_layer_config_ranges_file_to_archive(archive, model)) {
|
|
return false;
|
|
}*/
|
|
|
|
// BBS progress point
|
|
/*BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format("export 3mf EXPORT_STAGE_ADD_SUPPORT\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_SUPPORT, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
// Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt").
|
|
// All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
|
|
// The index differes from the index of an object ID of an object instance of a 3MF file!
|
|
if (!_add_sla_support_points_file_to_archive(archive, model)) {
|
|
return false;
|
|
}
|
|
|
|
if (!_add_sla_drain_holes_file_to_archive(archive, model)) {
|
|
return false;
|
|
}*/
|
|
|
|
// BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", before add custom gcodes\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_CUSTOM_GCODE, 0, 1, cb_cancel);
|
|
if (cb_cancel) return false;
|
|
}
|
|
|
|
// Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
|
|
// All custom gcode per height of whole Model are stored here
|
|
if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model, config)) { return false; }
|
|
|
|
// BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", before add project_settings\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_PRINT_CONFIG, 0, 1, cb_cancel);
|
|
if (cb_cancel) return false;
|
|
}
|
|
|
|
// Adds slic3r print config file ("Metadata/Slic3r_PE.config").
|
|
// This file contains the content of FullPrintConfing / SLAFullPrintConfig.
|
|
if (config != nullptr) {
|
|
// BBS: change to json format
|
|
// if (!_add_print_config_file_to_archive(archive, *config)) {
|
|
if (!_add_project_config_file_to_archive(archive, *config, model)) { return false; }
|
|
}
|
|
|
|
// BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", before add project embedded settings\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_CONFIG_FILE, 0, 1, cb_cancel);
|
|
if (cb_cancel) return false;
|
|
}
|
|
|
|
// BBS: add project config
|
|
if (project_presets.size() > 0) {
|
|
// BBS: add project embedded preset files
|
|
_add_project_embedded_presets_to_archive(archive, model, project_presets);
|
|
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", finished add project embedded settings, size %1%\n")%project_presets.size();
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_PROJECT_CONFIG, 0, 1, cb_cancel);
|
|
if (cb_cancel) return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add plate_N.gcode.md5 to file
|
|
if (!m_skip_static && m_save_gcode) {
|
|
for (int i = 0; i < plate_data_list.size(); i++) {
|
|
PlateData *plate_data = plate_data_list[i];
|
|
if (!plate_data->gcode_file.empty() && plate_data->is_sliced_valid && boost::filesystem::exists(plate_data->gcode_file)) {
|
|
unsigned char digest[16];
|
|
MD5_CTX ctx;
|
|
MD5_Init(&ctx);
|
|
auto src_gcode_file = plate_data->gcode_file;
|
|
boost::filesystem::ifstream ifs(src_gcode_file, std::ios::binary);
|
|
std::string buf(64 * 1024, 0);
|
|
const std::size_t & size = boost::filesystem::file_size(src_gcode_file);
|
|
std::size_t left_size = size;
|
|
while (ifs) {
|
|
ifs.read(buf.data(), buf.size());
|
|
int read_bytes = ifs.gcount();
|
|
MD5_Update(&ctx, (unsigned char *) buf.data(), read_bytes);
|
|
}
|
|
MD5_Final(digest, &ctx);
|
|
char md5_str[33];
|
|
for (int j = 0; j < 16; j++) { sprintf(&md5_str[j * 2], "%02X", (unsigned int) digest[j]); }
|
|
plate_data->gcode_file_md5 = std::string(md5_str);
|
|
std::string target_file = (boost::format("Metadata/plate_%1%.gcode.md5") % (plate_data->plate_index + 1)).str();
|
|
if (!mz_zip_writer_add_mem(&archive, target_file.c_str(), (const void *) plate_data->gcode_file_md5.c_str(), plate_data->gcode_file_md5.length(),
|
|
MZ_DEFAULT_COMPRESSION)) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__
|
|
<< boost::format(", store gcode md5 to 3mf's %1%, length %2%, failed\n") %target_file %plate_data->gcode_file_md5.length();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Adds gcode files ("Metadata/plate_1.gcode, plate_2.gcode, ...)
|
|
// Before _add_model_config_file_to_archive, because we modify plate_data
|
|
//if (!m_skip_static && !_add_gcode_file_to_archive(archive, model, plate_data_list, proFn)) {
|
|
if (!m_skip_static && m_save_gcode && !_add_gcode_file_to_archive(archive, model, plate_data_list, proFn)) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_gcode_file_to_archive failed\n");
|
|
return false;
|
|
}
|
|
|
|
// Adds slic3r model config file ("Metadata/Slic3r_PE_model.config").
|
|
// This file contains all the attributes of all ModelObjects and their ModelVolumes (names, parameter overrides).
|
|
// As there is just a single Indexed Triangle Set data stored per ModelObject, offsets of volumes into their respective Indexed Triangle Set data
|
|
// is stored here as well.
|
|
if (!_add_model_config_file_to_archive(archive, model, plate_data_list, objects_data, export_plate_idx)) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_model_config_file_to_archive failed\n");
|
|
return false;
|
|
}
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add sliced info to 3mf\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_SLICE_INFO, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
// Adds sliced info of plate file ("Metadata/slice_info.config")
|
|
// This file contains all sliced info of all plates
|
|
if (!_add_slice_info_config_file_to_archive(archive, model, plate_data_list)) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", _add_slice_info_config_file_to_archive failed\n");
|
|
return false;
|
|
}
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add auxiliary dir to 3mf\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_AUXILIARIES, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
if (!m_skip_static && !_add_auxiliary_dir_to_archive(archive, model.get_auxiliary_file_temp_path(), temp_data)) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", _add_auxiliary_dir_to_archive failed\n");
|
|
return false;
|
|
}
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", before add relation file to 3mf\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_RELATIONS, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
// Adds relationships file ("_rels/.rels").
|
|
// The content of this file is the same for each BambuStudio 3mf.
|
|
// The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA.
|
|
if (!_add_relationships_file_to_archive(archive, {}, {}, {}, temp_data, export_plate_idx)) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", _add_relationships_file_to_archive failed\n");
|
|
return false;
|
|
}
|
|
|
|
if (!mz_zip_writer_finalize_archive(&archive)) {
|
|
add_error("Unable to finalize the archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to finalize the archive\n");
|
|
return false;
|
|
}
|
|
|
|
//BBS progress point
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", finished exporting 3mf\n");
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_FINISH, 0, 1, cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
lock.filename = nullptr;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_file_to_archive(mz_zip_archive& archive, const std::string& path_in_zip, const std::string& src_file_path)
|
|
{
|
|
static std::string const nocomp_exts[] = {".png", ".jpg", ".mp4", ".jpeg", ".zip", ".3mf"};
|
|
auto end = nocomp_exts + sizeof(nocomp_exts) / sizeof(nocomp_exts[0]);
|
|
bool nocomp = std::find_if(nocomp_exts, end, [&path_in_zip](auto & ext) { return boost::algorithm::ends_with(path_in_zip, ext); }) != end;
|
|
#if WRITE_ZIP_LANGUAGE_ENCODING
|
|
bool result = mz_zip_writer_add_file(&archive, path_in_zip.c_str(), encode_path(src_file_path.c_str()).c_str(), NULL, 0, nocomp ? MZ_NO_COMPRESSION : MZ_DEFAULT_LEVEL);
|
|
#else
|
|
std::string native_path = encode_path(path_in_zip.c_str());
|
|
std::string extra = ZipUnicodePathExtraField::encode(path_in_zip, native_path);
|
|
bool result = mz_zip_writer_add_file_ex(&archive, native_path.c_str(), encode_path(src_file_path.c_str()).c_str(), NULL, 0, nocomp ? MZ_ZIP_FLAG_ASCII_FILENAME : MZ_DEFAULT_COMPRESSION,
|
|
extra.c_str(), extra.length(), extra.c_str(), extra.length());
|
|
#endif
|
|
if (!result) {
|
|
add_error("Unable to add file " + src_file_path + " to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", Unable to add file %1% to archive %2%\n") % src_file_path % path_in_zip;
|
|
} else {
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", add file %1% to archive %2%\n") % src_file_path % path_in_zip;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_content_types_file_to_archive(mz_zip_archive& archive)
|
|
{
|
|
std::stringstream stream;
|
|
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
stream << "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n";
|
|
stream << " <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n";
|
|
stream << " <Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/>\n";
|
|
stream << " <Default Extension=\"png\" ContentType=\"image/png\"/>\n";
|
|
stream << " <Default Extension=\"gcode\" ContentType=\"text/x.gcode\"/>\n";
|
|
stream << "</Types>";
|
|
|
|
std::string out = stream.str();
|
|
|
|
if (!mz_zip_writer_add_mem(&archive, CONTENT_TYPES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
add_error("Unable to add content types file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add content types file to archive\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, int index)
|
|
{
|
|
bool res = false;
|
|
|
|
size_t png_size = 0;
|
|
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_COMPRESSION, 1);
|
|
if (png_data != nullptr) {
|
|
std::string thumbnail_name = (boost::format("Metadata/plate_%1%.png") % (index + 1)).str();
|
|
res = mz_zip_writer_add_mem(&archive, thumbnail_name.c_str(), (const void*)png_data, png_size, MZ_NO_COMPRESSION);
|
|
mz_free(png_data);
|
|
}
|
|
|
|
if (!res) {
|
|
add_error("Unable to add thumbnail file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add thumbnail file to archive\n");
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_calibration_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data, int index)
|
|
{
|
|
bool res = false;
|
|
|
|
size_t png_size = 0;
|
|
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_COMPRESSION, 1);
|
|
if (png_data != nullptr) {
|
|
std::string thumbnail_name = (boost::format(PATTERN_FILE_FORMAT) % (index + 1)).str();
|
|
res = mz_zip_writer_add_mem(&archive, thumbnail_name.c_str(), (const void*)png_data, png_size, MZ_NO_COMPRESSION);
|
|
mz_free(png_data);
|
|
}
|
|
|
|
if (!res) {
|
|
add_error("Unable to add thumbnail file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add thumbnail file to archive\n");
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_bbox_file_to_archive(mz_zip_archive& archive, const PlateBBoxData& id_bboxes, int index)
|
|
{
|
|
bool res = false;
|
|
nlohmann::json j;
|
|
id_bboxes.to_json(j);
|
|
std::string out = j.dump();
|
|
|
|
std::string json_file_name = (boost::format(PATTERN_CONFIG_FILE_FORMAT) % (index + 1)).str();
|
|
if (!mz_zip_writer_add_mem(&archive, json_file_name.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
add_error("Unable to add json file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add json file to archive\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_relationships_file_to_archive(
|
|
mz_zip_archive &archive, std::string const &from, std::vector<std::string> const &targets, std::vector<std::string> const &types, PackingTemporaryData data, int export_plate_idx) const
|
|
{
|
|
std::stringstream stream;
|
|
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
stream << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n";
|
|
if (from.empty()) {
|
|
stream << " <Relationship Target=\"/" << MODEL_FILE << "\" Id=\"rel-1\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\"/>\n";
|
|
|
|
if (data._3mf_thumbnail.empty()) {
|
|
if (export_plate_idx < 0) {
|
|
stream << " <Relationship Target=\"/" << THUMBNAIL_FILE
|
|
<< "\" Id=\"rel-2\" Type=\"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail\"/>\n";
|
|
} else {
|
|
std::string thumbnail_file_str = (boost::format("Metadata/plate_%1%.png") % (export_plate_idx + 1)).str();
|
|
stream << " <Relationship Target=\"/" << thumbnail_file_str
|
|
<< "\" Id=\"rel-2\" Type=\"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail\"/>\n";
|
|
}
|
|
} else {
|
|
stream << " <Relationship Target=\"/" << data._3mf_thumbnail
|
|
<< "\" Id=\"rel-2\" Type=\"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail\"/>\n";
|
|
}
|
|
|
|
if (!data._3mf_printer_thumbnail_middle.empty()) {
|
|
stream << " <Relationship Target=\"/" << data._3mf_printer_thumbnail_middle
|
|
<< "\" Id=\"rel-4\" Type=\"http://schemas.bambulab.com/package/2021/cover-thumbnail-middle\"/>\n";
|
|
}
|
|
if (!data._3mf_printer_thumbnail_small.empty())
|
|
stream << " <Relationship Target=\"/" << data._3mf_printer_thumbnail_small
|
|
<< "\" Id=\"rel-5\" Type=\"http://schemas.bambulab.com/package/2021/cover-thumbnail-small\"/>\n";
|
|
}
|
|
else if (targets.empty()) {
|
|
return false;
|
|
}
|
|
else {
|
|
int i = 0;
|
|
for (auto & path : targets) {
|
|
for (auto & type : types)
|
|
stream << " <Relationship Target=\"/" << xml_escape(path) << "\" Id=\"rel-" << boost::to_string(++i) << "\" Type=\"" << type << "\"/>\n";
|
|
}
|
|
}
|
|
stream << "</Relationships>";
|
|
|
|
std::string out = stream.str();
|
|
|
|
if (!mz_zip_writer_add_mem(&archive, from.empty() ? RELATIONSHIPS_FILE.c_str() : from.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
add_error("Unable to add relationships file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add relationships file to archive\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static void reset_stream(std::stringstream &stream)
|
|
{
|
|
stream.str("");
|
|
stream.clear();
|
|
// https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10
|
|
// Conversion of a floating-point value to text and back is exact as long as at least max_digits10 were used (9 for float, 17 for double).
|
|
// It is guaranteed to produce the same floating-point value, even though the intermediate text representation is not exact.
|
|
// The default value of std::stream precision is 6 digits only!
|
|
stream << std::setprecision(std::numeric_limits<float>::max_digits10);
|
|
}
|
|
|
|
/*
|
|
* BBS: Production Extension (SplitModel)
|
|
* save sub model if objects_data is not empty
|
|
* not collect build items in sub model
|
|
*/
|
|
bool _BBS_3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data, Export3mfProgressFn proFn, BBLProject* project) const
|
|
{
|
|
bool sub_model = !objects_data.empty();
|
|
bool write_object = sub_model || !m_split_model;
|
|
|
|
#if WRITE_ZIP_LANGUAGE_ENCODING
|
|
auto & zip_filename = filename;
|
|
#else
|
|
std::string zip_filename = encode_path(filename.c_str());
|
|
std::string extra = sub_model ? ZipUnicodePathExtraField::encode(filename, zip_filename) : "";
|
|
#endif
|
|
mz_zip_writer_staged_context context;
|
|
if (!mz_zip_writer_add_staged_open(&archive, &context, sub_model ? zip_filename.c_str() : MODEL_FILE.c_str(),
|
|
m_zip64 ?
|
|
// Maximum expected and allowed 3MF file size is 16GiB.
|
|
// This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records.
|
|
(uint64_t(1) << 30) * 16 :
|
|
// Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see
|
|
// GH issue #6193.
|
|
(uint64_t(1) << 32) - 1,
|
|
#if WRITE_ZIP_LANGUAGE_ENCODING
|
|
nullptr, nullptr, 0, MZ_DEFAULT_LEVEL, nullptr, 0, nullptr, 0)) {
|
|
#else
|
|
nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, extra.c_str(), extra.length(), extra.c_str(), extra.length())) {
|
|
#endif
|
|
add_error("Unable to add model file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add model file to archive\n");
|
|
return false;
|
|
}
|
|
|
|
{
|
|
std::stringstream stream;
|
|
reset_stream(stream);
|
|
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\"";
|
|
if (m_production_ext)
|
|
stream << " xmlns:p=\"http://schemas.microsoft.com/3dmanufacturing/production/2015/06\" requiredextensions=\"p\"";
|
|
stream << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBS_3MF_VERSION << "\">" << VERSION_BBS_3MF << "</" << METADATA_TAG << ">\n";
|
|
|
|
//TODO: currently use version 0, no need to load&&save this string
|
|
/*if (model.is_fdm_support_painted())
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBS_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
|
|
|
|
if (model.is_seam_painted())
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBS_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";
|
|
|
|
if (model.is_mm_painted())
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBS_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "</" << METADATA_TAG << ">\n";*/
|
|
|
|
std::string name;
|
|
std::string user_name;
|
|
std::string user_id;
|
|
std::string design_cover;
|
|
std::string license;
|
|
std::string description;
|
|
std::string copyright;
|
|
std::string rating;
|
|
std::string model_id;
|
|
std::string region_code;
|
|
if (model.design_info) {
|
|
user_name = model.design_info->Designer;
|
|
user_id = model.design_info->DesignerUserId;
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, save_3mf found designer = " << user_name;
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, save_3mf found designer_user_id = " << user_id;
|
|
}
|
|
|
|
if (model.model_info) {
|
|
design_cover = model.model_info->cover_file;
|
|
license = model.model_info->license;
|
|
description = model.model_info->description;
|
|
copyright = model.model_info->copyright;
|
|
name = model.model_info->model_name;
|
|
BOOST_LOG_TRIVIAL(trace) << "design_info, save_3mf found designer_cover = " << design_cover;
|
|
}
|
|
|
|
if (project) {
|
|
model_id = project->project_model_id;
|
|
region_code = project->project_country_code;
|
|
}
|
|
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBL_MODEL_NAME_TAG << "\">" << name << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESIGNER_TAG << "\">" << user_name << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESIGNER_USER_ID_TAG << "\">" << user_id << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESIGNER_COVER_FILE_TAG << "\">" << design_cover << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBL_DESCRIPTION_TAG << "\">" << description << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBL_COPYRIGHT_TAG << "\">" << copyright << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBL_LICENSE_TAG << "\">" << license << "</" << METADATA_TAG << ">\n";
|
|
|
|
/* save model info */
|
|
if (!model_id.empty()) {
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBL_MODEL_ID_TAG << "\">" << model_id << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"" << BBL_REGION_TAG << "\">" << region_code << "</" << METADATA_TAG << ">\n";
|
|
}
|
|
|
|
std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc());
|
|
// keep only the date part of the string
|
|
date = date.substr(0, 10);
|
|
stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "</" << METADATA_TAG << ">\n";
|
|
stream << " <" << RESOURCES_TAG << ">\n";
|
|
std::string buf = stream.str();
|
|
if (! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) {
|
|
add_error("Unable to add model file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add model file to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects).
|
|
BuildItemsList build_items;
|
|
|
|
// The object_id here is a one based identifier of the first instance of a ModelObject in the 3MF file, where
|
|
// all the object instances of all ModelObjects are stored and indexed in a 1 based linear fashion.
|
|
// Therefore the list of object_ids here may not be continuous.
|
|
unsigned int object_id = 1;
|
|
|
|
bool cb_cancel = false;
|
|
int obj_idx = 0;
|
|
std::vector<unsigned int> object_ids;
|
|
std::vector<std::string> object_paths;
|
|
if (!m_skip_model) {
|
|
for (ModelObject* obj : model.objects) {
|
|
if (sub_model && obj != objects_data.begin()->second.object) continue;
|
|
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_MODELS, obj_idx, model.objects.size(), cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
obj_idx++;
|
|
}
|
|
|
|
if (obj == nullptr)
|
|
continue;
|
|
|
|
// Index of an object in the 3MF file corresponding to the 1st instance of a ModelObject.
|
|
IdToObjectDataMap::iterator object_it = objects_data.begin();
|
|
if (!sub_model) {
|
|
// For backup, use backup id as object id
|
|
int backup_id = const_cast<Model&>(model).get_object_backup_id(*obj);
|
|
if (m_skip_static) object_id = backup_id;
|
|
object_it = objects_data.insert({ (int) object_id, {obj, backup_id} }).first;
|
|
}
|
|
|
|
if (write_object) {
|
|
// Store geometry of all ModelVolumes contained in a single ModelObject into a single 3MF indexed triangle set object.
|
|
// object_it->second.volumes_objectID will contain the offsets of the ModelVolumes in that single indexed triangle set.
|
|
// object_id will be increased to point to the 1st instance of the next ModelObject.
|
|
if (!_add_object_to_model_stream(context, object_it->first, *obj, object_it->second.backup_id, object_it->second.volumes_objectID)) {
|
|
add_error("Unable to add object to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add object to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (sub_model) break;
|
|
|
|
object_ids.push_back(object_id);
|
|
unsigned int curr_id;
|
|
if (m_skip_static)
|
|
curr_id = object_id;
|
|
else
|
|
curr_id = object_id + obj->volumes.size();
|
|
|
|
object_id = object_id + obj->volumes.size() + 1;
|
|
|
|
unsigned int count = 0;
|
|
for (const ModelInstance* instance : obj->instances) {
|
|
Transform3d t = instance->get_matrix();
|
|
// instance_id is just a 1 indexed index in build_items.
|
|
//assert(m_skip_static || curr_id == build_items.size() + 1);
|
|
auto filename = boost::format("3D/Objects/%s_%d.model") % obj->name % object_it->second.backup_id;
|
|
if (count == 0)
|
|
object_paths.push_back(filename.str());
|
|
build_items.emplace_back(m_split_model ? "/" + filename.str() : "", curr_id, t, instance->printable);
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
std::stringstream stream;
|
|
reset_stream(stream);
|
|
stream << " </" << RESOURCES_TAG << ">\n";
|
|
|
|
// Store the transformations of all the ModelInstances of all ModelObjects, indexed in a linear fashion.
|
|
if (!_add_build_to_model_stream(stream, build_items)) {
|
|
add_error("Unable to add build to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add build to archive\n");
|
|
return false;
|
|
}
|
|
|
|
stream << "</" << MODEL_TAG << ">\n";
|
|
|
|
std::string buf = stream.str();
|
|
|
|
if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) ||
|
|
! mz_zip_writer_add_staged_finish(&context)) {
|
|
add_error("Unable to add model file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add model file to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (m_skip_model || write_object) return true;
|
|
|
|
// write model rels
|
|
_add_relationships_file_to_archive(archive, MODEL_RELS_FILE, object_paths, {"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"});
|
|
|
|
if (m_skip_static) {
|
|
for (ModelObject* obj : model.objects) {
|
|
if (obj == nullptr)
|
|
continue;
|
|
int object_id = obj->get_backup_id();
|
|
auto & volumes_objectID = objects_data.find(object_id)->second.volumes_objectID;
|
|
//unsigned int vertices_count = 0;
|
|
//unsigned int triangles_count = 0;
|
|
unsigned int volume_count = 0;
|
|
for (ModelVolume* volume : obj->volumes) {
|
|
if (volume == nullptr)
|
|
continue;
|
|
VolumeToObjectIDMap::iterator volume_it = volumes_objectID.insert({ volume, (object_id | ((volume_count+1)<<16)) }).first;
|
|
volume_count++;
|
|
//const indexed_triangle_set &its = volume->mesh().its;
|
|
//vertices_count += (int)its.vertices.size();
|
|
//volume_it->second.first_triangle_id = triangles_count;
|
|
//triangles_count += (int)its.indices.size();
|
|
//volume_it->second.last_triangle_id = triangles_count - 1;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
{
|
|
boost::mutex mutex;
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, objects_data.size(), 1), [this, &mutex, &model, &object_ids, &objects_data, &object_paths, main = &archive, project](const tbb::blocked_range<size_t>& range) {
|
|
for (size_t i = range.begin(); i < range.end(); ++i) {
|
|
auto iter = objects_data.find(object_ids[i]);
|
|
IdToObjectDataMap objects_data2;
|
|
objects_data2.insert(*iter);
|
|
auto & object = *iter->second.object;
|
|
mz_zip_archive archive;
|
|
mz_zip_zero_struct(&archive);
|
|
mz_zip_writer_init_heap(&archive, 0, 1024 * 1024);
|
|
_add_model_file_to_archive(object_paths[i], archive, model, objects_data2, nullptr, project);
|
|
iter->second = objects_data2.begin()->second;
|
|
void *ppBuf; size_t pSize;
|
|
mz_zip_writer_finalize_heap_archive(&archive, &ppBuf, &pSize);
|
|
mz_zip_writer_end(&archive);
|
|
mz_zip_zero_struct(&archive);
|
|
mz_zip_reader_init_mem(&archive, ppBuf, pSize, 0);
|
|
{
|
|
boost::unique_lock l(mutex);
|
|
mz_zip_writer_add_from_zip_reader(main, &archive, 0);
|
|
}
|
|
mz_zip_reader_end(&archive);
|
|
}
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int object_id, ModelObject const & object, unsigned int backup_id, VolumeToObjectIDMap& volumes_objectID) const
|
|
{
|
|
std::stringstream stream;
|
|
reset_stream(stream);
|
|
unsigned int id = 0;
|
|
unsigned int volume_start_id = object_id;
|
|
for (const ModelInstance* instance : object.instances) {
|
|
assert(instance != nullptr);
|
|
if (instance == nullptr)
|
|
continue;
|
|
|
|
//stream << " <" << OBJECT_TAG << " id=\"" << instance_id;
|
|
//if (m_production_ext)
|
|
// stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap<boost::uint32_t>{(boost::uint32_t)backup_id} << OBJECT_UUID_SUFFIX;
|
|
//stream << "\" type=\"model\">\n";
|
|
|
|
if (id == 0) {
|
|
std::string buf = stream.str();
|
|
reset_stream(stream);
|
|
// backup: make _add_mesh_to_object_stream() reusable
|
|
auto flush = [this, &context](std::string & buf, bool force = false) {
|
|
if ((force && !buf.empty()) || buf.size() >= 65536 * 16) {
|
|
if (!mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) {
|
|
add_error("Error during writing or compression");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Error during writing or compression\n");
|
|
return false;
|
|
}
|
|
buf.clear();
|
|
}
|
|
return true;
|
|
};
|
|
if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) ||
|
|
! _add_mesh_to_object_stream(flush, object, backup_id, volumes_objectID, volume_start_id)) {
|
|
add_error("Unable to add mesh to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add mesh to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
stream << " <" << OBJECT_TAG << " id=\"" << volume_start_id+id;
|
|
if ((id == 0) && m_production_ext)
|
|
stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap<boost::uint32_t>{(boost::uint32_t)backup_id} << OBJECT_UUID_SUFFIX;
|
|
stream << "\" type=\"model\">\n";
|
|
stream << " <" << COMPONENTS_TAG << ">\n";
|
|
if (id == 0) {
|
|
if (m_from_backup_save) {
|
|
for (unsigned int index = 1; index <= object.volumes.size(); index ++) {
|
|
unsigned int ref_id = object_id | (index << 16);
|
|
stream << " <" << COMPONENT_TAG << " objectid=\"" << ref_id << "\"/>\n";
|
|
}
|
|
}
|
|
else {
|
|
for (unsigned int index = object_id; index < volume_start_id; index ++)
|
|
stream << " <" << COMPONENT_TAG << " objectid=\"" << index << "\"/>\n";
|
|
}
|
|
}
|
|
else {
|
|
stream << " <" << COMPONENT_TAG << " objectid=\"" << volume_start_id << "\"/>\n";
|
|
}
|
|
stream << " </" << COMPONENTS_TAG << ">\n";
|
|
|
|
stream << " </" << OBJECT_TAG << ">\n";
|
|
|
|
++id;
|
|
}
|
|
|
|
std::string buf = stream.str();
|
|
return buf.empty() || mz_zip_writer_add_staged_data(&context, buf.data(), buf.size());
|
|
}
|
|
|
|
#if EXPORT_3MF_USE_SPIRIT_KARMA_FP
|
|
template <typename Num>
|
|
struct coordinate_policy_fixed : boost::spirit::karma::real_policies<Num>
|
|
{
|
|
static int floatfield(Num n) { return fmtflags::fixed; }
|
|
// Number of decimal digits to maintain float accuracy when storing into a text file and parsing back.
|
|
static unsigned precision(Num /* n */) { return std::numeric_limits<Num>::max_digits10 + 1; }
|
|
// No trailing zeros, thus for fmtflags::fixed usually much less than max_digits10 decimal numbers will be produced.
|
|
static bool trailing_zeros(Num /* n */) { return false; }
|
|
};
|
|
template <typename Num>
|
|
struct coordinate_policy_scientific : coordinate_policy_fixed<Num>
|
|
{
|
|
static int floatfield(Num n) { return fmtflags::scientific; }
|
|
};
|
|
// Define a new generator type based on the new coordinate policy.
|
|
using coordinate_type_fixed = boost::spirit::karma::real_generator<float, coordinate_policy_fixed<float>>;
|
|
using coordinate_type_scientific = boost::spirit::karma::real_generator<float, coordinate_policy_scientific<float>>;
|
|
#endif // EXPORT_3MF_USE_SPIRIT_KARMA_FP
|
|
|
|
//BBS: change volume to seperate objects
|
|
bool _BBS_3MF_Exporter::_add_mesh_to_object_stream(std::function<bool(std::string &,bool)> const & flush, ModelObject const & object, unsigned int backup_id, VolumeToObjectIDMap& volumes_objectID, unsigned int& obj_idx) const
|
|
{
|
|
std::string output_buffer;
|
|
|
|
#if 0
|
|
auto flush = [this, &output_buffer, &context](bool force = false) {
|
|
if ((force && ! output_buffer.empty()) || output_buffer.size() >= 65536 * 16) {
|
|
if (! mz_zip_writer_add_staged_data(&context, output_buffer.data(), output_buffer.size())) {
|
|
add_error("Error during writing or compression");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Error during writing or compression\n");
|
|
return false;
|
|
}
|
|
output_buffer.clear();
|
|
}
|
|
return true;
|
|
};
|
|
#endif
|
|
|
|
/*output_buffer += " <";
|
|
output_buffer += MESH_TAG;
|
|
output_buffer += ">\n <";
|
|
output_buffer += VERTICES_TAG;
|
|
output_buffer += ">\n";*/
|
|
|
|
auto format_coordinate = [](float f, char *buf) -> char* {
|
|
assert(is_decimal_separator_point());
|
|
#if EXPORT_3MF_USE_SPIRIT_KARMA_FP
|
|
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
|
|
// https://github.com/boostorg/spirit/pull/586
|
|
// where the exported string is one digit shorter than it should be to guarantee lossless round trip.
|
|
// The code is left here for the ocasion boost guys improve.
|
|
coordinate_type_fixed const coordinate_fixed = coordinate_type_fixed();
|
|
coordinate_type_scientific const coordinate_scientific = coordinate_type_scientific();
|
|
// Format "f" in a fixed format.
|
|
char *ptr = buf;
|
|
boost::spirit::karma::generate(ptr, coordinate_fixed, f);
|
|
// Format "f" in a scientific format.
|
|
char *ptr2 = ptr;
|
|
boost::spirit::karma::generate(ptr2, coordinate_scientific, f);
|
|
// Return end of the shorter string.
|
|
auto len2 = ptr2 - ptr;
|
|
if (ptr - buf > len2) {
|
|
// Move the shorter scientific form to the front.
|
|
memcpy(buf, ptr, len2);
|
|
ptr = buf + len2;
|
|
}
|
|
// Return pointer to the end.
|
|
return ptr;
|
|
#else
|
|
// Round-trippable float, shortest possible.
|
|
return buf + sprintf(buf, "%.9g", f);
|
|
#endif
|
|
};
|
|
|
|
char buf[256];
|
|
unsigned int vertices_count = 0;
|
|
//unsigned int triangles_count = 0;
|
|
unsigned int volume_idx, volume_count = 0;
|
|
for (ModelVolume* volume : object.volumes) {
|
|
if (volume == nullptr)
|
|
continue;
|
|
|
|
//if (!volume->mesh().stats().repaired())
|
|
// throw Slic3r::FileIOError("store_3mf() requires repair()");
|
|
unsigned int first_vertex_id = 0;
|
|
volume_count++;
|
|
if (m_from_backup_save)
|
|
volume_idx = (volume_count<<16 | obj_idx);
|
|
else {
|
|
volume_idx = obj_idx;
|
|
obj_idx++;
|
|
}
|
|
volumes_objectID.insert({ volume, volume_idx });
|
|
|
|
const indexed_triangle_set &its = volume->mesh().its;
|
|
if (its.vertices.empty()) {
|
|
add_error("Found invalid mesh");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Found invalid mesh\n");
|
|
return false;
|
|
}
|
|
|
|
std::string type = (volume->type() == ModelVolumeType::MODEL_PART)?"model":"other";
|
|
|
|
output_buffer += " <";
|
|
output_buffer += OBJECT_TAG;
|
|
output_buffer += " id=\"";
|
|
output_buffer += std::to_string(volume_idx);
|
|
/*if (m_production_ext) {
|
|
std::stringstream stream;
|
|
reset_stream(stream);
|
|
stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap<boost::uint32_t>{(boost::uint32_t)backup_id} << OBJECT_UUID_SUFFIX;
|
|
//output_buffer += "\" ";
|
|
//output_buffer += PUUID_ATTR;
|
|
//output_buffer += "=\"";
|
|
//output_buffer += std::to_string(hex_wrap<boost::uint32_t>{(boost::uint32_t)backup_id});
|
|
//output_buffer += OBJECT_UUID_SUFFIX;
|
|
output_buffer += stream.str();
|
|
}*/
|
|
output_buffer += "\" type=\"";
|
|
output_buffer += type;
|
|
output_buffer += "\">\n";
|
|
output_buffer += " <";
|
|
output_buffer += MESH_TAG;
|
|
output_buffer += ">\n <";
|
|
output_buffer += VERTICES_TAG;
|
|
output_buffer += ">\n";
|
|
|
|
vertices_count += (int)its.vertices.size();
|
|
|
|
const Transform3d& matrix = volume->get_matrix();
|
|
|
|
for (size_t i = 0; i < its.vertices.size(); ++i) {
|
|
Vec3f v = (matrix * its.vertices[i].cast<double>()).cast<float>();
|
|
char* ptr = buf;
|
|
boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << VERTEX_TAG << " x=\"");
|
|
ptr = format_coordinate(v.x(), ptr);
|
|
boost::spirit::karma::generate(ptr, "\" y=\"");
|
|
ptr = format_coordinate(v.y(), ptr);
|
|
boost::spirit::karma::generate(ptr, "\" z=\"");
|
|
ptr = format_coordinate(v.z(), ptr);
|
|
boost::spirit::karma::generate(ptr, "\"/>\n");
|
|
*ptr = '\0';
|
|
output_buffer += buf;
|
|
if (!flush(output_buffer, false))
|
|
return false;
|
|
}
|
|
//}
|
|
|
|
output_buffer += " </";
|
|
output_buffer += VERTICES_TAG;
|
|
output_buffer += ">\n <";
|
|
output_buffer += TRIANGLES_TAG;
|
|
output_buffer += ">\n";
|
|
|
|
//for (ModelVolume* volume : object.volumes) {
|
|
// if (volume == nullptr)
|
|
// continue;
|
|
|
|
bool is_left_handed = volume->is_left_handed();
|
|
//VolumeToOffsetsMap::iterator volume_it = volumes_objectID.find(volume);
|
|
//assert(volume_it != volumes_objectID.end());
|
|
|
|
//const indexed_triangle_set &its = volume->mesh().its;
|
|
|
|
// updates triangle offsets
|
|
//unsigned int first_triangle_id = triangles_count;
|
|
//triangles_count += (int)its.indices.size();
|
|
//unsigned int last_triangle_id = triangles_count - 1;
|
|
|
|
for (int i = 0; i < int(its.indices.size()); ++ i) {
|
|
{
|
|
const Vec3i &idx = its.indices[i];
|
|
char *ptr = buf;
|
|
boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG <<
|
|
" v1=\"" << boost::spirit::int_ <<
|
|
"\" v2=\"" << boost::spirit::int_ <<
|
|
"\" v3=\"" << boost::spirit::int_ << "\"",
|
|
idx[is_left_handed ? 2 : 0] + first_vertex_id,
|
|
idx[1] + first_vertex_id,
|
|
idx[is_left_handed ? 0 : 2] + first_vertex_id);
|
|
*ptr = '\0';
|
|
output_buffer += buf;
|
|
}
|
|
|
|
std::string custom_supports_data_string = volume->supported_facets.get_triangle_as_string(i);
|
|
if (! custom_supports_data_string.empty()) {
|
|
output_buffer += " ";
|
|
output_buffer += CUSTOM_SUPPORTS_ATTR;
|
|
output_buffer += "=\"";
|
|
output_buffer += custom_supports_data_string;
|
|
output_buffer += "\"";
|
|
}
|
|
|
|
std::string custom_seam_data_string = volume->seam_facets.get_triangle_as_string(i);
|
|
if (! custom_seam_data_string.empty()) {
|
|
output_buffer += " ";
|
|
output_buffer += CUSTOM_SEAM_ATTR;
|
|
output_buffer += "=\"";
|
|
output_buffer += custom_seam_data_string;
|
|
output_buffer += "\"";
|
|
}
|
|
|
|
std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i);
|
|
if (! mmu_painting_data_string.empty()) {
|
|
output_buffer += " ";
|
|
output_buffer += MMU_SEGMENTATION_ATTR;
|
|
output_buffer += "=\"";
|
|
output_buffer += mmu_painting_data_string;
|
|
output_buffer += "\"";
|
|
}
|
|
|
|
// BBS
|
|
if (i < its.properties.size()) {
|
|
std::string prop_str = its.properties[i].to_string();
|
|
if (!prop_str.empty()) {
|
|
output_buffer += " ";
|
|
output_buffer += FACE_PROPERTY_ATTR;
|
|
output_buffer += "=\"";
|
|
output_buffer += prop_str;
|
|
output_buffer += "\"";
|
|
}
|
|
}
|
|
|
|
output_buffer += "/>\n";
|
|
|
|
if (! flush(output_buffer, false))
|
|
return false;
|
|
}
|
|
output_buffer += " </";
|
|
output_buffer += TRIANGLES_TAG;
|
|
output_buffer += ">\n </";
|
|
output_buffer += MESH_TAG;
|
|
output_buffer += ">\n";
|
|
output_buffer += " </";
|
|
output_buffer += OBJECT_TAG;
|
|
output_buffer += ">\n";
|
|
}
|
|
|
|
// Force flush.
|
|
return flush(output_buffer, true);
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const
|
|
{
|
|
// This happens for empty projects
|
|
if (build_items.size() == 0) {
|
|
return true;
|
|
}
|
|
|
|
stream << " <" << BUILD_TAG;
|
|
if (m_production_ext)
|
|
stream << " " << PUUID_ATTR << "=\"" << BUILD_UUID << "\"";
|
|
stream << ">\n";
|
|
|
|
for (const BuildItem& item : build_items) {
|
|
stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "=\"" << item.id;
|
|
if (m_production_ext)
|
|
stream << "\" " << PUUID_ATTR << "=\"" << hex_wrap<boost::uint32_t>{item.id} << BUILD_UUID_SUFFIX;
|
|
if (!item.path.empty())
|
|
stream << "\" " << PPATH_ATTR << "=\"" << xml_escape(item.path);
|
|
stream << "\" " << TRANSFORM_ATTR << "=\"";
|
|
for (unsigned c = 0; c < 4; ++c) {
|
|
for (unsigned r = 0; r < 3; ++r) {
|
|
stream << item.transform(r, c);
|
|
if (r != 2 || c != 3)
|
|
stream << " ";
|
|
}
|
|
}
|
|
stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n";
|
|
}
|
|
|
|
stream << " </" << BUILD_TAG << ">\n";
|
|
|
|
return true;
|
|
}
|
|
|
|
/*bool _BBS_3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model)
|
|
{
|
|
assert(is_decimal_separator_point());
|
|
std::string out = "";
|
|
char buffer[1024];
|
|
|
|
unsigned int count = 0;
|
|
for (const ModelObject* object : model.objects) {
|
|
++count;
|
|
const std::vector<double>& layer_height_profile = object->layer_height_profile.get();
|
|
if (layer_height_profile.size() >= 4 && layer_height_profile.size() % 2 == 0) {
|
|
sprintf(buffer, "object_id=%d|", count);
|
|
out += buffer;
|
|
|
|
// Store the layer height profile as a single semicolon separated list.
|
|
for (size_t i = 0; i < layer_height_profile.size(); ++i) {
|
|
sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]);
|
|
out += buffer;
|
|
}
|
|
|
|
out += "\n";
|
|
}
|
|
}
|
|
|
|
if (!out.empty()) {
|
|
if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
add_error("Unable to add layer heights profile file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add layer heights profile file to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model)
|
|
{
|
|
std::string out = "";
|
|
pt::ptree tree;
|
|
|
|
unsigned int object_cnt = 0;
|
|
for (const ModelObject* object : model.objects) {
|
|
object_cnt++;
|
|
const t_layer_config_ranges& ranges = object->layer_config_ranges;
|
|
if (!ranges.empty())
|
|
{
|
|
pt::ptree& obj_tree = tree.add("objects.object","");
|
|
|
|
obj_tree.put("<xmlattr>.id", object_cnt);
|
|
|
|
// Store the layer config ranges.
|
|
for (const auto& range : ranges) {
|
|
pt::ptree& range_tree = obj_tree.add("range", "");
|
|
|
|
// store minX and maxZ
|
|
range_tree.put("<xmlattr>.min_z", range.first.first);
|
|
range_tree.put("<xmlattr>.max_z", range.first.second);
|
|
|
|
// store range configuration
|
|
const ModelConfig& config = range.second;
|
|
for (const std::string& opt_key : config.keys()) {
|
|
pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key));
|
|
opt_tree.put("<xmlattr>.opt_key", opt_key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!tree.empty()) {
|
|
std::ostringstream oss;
|
|
pt::write_xml(oss, tree);
|
|
out = oss.str();
|
|
|
|
// Post processing("beautification") of the output string for a better preview
|
|
boost::replace_all(out, "><object", ">\n <object");
|
|
boost::replace_all(out, "><range", ">\n <range");
|
|
boost::replace_all(out, "><option", ">\n <option");
|
|
boost::replace_all(out, "></range>", ">\n </range>");
|
|
boost::replace_all(out, "></object>", ">\n </object>");
|
|
// OR just
|
|
boost::replace_all(out, "><", ">\n<");
|
|
}
|
|
|
|
if (!out.empty()) {
|
|
if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
add_error("Unable to add layer heights profile file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add layer heights profile file to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model)
|
|
{
|
|
assert(is_decimal_separator_point());
|
|
std::string out = "";
|
|
char buffer[1024];
|
|
|
|
unsigned int count = 0;
|
|
for (const ModelObject* object : model.objects) {
|
|
++count;
|
|
const std::vector<sla::SupportPoint>& sla_support_points = object->sla_support_points;
|
|
if (!sla_support_points.empty()) {
|
|
sprintf(buffer, "object_id=%d|", count);
|
|
out += buffer;
|
|
|
|
// Store the layer height profile as a single space separated list.
|
|
for (size_t i = 0; i < sla_support_points.size(); ++i) {
|
|
sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island);
|
|
out += buffer;
|
|
}
|
|
out += "\n";
|
|
}
|
|
}
|
|
|
|
if (!out.empty()) {
|
|
// Adds version header at the beginning:
|
|
//out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out;
|
|
|
|
if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
add_error("Unable to add sla support points file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add sla support points file to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model)
|
|
{
|
|
assert(is_decimal_separator_point());
|
|
const char *const fmt = "object_id=%d|";
|
|
std::string out;
|
|
|
|
unsigned int count = 0;
|
|
for (const ModelObject* object : model.objects) {
|
|
++count;
|
|
sla::DrainHoles drain_holes = object->sla_drain_holes;
|
|
|
|
// The holes were placed 1mm above the mesh in the first implementation.
|
|
// This was a bad idea and the reference point was changed in 2.3 so
|
|
// to be on the mesh exactly. The elevated position is still saved
|
|
// in 3MFs for compatibility reasons.
|
|
for (sla::DrainHole& hole : drain_holes) {
|
|
hole.pos -= hole.normal.normalized();
|
|
hole.height += 1.f;
|
|
}
|
|
|
|
if (!drain_holes.empty()) {
|
|
out += string_printf(fmt, count);
|
|
|
|
// Store the layer height profile as a single space separated list.
|
|
for (size_t i = 0; i < drain_holes.size(); ++i)
|
|
out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"),
|
|
drain_holes[i].pos(0),
|
|
drain_holes[i].pos(1),
|
|
drain_holes[i].pos(2),
|
|
drain_holes[i].normal(0),
|
|
drain_holes[i].normal(1),
|
|
drain_holes[i].normal(2),
|
|
drain_holes[i].radius,
|
|
drain_holes[i].height);
|
|
|
|
out += "\n";
|
|
}
|
|
}
|
|
|
|
if (!out.empty()) {
|
|
// Adds version header at the beginning:
|
|
//out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out;
|
|
|
|
if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast<const void*>(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION))) {
|
|
add_error("Unable to add sla support points file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add sla support points file to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}*/
|
|
|
|
bool _BBS_3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
|
|
{
|
|
assert(is_decimal_separator_point());
|
|
char buffer[1024];
|
|
sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str());
|
|
std::string out = buffer;
|
|
|
|
for (const std::string &key : config.keys())
|
|
if (key != "compatible_printers")
|
|
out += "; " + key + " = " + config.opt_serialize(key) + "\n";
|
|
|
|
if (!out.empty()) {
|
|
if (!mz_zip_writer_add_mem(&archive, BBS_PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
add_error("Unable to add print config file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add print config file to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//BBS: add project config file logic for new json format
|
|
bool _BBS_3MF_Exporter::_add_project_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config, Model& model)
|
|
{
|
|
const std::string& temp_path = model.get_backup_path();
|
|
std::string temp_file = temp_path + std::string("/") + "_temp_1.config";
|
|
config.save_to_json(temp_file, std::string("project_settings"), std::string("project"), std::string(SLIC3R_VERSION));
|
|
return _add_file_to_archive(archive, BBS_PROJECT_CONFIG_FILE, temp_file);
|
|
}
|
|
|
|
//BBS: add project embedded preset files
|
|
bool _BBS_3MF_Exporter::_add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector<Preset*> project_presets)
|
|
{
|
|
char buffer[1024];
|
|
sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str());
|
|
std::string out = buffer;
|
|
int print_count = 0, filament_count = 0, printer_count = 0;
|
|
const std::string& temp_path = model.get_backup_path();
|
|
|
|
for (int i = 0; i < project_presets.size(); i++)
|
|
{
|
|
Preset* preset = project_presets[i];
|
|
|
|
if (preset) {
|
|
preset->file = temp_path + std::string("/") + "_temp_1.config";
|
|
DynamicPrintConfig& config = preset->config;
|
|
//config.save(preset->file);
|
|
config.save_to_json(preset->file, preset->name, std::string("project"), preset->version.to_string());
|
|
|
|
std::string dest_file;
|
|
if (preset->type == Preset::TYPE_PRINT) {
|
|
dest_file = (boost::format(EMBEDDED_PRINT_FILE_FORMAT) % (print_count + 1)).str();
|
|
print_count++;
|
|
}
|
|
else if (preset->type == Preset::TYPE_FILAMENT) {
|
|
dest_file = (boost::format(EMBEDDED_FILAMENT_FILE_FORMAT) % (filament_count + 1)).str();
|
|
filament_count++;
|
|
}
|
|
else if (preset->type == Preset::TYPE_PRINTER) {
|
|
dest_file = (boost::format(EMBEDDED_PRINTER_FILE_FORMAT) % (printer_count + 1)).str();
|
|
printer_count++;
|
|
}
|
|
else
|
|
continue;
|
|
|
|
_add_file_to_archive(archive, dest_file, preset->file);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const IdToObjectDataMap &objects_data, int export_plate_idx)
|
|
{
|
|
std::stringstream stream;
|
|
// Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back
|
|
// when loaded as accurately as possible.
|
|
stream << std::setprecision(std::numeric_limits<double>::max_digits10);
|
|
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
stream << "<" << CONFIG_TAG << ">\n";
|
|
|
|
for (const IdToObjectDataMap::value_type& obj_metadata : objects_data) {
|
|
const ModelObject* obj = obj_metadata.second.object;
|
|
if (obj != nullptr) {
|
|
// Output of instances count added because of github #3435, currently not used by PrusaSlicer
|
|
//stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\" " << INSTANCESCOUNT_ATTR << "=\"" << obj->instances.size() << "\">\n";
|
|
if (m_skip_static)
|
|
stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\">\n";
|
|
else
|
|
stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first + obj->volumes.size() << "\">\n";
|
|
|
|
// stores object's name
|
|
if (!obj->name.empty())
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n";
|
|
|
|
//BBS: store object's module name
|
|
if (!obj->module_name.empty())
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"module\" " << VALUE_ATTR << "=\"" << xml_escape(obj->module_name) << "\"/>\n";
|
|
|
|
// stores object's config data
|
|
for (const std::string& key : obj->config.keys()) {
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n";
|
|
}
|
|
|
|
for (const ModelVolume* volume : obj_metadata.second.object->volumes) {
|
|
if (volume != nullptr) {
|
|
const VolumeToObjectIDMap& objectIDs = obj_metadata.second.volumes_objectID;
|
|
VolumeToObjectIDMap::const_iterator it = objectIDs.find(volume);
|
|
if (it != objectIDs.end()) {
|
|
// stores volume's offsets
|
|
stream << " <" << PART_TAG << " ";
|
|
//stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" ";
|
|
//stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\" ";
|
|
stream << ID_ATTR << "=\"" << it->second << "\" ";
|
|
|
|
stream << SUBTYPE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\">\n";
|
|
//stream << " <" << PART_TAG << " " << ID_ATTR << "=\"" << it->second << "\" " << SUBTYPE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\">\n";
|
|
|
|
// stores volume's name
|
|
if (!volume->name.empty())
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n";
|
|
//stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n";
|
|
|
|
// stores volume's modifier field (legacy, to support old slicers)
|
|
/*if (volume->is_modifier())
|
|
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << PART_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
|
// stores volume's type (overrides the modifier field above)
|
|
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << PART_TYPE << "\" " << KEY_ATTR << "=\"" << PART_TYPE_KEY << "\" " <<
|
|
VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";*/
|
|
|
|
// stores volume's local matrix
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\"";
|
|
Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix();
|
|
for (int r = 0; r < 4; ++r) {
|
|
for (int c = 0; c < 4; ++c) {
|
|
stream << matrix(r, c);
|
|
if (r != 3 || c != 3)
|
|
stream << " ";
|
|
}
|
|
}
|
|
stream << "\"/>\n";
|
|
|
|
// stores volume's source data
|
|
{
|
|
std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
|
|
//std::string prefix = std::string(" <") + METADATA_TAG + " " + KEY_ATTR + "=\"";
|
|
std::string prefix = std::string(" <") + METADATA_TAG + " " + KEY_ATTR + "=\"";
|
|
if (! volume->source.input_file.empty()) {
|
|
stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n";
|
|
stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n";
|
|
stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n";
|
|
stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n";
|
|
stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n";
|
|
stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n";
|
|
}
|
|
assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters);
|
|
if (volume->source.is_converted_from_inches)
|
|
stream << prefix << SOURCE_IN_INCHES << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
|
else if (volume->source.is_converted_from_meters)
|
|
stream << prefix << SOURCE_IN_METERS << "\" " << VALUE_ATTR << "=\"1\"/>\n";
|
|
}
|
|
|
|
// stores volume's config data
|
|
for (const std::string& key : volume->config.keys()) {
|
|
stream << " <" << METADATA_TAG << " "<< KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n";
|
|
}
|
|
|
|
// stores mesh's statistics
|
|
const RepairedMeshErrors& stats = volume->mesh().stats().repaired_errors;
|
|
stream << " <" << MESH_STAT_TAG << " ";
|
|
stream << MESH_STAT_EDGES_FIXED << "=\"" << stats.edges_fixed << "\" ";
|
|
stream << MESH_STAT_DEGENERATED_FACETS << "=\"" << stats.degenerate_facets << "\" ";
|
|
stream << MESH_STAT_FACETS_REMOVED << "=\"" << stats.facets_removed << "\" ";
|
|
stream << MESH_STAT_FACETS_RESERVED << "=\"" << stats.facets_reversed << "\" ";
|
|
stream << MESH_STAT_BACKWARDS_EDGES << "=\"" << stats.backwards_edges << "\"/>\n";
|
|
|
|
stream << " </" << PART_TAG << ">\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
stream << " </" << OBJECT_TAG << ">\n";
|
|
}
|
|
}
|
|
|
|
//BBS: store plate related logic
|
|
std::vector<std::string> gcode_paths;
|
|
for (unsigned int i = 0; i < (unsigned int)plate_data_list.size(); ++i)
|
|
{
|
|
PlateData* plate_data = plate_data_list[i];
|
|
int instance_size = plate_data->objects_and_instances.size();
|
|
|
|
if (plate_data != nullptr) {
|
|
stream << " <" << PLATE_TAG << ">\n";
|
|
//plate index
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PLATERID_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->plate_index + 1 << "\"/>\n";
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << LOCK_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->locked<< "\"/>\n";
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << GCODE_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << plate_data->gcode_file << "\"/>\n";
|
|
if (!plate_data->gcode_file.empty()) {
|
|
gcode_paths.push_back(plate_data->gcode_file);
|
|
}
|
|
if (plate_data->plate_thumbnail.is_valid()) {
|
|
std::string thumbnail_file_in_3mf = (boost::format(THUMBNAIL_FILE_FORMAT) % (plate_data->plate_index + 1)).str();
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << THUMBNAIL_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << thumbnail_file_in_3mf << "\"/>\n";
|
|
}
|
|
|
|
if (!plate_data->pattern_file.empty()) {
|
|
std::string pattern_file_in_3mf = (boost::format(PATTERN_FILE_FORMAT) % (plate_data->plate_index + 1)).str();
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PATTERN_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << pattern_file_in_3mf << "\"/>\n";
|
|
}
|
|
if (!plate_data->pattern_bbox_file.empty()) {
|
|
std::string pattern_bbox_file_in_3mf = (boost::format(PATTERN_CONFIG_FILE_FORMAT) % (plate_data->plate_index + 1)).str();
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PATTERN_BBOX_FILE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha << pattern_bbox_file_in_3mf << "\"/>\n";
|
|
}
|
|
|
|
if (instance_size > 0)
|
|
{
|
|
for (unsigned int j = 0; j < instance_size; ++j)
|
|
{
|
|
stream << " <" << INSTANCE_TAG << ">\n";
|
|
int obj_id = plate_data->objects_and_instances[j].first;
|
|
int inst_id = plate_data->objects_and_instances[j].second;
|
|
if (m_skip_static) {
|
|
obj_id = model.objects[obj_id]->get_backup_id();
|
|
} else {
|
|
//inst_id = convert_instance_id_to_resource_id(model, obj_id, inst_id);
|
|
obj_id = convert_instance_id_to_resource_id(model, obj_id, 0);
|
|
}
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OBJECT_ID_ATTR << "\" " << VALUE_ATTR << "=\"" << obj_id << "\"/>\n";
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << INSTANCEID_ATTR << "\" " << VALUE_ATTR << "=\"" << inst_id << "\"/>\n";
|
|
stream << " </" << INSTANCE_TAG << ">\n";
|
|
}
|
|
}
|
|
stream << " </" << PLATE_TAG << ">\n";
|
|
}
|
|
}
|
|
// write model rels
|
|
_add_relationships_file_to_archive(archive, BBS_MODEL_CONFIG_RELS_FILE, gcode_paths, {"http://schemas.bambulab.com/package/2021/gcode"}, Slic3r::PackingTemporaryData(), export_plate_idx);
|
|
|
|
//BBS: store assemble related info
|
|
stream << " <" << ASSEMBLE_TAG << ">\n";
|
|
for (const IdToObjectDataMap::value_type& obj_metadata : objects_data) {
|
|
const ModelObject* obj = obj_metadata.second.object;
|
|
if (obj != nullptr) {
|
|
for (int instance_idx = 0; instance_idx < obj->instances.size(); ++instance_idx) {
|
|
if (obj->instances[instance_idx]->is_assemble_initialized()) {
|
|
if (m_skip_static)
|
|
stream << " <" << ASSEMBLE_ITEM_TAG << " " << OBJECT_ID_ATTR << "=\"" << obj_metadata.first << "\" ";
|
|
else
|
|
stream << " <" << ASSEMBLE_ITEM_TAG << " " << OBJECT_ID_ATTR << "=\"" << obj_metadata.first + obj->volumes.size() << "\" ";
|
|
stream << INSTANCEID_ATTR << "=\"" << instance_idx << "\" " << TRANSFORM_ATTR << "=\"";
|
|
for (unsigned c = 0; c < 4; ++c) {
|
|
for (unsigned r = 0; r < 3; ++r) {
|
|
const Transform3d assemble_trans = obj->instances[instance_idx]->get_assemble_transformation().get_matrix();
|
|
stream << assemble_trans(r, c);
|
|
if (r != 2 || c != 3)
|
|
stream << " ";
|
|
}
|
|
}
|
|
|
|
stream << "\" ";
|
|
|
|
stream << OFFSET_ATTR << "=\"";
|
|
Vec3d ofs2ass = obj->instances[instance_idx]->get_offset_to_assembly();
|
|
stream << ofs2ass(0) << " " << ofs2ass(1) << " " << ofs2ass(2);
|
|
stream << "\" />\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
stream << " </" << ASSEMBLE_TAG << ">\n";
|
|
|
|
stream << "</" << CONFIG_TAG << ">\n";
|
|
|
|
std::string out = stream.str();
|
|
if (!mz_zip_writer_add_mem(&archive, BBS_MODEL_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format("Unable to add model config file to archive\n");
|
|
add_error("Unable to add model config file to archive");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list)
|
|
{
|
|
std::stringstream stream;
|
|
// Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back
|
|
// when loaded as accurately as possible.
|
|
stream << std::setprecision(std::numeric_limits<double>::max_digits10);
|
|
stream << std::setiosflags(std::ios::fixed) << std::setprecision(2);
|
|
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
stream << "<" << CONFIG_TAG << ">\n";
|
|
|
|
// save slice header for debug
|
|
stream << " <" << SLICE_HEADER_TAG << ">\n";
|
|
stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Type" << "\" " << VALUE_ATTR << "=\"" << "slicer" << "\"/>\n";
|
|
stream << " <" << SLICE_HEADER_ITEM_TAG << " " << KEY_ATTR << "=\"" << "X-BBL-Client-Version" << "\" " << VALUE_ATTR << "=\"" << convert_to_full_version(SLIC3R_VERSION) << "\"/>\n";
|
|
stream << " </" << SLICE_HEADER_TAG << ">\n";
|
|
|
|
for (unsigned int i = 0; i < (unsigned int)plate_data_list.size(); ++i)
|
|
{
|
|
PlateData* plate_data = plate_data_list[i];
|
|
//int instance_size = plate_data->objects_and_instances.size();
|
|
|
|
if (plate_data != nullptr && plate_data->is_sliced_valid) {
|
|
stream << " <" << PLATE_TAG << ">\n";
|
|
//plate index
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << PLATE_IDX_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->plate_index + 1 << "\"/>\n";
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SLICE_PREDICTION_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->get_gcode_prediction_str() << "\"/>\n";
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << SLICE_WEIGHT_ATTR << "\" " << VALUE_ATTR << "=\"" << plate_data->get_gcode_weight_str() << "\"/>\n";
|
|
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << OUTSIDE_ATTR << "\" " << VALUE_ATTR << "=\"" << std::boolalpha<< plate_data->toolpath_outside << "\"/>\n";
|
|
|
|
for (auto it = plate_data->slice_filaments_info.begin(); it != plate_data->slice_filaments_info.end(); it++)
|
|
{
|
|
stream << " <" << FILAMENT_TAG << " " << FILAMENT_ID_TAG << "=\"" << std::to_string(it->id + 1) << "\" "
|
|
<< FILAMENT_TYPE_TAG << "=\"" << it->type << "\" "
|
|
<< FILAMENT_COLOR_TAG << "=\"" << it->color << "\" "
|
|
<< FILAMENT_USED_M_TAG << "=\"" << it->used_m << "\" "
|
|
<< FILAMENT_USED_G_TAG << "=\"" << it->used_g << "\" />\n";
|
|
}
|
|
stream << " </" << PLATE_TAG << ">\n";
|
|
}
|
|
}
|
|
stream << "</" << CONFIG_TAG << ">\n";
|
|
|
|
std::string out = stream.str();
|
|
|
|
if (!mz_zip_writer_add_mem(&archive, SLICE_INFO_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
add_error("Unable to add model config file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", store slice-info to 3mf, length %1%, failed\n") % out.length();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool _BBS_3MF_Exporter::_add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn)
|
|
{
|
|
bool result = true;
|
|
bool cb_cancel = false;
|
|
|
|
PlateDataPtrs plate_data_list2;
|
|
for (unsigned int i = 0; i < (unsigned int)plate_data_list.size(); ++i)
|
|
{
|
|
if (proFn) {
|
|
proFn(EXPORT_STAGE_ADD_GCODE, i, plate_data_list.size(), cb_cancel);
|
|
if (cb_cancel)
|
|
return false;
|
|
}
|
|
|
|
PlateData* plate_data = plate_data_list[i];
|
|
if (!plate_data->gcode_file.empty() && plate_data->is_sliced_valid && boost::filesystem::exists(plate_data->gcode_file)) {
|
|
plate_data_list2.push_back(plate_data);
|
|
}
|
|
}
|
|
|
|
boost::mutex mutex;
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, plate_data_list2.size(), 1), [this, &plate_data_list2, &root_archive = archive, &mutex, &result](const tbb::blocked_range<size_t>& range) {
|
|
for (int i = range.begin(); i < range.end(); ++i) {
|
|
PlateData* plate_data = plate_data_list2[i];
|
|
auto src_gcode_file = plate_data->gcode_file;
|
|
std::string gcode_in_3mf = (boost::format(GCODE_FILE_FORMAT) % (plate_data->plate_index + 1)).str();
|
|
|
|
plate_data->gcode_file = gcode_in_3mf;
|
|
mz_zip_archive archive;
|
|
mz_zip_writer_staged_context context;
|
|
mz_zip_zero_struct(&archive);
|
|
mz_zip_writer_init_heap(&archive, 0, 1024 * 1024);
|
|
{
|
|
mz_zip_writer_add_staged_open(&archive, &context, gcode_in_3mf.c_str(), m_zip64 ? (uint64_t(1) << 30) * 16 : (uint64_t(1) << 32) - 1, nullptr, nullptr, 0,
|
|
MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0);
|
|
boost::filesystem::path src_gcode_path(src_gcode_file);
|
|
if (!boost::filesystem::exists(src_gcode_path)) {
|
|
BOOST_LOG_TRIVIAL(error) << "Gcode is missing, filename = " << src_gcode_file;
|
|
result = false;
|
|
}
|
|
boost::filesystem::ifstream ifs(src_gcode_file, std::ios::binary);
|
|
std::string buf(64 * 1024, 0);
|
|
while (ifs) {
|
|
ifs.read(buf.data(), buf.size());
|
|
mz_zip_writer_add_staged_data(&context, buf.data(), ifs.gcount());
|
|
}
|
|
mz_zip_writer_add_staged_finish(&context);
|
|
}
|
|
void *ppBuf; size_t pSize;
|
|
mz_zip_writer_finalize_heap_archive(&archive, &ppBuf, &pSize);
|
|
mz_zip_writer_end(&archive);
|
|
mz_zip_zero_struct(&archive);
|
|
mz_zip_reader_init_mem(&archive, ppBuf, pSize, 0);
|
|
{
|
|
boost::unique_lock l(mutex);
|
|
mz_zip_writer_add_from_zip_reader(&root_archive, &archive, 0);
|
|
}
|
|
mz_zip_reader_end(&archive);
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format(", store %1% to 3mf %2%\n") % src_gcode_file % gcode_in_3mf;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config)
|
|
{
|
|
std::string out = "";
|
|
|
|
if (!model.custom_gcode_per_print_z.gcodes.empty()) {
|
|
pt::ptree tree;
|
|
pt::ptree& main_tree = tree.add("custom_gcodes_per_layer", "");
|
|
|
|
for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) {
|
|
pt::ptree& code_tree = main_tree.add("layer", "");
|
|
|
|
// store data of custom_gcode_per_print_z
|
|
code_tree.put("<xmlattr>.top_z" , code.print_z );
|
|
code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
|
|
code_tree.put("<xmlattr>.extruder" , code.extruder );
|
|
code_tree.put("<xmlattr>.color" , code.color );
|
|
code_tree.put("<xmlattr>.extra" , code.extra );
|
|
|
|
//BBS
|
|
std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") :
|
|
code.type == CustomGCode::PausePrint ? config->opt_string("machine_pause_gcode") :
|
|
//code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") :
|
|
code.type == CustomGCode::ToolChange ? "tool_change" : code.extra;
|
|
code_tree.put("<xmlattr>.gcode" , gcode );
|
|
}
|
|
|
|
pt::ptree& mode_tree = main_tree.add("mode", "");
|
|
// store mode of a custom_gcode_per_print_z
|
|
mode_tree.put("<xmlattr>.value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
|
|
model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode :
|
|
CustomGCode::MultiExtruderMode);
|
|
|
|
if (!tree.empty()) {
|
|
std::ostringstream oss;
|
|
boost::property_tree::write_xml(oss, tree);
|
|
out = oss.str();
|
|
|
|
// Post processing("beautification") of the output string
|
|
boost::replace_all(out, "><", ">\n<");
|
|
}
|
|
}
|
|
|
|
if (!out.empty()) {
|
|
if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
|
|
add_error("Unable to add custom Gcodes per print_z file to archive");
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":" << __LINE__ << boost::format(", Unable to add custom Gcodes per print_z file to archive\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _BBS_3MF_Exporter::_add_auxiliary_dir_to_archive(mz_zip_archive &archive, const std::string &aux_dir, PackingTemporaryData &data)
|
|
{
|
|
bool result = true;
|
|
|
|
if (aux_dir.empty()) {
|
|
//no accessory directories
|
|
return result;
|
|
}
|
|
|
|
boost::filesystem::path dir = boost::filesystem::path(aux_dir);
|
|
if (!boost::filesystem::exists(dir))
|
|
{
|
|
//no accessory directories
|
|
return result;
|
|
}
|
|
|
|
static std::string const nocomp_exts[] = {".png", ".jpg", ".mp4", ".jpeg"};
|
|
std::deque<boost::filesystem::path> directories({dir});
|
|
int root_dir_len = dir.string().length() + 1;
|
|
//boost file access
|
|
while (!directories.empty()) {
|
|
boost::filesystem::directory_iterator iterator(directories.front());
|
|
directories.pop_front();
|
|
for (auto &dir_entry : iterator)
|
|
{
|
|
std::string src_file;
|
|
std::string dst_in_3mf;
|
|
if (boost::filesystem::is_directory(dir_entry.path()))
|
|
{
|
|
if (dir_entry.path().filename() == THUMBNAILS_DIR) {
|
|
boost::filesystem::directory_iterator iterator(dir_entry.path());
|
|
for (auto &file_entry : iterator) {
|
|
if (boost::filesystem::is_regular_file(file_entry.path())) {
|
|
// BBS generate thumbnails
|
|
// copy to /Metadata folder
|
|
src_file = file_entry.path().string();
|
|
/* save to .thumbnails */
|
|
dst_in_3mf = file_entry.path().string();
|
|
dst_in_3mf.replace(0, root_dir_len, AUXILIARY_DIR);
|
|
std::replace(dst_in_3mf.begin(), dst_in_3mf.end(), '\\', '/');
|
|
if (file_entry.path().filename() == _3MF_COVER_FILE) {
|
|
data._3mf_thumbnail = dst_in_3mf;
|
|
} else if (file_entry.path().filename() == PRINTER_THUMBNAIL_SMALL_FILE) {
|
|
data._3mf_printer_thumbnail_small = dst_in_3mf;
|
|
} else if (file_entry.path().filename() == PRINTER_THUMBNAIL_MIDDLE_FILE) {
|
|
data._3mf_printer_thumbnail_middle = dst_in_3mf;
|
|
}
|
|
|
|
result &= _add_file_to_archive(archive, dst_in_3mf, src_file);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
directories.push_back(dir_entry.path());
|
|
}
|
|
continue;
|
|
}
|
|
if (boost::filesystem::is_regular_file(dir_entry.path()))
|
|
{
|
|
src_file = dir_entry.path().string();
|
|
dst_in_3mf = dir_entry.path().string();
|
|
dst_in_3mf.replace(0, root_dir_len, AUXILIARY_DIR);
|
|
std::replace(dst_in_3mf.begin(), dst_in_3mf.end(), '\\', '/');
|
|
result &= _add_file_to_archive(archive, dst_in_3mf, src_file);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Perform conversions based on the config values available.
|
|
//FIXME provide a version of PrusaSlicer that stored the project file (3MF).
|
|
static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config)
|
|
{
|
|
if (! config.has("brim_object_gap")) {
|
|
if (auto *opt_elephant_foot = config.option<ConfigOptionFloat>("elefant_foot_compensation", false); opt_elephant_foot) {
|
|
// Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation.
|
|
auto *opt_brim_separation = config.option<ConfigOptionFloat>("brim_object_gap", true);
|
|
opt_brim_separation->value = opt_elephant_foot->value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// backup backgroud thread to dispatch tasks and coperate with ui thread
|
|
class _BBS_Backup_Manager
|
|
{
|
|
public:
|
|
static _BBS_Backup_Manager& get() {
|
|
static _BBS_Backup_Manager m;
|
|
return m;
|
|
}
|
|
|
|
void set_post_callback(std::function<void(int)> c) {
|
|
boost::lock_guard lock(m_mutex);
|
|
m_post_callback = c;
|
|
}
|
|
|
|
void run_ui_tasks() {
|
|
std::deque<Task> tasks;
|
|
{
|
|
boost::lock_guard lock(m_mutex);
|
|
std::swap(tasks, m_ui_tasks);
|
|
}
|
|
for (auto& t : tasks)
|
|
{
|
|
process_ui_task(t);
|
|
}
|
|
}
|
|
|
|
void push_object_gaurd(ModelObject& object) {
|
|
m_gaurd_objects.push_back(std::make_pair(&object, 0));
|
|
}
|
|
|
|
void pop_object_gaurd() {
|
|
auto object = m_gaurd_objects.back();
|
|
m_gaurd_objects.pop_back();
|
|
if (object.second)
|
|
add_object_mesh(*object.first);
|
|
}
|
|
|
|
void add_object_mesh(ModelObject& object) {
|
|
for (auto& g : m_gaurd_objects) {
|
|
if (g.first == &object) {
|
|
++g.second;
|
|
return;
|
|
}
|
|
}
|
|
// clone object
|
|
auto model = object.get_model();
|
|
auto o = m_temp_model.add_object(object);
|
|
int backup_id = model->get_object_backup_id(object);
|
|
push_task({ AddObject, (size_t) backup_id, object.get_model()->get_backup_path(), o, 1 });
|
|
}
|
|
|
|
void remove_object_mesh(ModelObject& object) {
|
|
push_task({ RemoveObject, object.id().id, object.get_model()->get_backup_path() });
|
|
}
|
|
|
|
void backup_soon() {
|
|
boost::lock_guard lock(m_mutex);
|
|
m_other_changes_backup = true;
|
|
m_tasks.push_back({ Backup, 0, std::string(), nullptr, ++m_task_seq });
|
|
m_cond.notify_all();
|
|
}
|
|
|
|
void remove_backup(Model& model, bool removeAll) {
|
|
BOOST_LOG_TRIVIAL(info)
|
|
<< "remove_backup " << model.get_backup_path() << ", " << removeAll;
|
|
std::deque<Task> canceled_tasks;
|
|
boost::unique_lock lock(m_mutex);
|
|
if (removeAll && model.is_need_backup()) {
|
|
// running task may not be canceled
|
|
for (auto & t : m_ui_tasks)
|
|
canceled_tasks.push_back(t);
|
|
for (auto & t : m_tasks)
|
|
canceled_tasks.push_back(t);
|
|
m_ui_tasks.clear();
|
|
m_tasks.clear();
|
|
}
|
|
m_tasks.push_back({ RemoveBackup, model.id().id, model.get_backup_path(), nullptr, removeAll });
|
|
++m_task_seq;
|
|
if (model.is_need_backup()) {
|
|
m_other_changes = false;
|
|
m_other_changes_backup = false;
|
|
}
|
|
m_cond.notify_all();
|
|
lock.unlock();
|
|
for (auto& t : canceled_tasks) {
|
|
process_ui_task(t, true);
|
|
}
|
|
}
|
|
|
|
void set_interval(long n) {
|
|
boost::lock_guard lock(m_mutex);
|
|
m_next_backup -= boost::posix_time::seconds(m_interval);
|
|
m_interval = n;
|
|
m_next_backup += boost::posix_time::seconds(m_interval);
|
|
m_cond.notify_all();
|
|
}
|
|
|
|
void put_other_changes()
|
|
{
|
|
BOOST_LOG_TRIVIAL(info) << "put_other_changes";
|
|
m_other_changes = true;
|
|
m_other_changes_backup = true;
|
|
}
|
|
|
|
void clear_other_changes(bool backup)
|
|
{
|
|
if (backup)
|
|
m_other_changes_backup = false;
|
|
else
|
|
m_other_changes = false;
|
|
}
|
|
|
|
bool has_other_changes(bool backup)
|
|
{
|
|
return backup ? m_other_changes_backup : m_other_changes;
|
|
}
|
|
|
|
private:
|
|
enum TaskType {
|
|
None,
|
|
Backup, // this task is working as response in ui thread
|
|
AddObject,
|
|
RemoveObject,
|
|
RemoveBackup,
|
|
Exit
|
|
};
|
|
struct Task {
|
|
TaskType type;
|
|
size_t id = 0;
|
|
std::string path;
|
|
ModelObject* object = nullptr;
|
|
union {
|
|
size_t delay = 0; // delay sequence, only last task is delayed
|
|
size_t sequence;
|
|
bool removeAll;
|
|
};
|
|
friend bool operator==(Task const& l, Task const& r) {
|
|
return l.type == r.type && l.id == r.id;
|
|
}
|
|
std::string to_string() const {
|
|
constexpr char const *type_names[] = {"None",
|
|
"Backup",
|
|
"AddObject",
|
|
"RemoveObject",
|
|
"RemoveBackup",
|
|
"Exit"};
|
|
std::ostringstream os;
|
|
os << "{ type:" << type_names[type] << ", id:" << id
|
|
<< ", path:" << path
|
|
<< ", object:" << (object ? object->id().id : 0) << ", extra:" << delay << "}";
|
|
return os.str();
|
|
}
|
|
};
|
|
|
|
struct timer {
|
|
timer(char const * msg) : msg(msg), start(boost::posix_time::microsec_clock::universal_time()) { }
|
|
~timer() {
|
|
#ifdef __WIN32__
|
|
auto end = boost::posix_time::microsec_clock::universal_time();
|
|
int duration = (int)(end - start).total_milliseconds();
|
|
char buf[20];
|
|
OutputDebugStringA(msg);
|
|
OutputDebugStringA(": ");
|
|
OutputDebugStringA(itoa(duration, buf, 10));
|
|
OutputDebugStringA("\n");
|
|
#endif
|
|
}
|
|
char const* msg;
|
|
boost::posix_time::ptime start;
|
|
};
|
|
private:
|
|
_BBS_Backup_Manager() : m_thread(boost::ref(*this)) {
|
|
m_next_backup = boost::get_system_time() + boost::posix_time::seconds(m_interval);
|
|
}
|
|
|
|
~_BBS_Backup_Manager() {
|
|
push_task({Exit});
|
|
m_thread.join();
|
|
}
|
|
|
|
void push_task(Task const & t) {
|
|
boost::unique_lock lock(m_mutex);
|
|
if (t.delay && !m_tasks.empty() && m_tasks.back() == t) {
|
|
auto t2 = m_tasks.back();
|
|
m_tasks.back() = t;
|
|
m_tasks.back().delay = t2.delay + 1;
|
|
m_cond.notify_all();
|
|
lock.unlock();
|
|
process_ui_task(t2);
|
|
}
|
|
else {
|
|
m_tasks.push_back(t);
|
|
++m_task_seq;
|
|
m_cond.notify_all();
|
|
}
|
|
}
|
|
|
|
void process_ui_task(Task& t, bool canceled = false) {
|
|
BOOST_LOG_TRIVIAL(info) << "process_ui_task" << t.to_string();
|
|
switch (t.type) {
|
|
case Backup: {
|
|
if (canceled)
|
|
break;
|
|
std::function<void(int)> callback;
|
|
boost::unique_lock lock(m_mutex);
|
|
if (m_task_seq != t.sequence) {
|
|
if (find(m_tasks.begin(), m_tasks.end(), Task{ Backup }) == m_tasks.end()) {
|
|
t.sequence = ++m_task_seq; // may has pending tasks, retry later
|
|
m_tasks.push_back(t);
|
|
m_cond.notify_all();
|
|
}
|
|
break;
|
|
}
|
|
callback = m_post_callback;
|
|
lock.unlock();
|
|
{
|
|
timer t("backup cost");
|
|
try {
|
|
if (callback) callback(1);
|
|
} catch (...) {}
|
|
}
|
|
m_other_changes_backup = false;
|
|
break;
|
|
}
|
|
case AddObject:
|
|
m_temp_model.delete_object(t.object);
|
|
break;
|
|
case RemoveBackup:
|
|
if (t.removeAll) {
|
|
try {
|
|
boost::filesystem::remove(t.path + "/lock.txt");
|
|
boost::filesystem::remove_all(t.path);
|
|
BOOST_LOG_TRIVIAL(info) << "process_ui_task: remove all of backup path " << t.path;
|
|
} catch (std::exception &ex) {
|
|
BOOST_LOG_TRIVIAL(error) << "process_ui_task: failed to remove backup path" << t.path << ": " << ex.what();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void process_task(Task& t) {
|
|
BOOST_LOG_TRIVIAL(info) << "process_task" << t.to_string();
|
|
switch (t.type) {
|
|
case Backup:
|
|
// do it in response
|
|
break;
|
|
case AddObject: {
|
|
{
|
|
CNumericLocalesSetter locales_setter;
|
|
_BBS_3MF_Exporter e;
|
|
e.save_object_mesh(t.path, *t.object, (int) t.id);
|
|
// response to delete cloned object
|
|
}
|
|
break;
|
|
}
|
|
case RemoveObject: {
|
|
boost::filesystem::remove(t.path + "/mesh_" + boost::lexical_cast<std::string>(t.id) + ".xml");
|
|
t.type = None;
|
|
break;
|
|
}
|
|
case RemoveBackup: {
|
|
try {
|
|
boost::filesystem::remove(t.path + "/.3mf");
|
|
// We Saved with SplitModel now, so we can safe delete these sub models.
|
|
boost::filesystem::remove_all(t.path + "/3D/Objects");
|
|
boost::filesystem::create_directory(t.path + "/3D/Objects");
|
|
}
|
|
catch (...) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
void operator()() {
|
|
boost::unique_lock lock(m_mutex);
|
|
while (true)
|
|
{
|
|
while (m_tasks.empty()) {
|
|
m_cond.timed_wait(lock, m_next_backup);
|
|
if (m_interval > 0 && boost::get_system_time() > m_next_backup) {
|
|
m_tasks.push_back({ Backup, 0, std::string(), nullptr, ++m_task_seq });
|
|
m_next_backup += boost::posix_time::seconds(m_interval);
|
|
// Maybe wakeup from power sleep
|
|
if (m_next_backup < boost::get_system_time())
|
|
m_next_backup = boost::get_system_time() + boost::posix_time::seconds(m_interval);
|
|
}
|
|
}
|
|
Task t = m_tasks.front();
|
|
if (t.type == Exit) break;
|
|
if (t.object && t.delay) {
|
|
if (!delay_task(t, lock))
|
|
continue;
|
|
}
|
|
m_tasks.pop_front();
|
|
auto callback = m_post_callback;
|
|
lock.unlock();
|
|
process_task(t);
|
|
lock.lock();
|
|
if (t.type > None) {
|
|
m_ui_tasks.push_back(t);
|
|
if (m_ui_tasks.size() == 1 && callback)
|
|
callback(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool delay_task(Task& t, boost::unique_lock<boost::mutex> & lock) {
|
|
// delay last task for 3 seconds after last modify
|
|
auto now = boost::get_system_time();
|
|
auto delay_expire = now + boost::posix_time::seconds(10); // must not delay over this time
|
|
auto wait = now + boost::posix_time::seconds(3);
|
|
while (true) {
|
|
m_cond.timed_wait(lock, wait);
|
|
// Only delay when it's the only-one task
|
|
if (m_tasks.size() != 1 || m_tasks.front().delay == t.delay)
|
|
break;
|
|
t.delay = m_tasks.front().delay;
|
|
now = boost::get_system_time();
|
|
if (now >= delay_expire)
|
|
break;
|
|
wait = now + boost::posix_time::seconds(3);
|
|
if (wait > delay_expire)
|
|
wait = delay_expire;
|
|
};
|
|
// task maybe canceled
|
|
if (m_tasks.empty())
|
|
return false;
|
|
t = m_tasks.front();
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
boost::mutex m_mutex;
|
|
boost::condition_variable m_cond;
|
|
std::deque<Task> m_tasks;
|
|
std::deque<Task> m_ui_tasks;
|
|
size_t m_task_seq = 0;
|
|
// param 0: should call run_ui_tasks
|
|
// param 1: should backup current project
|
|
std::function<void(int)> m_post_callback;
|
|
long m_interval = 1 * 60;
|
|
boost::system_time m_next_backup;
|
|
Model m_temp_model; // visit only in main thread
|
|
bool m_other_changes = false; // visit only in main thread
|
|
bool m_other_changes_backup = false; // visit only in main thread
|
|
std::vector<std::pair<ModelObject*, size_t>> m_gaurd_objects;
|
|
boost::thread m_thread;
|
|
};
|
|
|
|
|
|
//BBS: add plate data list related logic
|
|
bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, PlateDataPtrs* plate_data_list, std::vector<Preset*>* project_presets, bool* is_bbl_3mf, Semver* file_version, Import3mfProgressFn proFn, LoadStrategy strategy, BBLProject *project)
|
|
{
|
|
if (path == nullptr || config == nullptr || model == nullptr)
|
|
return false;
|
|
|
|
// All import should use "C" locales for number formatting.
|
|
CNumericLocalesSetter locales_setter;
|
|
_BBS_3MF_Importer importer;
|
|
bool res = importer.load_model_from_file(path, *model, *plate_data_list, *project_presets, *config, *config_substitutions, strategy, *is_bbl_3mf, *file_version, proFn, project);
|
|
importer.log_errors();
|
|
//BBS: remove legacy project logic currently
|
|
//handle_legacy_project_loaded(importer.version(), *config);
|
|
return res;
|
|
}
|
|
|
|
std::string bbs_3mf_get_thumbnail(const char *path)
|
|
{
|
|
_BBS_3MF_Importer importer;
|
|
std::string data;
|
|
bool res = importer.get_thumbnail(path, data);
|
|
if (!res) importer.log_errors();
|
|
return data;
|
|
}
|
|
|
|
bool store_bbs_3mf(StoreParams& store_params)
|
|
{
|
|
// All export should use "C" locales for number formatting.
|
|
CNumericLocalesSetter locales_setter;
|
|
|
|
if (store_params.path == nullptr || store_params.model == nullptr)
|
|
return false;
|
|
|
|
_BBS_3MF_Exporter exporter;
|
|
bool res = exporter.save_model_to_file(store_params);
|
|
if (!res)
|
|
exporter.log_errors();
|
|
|
|
return res;
|
|
}
|
|
|
|
//BBS: release plate data list
|
|
void release_PlateData_list(PlateDataPtrs& plate_data_list)
|
|
{
|
|
//clear
|
|
for (unsigned int i = 0; i < plate_data_list.size(); i++)
|
|
{
|
|
delete plate_data_list[i];
|
|
}
|
|
plate_data_list.clear();
|
|
|
|
return;
|
|
}
|
|
|
|
// backup interface
|
|
|
|
void save_object_mesh(ModelObject& object)
|
|
{
|
|
if (!object.get_model() || !object.get_model()->is_need_backup())
|
|
return;
|
|
if (object.volumes.empty() || object.instances.empty())
|
|
return;
|
|
_BBS_Backup_Manager::get().add_object_mesh(object);
|
|
}
|
|
|
|
void delete_object_mesh(ModelObject& object)
|
|
{
|
|
// not really remove
|
|
// _BBS_Backup_Manager::get().remove_object_mesh(object);
|
|
}
|
|
|
|
void backup_soon()
|
|
{
|
|
_BBS_Backup_Manager::get().backup_soon();
|
|
}
|
|
|
|
void remove_backup(Model& model, bool removeAll)
|
|
{
|
|
_BBS_Backup_Manager::get().remove_backup(model, removeAll);
|
|
}
|
|
|
|
void set_backup_interval(long interval)
|
|
{
|
|
_BBS_Backup_Manager::get().set_interval(interval);
|
|
}
|
|
|
|
void set_backup_callback(std::function<void(int)> callback)
|
|
{
|
|
_BBS_Backup_Manager::get().set_post_callback(callback);
|
|
}
|
|
|
|
void run_backup_ui_tasks()
|
|
{
|
|
_BBS_Backup_Manager::get().run_ui_tasks();
|
|
}
|
|
|
|
bool has_restore_data(std::string & path, std::string& origin)
|
|
{
|
|
if (path.empty()) {
|
|
origin = "<lock>";
|
|
return false;
|
|
}
|
|
if (boost::filesystem::exists(path + "/lock.txt")) {
|
|
std::string pid;
|
|
boost::filesystem::load_string_file(path + "/lock.txt", pid);
|
|
try {
|
|
if (get_process_name(boost::lexical_cast<int>(pid)) ==
|
|
get_process_name(0)) {
|
|
origin = "<lock>";
|
|
return false;
|
|
}
|
|
}
|
|
catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
std::string file3mf = path + "/.3mf";
|
|
if (!boost::filesystem::exists(file3mf))
|
|
return false;
|
|
try {
|
|
if (boost::filesystem::exists(path + "/origin.txt"))
|
|
boost::filesystem::load_string_file(path + "/origin.txt", origin);
|
|
}
|
|
catch (...) {
|
|
}
|
|
path = file3mf;
|
|
return true;
|
|
}
|
|
|
|
void put_other_changes()
|
|
{
|
|
_BBS_Backup_Manager::get().put_other_changes();
|
|
}
|
|
|
|
void clear_other_changes(bool backup)
|
|
{
|
|
_BBS_Backup_Manager::get().clear_other_changes(backup);
|
|
}
|
|
|
|
bool has_other_changes(bool backup)
|
|
{
|
|
return _BBS_Backup_Manager::get().has_other_changes(backup);
|
|
}
|
|
|
|
SaveObjectGaurd::SaveObjectGaurd(ModelObject& object)
|
|
{
|
|
_BBS_Backup_Manager::get().push_object_gaurd(object);
|
|
}
|
|
|
|
SaveObjectGaurd::~SaveObjectGaurd()
|
|
{
|
|
_BBS_Backup_Manager::get().pop_object_gaurd();
|
|
}
|
|
|
|
} // namespace Slic3r
|