1 #include <limits>
2 #include <string.h>
3 #include <map>
4 #include <string>
5 #include <expat.h>
6 
7 #include <boost/nowide/cstdio.hpp>
8 
9 #include "../libslic3r.h"
10 #include "../Exception.hpp"
11 #include "../Model.hpp"
12 #include "../GCode.hpp"
13 #include "../PrintConfig.hpp"
14 #include "../Utils.hpp"
15 #include "../I18N.hpp"
16 #include "../Geometry.hpp"
17 #include "../CustomGCode.hpp"
18 
19 #include "AMF.hpp"
20 
21 #include <boost/property_tree/ptree.hpp>
22 #include <boost/property_tree/xml_parser.hpp>
23 namespace pt = boost::property_tree;
24 
25 #include <boost/filesystem/operations.hpp>
26 #include <boost/algorithm/string.hpp>
27 #include <boost/nowide/fstream.hpp>
28 #include "miniz_extension.hpp"
29 
30 #if 0
31 // Enable debugging and assert in this file.
32 #define DEBUG
33 #define _DEBUG
34 #undef NDEBUG
35 #endif
36 
37 #include <assert.h>
38 
39 // VERSION NUMBERS
40 // 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them.
41 // 1 : Introduction of amf versioning. No other change in data saved into amf files.
42 // 2 : Added z component of offset
43 //     Added x and y components of rotation
44 //     Added x, y and z components of scale
45 //     Added x, y and z components of mirror
46 // 3 : Added volumes' matrices and source data, meshes transformed back to their coordinate system on loading.
47 // WARNING !! -> the version number has been rolled back to 2
48 //               the next change should use 4
49 const unsigned int VERSION_AMF = 2;
50 const unsigned int VERSION_AMF_COMPATIBLE = 3;
51 const char* SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version";
52 
53 const char* SLIC3R_CONFIG_TYPE = "slic3rpe_config";
54 
55 namespace Slic3r
56 {
57 
58 //! macro used to mark string used at localization,
59 //! return same string
60 #define L(s) (s)
61 #define _(s) Slic3r::I18N::translate(s)
62 
63 struct AMFParserContext
64 {
AMFParserContextSlic3r::AMFParserContext65     AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model) :
66         m_parser(parser),
67         m_model(*model),
68         m_config(config),
69         m_config_substitutions(config_substitutions)
70     {
71         m_path.reserve(12);
72     }
73 
stopSlic3r::AMFParserContext74     void stop(const std::string &msg = std::string())
75     {
76         assert(! m_error);
77         assert(m_error_message.empty());
78         m_error = true;
79         m_error_message = msg;
80         XML_StopParser(m_parser, 0);
81     }
82 
errorSlic3r::AMFParserContext83     bool        error()         const { return m_error; }
error_messageSlic3r::AMFParserContext84     const char* error_message() const {
85         return m_error ?
86             // The error was signalled by the user code, not the expat parser.
87             (m_error_message.empty() ? "Invalid AMF format" : m_error_message.c_str()) :
88             // The error was signalled by the expat parser.
89             XML_ErrorString(XML_GetErrorCode(m_parser));
90     }
91 
92     void startElement(const char *name, const char **atts);
93     void endElement(const char *name);
94     void endDocument();
95     void characters(const XML_Char *s, int len);
96 
startElementSlic3r::AMFParserContext97     static void XMLCALL startElement(void *userData, const char *name, const char **atts)
98     {
99         AMFParserContext *ctx = (AMFParserContext*)userData;
100         ctx->startElement(name, atts);
101     }
102 
endElementSlic3r::AMFParserContext103     static void XMLCALL endElement(void *userData, const char *name)
104     {
105         AMFParserContext *ctx = (AMFParserContext*)userData;
106         ctx->endElement(name);
107     }
108 
109     /* s is not 0 terminated. */
charactersSlic3r::AMFParserContext110     static void XMLCALL characters(void *userData, const XML_Char *s, int len)
111     {
112         AMFParserContext *ctx = (AMFParserContext*)userData;
113         ctx->characters(s, len);
114     }
115 
get_attributeSlic3r::AMFParserContext116     static const char* get_attribute(const char **atts, const char *id) {
117         if (atts == nullptr)
118             return nullptr;
119         while (*atts != nullptr) {
120             if (strcmp(*(atts ++), id) == 0)
121                 return *atts;
122             ++ atts;
123         }
124         return nullptr;
125     }
126 
127     enum AMFNodeType {
128         NODE_TYPE_INVALID = 0,
129         NODE_TYPE_UNKNOWN,
130         NODE_TYPE_AMF,                  // amf
131                                         // amf/metadata
132         NODE_TYPE_MATERIAL,             // amf/material
133                                         // amf/material/metadata
134         NODE_TYPE_OBJECT,               // amf/object
135                                         // amf/object/metadata
136         NODE_TYPE_LAYER_CONFIG,         // amf/object/layer_config_ranges
137         NODE_TYPE_RANGE,                // amf/object/layer_config_ranges/range
138                                         // amf/object/layer_config_ranges/range/metadata
139         NODE_TYPE_MESH,                 // amf/object/mesh
140         NODE_TYPE_VERTICES,             // amf/object/mesh/vertices
141         NODE_TYPE_VERTEX,               // amf/object/mesh/vertices/vertex
142         NODE_TYPE_COORDINATES,          // amf/object/mesh/vertices/vertex/coordinates
143         NODE_TYPE_COORDINATE_X,         // amf/object/mesh/vertices/vertex/coordinates/x
144         NODE_TYPE_COORDINATE_Y,         // amf/object/mesh/vertices/vertex/coordinates/y
145         NODE_TYPE_COORDINATE_Z,         // amf/object/mesh/vertices/vertex/coordinates/z
146         NODE_TYPE_VOLUME,               // amf/object/mesh/volume
147                                         // amf/object/mesh/volume/metadata
148         NODE_TYPE_TRIANGLE,             // amf/object/mesh/volume/triangle
149         NODE_TYPE_VERTEX1,              // amf/object/mesh/volume/triangle/v1
150         NODE_TYPE_VERTEX2,              // amf/object/mesh/volume/triangle/v2
151         NODE_TYPE_VERTEX3,              // amf/object/mesh/volume/triangle/v3
152         NODE_TYPE_CONSTELLATION,        // amf/constellation
153         NODE_TYPE_INSTANCE,             // amf/constellation/instance
154         NODE_TYPE_DELTAX,               // amf/constellation/instance/deltax
155         NODE_TYPE_DELTAY,               // amf/constellation/instance/deltay
156         NODE_TYPE_DELTAZ,               // amf/constellation/instance/deltaz
157         NODE_TYPE_RX,                   // amf/constellation/instance/rx
158         NODE_TYPE_RY,                   // amf/constellation/instance/ry
159         NODE_TYPE_RZ,                   // amf/constellation/instance/rz
160         NODE_TYPE_SCALE,                // amf/constellation/instance/scale
161         NODE_TYPE_SCALEX,               // amf/constellation/instance/scalex
162         NODE_TYPE_SCALEY,               // amf/constellation/instance/scaley
163         NODE_TYPE_SCALEZ,               // amf/constellation/instance/scalez
164         NODE_TYPE_MIRRORX,              // amf/constellation/instance/mirrorx
165         NODE_TYPE_MIRRORY,              // amf/constellation/instance/mirrory
166         NODE_TYPE_MIRRORZ,              // amf/constellation/instance/mirrorz
167         NODE_TYPE_PRINTABLE,            // amf/constellation/instance/mirrorz
168         NODE_TYPE_CUSTOM_GCODE,         // amf/custom_code_per_height
169         NODE_TYPE_GCODE_PER_HEIGHT,     // amf/custom_code_per_height/code
170         NODE_TYPE_CUSTOM_GCODE_MODE,    // amf/custom_code_per_height/mode
171         NODE_TYPE_METADATA,             // anywhere under amf/*/metadata
172     };
173 
174     struct Instance {
InstanceSlic3r::AMFParserContext::Instance175         Instance()
176             : deltax_set(false), deltay_set(false), deltaz_set(false)
177             , rx_set(false), ry_set(false), rz_set(false)
178             , scalex_set(false), scaley_set(false), scalez_set(false)
179             , mirrorx_set(false), mirrory_set(false), mirrorz_set(false)
180             , printable(true) {}
181         // Shift in the X axis.
182         float deltax;
183         bool  deltax_set;
184         // Shift in the Y axis.
185         float deltay;
186         bool  deltay_set;
187         // Shift in the Z axis.
188         float deltaz;
189         bool  deltaz_set;
190         // Rotation around the X axis.
191         float rx;
192         bool  rx_set;
193         // Rotation around the Y axis.
194         float ry;
195         bool  ry_set;
196         // Rotation around the Z axis.
197         float rz;
198         bool  rz_set;
199         // Scaling factors
200         float scalex;
201         bool  scalex_set;
202         float scaley;
203         bool  scaley_set;
204         float scalez;
205         bool  scalez_set;
206         // Mirroring factors
207         float mirrorx;
208         bool  mirrorx_set;
209         float mirrory;
210         bool  mirrory_set;
211         float mirrorz;
212         bool  mirrorz_set;
213         // printable property
214         bool  printable;
215 
anything_setSlic3r::AMFParserContext::Instance216         bool anything_set() const { return deltax_set || deltay_set || deltaz_set ||
217                                            rx_set || ry_set || rz_set ||
218                                            scalex_set || scaley_set || scalez_set ||
219                                            mirrorx_set || mirrory_set || mirrorz_set; }
220     };
221 
222     struct Object {
ObjectSlic3r::AMFParserContext::Object223         Object() : idx(-1) {}
224         int                     idx;
225         std::vector<Instance>   instances;
226     };
227 
228     // Version of the amf file
229     unsigned int             m_version { 0 };
230     // Current Expat XML parser instance.
231     XML_Parser               m_parser;
232     // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state
233     // after returning from XML_Parse() function, thus we keep the error state here.
234     bool                     m_error { false };
235     std::string              m_error_message;
236     // Model to receive objects extracted from an AMF file.
237     Model                   &m_model;
238     // Current parsing path in the XML file.
239     std::vector<AMFNodeType> m_path;
240     // Current object allocated for an amf/object XML subtree.
241     ModelObject             *m_object { nullptr };
242     // Map from obect name to object idx & instances.
243     std::map<std::string, Object> m_object_instances_map;
244     // Vertices parsed for the current m_object.
245     std::vector<float>       m_object_vertices;
246     // Current volume allocated for an amf/object/mesh/volume subtree.
247     ModelVolume             *m_volume { nullptr };
248     // Faces collected for the current m_volume.
249     std::vector<int>         m_volume_facets;
250     // Transformation matrix of a volume mesh from its coordinate system to Object's coordinate system.
251     Transform3d 			 m_volume_transform;
252     // Current material allocated for an amf/metadata subtree.
253     ModelMaterial           *m_material { nullptr };
254     // Current instance allocated for an amf/constellation/instance subtree.
255     Instance                *m_instance { nullptr };
256     // Generic string buffer for vertices, face indices, metadata etc.
257     std::string              m_value[5];
258     // Pointer to config to update if config data are stored inside the amf file
259     DynamicPrintConfig      *m_config { nullptr };
260     // Config substitution rules and collected config substitution log.
261     ConfigSubstitutionContext *m_config_substitutions { nullptr };
262 
263 private:
264     AMFParserContext& operator=(AMFParserContext&);
265 };
266 
startElement(const char * name,const char ** atts)267 void AMFParserContext::startElement(const char *name, const char **atts)
268 {
269     AMFNodeType node_type_new = NODE_TYPE_UNKNOWN;
270     switch (m_path.size()) {
271     case 0:
272         // An AMF file must start with an <amf> tag.
273         node_type_new = NODE_TYPE_AMF;
274         if (strcmp(name, "amf") != 0)
275             this->stop();
276         break;
277     case 1:
278         if (strcmp(name, "metadata") == 0) {
279             const char *type = get_attribute(atts, "type");
280             if (type != nullptr) {
281                 m_value[0] = type;
282                 node_type_new = NODE_TYPE_METADATA;
283             }
284         } else if (strcmp(name, "material") == 0) {
285             const char *material_id = get_attribute(atts, "id");
286             m_material = m_model.add_material((material_id == nullptr) ? "_" : material_id);
287             node_type_new = NODE_TYPE_MATERIAL;
288         } else if (strcmp(name, "object") == 0) {
289             const char *object_id = get_attribute(atts, "id");
290             if (object_id == nullptr)
291                 this->stop();
292             else {
293 				assert(m_object_vertices.empty());
294                 m_object = m_model.add_object();
295                 m_object_instances_map[object_id].idx = int(m_model.objects.size())-1;
296                 node_type_new = NODE_TYPE_OBJECT;
297             }
298         } else if (strcmp(name, "constellation") == 0) {
299             node_type_new = NODE_TYPE_CONSTELLATION;
300         } else if (strcmp(name, "custom_gcodes_per_height") == 0) {
301             node_type_new = NODE_TYPE_CUSTOM_GCODE;
302         }
303         break;
304     case 2:
305         if (strcmp(name, "metadata") == 0) {
306             if (m_path[1] == NODE_TYPE_MATERIAL || m_path[1] == NODE_TYPE_OBJECT) {
307                 m_value[0] = get_attribute(atts, "type");
308                 node_type_new = NODE_TYPE_METADATA;
309             }
310         } else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT)
311                 node_type_new = NODE_TYPE_LAYER_CONFIG;
312         else if (strcmp(name, "mesh") == 0) {
313             if (m_path[1] == NODE_TYPE_OBJECT)
314                 node_type_new = NODE_TYPE_MESH;
315         } else if (strcmp(name, "instance") == 0) {
316             if (m_path[1] == NODE_TYPE_CONSTELLATION) {
317                 const char *object_id = get_attribute(atts, "objectid");
318                 if (object_id == nullptr)
319                     this->stop();
320                 else {
321                     m_object_instances_map[object_id].instances.push_back(AMFParserContext::Instance());
322                     m_instance = &m_object_instances_map[object_id].instances.back();
323                     node_type_new = NODE_TYPE_INSTANCE;
324                 }
325             }
326             else
327                 this->stop();
328         }
329         else if (m_path[1] == NODE_TYPE_CUSTOM_GCODE) {
330             if (strcmp(name, "code") == 0) {
331                 node_type_new = NODE_TYPE_GCODE_PER_HEIGHT;
332                 m_value[0] = get_attribute(atts, "print_z");
333                 m_value[1] = get_attribute(atts, "extruder");
334                 m_value[2] = get_attribute(atts, "color");
335                 if (get_attribute(atts, "type"))
336                 {
337                     m_value[3] = get_attribute(atts, "type");
338                     m_value[4] = get_attribute(atts, "extra");
339                 }
340                 else
341                 {
342                     // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
343                     // read old data ...
344                     std::string gcode = get_attribute(atts, "gcode");
345                     // ... and interpret them to the new data
346                     CustomGCode::Type type= gcode == "M600" ? CustomGCode::ColorChange :
347                                             gcode == "M601" ? CustomGCode::PausePrint :
348                                             gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom;
349                     m_value[3] = std::to_string(static_cast<int>(type));
350                     m_value[4] = type == CustomGCode::PausePrint ? m_value[2] :
351                                  type == CustomGCode::Custom ? gcode : "";
352                 }
353             }
354             else if (strcmp(name, "mode") == 0) {
355                 node_type_new = NODE_TYPE_CUSTOM_GCODE_MODE;
356                 m_value[0] = get_attribute(atts, "value");
357             }
358         }
359         break;
360     case 3:
361         if (m_path[2] == NODE_TYPE_MESH) {
362 			assert(m_object);
363             if (strcmp(name, "vertices") == 0)
364                 node_type_new = NODE_TYPE_VERTICES;
365 			else if (strcmp(name, "volume") == 0) {
366 				assert(! m_volume);
367                 m_volume = m_object->add_volume(TriangleMesh());
368                 m_volume_transform = Transform3d::Identity();
369                 node_type_new = NODE_TYPE_VOLUME;
370 			}
371         } else if (m_path[2] == NODE_TYPE_INSTANCE) {
372             assert(m_instance);
373             if (strcmp(name, "deltax") == 0)
374                 node_type_new = NODE_TYPE_DELTAX;
375             else if (strcmp(name, "deltay") == 0)
376                 node_type_new = NODE_TYPE_DELTAY;
377             else if (strcmp(name, "deltaz") == 0)
378                 node_type_new = NODE_TYPE_DELTAZ;
379             else if (strcmp(name, "rx") == 0)
380                 node_type_new = NODE_TYPE_RX;
381             else if (strcmp(name, "ry") == 0)
382                 node_type_new = NODE_TYPE_RY;
383             else if (strcmp(name, "rz") == 0)
384                 node_type_new = NODE_TYPE_RZ;
385             else if (strcmp(name, "scalex") == 0)
386                 node_type_new = NODE_TYPE_SCALEX;
387             else if (strcmp(name, "scaley") == 0)
388                 node_type_new = NODE_TYPE_SCALEY;
389             else if (strcmp(name, "scalez") == 0)
390                 node_type_new = NODE_TYPE_SCALEZ;
391             else if (strcmp(name, "scale") == 0)
392                 node_type_new = NODE_TYPE_SCALE;
393             else if (strcmp(name, "mirrorx") == 0)
394                 node_type_new = NODE_TYPE_MIRRORX;
395             else if (strcmp(name, "mirrory") == 0)
396                 node_type_new = NODE_TYPE_MIRRORY;
397             else if (strcmp(name, "mirrorz") == 0)
398                 node_type_new = NODE_TYPE_MIRRORZ;
399             else if (strcmp(name, "printable") == 0)
400                 node_type_new = NODE_TYPE_PRINTABLE;
401         }
402         else if (m_path[2] == NODE_TYPE_LAYER_CONFIG && strcmp(name, "range") == 0) {
403             assert(m_object);
404             node_type_new = NODE_TYPE_RANGE;
405         }
406         break;
407     case 4:
408         if (m_path[3] == NODE_TYPE_VERTICES) {
409             if (strcmp(name, "vertex") == 0)
410                 node_type_new = NODE_TYPE_VERTEX;
411         } else if (m_path[3] == NODE_TYPE_VOLUME) {
412             if (strcmp(name, "metadata") == 0) {
413                 const char *type = get_attribute(atts, "type");
414                 if (type == nullptr)
415                     this->stop();
416                 else {
417                     m_value[0] = type;
418                     node_type_new = NODE_TYPE_METADATA;
419                 }
420             } else if (strcmp(name, "triangle") == 0)
421                 node_type_new = NODE_TYPE_TRIANGLE;
422         }
423         else if (m_path[3] == NODE_TYPE_RANGE && strcmp(name, "metadata") == 0) {
424             m_value[0] = get_attribute(atts, "type");
425             node_type_new = NODE_TYPE_METADATA;
426         }
427         break;
428     case 5:
429         if (strcmp(name, "coordinates") == 0) {
430             if (m_path[4] == NODE_TYPE_VERTEX) {
431                 node_type_new = NODE_TYPE_COORDINATES;
432             } else
433                 this->stop();
434         } else if (name[0] == 'v' && name[1] >= '1' && name[1] <= '3' && name[2] == 0) {
435             if (m_path[4] == NODE_TYPE_TRIANGLE) {
436                 node_type_new = AMFNodeType(NODE_TYPE_VERTEX1 + name[1] - '1');
437             } else
438                 this->stop();
439         }
440         break;
441     case 6:
442         if ((name[0] == 'x' || name[0] == 'y' || name[0] == 'z') && name[1] == 0) {
443             if (m_path[5] == NODE_TYPE_COORDINATES)
444                 node_type_new = AMFNodeType(NODE_TYPE_COORDINATE_X + name[0] - 'x');
445             else
446                 this->stop();
447         }
448         break;
449     default:
450         break;
451     }
452 
453     m_path.push_back(node_type_new);
454 }
455 
characters(const XML_Char * s,int len)456 void AMFParserContext::characters(const XML_Char *s, int len)
457 {
458     if (m_path.back() == NODE_TYPE_METADATA) {
459         m_value[1].append(s, len);
460     }
461     else
462     {
463         switch (m_path.size()) {
464         case 4:
465             if (m_path.back() == NODE_TYPE_DELTAX ||
466                 m_path.back() == NODE_TYPE_DELTAY ||
467                 m_path.back() == NODE_TYPE_DELTAZ ||
468                 m_path.back() == NODE_TYPE_RX ||
469                 m_path.back() == NODE_TYPE_RY ||
470                 m_path.back() == NODE_TYPE_RZ ||
471                 m_path.back() == NODE_TYPE_SCALEX ||
472                 m_path.back() == NODE_TYPE_SCALEY ||
473                 m_path.back() == NODE_TYPE_SCALEZ ||
474                 m_path.back() == NODE_TYPE_SCALE ||
475                 m_path.back() == NODE_TYPE_MIRRORX ||
476                 m_path.back() == NODE_TYPE_MIRRORY ||
477                 m_path.back() == NODE_TYPE_MIRRORZ ||
478                 m_path.back() == NODE_TYPE_PRINTABLE)
479                 m_value[0].append(s, len);
480             break;
481         case 6:
482             switch (m_path.back()) {
483                 case NODE_TYPE_VERTEX1: m_value[0].append(s, len); break;
484                 case NODE_TYPE_VERTEX2: m_value[1].append(s, len); break;
485                 case NODE_TYPE_VERTEX3: m_value[2].append(s, len); break;
486                 default: break;
487             }
488         case 7:
489             switch (m_path.back()) {
490                 case NODE_TYPE_COORDINATE_X: m_value[0].append(s, len); break;
491                 case NODE_TYPE_COORDINATE_Y: m_value[1].append(s, len); break;
492                 case NODE_TYPE_COORDINATE_Z: m_value[2].append(s, len); break;
493                 default: break;
494             }
495         default:
496             break;
497         }
498     }
499 }
500 
endElement(const char *)501 void AMFParserContext::endElement(const char * /* name */)
502 {
503     switch (m_path.back()) {
504 
505     // Constellation transformation:
506     case NODE_TYPE_DELTAX:
507         assert(m_instance);
508         m_instance->deltax = float(atof(m_value[0].c_str()));
509         m_instance->deltax_set = true;
510         m_value[0].clear();
511         break;
512     case NODE_TYPE_DELTAY:
513         assert(m_instance);
514         m_instance->deltay = float(atof(m_value[0].c_str()));
515         m_instance->deltay_set = true;
516         m_value[0].clear();
517         break;
518     case NODE_TYPE_DELTAZ:
519         assert(m_instance);
520         m_instance->deltaz = float(atof(m_value[0].c_str()));
521         m_instance->deltaz_set = true;
522         m_value[0].clear();
523         break;
524     case NODE_TYPE_RX:
525         assert(m_instance);
526         m_instance->rx = float(atof(m_value[0].c_str()));
527         m_instance->rx_set = true;
528         m_value[0].clear();
529         break;
530     case NODE_TYPE_RY:
531         assert(m_instance);
532         m_instance->ry = float(atof(m_value[0].c_str()));
533         m_instance->ry_set = true;
534         m_value[0].clear();
535         break;
536     case NODE_TYPE_RZ:
537         assert(m_instance);
538         m_instance->rz = float(atof(m_value[0].c_str()));
539         m_instance->rz_set = true;
540         m_value[0].clear();
541         break;
542     case NODE_TYPE_SCALE:
543         assert(m_instance);
544         m_instance->scalex = float(atof(m_value[0].c_str()));
545         m_instance->scalex_set = true;
546         m_instance->scaley = float(atof(m_value[0].c_str()));
547         m_instance->scaley_set = true;
548         m_instance->scalez = float(atof(m_value[0].c_str()));
549         m_instance->scalez_set = true;
550         m_value[0].clear();
551         break;
552     case NODE_TYPE_SCALEX:
553         assert(m_instance);
554         m_instance->scalex = float(atof(m_value[0].c_str()));
555         m_instance->scalex_set = true;
556         m_value[0].clear();
557         break;
558     case NODE_TYPE_SCALEY:
559         assert(m_instance);
560         m_instance->scaley = float(atof(m_value[0].c_str()));
561         m_instance->scaley_set = true;
562         m_value[0].clear();
563         break;
564     case NODE_TYPE_SCALEZ:
565         assert(m_instance);
566         m_instance->scalez = float(atof(m_value[0].c_str()));
567         m_instance->scalez_set = true;
568         m_value[0].clear();
569         break;
570     case NODE_TYPE_MIRRORX:
571         assert(m_instance);
572         m_instance->mirrorx = float(atof(m_value[0].c_str()));
573         m_instance->mirrorx_set = true;
574         m_value[0].clear();
575         break;
576     case NODE_TYPE_MIRRORY:
577         assert(m_instance);
578         m_instance->mirrory = float(atof(m_value[0].c_str()));
579         m_instance->mirrory_set = true;
580         m_value[0].clear();
581         break;
582     case NODE_TYPE_MIRRORZ:
583         assert(m_instance);
584         m_instance->mirrorz = float(atof(m_value[0].c_str()));
585         m_instance->mirrorz_set = true;
586         m_value[0].clear();
587         break;
588     case NODE_TYPE_PRINTABLE:
589         assert(m_instance);
590         m_instance->printable = bool(atoi(m_value[0].c_str()));
591         m_value[0].clear();
592         break;
593 
594     // Object vertices:
595     case NODE_TYPE_VERTEX:
596         assert(m_object);
597         // Parse the vertex data
598         m_object_vertices.emplace_back((float)atof(m_value[0].c_str()));
599         m_object_vertices.emplace_back((float)atof(m_value[1].c_str()));
600         m_object_vertices.emplace_back((float)atof(m_value[2].c_str()));
601         m_value[0].clear();
602         m_value[1].clear();
603         m_value[2].clear();
604         break;
605 
606     // Faces of the current volume:
607     case NODE_TYPE_TRIANGLE:
608         assert(m_object && m_volume);
609         m_volume_facets.emplace_back(atoi(m_value[0].c_str()));
610         m_volume_facets.emplace_back(atoi(m_value[1].c_str()));
611         m_volume_facets.emplace_back(atoi(m_value[2].c_str()));
612         m_value[0].clear();
613         m_value[1].clear();
614         m_value[2].clear();
615         break;
616 
617     // Closing the current volume. Create an STL from m_volume_facets pointing to m_object_vertices.
618     case NODE_TYPE_VOLUME:
619     {
620 		assert(m_object && m_volume);
621 		TriangleMesh  mesh;
622         stl_file	 &stl = mesh.stl;
623         stl.stats.type = inmemory;
624         stl.stats.number_of_facets = int(m_volume_facets.size() / 3);
625         stl.stats.original_num_facets = stl.stats.number_of_facets;
626         stl_allocate(&stl);
627 
628         bool has_transform = ! m_volume_transform.isApprox(Transform3d::Identity(), 1e-10);
629         for (size_t i = 0; i < m_volume_facets.size();) {
630             stl_facet &facet = stl.facet_start[i/3];
631             for (unsigned int v = 0; v < 3; ++v)
632             {
633                 unsigned int tri_id = m_volume_facets[i++] * 3;
634                 if (tri_id < 0 || tri_id + 2 >= m_object_vertices.size()) {
635                     this->stop("Malformed triangle mesh");
636                     return;
637                 }
638                 facet.vertex[v] = Vec3f(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]);
639             }
640         }
641         stl_get_size(&stl);
642         mesh.repair();
643 		m_volume->set_mesh(std::move(mesh));
644         // stores the volume matrix taken from the metadata, if present
645         if (has_transform)
646             m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform);
647         if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART))
648         {
649             m_volume->source.object_idx = (int)m_model.objects.size() - 1;
650             m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1;
651             m_volume->center_geometry_after_creation();
652         }
653         else
654             // pass false if the mesh offset has been already taken from the data
655             m_volume->center_geometry_after_creation(m_volume->source.input_file.empty());
656 
657         m_volume->calculate_convex_hull();
658         m_volume_facets.clear();
659         m_volume = nullptr;
660         break;
661     }
662 
663     case NODE_TYPE_OBJECT:
664         assert(m_object);
665         m_object_vertices.clear();
666         m_object = nullptr;
667         break;
668 
669     case NODE_TYPE_MATERIAL:
670         assert(m_material);
671         m_material = nullptr;
672         break;
673 
674     case NODE_TYPE_INSTANCE:
675         assert(m_instance);
676         m_instance = nullptr;
677         break;
678 
679     case NODE_TYPE_GCODE_PER_HEIGHT: {
680         double print_z          = double(atof(m_value[0].c_str()));
681         int extruder            = atoi(m_value[1].c_str());
682         const std::string& color= m_value[2];
683         CustomGCode::Type type  = static_cast<CustomGCode::Type>(atoi(m_value[3].c_str()));
684         const std::string& extra= m_value[4];
685 
686         m_model.custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra});
687 
688         for (std::string& val: m_value)
689             val.clear();
690         break;
691         }
692 
693     case NODE_TYPE_CUSTOM_GCODE_MODE: {
694         const std::string& mode = m_value[0];
695 
696         m_model.custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder :
697                                                 mode == CustomGCode::MultiAsSingleMode  ? CustomGCode::Mode::MultiAsSingle  :
698                                                                                           CustomGCode::Mode::MultiExtruder;
699         for (std::string& val: m_value)
700             val.clear();
701         break;
702         }
703 
704     case NODE_TYPE_METADATA:
705         if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) {
706             m_config->load_from_gcode_string(m_value[1].c_str(), *m_config_substitutions);
707         }
708         else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
709             const char *opt_key = m_value[0].c_str() + 7;
710             if (print_config_def.options.find(opt_key) != print_config_def.options.end()) {
711                 ModelConfig *config = nullptr;
712                 if (m_path.size() == 3) {
713                     if (m_path[1] == NODE_TYPE_MATERIAL && m_material)
714                         config = &m_material->config;
715                     else if (m_path[1] == NODE_TYPE_OBJECT && m_object)
716                         config = &m_object->config;
717                 }
718                 else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume)
719                     config = &m_volume->config;
720                 else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_RANGE && m_object && !m_object->layer_config_ranges.empty()) {
721                     auto it  = --m_object->layer_config_ranges.end();
722                     config = &it->second;
723                 }
724                 if (config)
725                     config->set_deserialize(opt_key, m_value[1], *m_config_substitutions);
726             } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) {
727                 // Parse object's layer height profile, a semicolon separated list of floats.
728                 char *p = m_value[1].data();
729                 std::vector<coordf_t> data;
730                 for (;;) {
731                     char *end = strchr(p, ';');
732                     if (end != nullptr)
733 	                    *end = 0;
734                     data.emplace_back(float(atof(p)));
735 					if (end == nullptr)
736 						break;
737 					p = end + 1;
738                 }
739                 m_object->layer_height_profile.set(std::move(data));
740             }
741             else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "sla_support_points") == 0) {
742                 // Parse object's layer height profile, a semicolon separated list of floats.
743                 unsigned char coord_idx = 0;
744                 Eigen::Matrix<float, 5, 1, Eigen::DontAlign> point(Eigen::Matrix<float, 5, 1, Eigen::DontAlign>::Zero());
745                 char *p = m_value[1].data();
746                 for (;;) {
747                     char *end = strchr(p, ';');
748                     if (end != nullptr)
749 	                    *end = 0;
750 
751                     point(coord_idx) = float(atof(p));
752                     if (++coord_idx == 5) {
753                         m_object->sla_support_points.push_back(sla::SupportPoint(point));
754                         coord_idx = 0;
755                     }
756 					if (end == nullptr)
757 						break;
758 					p = end + 1;
759                 }
760                 m_object->sla_points_status = sla::PointsStatus::UserModified;
761             }
762             else if (m_path.size() == 5 && m_path[1] == NODE_TYPE_OBJECT && m_path[3] == NODE_TYPE_RANGE &&
763                      m_object && strcmp(opt_key, "layer_height_range") == 0) {
764                 // Parse object's layer_height_range, a semicolon separated doubles.
765                 char* p = m_value[1].data();
766                 char* end = strchr(p, ';');
767                 *end = 0;
768 
769                 const t_layer_height_range range = {double(atof(p)), double(atof(end + 1))};
770                 m_object->layer_config_ranges[range];
771             }
772             else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) {
773                 if (strcmp(opt_key, "modifier") == 0) {
774                     // Is this volume a modifier volume?
775                     // "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag.
776 					m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART);
777                 } else if (strcmp(opt_key, "volume_type") == 0) {
778                     m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
779                 }
780                 else if (strcmp(opt_key, "matrix") == 0) {
781                     m_volume_transform = Slic3r::Geometry::transform3d_from_string(m_value[1]);
782                 }
783                 else if (strcmp(opt_key, "source_file") == 0) {
784                     m_volume->source.input_file = m_value[1];
785                 }
786                 else if (strcmp(opt_key, "source_object_id") == 0) {
787                     m_volume->source.object_idx = ::atoi(m_value[1].c_str());
788                 }
789                 else if (strcmp(opt_key, "source_volume_id") == 0) {
790                     m_volume->source.volume_idx = ::atoi(m_value[1].c_str());
791                 }
792                 else if (strcmp(opt_key, "source_offset_x") == 0) {
793                     m_volume->source.mesh_offset(0) = ::atof(m_value[1].c_str());
794                 }
795                 else if (strcmp(opt_key, "source_offset_y") == 0) {
796                     m_volume->source.mesh_offset(1) = ::atof(m_value[1].c_str());
797                 }
798                 else if (strcmp(opt_key, "source_offset_z") == 0) {
799                     m_volume->source.mesh_offset(2) = ::atof(m_value[1].c_str());
800                 }
801                 else if (strcmp(opt_key, "source_in_inches") == 0) {
802                     m_volume->source.is_converted_from_inches = m_value[1] == "1";
803                 }
804             }
805         } else if (m_path.size() == 3) {
806             if (m_path[1] == NODE_TYPE_MATERIAL) {
807                 if (m_material)
808                     m_material->attributes[m_value[0]] = m_value[1];
809             } else if (m_path[1] == NODE_TYPE_OBJECT) {
810                 if (m_object && m_value[0] == "name")
811                     m_object->name = std::move(m_value[1]);
812             }
813         } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME) {
814             if (m_volume && m_value[0] == "name")
815                 m_volume->name = std::move(m_value[1]);
816         }
817         else if (strncmp(m_value[0].c_str(), SLIC3RPE_AMF_VERSION, strlen(SLIC3RPE_AMF_VERSION)) == 0) {
818             m_version = (unsigned int)atoi(m_value[1].c_str());
819         }
820 
821         m_value[0].clear();
822         m_value[1].clear();
823         break;
824     default:
825         break;
826     }
827     m_path.pop_back();
828 }
829 
endDocument()830 void AMFParserContext::endDocument()
831 {
832     for (const auto &object : m_object_instances_map) {
833         if (object.second.idx == -1) {
834             printf("Undefined object %s referenced in constellation\n", object.first.c_str());
835             continue;
836         }
837         for (const Instance &instance : object.second.instances)
838             if (instance.anything_set()) {
839                 ModelInstance *mi = m_model.objects[object.second.idx]->add_instance();
840                 mi->set_offset(Vec3d(instance.deltax_set ? (double)instance.deltax : 0.0, instance.deltay_set ? (double)instance.deltay : 0.0, instance.deltaz_set ? (double)instance.deltaz : 0.0));
841                 mi->set_rotation(Vec3d(instance.rx_set ? (double)instance.rx : 0.0, instance.ry_set ? (double)instance.ry : 0.0, instance.rz_set ? (double)instance.rz : 0.0));
842                 mi->set_scaling_factor(Vec3d(instance.scalex_set ? (double)instance.scalex : 1.0, instance.scaley_set ? (double)instance.scaley : 1.0, instance.scalez_set ? (double)instance.scalez : 1.0));
843                 mi->set_mirror(Vec3d(instance.mirrorx_set ? (double)instance.mirrorx : 1.0, instance.mirrory_set ? (double)instance.mirrory : 1.0, instance.mirrorz_set ? (double)instance.mirrorz : 1.0));
844                 mi->printable = instance.printable;
845         }
846     }
847 }
848 
849 // Load an AMF file into a provided model.
load_amf_file(const char * path,DynamicPrintConfig * config,ConfigSubstitutionContext * config_substitutions,Model * model)850 bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitutionContext *config_substitutions, Model *model)
851 {
852     if ((path == nullptr) || (model == nullptr))
853         return false;
854 
855     XML_Parser parser = XML_ParserCreate(nullptr); // encoding
856     if (!parser) {
857         printf("Couldn't allocate memory for parser\n");
858         return false;
859     }
860 
861     FILE *pFile = boost::nowide::fopen(path, "rt");
862     if (pFile == nullptr) {
863         printf("Cannot open file %s\n", path);
864         return false;
865     }
866 
867     AMFParserContext ctx(parser, config, config_substitutions, model);
868     XML_SetUserData(parser, (void*)&ctx);
869     XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
870     XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
871 
872     char buff[8192];
873     bool result = false;
874     for (;;) {
875         int len = (int)fread(buff, 1, 8192, pFile);
876         if (ferror(pFile)) {
877             printf("AMF parser: Read error\n");
878             break;
879         }
880         int done = feof(pFile);
881         if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR || ctx.error()) {
882             printf("AMF parser: Parse error at line %d:\n%s\n",
883                   (int)XML_GetCurrentLineNumber(parser),
884                   ctx.error_message());
885             break;
886         }
887         if (done) {
888             result = true;
889             break;
890         }
891     }
892 
893     XML_ParserFree(parser);
894     ::fclose(pFile);
895 
896     if (result)
897         ctx.endDocument();
898 
899     for (ModelObject* o : model->objects)
900     {
901         for (ModelVolume* v : o->volumes)
902         {
903             if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART))
904                 v->source.input_file = path;
905         }
906     }
907 
908     return result;
909 }
910 
extract_model_from_archive(mz_zip_archive & archive,const mz_zip_archive_file_stat & stat,DynamicPrintConfig * config,ConfigSubstitutionContext * config_substitutions,Model * model,bool check_version)911 bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
912 {
913     if (stat.m_uncomp_size == 0)
914     {
915         printf("Found invalid size\n");
916         close_zip_reader(&archive);
917         return false;
918     }
919 
920     XML_Parser parser = XML_ParserCreate(nullptr); // encoding
921     if (!parser) {
922         printf("Couldn't allocate memory for parser\n");
923         close_zip_reader(&archive);
924         return false;
925     }
926 
927     AMFParserContext ctx(parser, config, config_substitutions, model);
928     XML_SetUserData(parser, (void*)&ctx);
929     XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement);
930     XML_SetCharacterDataHandler(parser, AMFParserContext::characters);
931 
932     struct CallbackData
933     {
934         XML_Parser& parser;
935         AMFParserContext& ctx;
936         const mz_zip_archive_file_stat& stat;
937 
938         CallbackData(XML_Parser& parser, AMFParserContext& ctx, const mz_zip_archive_file_stat& stat) : parser(parser), ctx(ctx), stat(stat) {}
939     };
940 
941     CallbackData data(parser, ctx, stat);
942 
943     mz_bool res = 0;
944 
945     try
946     {
947         res = mz_zip_reader_extract_file_to_callback(&archive, stat.m_filename, [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t {
948             CallbackData* data = (CallbackData*)pOpaque;
949             if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->ctx.error())
950             {
951                 char error_buf[1024];
952                 ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", data->ctx.error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser));
953                 throw Slic3r::FileIOError(error_buf);
954             }
955 
956             return n;
957             }, &data, 0);
958     }
959     catch (std::exception& e)
960     {
961         printf("%s\n", e.what());
962         close_zip_reader(&archive);
963         return false;
964     }
965 
966     if (res == 0)
967     {
968         printf("Error while extracting model data from zip archive");
969         close_zip_reader(&archive);
970         return false;
971     }
972 
973     ctx.endDocument();
974 
975     if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE))
976     {
977         // std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
978         // throw Slic3r::FileIOError(msg.c_str());
979         const std::string msg = (boost::format(_(L("The selected amf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str();
980         throw Slic3r::FileIOError(msg);
981     }
982 
983     return true;
984 }
985 
986 // Load an AMF archive into a provided model.
load_amf_archive(const char * path,DynamicPrintConfig * config,ConfigSubstitutionContext * config_substitutions,Model * model,bool check_version)987 bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
988 {
989     if ((path == nullptr) || (model == nullptr))
990         return false;
991 
992     mz_zip_archive archive;
993     mz_zip_zero_struct(&archive);
994 
995     if (!open_zip_reader(&archive, path))
996     {
997         printf("Unable to init zip reader\n");
998         return false;
999     }
1000 
1001     mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
1002 
1003     mz_zip_archive_file_stat stat;
1004     // we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
1005     for (mz_uint i = 0; i < num_entries; ++i)
1006     {
1007         if (mz_zip_reader_file_stat(&archive, i, &stat))
1008         {
1009             if (boost::iends_with(stat.m_filename, ".amf"))
1010             {
1011                 try
1012                 {
1013                     if (!extract_model_from_archive(archive, stat, config, config_substitutions, model, check_version))
1014                     {
1015                         close_zip_reader(&archive);
1016                         printf("Archive does not contain a valid model");
1017                         return false;
1018                     }
1019                 }
1020                 catch (const std::exception& e)
1021                 {
1022                     // ensure the zip archive is closed and rethrow the exception
1023                     close_zip_reader(&archive);
1024                     throw Slic3r::FileIOError(e.what());
1025                 }
1026 
1027                 break;
1028             }
1029         }
1030     }
1031 
1032 #if 0 // forward compatibility
1033     // we then loop again the entries to read other files stored in the archive
1034     for (mz_uint i = 0; i < num_entries; ++i)
1035     {
1036         if (mz_zip_reader_file_stat(&archive, i, &stat))
1037         {
1038             // add code to extract the file
1039         }
1040     }
1041 #endif // forward compatibility
1042 
1043     close_zip_reader(&archive);
1044 
1045     for (ModelObject *o : model->objects)
1046         for (ModelVolume *v : o->volumes)
1047             if (v->source.input_file.empty() && (v->type() == ModelVolumeType::MODEL_PART))
1048                 v->source.input_file = path;
1049 
1050     return true;
1051 }
1052 
1053 // Load an AMF file into a provided model.
1054 // If config is not a null pointer, updates it if the amf file/archive contains config data
load_amf(const char * path,DynamicPrintConfig * config,ConfigSubstitutionContext * config_substitutions,Model * model,bool check_version)1055 bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version)
1056 {
1057     if (boost::iends_with(path, ".amf.xml"))
1058         // backward compatibility with older slic3r output
1059         return load_amf_file(path, config, config_substitutions, model);
1060     else if (boost::iends_with(path, ".amf"))
1061     {
1062         boost::nowide::ifstream file(path, boost::nowide::ifstream::binary);
1063         if (!file.good())
1064             return false;
1065 
1066         std::string zip_mask(2, '\0');
1067         file.read(zip_mask.data(), 2);
1068         file.close();
1069 
1070         return (zip_mask == "PK") ? load_amf_archive(path, config, config_substitutions, model, check_version) : load_amf_file(path, config, config_substitutions, model);
1071     }
1072     else
1073         return false;
1074 }
1075 
store_amf(const char * path,Model * model,const DynamicPrintConfig * config,bool fullpath_sources)1076 bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources)
1077 {
1078     if ((path == nullptr) || (model == nullptr))
1079         return false;
1080 
1081     // forces ".zip.amf" extension
1082     std::string export_path = path;
1083     if (!boost::iends_with(export_path, ".zip.amf"))
1084         export_path = boost::filesystem::path(export_path).replace_extension(".zip.amf").string();
1085 
1086     mz_zip_archive archive;
1087     mz_zip_zero_struct(&archive);
1088 
1089     if (!open_zip_writer(&archive, export_path)) return false;
1090 
1091     std::stringstream stream;
1092     // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10
1093     // Conversion of a floating-point value to text and back is exact as long as at least max_digits10 were used (9 for float, 17 for double).
1094     // It is guaranteed to produce the same floating-point value, even though the intermediate text representation is not exact.
1095     // The default value of std::stream precision is 6 digits only!
1096     stream << std::setprecision(std::numeric_limits<float>::max_digits10);
1097     stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1098     stream << "<amf unit=\"millimeter\">\n";
1099     stream << "<metadata type=\"cad\">Slic3r " << SLIC3R_VERSION << "</metadata>\n";
1100     stream << "<metadata type=\"" << SLIC3RPE_AMF_VERSION << "\">" << VERSION_AMF << "</metadata>\n";
1101 
1102     if (config != nullptr)
1103     {
1104         std::string str_config = "\n";
1105         for (const std::string &key : config->keys())
1106             if (key != "compatible_printers")
1107                 str_config += "; " + key + " = " + config->opt_serialize(key) + "\n";
1108         stream << "<metadata type=\"" << SLIC3R_CONFIG_TYPE << "\">" << xml_escape(str_config) << "</metadata>\n";
1109     }
1110 
1111     for (const auto &material : model->materials) {
1112         if (material.first.empty())
1113             continue;
1114         // note that material-id must never be 0 since it's reserved by the AMF spec
1115         stream << "  <material id=\"" << material.first << "\">\n";
1116         for (const auto &attr : material.second->attributes)
1117             stream << "    <metadata type=\"" << attr.first << "\">" << attr.second << "</metadata>\n";
1118         for (const std::string &key : material.second->config.keys())
1119             stream << "    <metadata type=\"slic3r." << key << "\">" << material.second->config.opt_serialize(key) << "</metadata>\n";
1120         stream << "  </material>\n";
1121     }
1122     std::string instances;
1123     for (size_t object_id = 0; object_id < model->objects.size(); ++ object_id) {
1124         ModelObject *object = model->objects[object_id];
1125         stream << "  <object id=\"" << object_id << "\">\n";
1126         for (const std::string &key : object->config.keys())
1127             stream << "    <metadata type=\"slic3r." << key << "\">" << object->config.opt_serialize(key) << "</metadata>\n";
1128         if (!object->name.empty())
1129             stream << "    <metadata type=\"name\">" << xml_escape(object->name) << "</metadata>\n";
1130         const std::vector<double> &layer_height_profile = object->layer_height_profile.get();
1131         if (layer_height_profile.size() >= 4 && (layer_height_profile.size() % 2) == 0) {
1132             // Store the layer height profile as a single semicolon separated list.
1133             stream << "    <metadata type=\"slic3r.layer_height_profile\">";
1134             stream << layer_height_profile.front();
1135             for (size_t i = 1; i < layer_height_profile.size(); ++i)
1136                 stream << ";" << layer_height_profile[i];
1137             stream << "\n    </metadata>\n";
1138         }
1139 
1140         // Export layer height ranges including the layer range specific config overrides.
1141         const t_layer_config_ranges& config_ranges = object->layer_config_ranges;
1142         if (!config_ranges.empty())
1143         {
1144             // Store the layer config range as a single semicolon separated list.
1145             stream << "    <layer_config_ranges>\n";
1146             size_t layer_counter = 0;
1147             for (const auto &range : config_ranges) {
1148                 stream << "      <range id=\"" << layer_counter << "\">\n";
1149 
1150                 stream << "        <metadata type=\"slic3r.layer_height_range\">";
1151                 stream << range.first.first << ";" << range.first.second << "</metadata>\n";
1152 
1153                 for (const std::string& key : range.second.keys())
1154                     stream << "        <metadata type=\"slic3r." << key << "\">" << range.second.opt_serialize(key) << "</metadata>\n";
1155 
1156                 stream << "      </range>\n";
1157                 layer_counter++;
1158             }
1159 
1160             stream << "    </layer_config_ranges>\n";
1161         }
1162 
1163 
1164         const std::vector<sla::SupportPoint>& sla_support_points = object->sla_support_points;
1165         if (!sla_support_points.empty()) {
1166             // Store the SLA supports as a single semicolon separated list.
1167             stream << "    <metadata type=\"slic3r.sla_support_points\">";
1168             for (size_t i = 0; i < sla_support_points.size(); ++i) {
1169                 if (i != 0)
1170                     stream << ";";
1171                 stream << sla_support_points[i].pos(0) << ";" << sla_support_points[i].pos(1) << ";" << sla_support_points[i].pos(2) << ";" << sla_support_points[i].head_front_radius << ";" << sla_support_points[i].is_new_island;
1172             }
1173             stream << "\n    </metadata>\n";
1174         }
1175 
1176         stream << "    <mesh>\n";
1177         stream << "      <vertices>\n";
1178         std::vector<int> vertices_offsets;
1179         int              num_vertices = 0;
1180         for (ModelVolume *volume : object->volumes) {
1181             vertices_offsets.push_back(num_vertices);
1182             if (! volume->mesh().repaired)
1183                 throw Slic3r::FileIOError("store_amf() requires repair()");
1184 			if (! volume->mesh().has_shared_vertices())
1185 				throw Slic3r::FileIOError("store_amf() requires shared vertices");
1186             const indexed_triangle_set &its = volume->mesh().its;
1187             const Transform3d& matrix = volume->get_matrix();
1188             for (size_t i = 0; i < its.vertices.size(); ++i) {
1189                 stream << "         <vertex>\n";
1190                 stream << "           <coordinates>\n";
1191                 Vec3f v = (matrix * its.vertices[i].cast<double>()).cast<float>();
1192                 stream << "             <x>" << v(0) << "</x>\n";
1193                 stream << "             <y>" << v(1) << "</y>\n";
1194                 stream << "             <z>" << v(2) << "</z>\n";
1195                 stream << "           </coordinates>\n";
1196                 stream << "         </vertex>\n";
1197             }
1198             num_vertices += (int)its.vertices.size();
1199         }
1200         stream << "      </vertices>\n";
1201         for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) {
1202             ModelVolume *volume = object->volumes[i_volume];
1203             int vertices_offset = vertices_offsets[i_volume];
1204             if (volume->material_id().empty())
1205                 stream << "      <volume>\n";
1206             else
1207                 stream << "      <volume materialid=\"" << volume->material_id() << "\">\n";
1208             for (const std::string &key : volume->config.keys())
1209                 stream << "        <metadata type=\"slic3r." << key << "\">" << volume->config.opt_serialize(key) << "</metadata>\n";
1210             if (!volume->name.empty())
1211                 stream << "        <metadata type=\"name\">" << xml_escape(volume->name) << "</metadata>\n";
1212             if (volume->is_modifier())
1213                 stream << "        <metadata type=\"slic3r.modifier\">1</metadata>\n";
1214             stream << "        <metadata type=\"slic3r.volume_type\">" << ModelVolume::type_to_string(volume->type()) << "</metadata>\n";
1215             stream << "        <metadata type=\"slic3r.matrix\">";
1216             const Transform3d& matrix = volume->get_matrix() * volume->source.transform.get_matrix();
1217             stream << std::setprecision(std::numeric_limits<double>::max_digits10);
1218             for (int r = 0; r < 4; ++r)
1219             {
1220                 for (int c = 0; c < 4; ++c)
1221                 {
1222                     stream << matrix(r, c);
1223                     if ((r != 3) || (c != 3))
1224                         stream << " ";
1225                 }
1226             }
1227             stream << "</metadata>\n";
1228             if (!volume->source.input_file.empty())
1229             {
1230                 std::string input_file = xml_escape(fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string());
1231                 stream << "        <metadata type=\"slic3r.source_file\">" << input_file << "</metadata>\n";
1232                 stream << "        <metadata type=\"slic3r.source_object_id\">" << volume->source.object_idx << "</metadata>\n";
1233                 stream << "        <metadata type=\"slic3r.source_volume_id\">" << volume->source.volume_idx << "</metadata>\n";
1234                 stream << "        <metadata type=\"slic3r.source_offset_x\">" << volume->source.mesh_offset(0) << "</metadata>\n";
1235                 stream << "        <metadata type=\"slic3r.source_offset_y\">" << volume->source.mesh_offset(1) << "</metadata>\n";
1236                 stream << "        <metadata type=\"slic3r.source_offset_z\">" << volume->source.mesh_offset(2) << "</metadata>\n";
1237             }
1238             if (volume->source.is_converted_from_inches)
1239                 stream << "        <metadata type=\"slic3r.source_in_inches\">1</metadata>\n";
1240 			stream << std::setprecision(std::numeric_limits<float>::max_digits10);
1241             const indexed_triangle_set &its = volume->mesh().its;
1242             for (size_t i = 0; i < its.indices.size(); ++i) {
1243                 stream << "        <triangle>\n";
1244                 for (int j = 0; j < 3; ++j)
1245                 stream << "          <v" << j + 1 << ">" << its.indices[i][j] + vertices_offset << "</v" << j + 1 << ">\n";
1246                 stream << "        </triangle>\n";
1247             }
1248             stream << "      </volume>\n";
1249         }
1250         stream << "    </mesh>\n";
1251         stream << "  </object>\n";
1252         if (!object->instances.empty()) {
1253             for (ModelInstance *instance : object->instances) {
1254                 char buf[512];
1255                 sprintf(buf,
1256                     "    <instance objectid=\"%zu\">\n"
1257                     "      <deltax>%lf</deltax>\n"
1258                     "      <deltay>%lf</deltay>\n"
1259                     "      <deltaz>%lf</deltaz>\n"
1260                     "      <rx>%lf</rx>\n"
1261                     "      <ry>%lf</ry>\n"
1262                     "      <rz>%lf</rz>\n"
1263                     "      <scalex>%lf</scalex>\n"
1264                     "      <scaley>%lf</scaley>\n"
1265                     "      <scalez>%lf</scalez>\n"
1266                     "      <mirrorx>%lf</mirrorx>\n"
1267                     "      <mirrory>%lf</mirrory>\n"
1268                     "      <mirrorz>%lf</mirrorz>\n"
1269                     "      <printable>%d</printable>\n"
1270                     "    </instance>\n",
1271                     object_id,
1272                     instance->get_offset(X),
1273                     instance->get_offset(Y),
1274                     instance->get_offset(Z),
1275                     instance->get_rotation(X),
1276                     instance->get_rotation(Y),
1277                     instance->get_rotation(Z),
1278                     instance->get_scaling_factor(X),
1279                     instance->get_scaling_factor(Y),
1280                     instance->get_scaling_factor(Z),
1281                     instance->get_mirror(X),
1282                     instance->get_mirror(Y),
1283                     instance->get_mirror(Z),
1284                     instance->printable);
1285 
1286                 //FIXME missing instance->scaling_factor
1287                 instances.append(buf);
1288             }
1289         }
1290     }
1291     if (! instances.empty()) {
1292         stream << "  <constellation id=\"1\">\n";
1293         stream << instances;
1294         stream << "  </constellation>\n";
1295     }
1296 
1297     if (!model->custom_gcode_per_print_z.gcodes.empty())
1298     {
1299         std::string out = "";
1300         pt::ptree tree;
1301 
1302         pt::ptree& main_tree = tree.add("custom_gcodes_per_height", "");
1303 
1304         for (const CustomGCode::Item& code : model->custom_gcode_per_print_z.gcodes)
1305         {
1306             pt::ptree& code_tree = main_tree.add("code", "");
1307             // store custom_gcode_per_print_z gcodes information
1308             code_tree.put("<xmlattr>.print_z"   , code.print_z  );
1309             code_tree.put("<xmlattr>.type"      , static_cast<int>(code.type));
1310             code_tree.put("<xmlattr>.extruder"  , code.extruder );
1311             code_tree.put("<xmlattr>.color"     , code.color    );
1312             code_tree.put("<xmlattr>.extra"     , code.extra    );
1313 
1314             // add gcode field data for the old version of the PrusaSlicer
1315             std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode")    :
1316                                 code.type == CustomGCode::PausePrint  ? config->opt_string("pause_print_gcode")     :
1317                                 code.type == CustomGCode::Template    ? config->opt_string("template_custom_gcode") :
1318                                 code.type == CustomGCode::ToolChange  ? "tool_change"   : code.extra;
1319             code_tree.put("<xmlattr>.gcode"     , gcode   );
1320         }
1321 
1322         pt::ptree& mode_tree = main_tree.add("mode", "");
1323         // store mode of a custom_gcode_per_print_z
1324         mode_tree.put("<xmlattr>.value",
1325                       model->custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode :
1326                       model->custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle  ?
1327                       CustomGCode::MultiAsSingleMode  : CustomGCode::MultiExtruderMode);
1328 
1329         if (!tree.empty())
1330         {
1331             std::ostringstream oss;
1332             pt::write_xml(oss, tree);
1333             out = oss.str();
1334 
1335             size_t del_header_pos = out.find("<custom_gcodes_per_height");
1336             if (del_header_pos != std::string::npos)
1337                 out.erase(out.begin(), out.begin() + del_header_pos);
1338 
1339             // Post processing("beautification") of the output string
1340             boost::replace_all(out, "><code", ">\n  <code");
1341             boost::replace_all(out, "><mode", ">\n  <mode");
1342             boost::replace_all(out, "><", ">\n<");
1343 
1344             stream << out << "\n";
1345         }
1346     }
1347 
1348     stream << "</amf>\n";
1349 
1350     std::string internal_amf_filename = boost::ireplace_last_copy(boost::filesystem::path(export_path).filename().string(), ".zip.amf", ".amf");
1351     std::string out = stream.str();
1352 
1353     if (!mz_zip_writer_add_mem(&archive, internal_amf_filename.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
1354     {
1355         close_zip_writer(&archive);
1356         boost::filesystem::remove(export_path);
1357         return false;
1358     }
1359 
1360     if (!mz_zip_writer_finalize_archive(&archive))
1361     {
1362         close_zip_writer(&archive);
1363         boost::filesystem::remove(export_path);
1364         return false;
1365     }
1366 
1367     close_zip_writer(&archive);
1368 
1369     return true;
1370 }
1371 
1372 }; // namespace Slic3r
1373