diff options
Diffstat (limited to 'src/libslic3r/Format')
-rw-r--r-- | src/libslic3r/Format/3mf.cpp | 2022 | ||||
-rw-r--r-- | src/libslic3r/Format/3mf.hpp | 19 | ||||
-rw-r--r-- | src/libslic3r/Format/AMF.cpp | 901 | ||||
-rw-r--r-- | src/libslic3r/Format/AMF.hpp | 19 | ||||
-rw-r--r-- | src/libslic3r/Format/OBJ.cpp | 118 | ||||
-rw-r--r-- | src/libslic3r/Format/OBJ.hpp | 14 | ||||
-rw-r--r-- | src/libslic3r/Format/PRUS.cpp | 399 | ||||
-rw-r--r-- | src/libslic3r/Format/PRUS.hpp | 14 | ||||
-rw-r--r-- | src/libslic3r/Format/STL.cpp | 58 | ||||
-rw-r--r-- | src/libslic3r/Format/STL.hpp | 17 | ||||
-rw-r--r-- | src/libslic3r/Format/objparser.cpp | 540 | ||||
-rw-r--r-- | src/libslic3r/Format/objparser.hpp | 109 |
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_ */ |