diff options
Diffstat (limited to 'src/libslic3r/Format/AMF.cpp')
-rw-r--r-- | src/libslic3r/Format/AMF.cpp | 901 |
1 files changed, 901 insertions, 0 deletions
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 |