Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/libslic3r/Format')
-rw-r--r--src/libslic3r/Format/3mf.cpp2022
-rw-r--r--src/libslic3r/Format/3mf.hpp19
-rw-r--r--src/libslic3r/Format/AMF.cpp901
-rw-r--r--src/libslic3r/Format/AMF.hpp19
-rw-r--r--src/libslic3r/Format/OBJ.cpp118
-rw-r--r--src/libslic3r/Format/OBJ.hpp14
-rw-r--r--src/libslic3r/Format/PRUS.cpp399
-rw-r--r--src/libslic3r/Format/PRUS.hpp14
-rw-r--r--src/libslic3r/Format/STL.cpp58
-rw-r--r--src/libslic3r/Format/STL.hpp17
-rw-r--r--src/libslic3r/Format/objparser.cpp540
-rw-r--r--src/libslic3r/Format/objparser.hpp109
12 files changed, 4230 insertions, 0 deletions
diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp
new file mode 100644
index 000000000..43c99f19f
--- /dev/null
+++ b/src/libslic3r/Format/3mf.cpp
@@ -0,0 +1,2022 @@
+#include "../libslic3r.h"
+#include "../Model.hpp"
+#include "../Utils.hpp"
+#include "../GCode.hpp"
+#include "../slic3r/GUI/PresetBundle.hpp"
+
+#include "3mf.hpp"
+
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/nowide/fstream.hpp>
+
+#include <expat.h>
+#include <Eigen/Dense>
+#include <miniz/miniz_zip.h>
+
+// 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.
+const unsigned int VERSION_3MF = 1;
+const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file
+
+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 CONTENT_TYPES_FILE = "[Content_Types].xml";
+const std::string RELATIONSHIPS_FILE = "_rels/.rels";
+const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config";
+const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
+const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
+
+const char* MODEL_TAG = "model";
+const char* RESOURCES_TAG = "resources";
+const char* OBJECT_TAG = "object";
+const char* MESH_TAG = "mesh";
+const char* VERTICES_TAG = "vertices";
+const char* VERTEX_TAG = "vertex";
+const char* TRIANGLES_TAG = "triangles";
+const char* TRIANGLE_TAG = "triangle";
+const char* COMPONENTS_TAG = "components";
+const char* COMPONENT_TAG = "component";
+const char* BUILD_TAG = "build";
+const char* ITEM_TAG = "item";
+const char* METADATA_TAG = "metadata";
+
+const char* CONFIG_TAG = "config";
+const char* VOLUME_TAG = "volume";
+
+const char* UNIT_ATTR = "unit";
+const char* NAME_ATTR = "name";
+const char* TYPE_ATTR = "type";
+const char* ID_ATTR = "id";
+const char* X_ATTR = "x";
+const char* Y_ATTR = "y";
+const char* Z_ATTR = "z";
+const char* V1_ATTR = "v1";
+const char* V2_ATTR = "v2";
+const char* V3_ATTR = "v3";
+const char* OBJECTID_ATTR = "objectid";
+const char* TRANSFORM_ATTR = "transform";
+
+const char* KEY_ATTR = "key";
+const char* VALUE_ATTR = "value";
+const char* FIRST_TRIANGLE_ID_ATTR = "firstid";
+const char* LAST_TRIANGLE_ID_ATTR = "lastid";
+
+const char* OBJECT_TYPE = "object";
+const char* VOLUME_TYPE = "volume";
+
+const char* NAME_KEY = "name";
+const char* MODIFIER_KEY = "modifier";
+const char* VOLUME_TYPE_KEY = "volume_type";
+
+const unsigned int VALID_OBJECT_TYPES_COUNT = 1;
+const char* VALID_OBJECT_TYPES[] =
+{
+ "model"
+};
+
+const unsigned int INVALID_OBJECT_TYPES_COUNT = 4;
+const char* INVALID_OBJECT_TYPES[] =
+{
+ "solidsupport",
+ "support",
+ "surface",
+ "other"
+};
+
+const char* 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 get_attribute_value_string(const char** attributes, unsigned int attributes_size, const char* attribute_key)
+{
+ const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key);
+ return (text != nullptr) ? text : "";
+}
+
+float get_attribute_value_float(const char** attributes, unsigned int attributes_size, const char* attribute_key)
+{
+ const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key);
+ return (text != nullptr) ? (float)::atof(text) : 0.0f;
+}
+
+int get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key)
+{
+ const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key);
+ return (text != nullptr) ? ::atoi(text) : 0;
+}
+
+Slic3r::Transform3d get_transform_from_string(const std::string& mat_str)
+{
+ if (mat_str.empty())
+ // empty string means default identity matrix
+ return Slic3r::Transform3d::Identity();
+
+ 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 Slic3r::Transform3d::Identity();
+
+ Slic3r::Transform3d ret = Slic3r::Transform3d::Identity();
+ 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;
+}
+
+float 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 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 < VALID_OBJECT_TYPES_COUNT; ++i)
+ {
+ if (::strcmp(type.c_str(), VALID_OBJECT_TYPES[i]) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+namespace Slic3r {
+
+ // Base class with error messages management
+ class _3MF_Base
+ {
+ std::vector<std::string> m_errors;
+
+ protected:
+ void add_error(const std::string& error) { m_errors.push_back(error); }
+ void clear_errors() { m_errors.clear(); }
+
+ public:
+ void log_errors()
+ {
+ for (const std::string& error : m_errors)
+ {
+ printf("%s\n", error.c_str());
+ }
+ }
+ };
+
+ class _3MF_Importer : public _3MF_Base
+ {
+ struct Component
+ {
+ int object_id;
+ Transform3d transform;
+
+ explicit Component(int object_id)
+ : object_id(object_id)
+ , transform(Transform3d::Identity())
+ {
+ }
+
+ Component(int object_id, const Transform3d& transform)
+ : object_id(object_id)
+ , transform(transform)
+ {
+ }
+ };
+
+ typedef std::vector<Component> ComponentsList;
+
+ struct Geometry
+ {
+ std::vector<float> vertices;
+ std::vector<unsigned int> triangles;
+
+ bool empty()
+ {
+ return vertices.empty() || triangles.empty();
+ }
+
+ void reset()
+ {
+ vertices.clear();
+ triangles.clear();
+ }
+ };
+
+ struct CurrentObject
+ {
+ int id;
+ Geometry geometry;
+ ModelObject* object;
+ ComponentsList components;
+
+ CurrentObject()
+ {
+ reset();
+ }
+
+ void reset()
+ {
+ id = -1;
+ geometry.reset();
+ object = nullptr;
+ components.clear();
+ }
+ };
+
+ struct CurrentConfig
+ {
+ int object_id;
+ int volume_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
+ {
+ unsigned int first_triangle_id;
+ unsigned int last_triangle_id;
+ MetadataList metadata;
+
+ VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id)
+ : first_triangle_id(first_triangle_id)
+ , last_triangle_id(last_triangle_id)
+ {
+ }
+ };
+
+ typedef std::vector<VolumeMetadata> VolumeMetadataList;
+
+ MetadataList metadata;
+ VolumeMetadataList volumes;
+ };
+
+ typedef std::map<int, ModelObject*> IdToModelObjectMap;
+ typedef std::map<int, ComponentsList> IdToAliasesMap;
+ typedef std::vector<Instance> InstancesList;
+ typedef std::map<int, ObjectMetadata> IdToMetadataMap;
+ typedef std::map<int, Geometry> IdToGeometryMap;
+ typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
+
+ // Version of the 3mf file
+ unsigned int m_version;
+
+ XML_Parser m_xml_parser;
+ Model* m_model;
+ float m_unit_factor;
+ CurrentObject m_curr_object;
+ IdToModelObjectMap m_objects;
+ IdToAliasesMap m_objects_aliases;
+ InstancesList m_instances;
+ IdToGeometryMap m_geometries;
+ CurrentConfig m_curr_config;
+ IdToMetadataMap m_objects_metadata;
+ IdToLayerHeightsProfileMap m_layer_heights_profiles;
+ std::string m_curr_metadata_name;
+ std::string m_curr_characters;
+
+ public:
+ _3MF_Importer();
+ ~_3MF_Importer();
+
+ bool load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle);
+
+ private:
+ void _destroy_xml_parser();
+ void _stop_xml_parser();
+
+ bool _load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle);
+ 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_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, const std::string& archive_filename);
+ bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model);
+
+ // 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_model_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(int object_id, const Transform3d& transform, 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_end_config_volume();
+
+ bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes);
+ bool _handle_end_config_metadata();
+
+ bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes);
+
+ // 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_model_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);
+ };
+
+ _3MF_Importer::_3MF_Importer()
+ : m_version(0)
+ , m_xml_parser(nullptr)
+ , m_model(nullptr)
+ , m_unit_factor(1.0f)
+ , m_curr_metadata_name("")
+ , m_curr_characters("")
+ {
+ }
+
+ _3MF_Importer::~_3MF_Importer()
+ {
+ _destroy_xml_parser();
+ }
+
+ bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle)
+ {
+ m_version = 0;
+ m_model = &model;
+ m_unit_factor = 1.0f;
+ m_curr_object.reset();
+ 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_curr_metadata_name.clear();
+ m_curr_characters.clear();
+ clear_errors();
+
+ return _load_model_from_file(filename, model, bundle);
+ }
+
+ void _3MF_Importer::_destroy_xml_parser()
+ {
+ if (m_xml_parser != nullptr)
+ {
+ XML_ParserFree(m_xml_parser);
+ m_xml_parser = nullptr;
+ }
+ }
+
+ void _3MF_Importer::_stop_xml_parser()
+ {
+ if (m_xml_parser != nullptr)
+ XML_StopParser(m_xml_parser, false);
+ }
+
+ bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, PresetBundle& bundle)
+ {
+ mz_zip_archive archive;
+ mz_zip_zero_struct(&archive);
+
+ mz_bool res = mz_zip_reader_init_file(&archive, filename.c_str(), 0);
+ if (res == 0)
+ {
+ 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;
+
+ // we first loop the entries to read from the archive the .model file only, in order to extract the version from it
+ for (mz_uint i = 0; i < num_entries; ++i)
+ {
+ if (mz_zip_reader_file_stat(&archive, i, &stat))
+ {
+ std::string name(stat.m_filename);
+ std::replace(name.begin(), name.end(), '\\', '/');
+
+ if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION))
+ {
+ // valid model name -> extract model
+ if (!_extract_model_from_archive(archive, stat))
+ {
+ mz_zip_reader_end(&archive);
+ add_error("Archive does not contain a valid model");
+ return false;
+ }
+ }
+ }
+ }
+
+ // 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))
+ {
+ std::string name(stat.m_filename);
+ std::replace(name.begin(), name.end(), '\\', '/');
+
+ if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE))
+ {
+ // extract slic3r lazer heights profile file
+ _extract_layer_heights_profile_config_from_archive(archive, stat);
+ }
+ else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
+ {
+ // extract slic3r print config file
+ _extract_print_config_from_archive(archive, stat, bundle, filename);
+ }
+ else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE))
+ {
+ // extract slic3r model config file
+ if (!_extract_model_config_from_archive(archive, stat, model))
+ {
+ mz_zip_reader_end(&archive);
+ add_error("Archive does not contain a valid model config");
+ return false;
+ }
+ }
+ }
+ }
+
+ mz_zip_reader_end(&archive);
+
+ for (const IdToModelObjectMap::value_type& object : m_objects)
+ {
+ ObjectMetadata::VolumeMetadataList volumes;
+ ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr;
+
+ 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;
+ }
+
+ IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.first);
+ if (obj_layer_heights_profile != m_layer_heights_profiles.end())
+ {
+ object.second->layer_height_profile = obj_layer_heights_profile->second;
+ object.second->layer_height_profile_valid = true;
+ }
+
+ IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
+ 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")
+ object.second->name = metadata.value;
+ else
+ object.second->config.set_deserialize(metadata.key, metadata.value);
+ }
+
+ // 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() / 3 - 1);
+
+ // select as volumes
+ volumes_ptr = &volumes;
+ }
+
+ if (!_generate_volumes(*object.second, obj_geometry->second, *volumes_ptr))
+ return false;
+ }
+
+ // fixes the min z of the model if negative
+ model.adjust_min_z();
+
+ return true;
+ }
+
+ bool _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, _3MF_Importer::_handle_start_model_xml_element, _3MF_Importer::_handle_end_model_xml_element);
+ XML_SetCharacterDataHandler(m_xml_parser, _3MF_Importer::_handle_model_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 model 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)), XML_GetCurrentLineNumber(m_xml_parser));
+ add_error(error_buf);
+ return false;
+ }
+
+ return true;
+ }
+
+ void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, PresetBundle& bundle, 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;
+ }
+ bundle.load_config_string(buffer.data(), archive_filename.c_str());
+ }
+ }
+
+ void _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_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 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(IdToLayerHeightsProfileMap::value_type(object_id, profile));
+ }
+ }
+ }
+
+ bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
+ {
+ 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, _3MF_Importer::_handle_start_config_xml_element, _3MF_Importer::_handle_end_config_xml_element);
+
+ 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)), XML_GetCurrentLineNumber(m_xml_parser));
+ add_error(error_buf);
+ return false;
+ }
+
+ return true;
+ }
+
+ void _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 _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 _3MF_Importer::_handle_model_xml_characters(const XML_Char* s, int len)
+ {
+ m_curr_characters.append(s, len);
+ }
+
+ void _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(METADATA_TAG, name) == 0)
+ res = _handle_start_config_metadata(attributes, num_attributes);
+
+ if (!res)
+ _stop_xml_parser();
+ }
+
+ void _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(METADATA_TAG, name) == 0)
+ res = _handle_end_config_metadata();
+
+ if (!res)
+ _stop_xml_parser();
+ }
+
+ bool _3MF_Importer::_handle_start_model(const char** attributes, unsigned int num_attributes)
+ {
+ m_unit_factor = get_unit_factor(get_attribute_value_string(attributes, num_attributes, UNIT_ATTR));
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_model()
+ {
+ // deletes all non-built or non-instanced objects
+ for (const IdToModelObjectMap::value_type& object : m_objects)
+ {
+ if ((object.second != nullptr) && (object.second->instances.size() == 0))
+ m_model->delete_object(object.second);
+ }
+
+ // applies instances' matrices
+ for (Instance& instance : m_instances)
+ {
+ if (instance.instance != nullptr)
+ {
+ ModelObject* object = instance.instance->get_object();
+ if (object != nullptr)
+ {
+ // apply the transform to the instance
+ _apply_transform(*instance.instance, instance.transform);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_resources(const char** attributes, unsigned int num_attributes)
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_resources()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_object(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current data
+ m_curr_object.reset();
+
+ if (is_valid_object_type(get_attribute_value_string(attributes, num_attributes, TYPE_ATTR)))
+ {
+ // create new object (it may be removed later if no instances are generated from it)
+ m_curr_object.object = m_model->add_object();
+ if (m_curr_object.object == nullptr)
+ {
+ add_error("Unable to create object");
+ return false;
+ }
+
+ // set object data
+ m_curr_object.object->name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR);
+ m_curr_object.id = get_attribute_value_int(attributes, num_attributes, ID_ATTR);
+ }
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_object()
+ {
+ if (m_curr_object.object != nullptr)
+ {
+ 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(m_curr_object.id);
+ if (object_item != m_objects.end())
+ m_objects.erase(object_item);
+
+ IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id);
+ if (alias_item != m_objects_aliases.end())
+ m_objects_aliases.erase(alias_item);
+ }
+ else
+ // adds components to aliases
+ m_objects_aliases.insert(IdToAliasesMap::value_type(m_curr_object.id, m_curr_object.components));
+ }
+ else
+ {
+ // geometry defined, store it for later use
+ m_geometries.insert(IdToGeometryMap::value_type(m_curr_object.id, std::move(m_curr_object.geometry)));
+
+ // stores the object for later use
+ if (m_objects.find(m_curr_object.id) == m_objects.end())
+ {
+ m_objects.insert(IdToModelObjectMap::value_type(m_curr_object.id, m_curr_object.object));
+ m_objects_aliases.insert(IdToAliasesMap::value_type(m_curr_object.id, ComponentsList(1, Component(m_curr_object.id)))); // aliases itself
+ }
+ else
+ {
+ add_error("Found object with duplicate id");
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_mesh(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current geometry
+ m_curr_object.geometry.reset();
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_mesh()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_vertices(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current vertices
+ m_curr_object.geometry.vertices.clear();
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_vertices()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_vertex(const char** attributes, unsigned int num_attributes)
+ {
+ // appends the vertex coordinates
+ // missing values are set equal to ZERO
+ m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR));
+ m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR));
+ m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR));
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_vertex()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current triangles
+ m_curr_object.geometry.triangles.clear();
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_triangles()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _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
+ m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR));
+ m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR));
+ m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR));
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_triangle()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes)
+ {
+ // reset current components
+ m_curr_object.components.clear();
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_components()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes)
+ {
+ int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
+ Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
+
+ IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
+ if (object_item == m_objects.end())
+ {
+ IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id);
+ if (alias_item == m_objects_aliases.end())
+ {
+ add_error("Found component with invalid object id");
+ return false;
+ }
+ }
+
+ m_curr_object.components.emplace_back(object_id, transform);
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_component()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_build(const char** attributes, unsigned int num_attributes)
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_build()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _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 = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR);
+ Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR));
+
+ return _create_object_instance(object_id, transform, 1);
+ }
+
+ bool _3MF_Importer::_handle_end_item()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes)
+ {
+ m_curr_characters.clear();
+
+ std::string name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR);
+ if (!name.empty())
+ m_curr_metadata_name = name;
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_metadata()
+ {
+ if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION)
+ m_version = (unsigned int)atoi(m_curr_characters.c_str());
+
+ return true;
+ }
+
+ bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, 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;
+ }
+
+ IdToAliasesMap::iterator it = m_objects_aliases.find(object_id);
+ if (it == m_objects_aliases.end())
+ {
+ add_error("Found item with invalid object id");
+ return false;
+ }
+
+ if ((it->second.size() == 1) && (it->second[0].object_id == object_id))
+ {
+ // aliasing to itself
+
+ IdToModelObjectMap::iterator object_item = m_objects.find(object_id);
+ if ((object_item == m_objects.end()) || (object_item->second == nullptr))
+ {
+ add_error("Found invalid object");
+ return false;
+ }
+ else
+ {
+ ModelInstance* instance = object_item->second->add_instance();
+ if (instance == nullptr)
+ {
+ add_error("Unable to add object instance");
+ return false;
+ }
+
+ m_instances.emplace_back(instance, transform);
+ }
+ }
+ else
+ {
+ // recursively process nested components
+ for (const Component& component : it->second)
+ {
+ if (!_create_object_instance(component.object_id, transform * component.transform, recur_counter + 1))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void _3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform)
+ {
+ // slic3r ModelInstance cannot be transformed using a matrix
+ // we extract from the given matrix only the values currently used
+
+ // translation
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ Vec3d offset(transform(0, 3), transform(1, 3), transform(2, 3));
+#else
+ double offset_x = transform(0, 3);
+ double offset_y = transform(1, 3);
+ double offset_z = transform(2, 3);
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+
+ // scale
+ double sx = ::sqrt(sqr(transform(0, 0)) + sqr(transform(1, 0)) + sqr(transform(2, 0)));
+ double sy = ::sqrt(sqr(transform(0, 1)) + sqr(transform(1, 1)) + sqr(transform(2, 1)));
+ double sz = ::sqrt(sqr(transform(0, 2)) + sqr(transform(1, 2)) + sqr(transform(2, 2)));
+
+ // invalid scale value, return
+ if ((sx == 0.0) || (sy == 0.0) || (sz == 0.0))
+ return;
+
+ // non-uniform scale value, return
+ if ((std::abs(sx - sy) > 0.00001) || (std::abs(sx - sz) > 0.00001))
+ return;
+
+ double inv_sx = 1.0 / sx;
+ double inv_sy = 1.0 / sy;
+ double inv_sz = 1.0 / sz;
+
+ Eigen::Matrix3d m3x3;
+ m3x3 << transform(0, 0) * inv_sx, transform(0, 1) * inv_sy, transform(0, 2) * inv_sz,
+ transform(1, 0) * inv_sx, transform(1, 1) * inv_sy, transform(1, 2) * inv_sz,
+ transform(2, 0) * inv_sx, transform(2, 1) * inv_sy, transform(2, 2) * inv_sz;
+
+ Eigen::AngleAxisd rotation;
+ rotation.fromRotationMatrix(m3x3);
+
+ // invalid rotation axis, we currently handle only rotations around Z axis
+ if ((rotation.angle() != 0.0) && (rotation.axis() != Vec3d::UnitZ()) && (rotation.axis() != -Vec3d::UnitZ()))
+ return;
+
+ double angle_z = (rotation.axis() == Vec3d::UnitZ()) ? rotation.angle() : -rotation.angle();
+
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ instance.set_offset(offset);
+#else
+ instance.offset(0) = offset_x;
+ instance.offset(1) = offset_y;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ instance.scaling_factor = sx;
+ instance.rotation = angle_z;
+ }
+
+ bool _3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes)
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_config()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_config_object(const char** attributes, unsigned int num_attributes)
+ {
+ int object_id = 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("Found duplicated object id");
+ return false;
+ }
+
+ m_objects_metadata.insert(IdToMetadataMap::value_type(object_id, ObjectMetadata()));
+ m_curr_config.object_id = object_id;
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_config_object()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _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("Cannot assign volume to a valid object");
+ return false;
+ }
+
+ m_curr_config.volume_id = object->second.volumes.size();
+
+ unsigned int first_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR);
+ unsigned int last_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR);
+
+ object->second.volumes.emplace_back(first_triangle_id, last_triangle_id);
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_config_volume()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_start_config_metadata(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("Cannot assign metadata to valid object id");
+ return false;
+ }
+
+ std::string type = get_attribute_value_string(attributes, num_attributes, TYPE_ATTR);
+ std::string key = get_attribute_value_string(attributes, num_attributes, KEY_ATTR);
+ std::string value = get_attribute_value_string(attributes, num_attributes, VALUE_ATTR);
+
+ if (type == OBJECT_TYPE)
+ object->second.metadata.emplace_back(key, value);
+ else if (type == VOLUME_TYPE)
+ {
+ if (m_curr_config.volume_id < object->second.volumes.size())
+ object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value);
+ }
+ else
+ {
+ add_error("Found invalid metadata type");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool _3MF_Importer::_handle_end_config_metadata()
+ {
+ // do nothing
+ return true;
+ }
+
+ bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes)
+ {
+ if (!object.volumes.empty())
+ {
+ add_error("Found invalid volumes count");
+ return false;
+ }
+
+ unsigned int geo_tri_count = geometry.triangles.size() / 3;
+
+ 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;
+ }
+
+ // splits volume out of imported geometry
+ unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1;
+ ModelVolume* volume = object.add_volume(TriangleMesh());
+ stl_file& stl = volume->mesh.stl;
+ stl.stats.type = inmemory;
+ stl.stats.number_of_facets = (uint32_t)triangles_count;
+ stl.stats.original_num_facets = (int)stl.stats.number_of_facets;
+ stl_allocate(&stl);
+
+ unsigned int src_start_id = volume_data.first_triangle_id * 3;
+
+ for (size_t i = 0; i < triangles_count; ++i)
+ {
+ unsigned int ii = i * 3;
+ stl_facet& facet = stl.facet_start[i];
+ for (unsigned int v = 0; v < 3; ++v)
+ {
+ ::memcpy(facet.vertex[v].data(), (const void*)&geometry.vertices[geometry.triangles[src_start_id + ii + v] * 3], 3 * sizeof(float));
+ }
+ }
+
+ stl_get_size(&stl);
+ volume->mesh.repair();
+ volume->calculate_convex_hull();
+
+ // apply volume's name and config data
+ 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(ModelVolume::PARAMETER_MODIFIER);
+ else if (metadata.key == VOLUME_TYPE_KEY)
+ volume->set_type(ModelVolume::type_from_string(metadata.value));
+ else
+ volume->config.set_deserialize(metadata.key, metadata.value);
+ }
+ }
+
+ return true;
+ }
+
+ void XMLCALL _3MF_Importer::_handle_start_model_xml_element(void* userData, const char* name, const char** attributes)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_start_model_xml_element(name, attributes);
+ }
+
+ void XMLCALL _3MF_Importer::_handle_end_model_xml_element(void* userData, const char* name)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_end_model_xml_element(name);
+ }
+
+ void XMLCALL _3MF_Importer::_handle_model_xml_characters(void* userData, const XML_Char* s, int len)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_model_xml_characters(s, len);
+ }
+
+ void XMLCALL _3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_start_config_xml_element(name, attributes);
+ }
+
+ void XMLCALL _3MF_Importer::_handle_end_config_xml_element(void* userData, const char* name)
+ {
+ _3MF_Importer* importer = (_3MF_Importer*)userData;
+ if (importer != nullptr)
+ importer->_handle_end_config_xml_element(name);
+ }
+
+ class _3MF_Exporter : public _3MF_Base
+ {
+ struct BuildItem
+ {
+ unsigned int id;
+ Transform3d transform;
+
+ BuildItem(unsigned int id, const Transform3d& transform)
+ : id(id)
+ , transform(transform)
+ {
+ }
+ };
+
+ 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;
+
+ struct ObjectData
+ {
+ ModelObject* object;
+ VolumeToOffsetsMap volumes_offsets;
+
+ explicit ObjectData(ModelObject* object)
+ : object(object)
+ {
+ }
+ };
+
+ typedef std::vector<BuildItem> BuildItemsList;
+ typedef std::map<int, ObjectData> IdToObjectDataMap;
+
+ IdToObjectDataMap m_objects_data;
+
+ public:
+ bool save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config);
+
+ private:
+ bool _save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config);
+ bool _add_content_types_file_to_archive(mz_zip_archive& archive);
+ bool _add_relationships_file_to_archive(mz_zip_archive& archive);
+ bool _add_model_file_to_archive(mz_zip_archive& archive, Model& model);
+ bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets);
+ bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets);
+ bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items);
+ bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
+ bool _add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print);
+ bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model);
+ };
+
+ bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config)
+ {
+ clear_errors();
+ return _save_model_to_file(filename, model, print, export_print_config);
+ }
+
+ bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const Print& print, bool export_print_config)
+ {
+ mz_zip_archive archive;
+ mz_zip_zero_struct(&archive);
+
+ m_objects_data.clear();
+
+ mz_bool res = mz_zip_writer_init_file(&archive, filename.c_str(), 0);
+ if (res == 0)
+ {
+ add_error("Unable to open the file");
+ return false;
+ }
+
+ // adds content types file
+ if (!_add_content_types_file_to_archive(archive))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ // adds relationships file
+ if (!_add_relationships_file_to_archive(archive))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ // adds model file
+ if (!_add_model_file_to_archive(archive, model))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ // adds layer height profile file
+ if (!_add_layer_height_profile_file_to_archive(archive, model))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ // adds slic3r print config file
+ if (export_print_config)
+ {
+ if (!_add_print_config_file_to_archive(archive, print))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+ }
+
+ // adds slic3r model config file
+ if (!_add_model_config_file_to_archive(archive, model))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ return false;
+ }
+
+ if (!mz_zip_writer_finalize_archive(&archive))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(filename);
+ add_error("Unable to finalize the archive");
+ return false;
+ }
+
+ mz_zip_writer_end(&archive);
+
+ return true;
+ }
+
+ bool _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 << "</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");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_relationships_file_to_archive(mz_zip_archive& archive)
+ {
+ std::stringstream stream;
+ stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ stream << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n";
+ stream << " <Relationship Target=\"/" << MODEL_FILE << "\" Id=\"rel-1\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\" />\n";
+ stream << "</Relationships>";
+
+ std::string out = stream.str();
+
+ if (!mz_zip_writer_add_mem(&archive, RELATIONSHIPS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add relationships file to archive");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_model_file_to_archive(mz_zip_archive& archive, Model& model)
+ {
+ std::stringstream 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\">\n";
+ stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "</" << METADATA_TAG << ">\n";
+ stream << " <" << RESOURCES_TAG << ">\n";
+
+ BuildItemsList build_items;
+
+ unsigned int object_id = 1;
+ for (ModelObject* obj : model.objects)
+ {
+ if (obj == nullptr)
+ continue;
+
+ unsigned int curr_id = object_id;
+ IdToObjectDataMap::iterator object_it = m_objects_data.insert(IdToObjectDataMap::value_type(curr_id, ObjectData(obj))).first;
+
+ if (!_add_object_to_model_stream(stream, object_id, *obj, build_items, object_it->second.volumes_offsets))
+ {
+ add_error("Unable to add object to archive");
+ return false;
+ }
+ }
+
+ stream << " </" << RESOURCES_TAG << ">\n";
+
+ if (!_add_build_to_model_stream(stream, build_items))
+ {
+ add_error("Unable to add build to archive");
+ return false;
+ }
+
+ stream << "</" << MODEL_TAG << ">\n";
+
+ std::string out = stream.str();
+
+ if (!mz_zip_writer_add_mem(&archive, MODEL_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add model file to archive");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets)
+ {
+ unsigned int id = 0;
+ for (const ModelInstance* instance : object.instances)
+ {
+ if (instance == nullptr)
+ continue;
+
+ unsigned int instance_id = object_id + id;
+ stream << " <" << OBJECT_TAG << " id=\"" << instance_id << "\" type=\"model\">\n";
+
+ if (id == 0)
+ {
+ if (!_add_mesh_to_object_stream(stream, object, volumes_offsets))
+ {
+ add_error("Unable to add mesh to archive");
+ return false;
+ }
+ }
+ else
+ {
+ stream << " <" << COMPONENTS_TAG << ">\n";
+ stream << " <" << COMPONENT_TAG << " objectid=\"" << object_id << "\" />\n";
+ stream << " </" << COMPONENTS_TAG << ">\n";
+ }
+
+ Transform3d t = instance->world_matrix();
+ build_items.emplace_back(instance_id, t);
+
+ stream << " </" << OBJECT_TAG << ">\n";
+
+ ++id;
+ }
+
+ object_id += id;
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets)
+ {
+ stream << " <" << MESH_TAG << ">\n";
+ stream << " <" << VERTICES_TAG << ">\n";
+
+ unsigned int vertices_count = 0;
+ for (ModelVolume* volume : object.volumes)
+ {
+ if (volume == nullptr)
+ continue;
+
+ VolumeToOffsetsMap::iterator volume_it = volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first;
+
+ if (!volume->mesh.repaired)
+ volume->mesh.repair();
+
+ stl_file& stl = volume->mesh.stl;
+ if (stl.v_shared == nullptr)
+ stl_generate_shared_vertices(&stl);
+
+ if (stl.stats.shared_vertices == 0)
+ {
+ add_error("Found invalid mesh");
+ return false;
+ }
+
+ vertices_count += stl.stats.shared_vertices;
+
+ for (int i = 0; i < stl.stats.shared_vertices; ++i)
+ {
+ stream << " <" << VERTEX_TAG << " ";
+ stream << "x=\"" << stl.v_shared[i](0) << "\" ";
+ stream << "y=\"" << stl.v_shared[i](1) << "\" ";
+ stream << "z=\"" << stl.v_shared[i](2) << "\" />\n";
+ }
+ }
+
+ stream << " </" << VERTICES_TAG << ">\n";
+ stream << " <" << TRIANGLES_TAG << ">\n";
+
+ unsigned int triangles_count = 0;
+ for (ModelVolume* volume : object.volumes)
+ {
+ if (volume == nullptr)
+ continue;
+
+ VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume);
+ assert(volume_it != volumes_offsets.end());
+
+ stl_file& stl = volume->mesh.stl;
+
+ // updates triangle offsets
+ volume_it->second.first_triangle_id = triangles_count;
+ triangles_count += stl.stats.number_of_facets;
+ volume_it->second.last_triangle_id = triangles_count - 1;
+
+ for (uint32_t i = 0; i < stl.stats.number_of_facets; ++i)
+ {
+ stream << " <" << TRIANGLE_TAG << " ";
+ for (int j = 0; j < 3; ++j)
+ {
+ stream << "v" << j + 1 << "=\"" << stl.v_indices[i].vertex[j] + volume_it->second.first_vertex_id << "\" ";
+ }
+ stream << "/>\n";
+ }
+ }
+
+ stream << " </" << TRIANGLES_TAG << ">\n";
+ stream << " </" << MESH_TAG << ">\n";
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items)
+ {
+ if (build_items.size() == 0)
+ {
+ add_error("No build item found");
+ return false;
+ }
+
+ stream << " <" << BUILD_TAG << ">\n";
+
+ for (const BuildItem& item : build_items)
+ {
+ stream << " <" << ITEM_TAG << " objectid=\"" << item.id << "\" transform =\"";
+ 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 << "\" />\n";
+ }
+
+ stream << " </" << BUILD_TAG << ">\n";
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model)
+ {
+ std::string out = "";
+ char buffer[1024];
+
+ unsigned int count = 0;
+ for (const ModelObject* object : model.objects)
+ {
+ ++count;
+ std::vector<double> layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector<double>();
+ 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");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const Print& print)
+ {
+ char buffer[1024];
+ sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str());
+ std::string out = buffer;
+
+ GCode::append_full_config(print, out);
+
+ if (!out.empty())
+ {
+ if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add print config file to archive");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool _3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model)
+ {
+ std::stringstream stream;
+ stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ stream << "<" << CONFIG_TAG << ">\n";
+
+ for (const IdToObjectDataMap::value_type& obj_metadata : m_objects_data)
+ {
+ const ModelObject* obj = obj_metadata.second.object;
+ if (obj != nullptr)
+ {
+ stream << " <" << OBJECT_TAG << " id=\"" << obj_metadata.first << "\">\n";
+
+ // stores object's name
+ if (!obj->name.empty())
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n";
+
+ // stores object's config data
+ for (const std::string& key : obj->config.keys())
+ {
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.serialize(key) << "\"/>\n";
+ }
+
+ for (const ModelVolume* volume : obj_metadata.second.object->volumes)
+ {
+ if (volume != nullptr)
+ {
+ const VolumeToOffsetsMap& offsets = obj_metadata.second.volumes_offsets;
+ VolumeToOffsetsMap::const_iterator it = offsets.find(volume);
+ if (it != offsets.end())
+ {
+ // stores volume's offsets
+ stream << " <" << VOLUME_TAG << " ";
+ stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" ";
+ stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\">\n";
+
+ // stores volume's name
+ if (!volume->name.empty())
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << 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 << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n";
+ // stores volume's type (overrides the modifier field above)
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " <<
+ VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n";
+
+ // stores volume's config data
+ for (const std::string& key : volume->config.keys())
+ {
+ stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.serialize(key) << "\"/>\n";
+ }
+
+ stream << " </" << VOLUME_TAG << ">\n";
+ }
+ }
+ }
+
+ stream << " </" << OBJECT_TAG << ">\n";
+ }
+ }
+
+ stream << "</" << CONFIG_TAG << ">\n";
+
+ std::string out = stream.str();
+
+ if (!mz_zip_writer_add_mem(&archive, MODEL_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ add_error("Unable to add model config file to archive");
+ return false;
+ }
+
+ return true;
+ }
+
+ bool load_3mf(const char* path, PresetBundle* bundle, Model* model)
+ {
+ if ((path == nullptr) || (bundle == nullptr) || (model == nullptr))
+ return false;
+
+ _3MF_Importer importer;
+ bool res = importer.load_model_from_file(path, *model, *bundle);
+ importer.log_errors();
+ return res;
+ }
+
+ bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config)
+ {
+ if ((path == nullptr) || (model == nullptr) || (print == nullptr))
+ return false;
+
+ _3MF_Exporter exporter;
+ bool res = exporter.save_model_to_file(path, *model, *print, export_print_config);
+
+ if (!res)
+ exporter.log_errors();
+
+ return res;
+ }
+} // namespace Slic3r
diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp
new file mode 100644
index 000000000..85bc812e3
--- /dev/null
+++ b/src/libslic3r/Format/3mf.hpp
@@ -0,0 +1,19 @@
+#ifndef slic3r_Format_3mf_hpp_
+#define slic3r_Format_3mf_hpp_
+
+namespace Slic3r {
+
+ class Model;
+ class Print;
+ class PresetBundle;
+
+ // Load the content of a 3mf file into the given model and preset bundle.
+ extern bool load_3mf(const char* path, PresetBundle* bundle, Model* model);
+
+ // Save the given model and the config data contained in the given Print into a 3mf file.
+ // The model could be modified during the export process if meshes are not repaired or have no shared vertices
+ extern bool store_3mf(const char* path, Model* model, Print* print, bool export_print_config);
+
+}; // namespace Slic3r
+
+#endif /* slic3r_Format_3mf_hpp_ */
diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp
new file mode 100644
index 000000000..458ce79de
--- /dev/null
+++ b/src/libslic3r/Format/AMF.cpp
@@ -0,0 +1,901 @@
+#include <string.h>
+#include <map>
+#include <string>
+#include <expat/expat.h>
+
+#include <boost/nowide/cstdio.hpp>
+
+#include "../libslic3r.h"
+#include "../Model.hpp"
+#include "../GCode.hpp"
+#include "../Utils.hpp"
+#include "../slic3r/GUI/PresetBundle.hpp"
+#include "AMF.hpp"
+
+#include <boost/filesystem/operations.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/nowide/fstream.hpp>
+#include <miniz/miniz_zip.h>
+
+#if 0
+// Enable debugging and assert in this file.
+#define DEBUG
+#define _DEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+
+// VERSION NUMBERS
+// 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them.
+// 1 : Introduction of amf versioning. No other change in data saved into amf files.
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+// 2 : Added z component of offset.
+const unsigned int VERSION_AMF = 2;
+#else
+const unsigned int VERSION_AMF = 1;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version";
+
+const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config";
+
+namespace Slic3r
+{
+
+struct AMFParserContext
+{
+ AMFParserContext(XML_Parser parser, const std::string& archive_filename, PresetBundle* preset_bundle, Model *model) :
+ m_version(0),
+ m_parser(parser),
+ m_model(*model),
+ m_object(nullptr),
+ m_volume(nullptr),
+ m_material(nullptr),
+ m_instance(nullptr),
+ m_preset_bundle(preset_bundle),
+ m_archive_filename(archive_filename)
+ {
+ m_path.reserve(12);
+ }
+
+ void stop()
+ {
+ XML_StopParser(m_parser, 0);
+ }
+
+ void startElement(const char *name, const char **atts);
+ void endElement(const char *name);
+ void endDocument();
+ void characters(const XML_Char *s, int len);
+
+ static void XMLCALL startElement(void *userData, const char *name, const char **atts)
+ {
+ AMFParserContext *ctx = (AMFParserContext*)userData;
+ ctx->startElement(name, atts);
+ }
+
+ static void XMLCALL endElement(void *userData, const char *name)
+ {
+ AMFParserContext *ctx = (AMFParserContext*)userData;
+ ctx->endElement(name);
+ }
+
+ /* s is not 0 terminated. */
+ static void XMLCALL characters(void *userData, const XML_Char *s, int len)
+ {
+ AMFParserContext *ctx = (AMFParserContext*)userData;
+ ctx->characters(s, len);
+ }
+
+ static const char* get_attribute(const char **atts, const char *id) {
+ if (atts == nullptr)
+ return nullptr;
+ while (*atts != nullptr) {
+ if (strcmp(*(atts ++), id) == 0)
+ return *atts;
+ ++ atts;
+ }
+ return nullptr;
+ }
+
+ enum AMFNodeType {
+ NODE_TYPE_INVALID = 0,
+ NODE_TYPE_UNKNOWN,
+ NODE_TYPE_AMF, // amf
+ // amf/metadata
+ NODE_TYPE_MATERIAL, // amf/material
+ // amf/material/metadata
+ NODE_TYPE_OBJECT, // amf/object
+ // amf/object/metadata
+ NODE_TYPE_MESH, // amf/object/mesh
+ NODE_TYPE_VERTICES, // amf/object/mesh/vertices
+ NODE_TYPE_VERTEX, // amf/object/mesh/vertices/vertex
+ NODE_TYPE_COORDINATES, // amf/object/mesh/vertices/vertex/coordinates
+ NODE_TYPE_COORDINATE_X, // amf/object/mesh/vertices/vertex/coordinates/x
+ NODE_TYPE_COORDINATE_Y, // amf/object/mesh/vertices/vertex/coordinates/y
+ NODE_TYPE_COORDINATE_Z, // amf/object/mesh/vertices/vertex/coordinates/z
+ NODE_TYPE_VOLUME, // amf/object/mesh/volume
+ // amf/object/mesh/volume/metadata
+ NODE_TYPE_TRIANGLE, // amf/object/mesh/volume/triangle
+ NODE_TYPE_VERTEX1, // amf/object/mesh/volume/triangle/v1
+ NODE_TYPE_VERTEX2, // amf/object/mesh/volume/triangle/v2
+ NODE_TYPE_VERTEX3, // amf/object/mesh/volume/triangle/v3
+ NODE_TYPE_CONSTELLATION, // amf/constellation
+ NODE_TYPE_INSTANCE, // amf/constellation/instance
+ NODE_TYPE_DELTAX, // amf/constellation/instance/deltax
+ NODE_TYPE_DELTAY, // amf/constellation/instance/deltay
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ NODE_TYPE_DELTAZ, // amf/constellation/instance/deltaz
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ NODE_TYPE_RZ, // amf/constellation/instance/rz
+ NODE_TYPE_SCALE, // amf/constellation/instance/scale
+ NODE_TYPE_METADATA, // anywhere under amf/*/metadata
+ };
+
+ struct Instance {
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ Instance() : deltax_set(false), deltay_set(false), deltaz_set(false), rz_set(false), scale_set(false) {}
+#else
+ Instance() : deltax_set(false), deltay_set(false), rz_set(false), scale_set(false) {}
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ // Shift in the X axis.
+ float deltax;
+ bool deltax_set;
+ // Shift in the Y axis.
+ float deltay;
+ bool deltay_set;
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ // Shift in the Z axis.
+ float deltaz;
+ bool deltaz_set;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ // Rotation around the Z axis.
+ float rz;
+ bool rz_set;
+ // Scaling factor
+ float scale;
+ bool scale_set;
+ };
+
+ struct Object {
+ Object() : idx(-1) {}
+ int idx;
+ std::vector<Instance> instances;
+ };
+
+ // Version of the amf file
+ unsigned int m_version;
+ // Current Expat XML parser instance.
+ XML_Parser m_parser;
+ // Model to receive objects extracted from an AMF file.
+ Model &m_model;
+ // Current parsing path in the XML file.
+ std::vector<AMFNodeType> m_path;
+ // Current object allocated for an amf/object XML subtree.
+ ModelObject *m_object;
+ // Map from obect name to object idx & instances.
+ std::map<std::string, Object> m_object_instances_map;
+ // Vertices parsed for the current m_object.
+ std::vector<float> m_object_vertices;
+ // Current volume allocated for an amf/object/mesh/volume subtree.
+ ModelVolume *m_volume;
+ // Faces collected for the current m_volume.
+ std::vector<int> m_volume_facets;
+ // Current material allocated for an amf/metadata subtree.
+ ModelMaterial *m_material;
+ // Current instance allocated for an amf/constellation/instance subtree.
+ Instance *m_instance;
+ // Generic string buffer for vertices, face indices, metadata etc.
+ std::string m_value[3];
+ // Pointer to preset bundle to update if config data are stored inside the amf file
+ PresetBundle* m_preset_bundle;
+ // Fullpath name of the amf file
+ std::string m_archive_filename;
+
+private:
+ AMFParserContext& operator=(AMFParserContext&);
+};
+
+void AMFParserContext::startElement(const char *name, const char **atts)
+{
+ AMFNodeType node_type_new = NODE_TYPE_UNKNOWN;
+ switch (m_path.size()) {
+ case 0:
+ // An AMF file must start with an <amf> tag.
+ node_type_new = NODE_TYPE_AMF;
+ if (strcmp(name, "amf") != 0)
+ this->stop();
+ break;
+ case 1:
+ if (strcmp(name, "metadata") == 0) {
+ const char *type = get_attribute(atts, "type");
+ if (type != nullptr) {
+ m_value[0] = type;
+ node_type_new = NODE_TYPE_METADATA;
+ }
+ } else if (strcmp(name, "material") == 0) {
+ const char *material_id = get_attribute(atts, "id");
+ m_material = m_model.add_material((material_id == nullptr) ? "_" : material_id);
+ node_type_new = NODE_TYPE_MATERIAL;
+ } else if (strcmp(name, "object") == 0) {
+ const char *object_id = get_attribute(atts, "id");
+ if (object_id == nullptr)
+ this->stop();
+ else {
+ assert(m_object_vertices.empty());
+ m_object = m_model.add_object();
+ m_object_instances_map[object_id].idx = int(m_model.objects.size())-1;
+ node_type_new = NODE_TYPE_OBJECT;
+ }
+ } else if (strcmp(name, "constellation") == 0) {
+ node_type_new = NODE_TYPE_CONSTELLATION;
+ }
+ break;
+ case 2:
+ if (strcmp(name, "metadata") == 0) {
+ if (m_path[1] == NODE_TYPE_MATERIAL || m_path[1] == NODE_TYPE_OBJECT) {
+ m_value[0] = get_attribute(atts, "type");
+ node_type_new = NODE_TYPE_METADATA;
+ }
+ } else if (strcmp(name, "mesh") == 0) {
+ if (m_path[1] == NODE_TYPE_OBJECT)
+ node_type_new = NODE_TYPE_MESH;
+ } else if (strcmp(name, "instance") == 0) {
+ if (m_path[1] == NODE_TYPE_CONSTELLATION) {
+ const char *object_id = get_attribute(atts, "objectid");
+ if (object_id == nullptr)
+ this->stop();
+ else {
+ m_object_instances_map[object_id].instances.push_back(AMFParserContext::Instance());
+ m_instance = &m_object_instances_map[object_id].instances.back();
+ node_type_new = NODE_TYPE_INSTANCE;
+ }
+ }
+ else
+ this->stop();
+ }
+ break;
+ case 3:
+ if (m_path[2] == NODE_TYPE_MESH) {
+ assert(m_object);
+ if (strcmp(name, "vertices") == 0)
+ node_type_new = NODE_TYPE_VERTICES;
+ else if (strcmp(name, "volume") == 0) {
+ assert(! m_volume);
+ m_volume = m_object->add_volume(TriangleMesh());
+ node_type_new = NODE_TYPE_VOLUME;
+ }
+ } else if (m_path[2] == NODE_TYPE_INSTANCE) {
+ assert(m_instance);
+ if (strcmp(name, "deltax") == 0)
+ node_type_new = NODE_TYPE_DELTAX;
+ else if (strcmp(name, "deltay") == 0)
+ node_type_new = NODE_TYPE_DELTAY;
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ else if (strcmp(name, "deltaz") == 0)
+ node_type_new = NODE_TYPE_DELTAZ;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ else if (strcmp(name, "rz") == 0)
+ node_type_new = NODE_TYPE_RZ;
+ else if (strcmp(name, "scale") == 0)
+ node_type_new = NODE_TYPE_SCALE;
+ }
+ break;
+ case 4:
+ if (m_path[3] == NODE_TYPE_VERTICES) {
+ if (strcmp(name, "vertex") == 0)
+ node_type_new = NODE_TYPE_VERTEX;
+ } else if (m_path[3] == NODE_TYPE_VOLUME) {
+ if (strcmp(name, "metadata") == 0) {
+ const char *type = get_attribute(atts, "type");
+ if (type == nullptr)
+ this->stop();
+ else {
+ m_value[0] = type;
+ node_type_new = NODE_TYPE_METADATA;
+ }
+ } else if (strcmp(name, "triangle") == 0)
+ node_type_new = NODE_TYPE_TRIANGLE;
+ }
+ break;
+ case 5:
+ if (strcmp(name, "coordinates") == 0) {
+ if (m_path[4] == NODE_TYPE_VERTEX) {
+ node_type_new = NODE_TYPE_COORDINATES;
+ } else
+ this->stop();
+ } else if (name[0] == 'v' && name[1] >= '1' && name[1] <= '3' && name[2] == 0) {
+ if (m_path[4] == NODE_TYPE_TRIANGLE) {
+ node_type_new = AMFNodeType(NODE_TYPE_VERTEX1 + name[1] - '1');
+ } else
+ this->stop();
+ }
+ break;
+ case 6:
+ if ((name[0] == 'x' || name[0] == 'y' || name[0] == 'z') && name[1] == 0) {
+ if (m_path[5] == NODE_TYPE_COORDINATES)
+ node_type_new = AMFNodeType(NODE_TYPE_COORDINATE_X + name[0] - 'x');
+ else
+ this->stop();
+ }
+ break;
+ default:
+ break;
+ }
+
+ m_path.push_back(node_type_new);
+}
+
+void AMFParserContext::characters(const XML_Char *s, int len)
+{
+ if (m_path.back() == NODE_TYPE_METADATA) {
+ m_value[1].append(s, len);
+ }
+ else
+ {
+ switch (m_path.size()) {
+ case 4:
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ if (m_path.back() == NODE_TYPE_DELTAX ||
+ m_path.back() == NODE_TYPE_DELTAY ||
+ m_path.back() == NODE_TYPE_DELTAZ ||
+ m_path.back() == NODE_TYPE_RZ ||
+ m_path.back() == NODE_TYPE_SCALE)
+#else
+ if (m_path.back() == NODE_TYPE_DELTAX || m_path.back() == NODE_TYPE_DELTAY || m_path.back() == NODE_TYPE_RZ || m_path.back() == NODE_TYPE_SCALE)
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ m_value[0].append(s, len);
+ break;
+ case 6:
+ switch (m_path.back()) {
+ case NODE_TYPE_VERTEX1: m_value[0].append(s, len); break;
+ case NODE_TYPE_VERTEX2: m_value[1].append(s, len); break;
+ case NODE_TYPE_VERTEX3: m_value[2].append(s, len); break;
+ default: break;
+ }
+ case 7:
+ switch (m_path.back()) {
+ case NODE_TYPE_COORDINATE_X: m_value[0].append(s, len); break;
+ case NODE_TYPE_COORDINATE_Y: m_value[1].append(s, len); break;
+ case NODE_TYPE_COORDINATE_Z: m_value[2].append(s, len); break;
+ default: break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+void AMFParserContext::endElement(const char * /* name */)
+{
+ switch (m_path.back()) {
+
+ // Constellation transformation:
+ case NODE_TYPE_DELTAX:
+ assert(m_instance);
+ m_instance->deltax = float(atof(m_value[0].c_str()));
+ m_instance->deltax_set = true;
+ m_value[0].clear();
+ break;
+ case NODE_TYPE_DELTAY:
+ assert(m_instance);
+ m_instance->deltay = float(atof(m_value[0].c_str()));
+ m_instance->deltay_set = true;
+ m_value[0].clear();
+ break;
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ case NODE_TYPE_DELTAZ:
+ assert(m_instance);
+ m_instance->deltaz = float(atof(m_value[0].c_str()));
+ m_instance->deltaz_set = true;
+ m_value[0].clear();
+ break;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ case NODE_TYPE_RZ:
+ assert(m_instance);
+ m_instance->rz = float(atof(m_value[0].c_str()));
+ m_instance->rz_set = true;
+ m_value[0].clear();
+ break;
+ case NODE_TYPE_SCALE:
+ assert(m_instance);
+ m_instance->scale = float(atof(m_value[0].c_str()));
+ m_instance->scale_set = true;
+ m_value[0].clear();
+ break;
+
+ // Object vertices:
+ case NODE_TYPE_VERTEX:
+ assert(m_object);
+ // Parse the vertex data
+ m_object_vertices.emplace_back((float)atof(m_value[0].c_str()));
+ m_object_vertices.emplace_back((float)atof(m_value[1].c_str()));
+ m_object_vertices.emplace_back((float)atof(m_value[2].c_str()));
+ m_value[0].clear();
+ m_value[1].clear();
+ m_value[2].clear();
+ break;
+
+ // Faces of the current volume:
+ case NODE_TYPE_TRIANGLE:
+ assert(m_object && m_volume);
+ m_volume_facets.push_back(atoi(m_value[0].c_str()));
+ m_volume_facets.push_back(atoi(m_value[1].c_str()));
+ m_volume_facets.push_back(atoi(m_value[2].c_str()));
+ m_value[0].clear();
+ m_value[1].clear();
+ m_value[2].clear();
+ break;
+
+ // Closing the current volume. Create an STL from m_volume_facets pointing to m_object_vertices.
+ case NODE_TYPE_VOLUME:
+ {
+ assert(m_object && m_volume);
+ stl_file &stl = m_volume->mesh.stl;
+ stl.stats.type = inmemory;
+ stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
+ stl.stats.original_num_facets = stl.stats.number_of_facets;
+ stl_allocate(&stl);
+ for (size_t i = 0; i < m_volume_facets.size();) {
+ stl_facet &facet = stl.facet_start[i/3];
+ for (unsigned int v = 0; v < 3; ++ v)
+ memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float));
+ }
+ stl_get_size(&stl);
+ m_volume->mesh.repair();
+ m_volume->calculate_convex_hull();
+ m_volume_facets.clear();
+ m_volume = nullptr;
+ break;
+ }
+
+ case NODE_TYPE_OBJECT:
+ assert(m_object);
+ m_object_vertices.clear();
+ m_object = nullptr;
+ break;
+
+ case NODE_TYPE_MATERIAL:
+ assert(m_material);
+ m_material = nullptr;
+ break;
+
+ case NODE_TYPE_INSTANCE:
+ assert(m_instance);
+ m_instance = nullptr;
+ break;
+
+ case NODE_TYPE_METADATA:
+ if ((m_preset_bundle != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) {
+ m_preset_bundle->load_config_string(m_value[1].c_str(), m_archive_filename.c_str());
+ }
+ else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
+ const char *opt_key = m_value[0].c_str() + 7;
+ if (print_config_def.options.find(opt_key) != print_config_def.options.end()) {
+ DynamicPrintConfig *config = nullptr;
+ if (m_path.size() == 3) {
+ if (m_path[1] == NODE_TYPE_MATERIAL && m_material)
+ config = &m_material->config;
+ else if (m_path[1] == NODE_TYPE_OBJECT && m_object)
+ config = &m_object->config;
+ } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume)
+ config = &m_volume->config;
+ if (config)
+ config->set_deserialize(opt_key, m_value[1]);
+ } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) {
+ // Parse object's layer height profile, a semicolon separated list of floats.
+ char *p = const_cast<char*>(m_value[1].c_str());
+ for (;;) {
+ char *end = strchr(p, ';');
+ if (end != nullptr)
+ *end = 0;
+ m_object->layer_height_profile.push_back(float(atof(p)));
+ if (end == nullptr)
+ break;
+ p = end + 1;
+ }
+ m_object->layer_height_profile_valid = true;
+ } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) {
+ if (strcmp(opt_key, "modifier") == 0) {
+ // Is this volume a modifier volume?
+ // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag.
+ m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
+ } else if (strcmp(opt_key, "volume_type") == 0) {
+ m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
+ }
+ }
+ } else if (m_path.size() == 3) {
+ if (m_path[1] == NODE_TYPE_MATERIAL) {
+ if (m_material)
+ m_material->attributes[m_value[0]] = m_value[1];
+ } else if (m_path[1] == NODE_TYPE_OBJECT) {
+ if (m_object && m_value[0] == "name")
+ m_object->name = std::move(m_value[1]);
+ }
+ } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) {
+ if (m_volume && m_value[0] == "name")
+ m_volume->name = std::move(m_value[1]);
+ }
+ else if (strncmp(m_value[0].c_str(), SLIC3RPE_AMF_VERSION, strlen(SLIC3RPE_AMF_VERSION)) == 0) {
+ m_version = (unsigned int)atoi(m_value[1].c_str());
+ }
+
+ m_value[0].clear();
+ m_value[1].clear();
+ break;
+ default:
+ break;
+ }
+
+ m_path.pop_back();
+}
+
+void AMFParserContext::endDocument()
+{
+ for (const auto &object : m_object_instances_map) {
+ if (object.second.idx == -1) {
+ printf("Undefined object %s referenced in constellation\n", object.first.c_str());
+ continue;
+ }
+ for (const Instance &instance : object.second.instances)
+ if (instance.deltax_set && instance.deltay_set) {
+ ModelInstance *mi = m_model.objects[object.second.idx]->add_instance();
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ mi->set_offset(Vec3d((double)instance.deltax, (double)instance.deltay, (double)instance.deltaz));
+#else
+ mi->offset(0) = instance.deltax;
+ mi->offset(1) = instance.deltay;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ mi->rotation = instance.rz_set ? instance.rz : 0.f;
+ mi->scaling_factor = instance.scale_set ? instance.scale : 1.f;
+ }
+ }
+}
+
+// Load an AMF file into a provided model.
+bool load_amf_file(const char *path, PresetBundle* bundle, Model *model)
+{
+ if ((path == nullptr) || (model == nullptr))
+ return false;
+
+ XML_Parser parser = XML_ParserCreate(nullptr); // encoding
+ if (!parser) {
+ printf("Couldn't allocate memory for parser\n");
+ return false;
+ }
+
+ FILE *pFile = boost::nowide::fopen(path, "rt");
+ if (pFile == nullptr) {
+ printf("Cannot open file %s\n", path);
+ return false;
+ }
+
+ AMFParserContext ctx(parser, path, bundle, model);
+ XML_SetUserData(parser, (void*)&ctx);
+ XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
+ XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
+
+ char buff[8192];
+ bool result = false;
+ for (;;) {
+ int len = (int)fread(buff, 1, 8192, pFile);
+ if (ferror(pFile)) {
+ printf("AMF parser: Read error\n");
+ break;
+ }
+ int done = feof(pFile);
+ if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR) {
+ printf("AMF parser: Parse error at line %ul:\n%s\n",
+ XML_GetCurrentLineNumber(parser),
+ XML_ErrorString(XML_GetErrorCode(parser)));
+ break;
+ }
+ if (done) {
+ result = true;
+ break;
+ }
+ }
+
+ XML_ParserFree(parser);
+ ::fclose(pFile);
+
+ if (result)
+ ctx.endDocument();
+
+ return result;
+}
+
+bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, const char* path, PresetBundle* bundle, Model* model, unsigned int& version)
+{
+ if (stat.m_uncomp_size == 0)
+ {
+ printf("Found invalid size\n");
+ mz_zip_reader_end(&archive);
+ return false;
+ }
+
+ XML_Parser parser = XML_ParserCreate(nullptr); // encoding
+ if (!parser) {
+ printf("Couldn't allocate memory for parser\n");
+ mz_zip_reader_end(&archive);
+ return false;
+ }
+
+ AMFParserContext ctx(parser, path, bundle, model);
+ XML_SetUserData(parser, (void*)&ctx);
+ XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
+ XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
+
+ void* parser_buffer = XML_GetBuffer(parser, (int)stat.m_uncomp_size);
+ if (parser_buffer == nullptr)
+ {
+ printf("Unable to create buffer\n");
+ mz_zip_reader_end(&archive);
+ 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)
+ {
+ printf("Error while reading model data to buffer\n");
+ mz_zip_reader_end(&archive);
+ return false;
+ }
+
+ if (!XML_ParseBuffer(parser, (int)stat.m_uncomp_size, 1))
+ {
+ printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
+ mz_zip_reader_end(&archive);
+ return false;
+ }
+
+ ctx.endDocument();
+
+ version = ctx.m_version;
+
+ return true;
+}
+
+// Load an AMF archive into a provided model.
+bool load_amf_archive(const char *path, PresetBundle* bundle, Model *model)
+{
+ if ((path == nullptr) || (model == nullptr))
+ return false;
+
+ unsigned int version = 0;
+
+ mz_zip_archive archive;
+ mz_zip_zero_struct(&archive);
+
+ mz_bool res = mz_zip_reader_init_file(&archive, path, 0);
+ if (res == 0)
+ {
+ printf("Unable to init zip reader\n");
+ return false;
+ }
+
+ mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
+
+ mz_zip_archive_file_stat stat;
+ // we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
+ for (mz_uint i = 0; i < num_entries; ++i)
+ {
+ if (mz_zip_reader_file_stat(&archive, i, &stat))
+ {
+ if (boost::iends_with(stat.m_filename, ".amf"))
+ {
+ if (!extract_model_from_archive(archive, stat, path, bundle, model, version))
+ {
+ mz_zip_reader_end(&archive);
+ printf("Archive does not contain a valid model");
+ return false;
+ }
+
+ break;
+ }
+ }
+ }
+
+#if 0 // forward compatibility
+ // 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))
+ {
+ // add code to extract the file
+ }
+ }
+#endif // forward compatibility
+
+ mz_zip_reader_end(&archive);
+ return true;
+}
+
+// Load an AMF file into a provided model.
+// If bundle is not a null pointer, updates it if the amf file/archive contains config data
+bool load_amf(const char *path, PresetBundle* bundle, Model *model)
+{
+ if (boost::iends_with(path, ".amf.xml"))
+ // backward compatibility with older slic3r output
+ return load_amf_file(path, bundle, model);
+ else if (boost::iends_with(path, ".amf"))
+ {
+ boost::nowide::ifstream file(path, boost::nowide::ifstream::binary);
+ if (!file.good())
+ return false;
+
+ std::string zip_mask(2, '\0');
+ file.read(const_cast<char*>(zip_mask.data()), 2);
+ file.close();
+
+ return (zip_mask == "PK") ? load_amf_archive(path, bundle, model) : load_amf_file(path, bundle, model);
+ }
+ else
+ return false;
+}
+
+bool store_amf(const char *path, Model *model, Print* print, bool export_print_config)
+{
+ if ((path == nullptr) || (model == nullptr) || (print == nullptr))
+ return false;
+
+ // forces ".zip.amf" extension
+ std::string export_path = path;
+ if (!boost::iends_with(export_path, ".zip.amf"))
+ export_path = boost::filesystem::path(export_path).replace_extension(".zip.amf").string();
+
+ mz_zip_archive archive;
+ mz_zip_zero_struct(&archive);
+
+ mz_bool res = mz_zip_writer_init_file(&archive, export_path.c_str(), 0);
+ if (res == 0)
+ return false;
+
+ std::stringstream stream;
+ stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ stream << "<amf unit=\"millimeter\">\n";
+ stream << "<metadata type=\"cad\">Slic3r " << SLIC3R_VERSION << "</metadata>\n";
+ stream << "<metadata type=\"" << SLIC3RPE_AMF_VERSION << "\">" << VERSION_AMF << "</metadata>\n";
+
+ if (export_print_config)
+ {
+ std::string config = "\n";
+ GCode::append_full_config(*print, config);
+ stream << "<metadata type=\"" << SLIC3R_CONFIG_TYPE << "\">" << xml_escape(config) << "</metadata>\n";
+ }
+
+ for (const auto &material : model->materials) {
+ if (material.first.empty())
+ continue;
+ // note that material-id must never be 0 since it's reserved by the AMF spec
+ stream << " <material id=\"" << material.first << "\">\n";
+ for (const auto &attr : material.second->attributes)
+ stream << " <metadata type=\"" << attr.first << "\">" << attr.second << "</metadata>\n";
+ for (const std::string &key : material.second->config.keys())
+ stream << " <metadata type=\"slic3r." << key << "\">" << material.second->config.serialize(key) << "</metadata>\n";
+ stream << " </material>\n";
+ }
+ std::string instances;
+ for (size_t object_id = 0; object_id < model->objects.size(); ++ object_id) {
+ ModelObject *object = model->objects[object_id];
+ stream << " <object id=\"" << object_id << "\">\n";
+ for (const std::string &key : object->config.keys())
+ stream << " <metadata type=\"slic3r." << key << "\">" << object->config.serialize(key) << "</metadata>\n";
+ if (!object->name.empty())
+ stream << " <metadata type=\"name\">" << xml_escape(object->name) << "</metadata>\n";
+ std::vector<double> layer_height_profile = object->layer_height_profile_valid ? object->layer_height_profile : std::vector<double>();
+ if (layer_height_profile.size() >= 4 && (layer_height_profile.size() % 2) == 0) {
+ // Store the layer height profile as a single semicolon separated list.
+ stream << " <metadata type=\"slic3r.layer_height_profile\">";
+ stream << layer_height_profile.front();
+ for (size_t i = 1; i < layer_height_profile.size(); ++i)
+ stream << ";" << layer_height_profile[i];
+ stream << "\n </metadata>\n";
+ }
+ //FIXME Store the layer height ranges (ModelObject::layer_height_ranges)
+ stream << " <mesh>\n";
+ stream << " <vertices>\n";
+ std::vector<int> vertices_offsets;
+ int num_vertices = 0;
+ for (ModelVolume *volume : object->volumes) {
+ vertices_offsets.push_back(num_vertices);
+ if (! volume->mesh.repaired)
+ throw std::runtime_error("store_amf() requires repair()");
+ auto &stl = volume->mesh.stl;
+ if (stl.v_shared == nullptr)
+ stl_generate_shared_vertices(&stl);
+ for (size_t i = 0; i < stl.stats.shared_vertices; ++ i) {
+ stream << " <vertex>\n";
+ stream << " <coordinates>\n";
+ stream << " <x>" << stl.v_shared[i](0) << "</x>\n";
+ stream << " <y>" << stl.v_shared[i](1) << "</y>\n";
+ stream << " <z>" << stl.v_shared[i](2) << "</z>\n";
+ stream << " </coordinates>\n";
+ stream << " </vertex>\n";
+ }
+ num_vertices += stl.stats.shared_vertices;
+ }
+ stream << " </vertices>\n";
+ for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) {
+ ModelVolume *volume = object->volumes[i_volume];
+ int vertices_offset = vertices_offsets[i_volume];
+ if (volume->material_id().empty())
+ stream << " <volume>\n";
+ else
+ stream << " <volume materialid=\"" << volume->material_id() << "\">\n";
+ for (const std::string &key : volume->config.keys())
+ stream << " <metadata type=\"slic3r." << key << "\">" << volume->config.serialize(key) << "</metadata>\n";
+ if (!volume->name.empty())
+ stream << " <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
+ if (volume->is_modifier())
+ stream << " <metadata type=\"slic3r.modifier\">1</metadata>\n";
+ stream << " <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
+ for (int i = 0; i < volume->mesh.stl.stats.number_of_facets; ++i) {
+ stream << " <triangle>\n";
+ for (int j = 0; j < 3; ++j)
+ stream << " <v" << j + 1 << ">" << volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset << "</v" << j + 1 << ">\n";
+ stream << " </triangle>\n";
+ }
+ stream << " </volume>\n";
+ }
+ stream << " </mesh>\n";
+ stream << " </object>\n";
+ if (!object->instances.empty()) {
+ for (ModelInstance *instance : object->instances) {
+ char buf[512];
+ sprintf(buf,
+ " <instance objectid=\"" PRINTF_ZU "\">\n"
+ " <deltax>%lf</deltax>\n"
+ " <deltay>%lf</deltay>\n"
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ " <deltaz>%lf</deltaz>\n"
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ " <rz>%lf</rz>\n"
+ " <scale>%lf</scale>\n"
+ " </instance>\n",
+ object_id,
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ instance->get_offset(X),
+ instance->get_offset(Y),
+ instance->get_offset(Z),
+#else
+ instance->offset(0),
+ instance->offset(1),
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ instance->rotation,
+ instance->scaling_factor);
+ //FIXME missing instance->scaling_factor
+ instances.append(buf);
+ }
+ }
+ }
+ if (! instances.empty()) {
+ stream << " <constellation id=\"1\">\n";
+ stream << instances;
+ stream << " </constellation>\n";
+ }
+ stream << "</amf>\n";
+
+ std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(export_path).filename().string(), ".zip.amf", ".amf");
+ std::string out = stream.str();
+
+ if (!mz_zip_writer_add_mem(&archive, internal_amf_filename.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(export_path);
+ return false;
+ }
+
+ if (!mz_zip_writer_finalize_archive(&archive))
+ {
+ mz_zip_writer_end(&archive);
+ boost::filesystem::remove(export_path);
+ return false;
+ }
+
+ mz_zip_writer_end(&archive);
+
+ return true;
+}
+
+}; // namespace Slic3r
diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp
new file mode 100644
index 000000000..4779e9a51
--- /dev/null
+++ b/src/libslic3r/Format/AMF.hpp
@@ -0,0 +1,19 @@
+#ifndef slic3r_Format_AMF_hpp_
+#define slic3r_Format_AMF_hpp_
+
+namespace Slic3r {
+
+class Model;
+class Print;
+class PresetBundle;
+
+// Load the content of an amf file into the given model and preset bundle.
+extern bool load_amf(const char *path, PresetBundle* bundle, Model *model);
+
+// Save the given model and the config data contained in the given Print into an amf file.
+// The model could be modified during the export process if meshes are not repaired or have no shared vertices
+extern bool store_amf(const char *path, Model *model, Print* print, bool export_print_config);
+
+}; // namespace Slic3r
+
+#endif /* slic3r_Format_AMF_hpp_ */ \ No newline at end of file
diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp
new file mode 100644
index 000000000..ee5756083
--- /dev/null
+++ b/src/libslic3r/Format/OBJ.cpp
@@ -0,0 +1,118 @@
+#include "../libslic3r.h"
+#include "../Model.hpp"
+#include "../TriangleMesh.hpp"
+
+#include "OBJ.hpp"
+#include "objparser.hpp"
+
+#include <string>
+
+#ifdef _WIN32
+#define DIR_SEPARATOR '\\'
+#else
+#define DIR_SEPARATOR '/'
+#endif
+
+namespace Slic3r {
+
+bool load_obj(const char *path, Model *model, const char *object_name_in)
+{
+ // Parse the OBJ file.
+ ObjParser::ObjData data;
+ if (! ObjParser::objparse(path, data)) {
+// die "Failed to parse $file\n" if !-e $path;
+ return false;
+ }
+
+ // Count the faces and verify, that all faces are triangular.
+ size_t num_faces = 0;
+ size_t num_quads = 0;
+ for (size_t i = 0; i < data.vertices.size(); ) {
+ size_t j = i;
+ for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
+ if (i == j)
+ continue;
+ size_t face_vertices = j - i;
+ if (face_vertices != 3 && face_vertices != 4) {
+ // Non-triangular and non-quad faces are not supported as of now.
+ return false;
+ }
+ if (face_vertices == 4)
+ ++ num_quads;
+ ++ num_faces;
+ i = j + 1;
+ }
+
+ // Convert ObjData into STL.
+ TriangleMesh mesh;
+ stl_file &stl = mesh.stl;
+ stl.stats.type = inmemory;
+ stl.stats.number_of_facets = int(num_faces + num_quads);
+ stl.stats.original_num_facets = int(num_faces + num_quads);
+ // stl_allocate clears all the allocated data to zero, all normals are set to zeros as well.
+ stl_allocate(&stl);
+ size_t i_face = 0;
+ for (size_t i = 0; i < data.vertices.size(); ++ i) {
+ if (data.vertices[i].coordIdx == -1)
+ continue;
+ stl_facet &facet = stl.facet_start[i_face ++];
+ size_t num_normals = 0;
+ stl_normal normal(stl_normal::Zero());
+ for (unsigned int v = 0; v < 3; ++ v) {
+ const ObjParser::ObjVertex &vertex = data.vertices[i++];
+ memcpy(facet.vertex[v].data(), &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float));
+ if (vertex.normalIdx != -1) {
+ normal(0) += data.normals[vertex.normalIdx*3];
+ normal(1) += data.normals[vertex.normalIdx*3+1];
+ normal(2) += data.normals[vertex.normalIdx*3+2];
+ ++ num_normals;
+ }
+ }
+ if (data.vertices[i].coordIdx != -1) {
+ // This is a quad. Produce the other triangle.
+ stl_facet &facet2 = stl.facet_start[i_face++];
+ facet2.vertex[0] = facet.vertex[0];
+ facet2.vertex[1] = facet.vertex[2];
+ const ObjParser::ObjVertex &vertex = data.vertices[i++];
+ memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float));
+ if (vertex.normalIdx != -1) {
+ normal(0) += data.normals[vertex.normalIdx*3];
+ normal(1) += data.normals[vertex.normalIdx*3+1];
+ normal(2) += data.normals[vertex.normalIdx*3+2];
+ ++ num_normals;
+ }
+ if (num_normals == 4) {
+ // Normalize an average normal of a quad.
+ float len = facet.normal.norm();
+ if (len > EPSILON) {
+ normal /= len;
+ facet.normal = normal;
+ facet2.normal = normal;
+ }
+ }
+ } else if (num_normals == 3) {
+ // Normalize an average normal of a triangle.
+ float len = facet.normal.norm();
+ if (len > EPSILON)
+ facet.normal = normal / len;
+ }
+ }
+ stl_get_size(&stl);
+ mesh.repair();
+ if (mesh.facets_count() == 0) {
+ // die "This STL file couldn't be read because it's empty.\n"
+ return false;
+ }
+
+ std::string object_name;
+ if (object_name_in == nullptr) {
+ const char *last_slash = strrchr(path, DIR_SEPARATOR);
+ object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
+ } else
+ object_name.assign(object_name_in);
+
+ model->add_object(object_name.c_str(), path, std::move(mesh));
+ return true;
+}
+
+}; // namespace Slic3r
diff --git a/src/libslic3r/Format/OBJ.hpp b/src/libslic3r/Format/OBJ.hpp
new file mode 100644
index 000000000..9a8790bff
--- /dev/null
+++ b/src/libslic3r/Format/OBJ.hpp
@@ -0,0 +1,14 @@
+#ifndef slic3r_Format_OBJ_hpp_
+#define slic3r_Format_OBJ_hpp_
+
+namespace Slic3r {
+
+class TriangleMesh;
+class Model;
+
+// Load an OBJ file into a provided model.
+extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr);
+
+}; // namespace Slic3r
+
+#endif /* slic3r_Format_OBJ_hpp_ */
diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp
new file mode 100644
index 000000000..45eb56c63
--- /dev/null
+++ b/src/libslic3r/Format/PRUS.cpp
@@ -0,0 +1,399 @@
+#ifdef SLIC3R_PRUS
+
+#include <string.h>
+
+#include <boost/nowide/convert.hpp>
+
+#include <wx/string.h>
+#include <wx/wfstream.h>
+#include <wx/zipstrm.h>
+
+#include <Eigen/Geometry>
+
+#include "../libslic3r.h"
+#include "../Model.hpp"
+
+#include "PRUS.hpp"
+
+#if 0
+// Enable debugging and assert in this file.
+#define DEBUG
+#define _DEBUG
+#undef NDEBUG
+#endif
+
+#include <assert.h>
+
+namespace Slic3r
+{
+
+struct StlHeader
+{
+ char comment[80];
+ uint32_t nTriangles;
+};
+
+static_assert(sizeof(StlHeader) == 84, "StlHeader size not correct");
+
+// Buffered line reader for the wxInputStream.
+class LineReader
+{
+public:
+ LineReader(wxInputStream &input_stream, const char *initial_data, int initial_len) :
+ m_input_stream(input_stream),
+ m_pos(0),
+ m_len(initial_len)
+ {
+ assert(initial_len >= 0 && initial_len < m_bufsize);
+ memcpy(m_buffer, initial_data, initial_len);
+ }
+
+ const char* next_line() {
+ for (;;) {
+ // Skip empty lines.
+ while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n'))
+ ++ m_pos;
+ if (m_pos == m_len) {
+ // Empty buffer, fill it from the input stream.
+ m_pos = 0;
+ m_input_stream.Read(m_buffer, m_bufsize - 1);
+ m_len = m_input_stream.LastRead();
+ assert(m_len >= 0 && m_len < m_bufsize);
+ if (m_len == 0)
+ // End of file.
+ return nullptr;
+ // Skip empty lines etc.
+ continue;
+ }
+ // The buffer is nonempty and it does not start with end of lines. Find the first end of line.
+ int end = m_pos + 1;
+ while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n')
+ ++ end;
+ if (end == m_len && ! m_input_stream.Eof() && m_len < m_bufsize) {
+ // Move the buffer content to the buffer start and fill the rest of the buffer.
+ assert(m_pos > 0);
+ memmove(m_buffer, m_buffer + m_pos, m_len - m_pos);
+ m_len -= m_pos;
+ assert(m_len >= 0 && m_len < m_bufsize);
+ m_pos = 0;
+ m_input_stream.Read(m_buffer + m_len, m_bufsize - 1 - m_len);
+ int new_data = m_input_stream.LastRead();
+ if (new_data > 0) {
+ m_len += new_data;
+ assert(m_len >= 0 && m_len < m_bufsize);
+ continue;
+ }
+ }
+ char *ptr_out = m_buffer + m_pos;
+ m_pos = end + 1;
+ m_buffer[end] = 0;
+ if (m_pos >= m_len) {
+ m_pos = 0;
+ m_len = 0;
+ }
+ return ptr_out;
+ }
+ }
+
+ int next_line_scanf(const char *format, ...)
+ {
+ const char *line = next_line();
+ if (line == nullptr)
+ return -1;
+ int result;
+ va_list arglist;
+ va_start(arglist, format);
+ result = vsscanf(line, format, arglist);
+ va_end(arglist);
+ return result;
+ }
+
+private:
+ wxInputStream &m_input_stream;
+ static const int m_bufsize = 4096;
+ char m_buffer[m_bufsize];
+ int m_pos = 0;
+ int m_len = 0;
+};
+
+// Load a PrusaControl project file into a provided model.
+bool load_prus(const char *path, Model *model)
+{
+ // To receive the content of the zipped 'scene.xml' file.
+ std::vector<char> scene_xml_data;
+ wxFFileInputStream in(
+#ifdef WIN32
+ // On Windows, convert to a 16bit unicode string.
+ boost::nowide::widen(path).c_str()
+#else
+ path
+#endif
+ );
+ wxZipInputStream zip(in);
+ std::unique_ptr<wxZipEntry> entry;
+ size_t num_models = 0;
+ std::map<int, ModelObject*> group_to_model_object;
+ while (entry.reset(zip.GetNextEntry()), entry.get() != NULL) {
+ wxString name = entry->GetName();
+ if (name == "scene.xml") {
+ if (! scene_xml_data.empty()) {
+ // scene.xml has been found more than once in the archive.
+ return false;
+ }
+ size_t size_last = 0;
+ size_t size_incr = 4096;
+ scene_xml_data.resize(size_incr);
+ while (! zip.Read(scene_xml_data.data() + size_last, size_incr).Eof()) {
+ size_last += zip.LastRead();
+ if (scene_xml_data.size() < size_last + size_incr)
+ scene_xml_data.resize(size_last + size_incr);
+ }
+ size_last += zip.LastRead();
+ if (scene_xml_data.size() == size_last)
+ scene_xml_data.resize(size_last + 1);
+ else if (scene_xml_data.size() > size_last + 1)
+ scene_xml_data.erase(scene_xml_data.begin() + size_last + 1, scene_xml_data.end());
+ scene_xml_data[size_last] = 0;
+ }
+ else if (name.EndsWith(".stl") || name.EndsWith(".STL")) {
+ // Find the model entry in the XML data.
+ const wxScopedCharBuffer name_utf8 = name.ToUTF8();
+ char model_name_tag[1024];
+ sprintf(model_name_tag, "<model name=\"%s\">", name_utf8.data());
+ const char *model_xml = strstr(scene_xml_data.data(), model_name_tag);
+ const char *zero_tag = "<zero>";
+ const char *zero_xml = strstr(scene_xml_data.data(), zero_tag);
+ float trafo[3][4] = { 0 };
+ double instance_rotation = 0.;
+ double instance_scaling_factor = 1.f;
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ Vec3d instance_offset = Vec3d::Zero();
+#else
+ Vec2d instance_offset(0., 0.);
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ bool trafo_set = false;
+ unsigned int group_id = (unsigned int)-1;
+ unsigned int extruder_id = (unsigned int)-1;
+ ModelObject *model_object = nullptr;
+ if (model_xml != nullptr) {
+ model_xml += strlen(model_name_tag);
+ const char *position_tag = "<position>";
+ const char *position_xml = strstr(model_xml, position_tag);
+ const char *rotation_tag = "<rotation>";
+ const char *rotation_xml = strstr(model_xml, rotation_tag);
+ const char *scale_tag = "<scale>";
+ const char *scale_xml = strstr(model_xml, scale_tag);
+ float position[3], rotation[3], scale[3], zero[3];
+ if (position_xml != nullptr && rotation_xml != nullptr && scale_xml != nullptr && zero_xml != nullptr &&
+ sscanf(position_xml+strlen(position_tag),
+ "[%f, %f, %f]", position, position+1, position+2) == 3 &&
+ sscanf(rotation_xml+strlen(rotation_tag),
+ "[%f, %f, %f]", rotation, rotation+1, rotation+2) == 3 &&
+ sscanf(scale_xml+strlen(scale_tag),
+ "[%f, %f, %f]", scale, scale+1, scale+2) == 3 &&
+ sscanf(zero_xml+strlen(zero_tag),
+ "[%f, %f, %f]", zero, zero+1, zero+2) == 3) {
+ if (scale[0] == scale[1] && scale[1] == scale[2]) {
+ instance_scaling_factor = scale[0];
+ scale[0] = scale[1] = scale[2] = 1.;
+ }
+ if (rotation[0] == 0. && rotation[1] == 0.) {
+ instance_rotation = - rotation[2];
+ rotation[2] = 0.;
+ }
+ Eigen::Matrix3f mat_rot, mat_scale, mat_trafo;
+ mat_rot = Eigen::AngleAxisf(-rotation[2], Eigen::Vector3f::UnitZ()) *
+ Eigen::AngleAxisf(-rotation[1], Eigen::Vector3f::UnitY()) *
+ Eigen::AngleAxisf(-rotation[0], Eigen::Vector3f::UnitX());
+ mat_scale = Eigen::Scaling(scale[0], scale[1], scale[2]);
+ mat_trafo = mat_rot * mat_scale;
+ for (size_t r = 0; r < 3; ++ r) {
+ for (size_t c = 0; c < 3; ++ c)
+ trafo[r][c] += mat_trafo(r, c);
+ }
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2]));
+#else
+ instance_offset(0) = position[0] - zero[0];
+ instance_offset(1) = position[1] - zero[1];
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ trafo[2][3] = position[2] / instance_scaling_factor;
+ trafo_set = true;
+ }
+ const char *group_tag = "<group>";
+ const char *group_xml = strstr(model_xml, group_tag);
+ const char *extruder_tag = "<extruder>";
+ const char *extruder_xml = strstr(model_xml, extruder_tag);
+ if (group_xml != nullptr) {
+ int group = atoi(group_xml + strlen(group_tag));
+ if (group > 0) {
+ group_id = group;
+ auto it = group_to_model_object.find(group_id);
+ if (it != group_to_model_object.end())
+ model_object = it->second;
+ }
+ }
+ if (extruder_xml != nullptr) {
+ int e = atoi(extruder_xml + strlen(extruder_tag));
+ if (e > 0)
+ extruder_id = e;
+ }
+ }
+ if (trafo_set) {
+ // Extract the STL.
+ StlHeader header;
+ TriangleMesh mesh;
+ bool mesh_valid = false;
+ bool stl_ascii = false;
+ if (!zip.Read((void*)&header, sizeof(StlHeader)).Eof()) {
+ if (strncmp(header.comment, "solid ", 6) == 0)
+ stl_ascii = true;
+ else {
+ // Header has been extracted. Now read the faces.
+ stl_file &stl = mesh.stl;
+ stl.error = 0;
+ stl.stats.type = inmemory;
+ stl.stats.number_of_facets = header.nTriangles;
+ stl.stats.original_num_facets = header.nTriangles;
+ stl_allocate(&stl);
+ if (header.nTriangles > 0 && zip.ReadAll((void*)stl.facet_start, 50 * header.nTriangles)) {
+ if (sizeof(stl_facet) > SIZEOF_STL_FACET) {
+ // The stl.facet_start is not packed tightly. Unpack the array of stl_facets.
+ unsigned char *data = (unsigned char*)stl.facet_start;
+ for (size_t i = header.nTriangles - 1; i > 0; -- i)
+ memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET);
+ }
+ // All the faces have been read.
+ stl_get_size(&stl);
+ mesh.repair();
+ // Transform the model.
+ stl_transform(&stl, &trafo[0][0]);
+ if (std::abs(stl.stats.min(2)) < EPSILON)
+ stl.stats.min(2) = 0.;
+ // Add a mesh to a model.
+ if (mesh.facets_count() > 0)
+ mesh_valid = true;
+ }
+ }
+ } else
+ stl_ascii = true;
+ if (stl_ascii) {
+ // Try to parse ASCII STL.
+ char normal_buf[3][32];
+ stl_facet facet;
+ std::vector<stl_facet> facets;
+ LineReader line_reader(zip, (char*)&header, zip.LastRead());
+ std::string solid_name;
+ facet.extra[0] = facet.extra[1] = 0;
+ for (;;) {
+ const char *line = line_reader.next_line();
+ if (line == nullptr)
+ // End of file.
+ break;
+ if (strncmp(line, "solid", 5) == 0) {
+ // Opening the "solid" block.
+ if (! solid_name.empty()) {
+ // Error, solid block is already open.
+ facets.clear();
+ break;
+ }
+ solid_name = line + 5;
+ if (solid_name.empty())
+ solid_name = "unknown";
+ continue;
+ }
+ if (strncmp(line, "endsolid", 8) == 0) {
+ // Closing the "solid" block.
+ if (solid_name.empty()) {
+ // Error, no solid block is open.
+ facets.clear();
+ break;
+ }
+ solid_name.clear();
+ continue;
+ }
+ // Line has to start with the word solid.
+ int res_normal = sscanf(line, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]);
+ assert(res_normal == 3);
+ int res_outer_loop = line_reader.next_line_scanf(" outer loop");
+ assert(res_outer_loop == 0);
+ int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2));
+ assert(res_vertex1 == 3);
+ int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2));
+ assert(res_vertex2 == 3);
+ int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2));
+ assert(res_vertex3 == 3);
+ int res_endloop = line_reader.next_line_scanf(" endloop");
+ assert(res_endloop == 0);
+ int res_endfacet = line_reader.next_line_scanf(" endfacet");
+ if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) {
+ // perror("Something is syntactically very wrong with this ASCII STL!");
+ facets.clear();
+ break;
+ }
+ // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition.
+ if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 ||
+ sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 ||
+ sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
+ // Normal was mangled. Maybe denormals or "not a number" were stored?
+ // Just reset the normal and silently ignore it.
+ memset(&facet.normal, 0, sizeof(facet.normal));
+ }
+ facets.emplace_back(facet);
+ }
+ if (! facets.empty() && solid_name.empty()) {
+ stl_file &stl = mesh.stl;
+ stl.stats.type = inmemory;
+ stl.stats.number_of_facets = facets.size();
+ stl.stats.original_num_facets = facets.size();
+ stl_allocate(&stl);
+ memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50);
+ stl_get_size(&stl);
+ mesh.repair();
+ // Transform the model.
+ stl_transform(&stl, &trafo[0][0]);
+ // Add a mesh to a model.
+ if (mesh.facets_count() > 0)
+ mesh_valid = true;
+ }
+ }
+
+ if (mesh_valid) {
+ // Add this mesh to the model.
+ ModelVolume *volume = nullptr;
+ if (model_object == nullptr) {
+ // This is a first mesh of a group. Create a new object & volume.
+ model_object = model->add_object(name_utf8.data(), path, std::move(mesh));
+ volume = model_object->volumes.front();
+ ModelInstance *instance = model_object->add_instance();
+ instance->rotation = instance_rotation;
+ instance->scaling_factor = instance_scaling_factor;
+#if ENABLE_MODELINSTANCE_3D_OFFSET
+ instance->set_offset(instance_offset);
+#else
+ instance->offset = instance_offset;
+#endif // ENABLE_MODELINSTANCE_3D_OFFSET
+ ++num_models;
+ if (group_id != (size_t)-1)
+ group_to_model_object[group_id] = model_object;
+ } else {
+ // This is not the 1st mesh of a group. Add it to the ModelObject.
+ volume = model_object->add_volume(std::move(mesh));
+ volume->name = name_utf8.data();
+ }
+ // Set the extruder to the volume.
+ if (extruder_id != (unsigned int)-1) {
+ char str_extruder[64];
+ sprintf(str_extruder, "%ud", extruder_id);
+ volume->config.set_deserialize("extruder", str_extruder);
+ }
+ }
+ }
+ }
+ }
+ return num_models > 0;
+}
+
+}; // namespace Slic3r
+
+#endif /* SLIC3R_PRUS */
diff --git a/src/libslic3r/Format/PRUS.hpp b/src/libslic3r/Format/PRUS.hpp
new file mode 100644
index 000000000..8559a70d6
--- /dev/null
+++ b/src/libslic3r/Format/PRUS.hpp
@@ -0,0 +1,14 @@
+#if defined(SLIC3R_PRUS) && ! defined(slic3r_Format_PRUS_hpp_)
+#define slic3r_Format_PRUS_hpp_
+
+namespace Slic3r {
+
+class TriangleMesh;
+class Model;
+
+// Load a PrusaControl project file into a provided model.
+extern bool load_prus(const char *path, Model *model);
+
+}; // namespace Slic3r
+
+#endif /* SLIC3R_PRUS && ! defined(slic3r_Format_PRUS_hpp_) */
diff --git a/src/libslic3r/Format/STL.cpp b/src/libslic3r/Format/STL.cpp
new file mode 100644
index 000000000..99e2ff193
--- /dev/null
+++ b/src/libslic3r/Format/STL.cpp
@@ -0,0 +1,58 @@
+#include "../libslic3r.h"
+#include "../Model.hpp"
+#include "../TriangleMesh.hpp"
+
+#include "STL.hpp"
+
+#include <string>
+
+#ifdef _WIN32
+#define DIR_SEPARATOR '\\'
+#else
+#define DIR_SEPARATOR '/'
+#endif
+
+namespace Slic3r {
+
+bool load_stl(const char *path, Model *model, const char *object_name_in)
+{
+ TriangleMesh mesh;
+ mesh.ReadSTLFile(path);
+ if (mesh.stl.error) {
+// die "Failed to open $file\n" if !-e $path;
+ return false;
+ }
+ mesh.repair();
+ if (mesh.facets_count() == 0) {
+ // die "This STL file couldn't be read because it's empty.\n"
+ return false;
+ }
+
+ std::string object_name;
+ if (object_name_in == nullptr) {
+ const char *last_slash = strrchr(path, DIR_SEPARATOR);
+ object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
+ } else
+ object_name.assign(object_name_in);
+
+ model->add_object(object_name.c_str(), path, std::move(mesh));
+ return true;
+}
+
+bool store_stl(const char *path, TriangleMesh *mesh, bool binary)
+{
+ if (binary)
+ mesh->write_binary(path);
+ else
+ mesh->write_ascii(path);
+ //FIXME returning false even if write failed.
+ return true;
+}
+
+bool store_stl(const char *path, ModelObject *model_object, bool binary)
+{
+ TriangleMesh mesh = model_object->mesh();
+ return store_stl(path, &mesh, binary);
+}
+
+}; // namespace Slic3r
diff --git a/src/libslic3r/Format/STL.hpp b/src/libslic3r/Format/STL.hpp
new file mode 100644
index 000000000..2fd32324c
--- /dev/null
+++ b/src/libslic3r/Format/STL.hpp
@@ -0,0 +1,17 @@
+#ifndef slic3r_Format_STL_hpp_
+#define slic3r_Format_STL_hpp_
+
+namespace Slic3r {
+
+class TriangleMesh;
+class ModelObject;
+
+// Load an STL file into a provided model.
+extern bool load_stl(const char *path, Model *model, const char *object_name = nullptr);
+
+extern bool store_stl(const char *path, TriangleMesh *mesh, bool binary);
+extern bool store_stl(const char *path, ModelObject *model_object, bool binary);
+
+}; // namespace Slic3r
+
+#endif /* slic3r_Format_STL_hpp_ */
diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp
new file mode 100644
index 000000000..88dfae695
--- /dev/null
+++ b/src/libslic3r/Format/objparser.cpp
@@ -0,0 +1,540 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include <boost/nowide/cstdio.hpp>
+
+#include "objparser.hpp"
+
+namespace ObjParser {
+
+static bool obj_parseline(const char *line, ObjData &data)
+{
+#define EATWS() while (*line == ' ' || *line == '\t') ++ line
+
+ if (*line == 0)
+ return true;
+
+ // Ignore whitespaces at the beginning of the line.
+ //FIXME is this a good idea?
+ EATWS();
+
+ char c1 = *line ++;
+ switch (c1) {
+ case '#':
+ // Comment, ignore the rest of the line.
+ break;
+ case 'v':
+ {
+ // Parse vertex geometry (position, normal, texture coordinates)
+ char c2 = *line ++;
+ switch (c2) {
+ case 't':
+ {
+ // vt - vertex texture parameter
+ // u v [w], w == 0 (or w == 1)
+ char c2 = *line ++;
+ if (c2 != ' ' && c2 != '\t')
+ return false;
+ EATWS();
+ char *endptr = 0;
+ double u = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
+ return false;
+ line = endptr;
+ EATWS();
+ double v = 0;
+ if (*line != 0) {
+ v = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ EATWS();
+ }
+ double w = 0;
+ if (*line != 0) {
+ w = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ EATWS();
+ }
+ if (*line != 0)
+ return false;
+ data.textureCoordinates.push_back((float)u);
+ data.textureCoordinates.push_back((float)v);
+ data.textureCoordinates.push_back((float)w);
+ break;
+ }
+ case 'n':
+ {
+ // vn - vertex normal
+ // x y z
+ char c2 = *line ++;
+ if (c2 != ' ' && c2 != '\t')
+ return false;
+ EATWS();
+ char *endptr = 0;
+ double x = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
+ return false;
+ line = endptr;
+ EATWS();
+ double y = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
+ return false;
+ line = endptr;
+ EATWS();
+ double z = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ EATWS();
+ if (*line != 0)
+ return false;
+ data.normals.push_back((float)x);
+ data.normals.push_back((float)y);
+ data.normals.push_back((float)z);
+ break;
+ }
+ case 'p':
+ {
+ // vp - vertex parameter
+ char c2 = *line ++;
+ if (c2 != ' ' && c2 != '\t')
+ return false;
+ EATWS();
+ char *endptr = 0;
+ double u = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ EATWS();
+ double v = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ EATWS();
+ double w = 0;
+ if (*line != 0) {
+ w = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ EATWS();
+ }
+ if (*line != 0)
+ return false;
+ data.parameters.push_back((float)u);
+ data.parameters.push_back((float)v);
+ data.parameters.push_back((float)w);
+ break;
+ }
+ default:
+ {
+ // v - vertex geometry
+ if (c2 != ' ' && c2 != '\t')
+ return false;
+ EATWS();
+ char *endptr = 0;
+ double x = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
+ return false;
+ line = endptr;
+ EATWS();
+ double y = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t'))
+ return false;
+ line = endptr;
+ EATWS();
+ double z = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ EATWS();
+ double w = 1.0;
+ if (*line != 0) {
+ w = strtod(line, &endptr);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ EATWS();
+ }
+ if (*line != 0)
+ return false;
+ data.coordinates.push_back((float)x);
+ data.coordinates.push_back((float)y);
+ data.coordinates.push_back((float)z);
+ data.coordinates.push_back((float)w);
+ break;
+ }
+ }
+ break;
+ }
+ case 'f':
+ {
+ // face
+ EATWS();
+ if (*line == 0)
+ return false;
+ // number of vertices of this face
+ int n = 0;
+ // current vertex to be parsed
+ ObjVertex vertex;
+ char *endptr = 0;
+ while (*line != 0) {
+ // Parse a single vertex reference.
+ vertex.coordIdx = 0;
+ vertex.normalIdx = 0;
+ vertex.textureCoordIdx = 0;
+ vertex.coordIdx = strtol(line, &endptr, 10);
+ // Coordinate has to be defined
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0))
+ return false;
+ line = endptr;
+ if (*line == '/') {
+ ++ line;
+ // Texture coordinate index may be missing after a 1st slash, but then the normal index has to be present.
+ if (*line != '/') {
+ // Parse the texture coordinate index.
+ vertex.textureCoordIdx = strtol(line, &endptr, 10);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != '/' && *endptr != 0))
+ return false;
+ line = endptr;
+ }
+ if (*line == '/') {
+ // Parse normal index.
+ ++ line;
+ vertex.normalIdx = strtol(line, &endptr, 10);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ }
+ }
+ if (vertex.coordIdx < 0)
+ vertex.coordIdx += data.coordinates.size() / 4;
+ else
+ -- vertex.coordIdx;
+ if (vertex.normalIdx < 0)
+ vertex.normalIdx += data.normals.size() / 3;
+ else
+ -- vertex.normalIdx;
+ if (vertex.textureCoordIdx < 0)
+ vertex.textureCoordIdx += data.textureCoordinates.size() / 3;
+ else
+ -- vertex.textureCoordIdx;
+ data.vertices.push_back(vertex);
+ EATWS();
+ }
+ vertex.coordIdx = -1;
+ vertex.normalIdx = -1;
+ vertex.textureCoordIdx = -1;
+ data.vertices.push_back(vertex);
+ break;
+ }
+ case 'm':
+ {
+ if (*(line ++) != 't' ||
+ *(line ++) != 'l' ||
+ *(line ++) != 'l' ||
+ *(line ++) != 'i' ||
+ *(line ++) != 'b')
+ return false;
+ // mtllib [external .mtl file name]
+ // printf("mtllib %s\r\n", line);
+ EATWS();
+ data.mtllibs.push_back(std::string(line));
+ break;
+ }
+ case 'u':
+ {
+ if (*(line ++) != 's' ||
+ *(line ++) != 'e' ||
+ *(line ++) != 'm' ||
+ *(line ++) != 't' ||
+ *(line ++) != 'l')
+ return false;
+ // usemtl [material name]
+ // printf("usemtl %s\r\n", line);
+ EATWS();
+ ObjUseMtl usemtl;
+ usemtl.vertexIdxFirst = data.vertices.size();
+ usemtl.name = line;
+ data.usemtls.push_back(usemtl);
+ break;
+ }
+ case 'o':
+ {
+ // o [object name]
+ EATWS();
+ const char *name = line;
+ while (*line != ' ' && *line != '\t' && *line != 0)
+ ++ line;
+ // copy name to line.
+ EATWS();
+ if (*line != 0)
+ return false;
+ ObjObject object;
+ object.vertexIdxFirst = data.vertices.size();
+ object.name = line;
+ data.objects.push_back(object);
+ break;
+ }
+ case 'g':
+ {
+ // g [group name]
+ // printf("group %s\r\n", line);
+ ObjGroup group;
+ group.vertexIdxFirst = data.vertices.size();
+ group.name = line;
+ data.groups.push_back(group);
+ break;
+ }
+ case 's':
+ {
+ // s 1 / off
+ char c2 = *line ++;
+ if (c2 != ' ' && c2 != '\t')
+ return false;
+ EATWS();
+ char *endptr = 0;
+ long g = strtol(line, &endptr, 10);
+ if (endptr == 0 || (*endptr != ' ' && *endptr != '\t' && *endptr != 0))
+ return false;
+ line = endptr;
+ EATWS();
+ if (*line != 0)
+ return false;
+ ObjSmoothingGroup group;
+ group.vertexIdxFirst = data.vertices.size();
+ group.smoothingGroupID = g;
+ data.smoothingGroups.push_back(group);
+ break;
+ }
+ default:
+ printf("ObjParser: Unknown command: %c\r\n", c1);
+ break;
+ }
+
+ return true;
+}
+
+bool objparse(const char *path, ObjData &data)
+{
+ FILE *pFile = boost::nowide::fopen(path, "rt");
+ if (pFile == 0)
+ return false;
+
+ try {
+ char buf[65536 * 2];
+ size_t len = 0;
+ size_t lenPrev = 0;
+ while ((len = ::fread(buf + lenPrev, 1, 65536, pFile)) != 0) {
+ len += lenPrev;
+ size_t lastLine = 0;
+ for (size_t i = 0; i < len; ++ i)
+ if (buf[i] == '\r' || buf[i] == '\n') {
+ buf[i] = 0;
+ char *c = buf + lastLine;
+ while (*c == ' ' || *c == '\t')
+ ++ c;
+ obj_parseline(c, data);
+ lastLine = i + 1;
+ }
+ lenPrev = len - lastLine;
+ memmove(buf, buf + lastLine, lenPrev);
+ }
+ } catch (std::bad_alloc &ex) {
+ printf("Out of memory\r\n");
+ }
+ ::fclose(pFile);
+
+ // printf("vertices: %d\r\n", data.vertices.size() / 4);
+ // printf("coords: %d\r\n", data.coordinates.size());
+ return true;
+}
+
+template<typename T>
+bool savevector(FILE *pFile, const std::vector<T> &v)
+{
+ size_t cnt = v.size();
+ ::fwrite(&cnt, 1, sizeof(cnt), pFile);
+ //FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type.
+ if (! v.empty())
+ ::fwrite(&v.front(), 1, sizeof(T) * cnt, pFile);
+ return true;
+}
+
+bool savevector(FILE *pFile, const std::vector<std::string> &v)
+{
+ size_t cnt = v.size();
+ ::fwrite(&cnt, 1, sizeof(cnt), pFile);
+ for (size_t i = 0; i < cnt; ++ i) {
+ size_t len = v[i].size();
+ ::fwrite(&len, 1, sizeof(cnt), pFile);
+ ::fwrite(v[i].c_str(), 1, len, pFile);
+ }
+ return true;
+}
+
+template<typename T>
+bool savevectornameidx(FILE *pFile, const std::vector<T> &v)
+{
+ size_t cnt = v.size();
+ ::fwrite(&cnt, 1, sizeof(cnt), pFile);
+ for (size_t i = 0; i < cnt; ++ i) {
+ ::fwrite(&v[i].vertexIdxFirst, 1, sizeof(int), pFile);
+ size_t len = v[i].name.size();
+ ::fwrite(&len, 1, sizeof(cnt), pFile);
+ ::fwrite(v[i].name.c_str(), 1, len, pFile);
+ }
+ return true;
+}
+
+template<typename T>
+bool loadvector(FILE *pFile, std::vector<T> &v)
+{
+ v.clear();
+ size_t cnt = 0;
+ if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1)
+ return false;
+ //FIXME sizeof(T) works for data types leaving no gaps in the allocated vector because of alignment of the T type.
+ if (cnt != 0) {
+ v.assign(cnt, T());
+ if (::fread(&v.front(), sizeof(T), cnt, pFile) != cnt)
+ return false;
+ }
+ return true;
+}
+
+bool loadvector(FILE *pFile, std::vector<std::string> &v)
+{
+ v.clear();
+ size_t cnt = 0;
+ if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1)
+ return false;
+ v.reserve(cnt);
+ for (size_t i = 0; i < cnt; ++ i) {
+ size_t len = 0;
+ if (::fread(&len, sizeof(len), 1, pFile) != 1)
+ return false;
+ std::string s(" ", len);
+ if (::fread(const_cast<char*>(s.c_str()), 1, len, pFile) != len)
+ return false;
+ v.push_back(std::move(s));
+ }
+ return true;
+}
+
+template<typename T>
+bool loadvectornameidx(FILE *pFile, std::vector<T> &v)
+{
+ v.clear();
+ size_t cnt = 0;
+ if (::fread(&cnt, sizeof(cnt), 1, pFile) != 1)
+ return false;
+ v.assign(cnt, T());
+ for (size_t i = 0; i < cnt; ++ i) {
+ if (::fread(&v[i].vertexIdxFirst, sizeof(int), 1, pFile) != 1)
+ return false;
+ size_t len = 0;
+ if (::fread(&len, sizeof(len), 1, pFile) != 1)
+ return false;
+ v[i].name.assign(" ", len);
+ if (::fread(const_cast<char*>(v[i].name.c_str()), 1, len, pFile) != len)
+ return false;
+ }
+ return true;
+}
+
+bool objbinsave(const char *path, const ObjData &data)
+{
+ FILE *pFile = boost::nowide::fopen(path, "wb");
+ if (pFile == 0)
+ return false;
+
+ size_t version = 1;
+ ::fwrite(&version, 1, sizeof(version), pFile);
+
+ bool result =
+ savevector(pFile, data.coordinates) &&
+ savevector(pFile, data.textureCoordinates) &&
+ savevector(pFile, data.normals) &&
+ savevector(pFile, data.parameters) &&
+ savevector(pFile, data.mtllibs) &&
+ savevectornameidx(pFile, data.usemtls) &&
+ savevectornameidx(pFile, data.objects) &&
+ savevectornameidx(pFile, data.groups) &&
+ savevector(pFile, data.smoothingGroups) &&
+ savevector(pFile, data.vertices);
+
+ ::fclose(pFile);
+ return result;
+}
+
+bool objbinload(const char *path, ObjData &data)
+{
+ FILE *pFile = boost::nowide::fopen(path, "rb");
+ if (pFile == 0)
+ return false;
+
+ data.version = 0;
+ if (::fread(&data.version, sizeof(data.version), 1, pFile) != 1)
+ return false;
+ if (data.version != 1)
+ return false;
+
+ bool result =
+ loadvector(pFile, data.coordinates) &&
+ loadvector(pFile, data.textureCoordinates) &&
+ loadvector(pFile, data.normals) &&
+ loadvector(pFile, data.parameters) &&
+ loadvector(pFile, data.mtllibs) &&
+ loadvectornameidx(pFile, data.usemtls) &&
+ loadvectornameidx(pFile, data.objects) &&
+ loadvectornameidx(pFile, data.groups) &&
+ loadvector(pFile, data.smoothingGroups) &&
+ loadvector(pFile, data.vertices);
+
+ ::fclose(pFile);
+ return result;
+}
+
+template<typename T>
+bool vectorequal(const std::vector<T> &v1, const std::vector<T> &v2)
+{
+ if (v1.size() != v2.size())
+ return false;
+ for (size_t i = 0; i < v1.size(); ++ i)
+ if (! (v1[i] == v2[i]))
+ return false;
+ return true;
+}
+
+bool vectorequal(const std::vector<std::string> &v1, const std::vector<std::string> &v2)
+{
+ if (v1.size() != v2.size())
+ return false;
+ for (size_t i = 0; i < v1.size(); ++ i)
+ if (v1[i].compare(v2[i]) != 0)
+ return false;
+ return true;
+}
+
+extern bool objequal(const ObjData &data1, const ObjData &data2)
+{
+ //FIXME ignore version number
+ // version;
+
+ return
+ vectorequal(data1.coordinates, data2.coordinates) &&
+ vectorequal(data1.textureCoordinates, data2.textureCoordinates) &&
+ vectorequal(data1.normals, data2.normals) &&
+ vectorequal(data1.parameters, data2.parameters) &&
+ vectorequal(data1.mtllibs, data2.mtllibs) &&
+ vectorequal(data1.usemtls, data2.usemtls) &&
+ vectorequal(data1.objects, data2.objects) &&
+ vectorequal(data1.groups, data2.groups) &&
+ vectorequal(data1.vertices, data2.vertices);
+}
+
+} // namespace ObjParser
diff --git a/src/libslic3r/Format/objparser.hpp b/src/libslic3r/Format/objparser.hpp
new file mode 100644
index 000000000..5fc25e297
--- /dev/null
+++ b/src/libslic3r/Format/objparser.hpp
@@ -0,0 +1,109 @@
+#ifndef slic3r_Format_objparser_hpp_
+#define slic3r_Format_objparser_hpp_
+
+#include <string>
+#include <vector>
+
+namespace ObjParser {
+
+struct ObjVertex
+{
+ int coordIdx;
+ int textureCoordIdx;
+ int normalIdx;
+};
+
+inline bool operator==(const ObjVertex &v1, const ObjVertex &v2)
+{
+ return
+ v1.coordIdx == v2.coordIdx &&
+ v1.textureCoordIdx == v2.textureCoordIdx &&
+ v1.normalIdx == v2.normalIdx;
+}
+
+struct ObjUseMtl
+{
+ int vertexIdxFirst;
+ std::string name;
+};
+
+inline bool operator==(const ObjUseMtl &v1, const ObjUseMtl &v2)
+{
+ return
+ v1.vertexIdxFirst == v2.vertexIdxFirst &&
+ v1.name.compare(v2.name) == 0;
+}
+
+struct ObjObject
+{
+ int vertexIdxFirst;
+ std::string name;
+};
+
+inline bool operator==(const ObjObject &v1, const ObjObject &v2)
+{
+ return
+ v1.vertexIdxFirst == v2.vertexIdxFirst &&
+ v1.name.compare(v2.name) == 0;
+}
+
+struct ObjGroup
+{
+ int vertexIdxFirst;
+ std::string name;
+};
+
+inline bool operator==(const ObjGroup &v1, const ObjGroup &v2)
+{
+ return
+ v1.vertexIdxFirst == v2.vertexIdxFirst &&
+ v1.name.compare(v2.name) == 0;
+}
+
+struct ObjSmoothingGroup
+{
+ int vertexIdxFirst;
+ int smoothingGroupID;
+};
+
+inline bool operator==(const ObjSmoothingGroup &v1, const ObjSmoothingGroup &v2)
+{
+ return
+ v1.vertexIdxFirst == v2.vertexIdxFirst &&
+ v1.smoothingGroupID == v2.smoothingGroupID;
+}
+
+struct ObjData {
+ // Version of the data structure for load / store in the private binary format.
+ int version;
+
+ // x, y, z, w
+ std::vector<float> coordinates;
+ // u, v, w
+ std::vector<float> textureCoordinates;
+ // x, y, z
+ std::vector<float> normals;
+ // u, v, w
+ std::vector<float> parameters;
+
+ std::vector<std::string> mtllibs;
+ std::vector<ObjUseMtl> usemtls;
+ std::vector<ObjObject> objects;
+ std::vector<ObjGroup> groups;
+ std::vector<ObjSmoothingGroup> smoothingGroups;
+
+ // List of faces, delimited by an ObjVertex with all members set to -1.
+ std::vector<ObjVertex> vertices;
+};
+
+extern bool objparse(const char *path, ObjData &data);
+
+extern bool objbinsave(const char *path, const ObjData &data);
+
+extern bool objbinload(const char *path, ObjData &data);
+
+extern bool objequal(const ObjData &data1, const ObjData &data2);
+
+} // namespace ObjParser
+
+#endif /* slic3r_Format_objparser_hpp_ */