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