2019-02-27 10:31:53 +01:00
#include <limits>
2017-02-26 21:46:33 +01:00
#include <string.h>
#include <map>
#include <string>
2019-05-21 14:12:37 -05:00
#include <expat.h>
2017-02-26 21:46:33 +01:00
2017-08-03 17:31:31 +02:00
#include <boost/nowide/cstdio.hpp>
2017-02-26 21:46:33 +01:00
#include "../libslic3r.h"
#include "../Model.hpp"
2018-02-14 14:30:03 +01:00
#include "../GCode.hpp"
2018-09-25 11:53:05 +02:00
#include "../PrintConfig.hpp"
2018-07-23 14:39:50 +02:00
#include "../Utils.hpp"
2019-08-26 09:35:04 +02:00
#include "../I18N.hpp"
2019-09-19 12:39:59 +02:00
#include "../Geometry.hpp"
2020-01-23 12:49:39 +01:00
#include "../CustomGCode.hpp"
2019-08-26 09:35:04 +02:00
2017-02-26 21:46:33 +01:00
#include "AMF.hpp"
2019-11-11 16:01:34 +01:00
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
namespace pt = boost :: property_tree ;
2018-02-14 14:30:03 +01:00
#include <boost/filesystem/operations.hpp>
#include <boost/algorithm/string.hpp>
2018-05-24 09:57:12 +02:00
#include <boost/nowide/fstream.hpp>
2019-06-03 15:27:46 +02:00
#include "miniz_extension.hpp"
2018-02-14 14:30:03 +01:00
2017-02-26 21:46:33 +01:00
#if 0
// Enable debugging and assert in this file.
#define DEBUG
#define _DEBUG
#undef NDEBUG
#endif
#include <assert.h>
2018-04-30 12:03:06 +02:00
// VERSION NUMBERS
// 0 : .amf, .amf.xml and .zip.amf files saved by older slic3r. No version definition in them.
// 1 : Introduction of amf versioning. No other change in data saved into amf files.
2018-09-20 15:00:40 +02:00
// 2 : Added z component of offset
// Added x and y components of rotation
2018-09-24 15:54:09 +02:00
// Added x, y and z components of scale
2018-10-22 11:45:03 +02:00
// Added x, y and z components of mirror
2019-12-19 10:22:46 +01:00
// 3 : Added volumes' matrices and source data, meshes transformed back to their coordinate system on loading.
2019-12-19 09:41:30 +01:00
// WARNING !! -> the version number has been rolled back to 2
// the next change should use 4
const unsigned int VERSION_AMF = 2 ;
2019-12-19 10:22:46 +01:00
const unsigned int VERSION_AMF_COMPATIBLE = 3 ;
2018-04-30 12:03:06 +02:00
const char * SLIC3RPE_AMF_VERSION = "slic3rpe_amf_version" ;
2018-02-14 14:30:03 +01:00
const char * SLIC3R_CONFIG_TYPE = "slic3rpe_config" ;
2017-02-26 21:46:33 +01:00
namespace Slic3r
{
2019-08-26 09:35:04 +02:00
//! macro used to mark string used at localization,
//! return same string
#define L(s) (s)
#define _(s) Slic3r::I18N::translate(s)
2017-02-26 21:46:33 +01:00
struct AMFParserContext
{
2019-08-23 13:12:31 +02:00
AMFParserContext ( XML_Parser parser , DynamicPrintConfig * config , Model * model ) :
2018-04-30 12:03:06 +02:00
m_version ( 0 ),
2017-02-26 21:46:33 +01:00
m_parser ( parser ),
m_model ( * model ),
m_object ( nullptr ),
m_volume ( nullptr ),
m_material ( nullptr ),
2018-02-14 14:30:03 +01:00
m_instance ( nullptr ),
2018-09-25 11:53:05 +02:00
m_config ( config )
2017-02-26 21:46:33 +01:00
{
m_path . reserve ( 12 );
}
void stop ()
{
XML_StopParser ( m_parser , 0 );
}
void startElement ( const char * name , const char ** atts );
void endElement ( const char * name );
void endDocument ();
void characters ( const XML_Char * s , int len );
static void XMLCALL startElement ( void * userData , const char * name , const char ** atts )
{
AMFParserContext * ctx = ( AMFParserContext * ) userData ;
ctx -> startElement ( name , atts );
}
static void XMLCALL endElement ( void * userData , const char * name )
{
AMFParserContext * ctx = ( AMFParserContext * ) userData ;
ctx -> endElement ( name );
}
/* s is not 0 terminated. */
static void XMLCALL characters ( void * userData , const XML_Char * s , int len )
{
AMFParserContext * ctx = ( AMFParserContext * ) userData ;
ctx -> characters ( s , len );
}
static const char * get_attribute ( const char ** atts , const char * id ) {
if ( atts == nullptr )
return nullptr ;
while ( * atts != nullptr ) {
if ( strcmp ( * ( atts ++ ), id ) == 0 )
return * atts ;
++ atts ;
}
return nullptr ;
}
enum AMFNodeType {
NODE_TYPE_INVALID = 0 ,
NODE_TYPE_UNKNOWN ,
NODE_TYPE_AMF , // amf
// amf/metadata
NODE_TYPE_MATERIAL , // amf/material
// amf/material/metadata
NODE_TYPE_OBJECT , // amf/object
// amf/object/metadata
2019-06-11 09:50:58 +02:00
NODE_TYPE_LAYER_CONFIG , // amf/object/layer_config_ranges
NODE_TYPE_RANGE , // amf/object/layer_config_ranges/range
// amf/object/layer_config_ranges/range/metadata
2017-02-26 21:46:33 +01:00
NODE_TYPE_MESH , // amf/object/mesh
NODE_TYPE_VERTICES , // amf/object/mesh/vertices
NODE_TYPE_VERTEX , // amf/object/mesh/vertices/vertex
NODE_TYPE_COORDINATES , // amf/object/mesh/vertices/vertex/coordinates
NODE_TYPE_COORDINATE_X , // amf/object/mesh/vertices/vertex/coordinates/x
NODE_TYPE_COORDINATE_Y , // amf/object/mesh/vertices/vertex/coordinates/y
NODE_TYPE_COORDINATE_Z , // amf/object/mesh/vertices/vertex/coordinates/z
NODE_TYPE_VOLUME , // amf/object/mesh/volume
// amf/object/mesh/volume/metadata
NODE_TYPE_TRIANGLE , // amf/object/mesh/volume/triangle
NODE_TYPE_VERTEX1 , // amf/object/mesh/volume/triangle/v1
NODE_TYPE_VERTEX2 , // amf/object/mesh/volume/triangle/v2
NODE_TYPE_VERTEX3 , // amf/object/mesh/volume/triangle/v3
NODE_TYPE_CONSTELLATION , // amf/constellation
NODE_TYPE_INSTANCE , // amf/constellation/instance
NODE_TYPE_DELTAX , // amf/constellation/instance/deltax
NODE_TYPE_DELTAY , // amf/constellation/instance/deltay
2018-09-17 15:12:13 +02:00
NODE_TYPE_DELTAZ , // amf/constellation/instance/deltaz
2018-09-20 15:00:40 +02:00
NODE_TYPE_RX , // amf/constellation/instance/rx
NODE_TYPE_RY , // amf/constellation/instance/ry
2017-02-26 21:46:33 +01:00
NODE_TYPE_RZ , // amf/constellation/instance/rz
2017-03-06 17:35:38 +01:00
NODE_TYPE_SCALE , // amf/constellation/instance/scale
2018-10-22 11:45:03 +02:00
NODE_TYPE_SCALEX , // amf/constellation/instance/scalex
NODE_TYPE_SCALEY , // amf/constellation/instance/scaley
NODE_TYPE_SCALEZ , // amf/constellation/instance/scalez
NODE_TYPE_MIRRORX , // amf/constellation/instance/mirrorx
NODE_TYPE_MIRRORY , // amf/constellation/instance/mirrory
NODE_TYPE_MIRRORZ , // amf/constellation/instance/mirrorz
2019-08-06 14:54:38 +02:00
NODE_TYPE_PRINTABLE , // amf/constellation/instance/mirrorz
2019-11-11 16:01:34 +01:00
NODE_TYPE_CUSTOM_GCODE , // amf/custom_code_per_height
NODE_TYPE_GCODE_PER_HEIGHT , // amf/custom_code_per_height/code
2020-01-23 12:49:39 +01:00
NODE_TYPE_CUSTOM_GCODE_MODE , // amf/custom_code_per_height/mode
2017-02-26 21:46:33 +01:00
NODE_TYPE_METADATA , // anywhere under amf/*/metadata
};
struct Instance {
2018-10-22 11:45:03 +02:00
Instance ()
: deltax_set ( false ), deltay_set ( false ), deltaz_set ( false )
, rx_set ( false ), ry_set ( false ), rz_set ( false )
, scalex_set ( false ), scaley_set ( false ), scalez_set ( false )
2019-08-06 14:54:38 +02:00
, mirrorx_set ( false ), mirrory_set ( false ), mirrorz_set ( false )
, printable ( true ) {}
2017-02-26 21:46:33 +01:00
// Shift in the X axis.
float deltax ;
bool deltax_set ;
// Shift in the Y axis.
float deltay ;
bool deltay_set ;
2018-09-17 15:12:13 +02:00
// Shift in the Z axis.
float deltaz ;
bool deltaz_set ;
2018-09-20 15:00:40 +02:00
// Rotation around the X axis.
float rx ;
bool rx_set ;
// Rotation around the Y axis.
float ry ;
bool ry_set ;
2017-02-26 21:46:33 +01:00
// Rotation around the Z axis.
float rz ;
bool rz_set ;
2018-09-24 15:54:09 +02:00
// Scaling factors
float scalex ;
bool scalex_set ;
float scaley ;
bool scaley_set ;
float scalez ;
bool scalez_set ;
2018-10-22 11:45:03 +02:00
// Mirroring factors
float mirrorx ;
bool mirrorx_set ;
float mirrory ;
bool mirrory_set ;
float mirrorz ;
bool mirrorz_set ;
2019-08-06 14:54:38 +02:00
// printable property
bool printable ;
2019-03-25 12:07:43 +01:00
bool anything_set () const { return deltax_set || deltay_set || deltaz_set ||
rx_set || ry_set || rz_set ||
scalex_set || scaley_set || scalez_set ||
mirrorx_set || mirrory_set || mirrorz_set ; }
2017-02-26 21:46:33 +01:00
};
struct Object {
Object () : idx ( - 1 ) {}
int idx ;
std :: vector < Instance > instances ;
};
2018-04-30 12:03:06 +02:00
// Version of the amf file
2019-06-20 16:15:09 +02:00
unsigned int m_version ;
2017-02-26 21:46:33 +01:00
// Current Expat XML parser instance.
XML_Parser m_parser ;
// Model to receive objects extracted from an AMF file.
Model & m_model ;
// Current parsing path in the XML file.
std :: vector < AMFNodeType > m_path ;
// Current object allocated for an amf/object XML subtree.
ModelObject * m_object ;
// Map from obect name to object idx & instances.
std :: map < std :: string , Object > m_object_instances_map ;
// Vertices parsed for the current m_object.
std :: vector < float > m_object_vertices ;
// Current volume allocated for an amf/object/mesh/volume subtree.
ModelVolume * m_volume ;
// Faces collected for the current m_volume.
std :: vector < int > m_volume_facets ;
2019-12-19 10:22:46 +01:00
// Transformation matrix of a volume mesh from its coordinate system to Object's coordinate system.
Transform3d m_volume_transform ;
2017-02-26 21:46:33 +01:00
// Current material allocated for an amf/metadata subtree.
ModelMaterial * m_material ;
// Current instance allocated for an amf/constellation/instance subtree.
Instance * m_instance ;
// Generic string buffer for vertices, face indices, metadata etc.
2019-11-11 16:01:34 +01:00
std :: string m_value [ 4 ];
2018-09-25 11:53:05 +02:00
// Pointer to config to update if config data are stored inside the amf file
DynamicPrintConfig * m_config ;
2017-05-03 18:28:22 +02:00
private :
AMFParserContext & operator = ( AMFParserContext & );
2017-02-26 21:46:33 +01:00
};
void AMFParserContext :: startElement ( const char * name , const char ** atts )
{
AMFNodeType node_type_new = NODE_TYPE_UNKNOWN ;
switch ( m_path . size ()) {
case 0 :
// An AMF file must start with an <amf> tag.
node_type_new = NODE_TYPE_AMF ;
if ( strcmp ( name , "amf" ) != 0 )
this -> stop ();
break ;
case 1 :
if ( strcmp ( name , "metadata" ) == 0 ) {
const char * type = get_attribute ( atts , "type" );
if ( type != nullptr ) {
m_value [ 0 ] = type ;
node_type_new = NODE_TYPE_METADATA ;
}
} else if ( strcmp ( name , "material" ) == 0 ) {
const char * material_id = get_attribute ( atts , "id" );
m_material = m_model . add_material (( material_id == nullptr ) ? "_" : material_id );
node_type_new = NODE_TYPE_MATERIAL ;
} else if ( strcmp ( name , "object" ) == 0 ) {
const char * object_id = get_attribute ( atts , "id" );
if ( object_id == nullptr )
this -> stop ();
else {
assert ( m_object_vertices . empty ());
m_object = m_model . add_object ();
m_object_instances_map [ object_id ]. idx = int ( m_model . objects . size ()) - 1 ;
node_type_new = NODE_TYPE_OBJECT ;
}
} else if ( strcmp ( name , "constellation" ) == 0 ) {
node_type_new = NODE_TYPE_CONSTELLATION ;
2019-11-11 16:01:34 +01:00
} else if ( strcmp ( name , "custom_gcodes_per_height" ) == 0 ) {
node_type_new = NODE_TYPE_CUSTOM_GCODE ;
2017-02-26 21:46:33 +01:00
}
break ;
case 2 :
if ( strcmp ( name , "metadata" ) == 0 ) {
if ( m_path [ 1 ] == NODE_TYPE_MATERIAL || m_path [ 1 ] == NODE_TYPE_OBJECT ) {
m_value [ 0 ] = get_attribute ( atts , "type" );
node_type_new = NODE_TYPE_METADATA ;
}
2019-06-11 09:50:58 +02:00
} else if ( strcmp ( name , "layer_config_ranges" ) == 0 && m_path [ 1 ] == NODE_TYPE_OBJECT )
node_type_new = NODE_TYPE_LAYER_CONFIG ;
else if ( strcmp ( name , "mesh" ) == 0 ) {
2017-02-26 21:46:33 +01:00
if ( m_path [ 1 ] == NODE_TYPE_OBJECT )
node_type_new = NODE_TYPE_MESH ;
} else if ( strcmp ( name , "instance" ) == 0 ) {
if ( m_path [ 1 ] == NODE_TYPE_CONSTELLATION ) {
const char * object_id = get_attribute ( atts , "objectid" );
if ( object_id == nullptr )
this -> stop ();
else {
m_object_instances_map [ object_id ]. instances . push_back ( AMFParserContext :: Instance ());
m_instance = & m_object_instances_map [ object_id ]. instances . back ();
node_type_new = NODE_TYPE_INSTANCE ;
}
}
else
this -> stop ();
2019-11-11 16:01:34 +01:00
}
2020-01-23 12:49:39 +01:00
else if ( m_path [ 1 ] == NODE_TYPE_CUSTOM_GCODE ) {
if ( strcmp ( name , "code" ) == 0 ) {
node_type_new = NODE_TYPE_GCODE_PER_HEIGHT ;
m_value [ 0 ] = get_attribute ( atts , "print_z" );
m_value [ 1 ] = get_attribute ( atts , "gcode" );
m_value [ 2 ] = get_attribute ( atts , "extruder" );
m_value [ 3 ] = get_attribute ( atts , "color" );
}
else if ( strcmp ( name , "mode" ) == 0 ) {
node_type_new = NODE_TYPE_CUSTOM_GCODE_MODE ;
m_value [ 0 ] = get_attribute ( atts , "value" );
}
2017-02-26 21:46:33 +01:00
}
break ;
case 3 :
if ( m_path [ 2 ] == NODE_TYPE_MESH ) {
assert ( m_object );
if ( strcmp ( name , "vertices" ) == 0 )
node_type_new = NODE_TYPE_VERTICES ;
else if ( strcmp ( name , "volume" ) == 0 ) {
assert ( ! m_volume );
2019-01-18 12:52:09 +01:00
m_volume = m_object -> add_volume ( TriangleMesh ());
2019-12-19 10:22:46 +01:00
m_volume_transform = Transform3d :: Identity ();
2019-01-18 12:52:09 +01:00
node_type_new = NODE_TYPE_VOLUME ;
2017-02-26 21:46:33 +01:00
}
} else if ( m_path [ 2 ] == NODE_TYPE_INSTANCE ) {
assert ( m_instance );
if ( strcmp ( name , "deltax" ) == 0 )
node_type_new = NODE_TYPE_DELTAX ;
else if ( strcmp ( name , "deltay" ) == 0 )
node_type_new = NODE_TYPE_DELTAY ;
2018-09-17 15:12:13 +02:00
else if ( strcmp ( name , "deltaz" ) == 0 )
node_type_new = NODE_TYPE_DELTAZ ;
2018-09-20 15:00:40 +02:00
else if ( strcmp ( name , "rx" ) == 0 )
node_type_new = NODE_TYPE_RX ;
else if ( strcmp ( name , "ry" ) == 0 )
node_type_new = NODE_TYPE_RY ;
2017-02-26 21:46:33 +01:00
else if ( strcmp ( name , "rz" ) == 0 )
node_type_new = NODE_TYPE_RZ ;
2018-09-24 15:54:09 +02:00
else if ( strcmp ( name , "scalex" ) == 0 )
node_type_new = NODE_TYPE_SCALEX ;
else if ( strcmp ( name , "scaley" ) == 0 )
node_type_new = NODE_TYPE_SCALEY ;
else if ( strcmp ( name , "scalez" ) == 0 )
node_type_new = NODE_TYPE_SCALEZ ;
2017-03-06 17:35:38 +01:00
else if ( strcmp ( name , "scale" ) == 0 )
node_type_new = NODE_TYPE_SCALE ;
2018-10-22 11:45:03 +02:00
else if ( strcmp ( name , "mirrorx" ) == 0 )
node_type_new = NODE_TYPE_MIRRORX ;
else if ( strcmp ( name , "mirrory" ) == 0 )
node_type_new = NODE_TYPE_MIRRORY ;
else if ( strcmp ( name , "mirrorz" ) == 0 )
node_type_new = NODE_TYPE_MIRRORZ ;
2019-08-06 14:54:38 +02:00
else if ( strcmp ( name , "printable" ) == 0 )
node_type_new = NODE_TYPE_PRINTABLE ;
2017-02-26 21:46:33 +01:00
}
2019-06-11 09:50:58 +02:00
else if ( m_path [ 2 ] == NODE_TYPE_LAYER_CONFIG && strcmp ( name , "range" ) == 0 ) {
assert ( m_object );
node_type_new = NODE_TYPE_RANGE ;
}
2017-02-26 21:46:33 +01:00
break ;
case 4 :
if ( m_path [ 3 ] == NODE_TYPE_VERTICES ) {
if ( strcmp ( name , "vertex" ) == 0 )
node_type_new = NODE_TYPE_VERTEX ;
} else if ( m_path [ 3 ] == NODE_TYPE_VOLUME ) {
if ( strcmp ( name , "metadata" ) == 0 ) {
const char * type = get_attribute ( atts , "type" );
if ( type == nullptr )
this -> stop ();
else {
m_value [ 0 ] = type ;
node_type_new = NODE_TYPE_METADATA ;
}
} else if ( strcmp ( name , "triangle" ) == 0 )
node_type_new = NODE_TYPE_TRIANGLE ;
}
2019-06-11 09:50:58 +02:00
else if ( m_path [ 3 ] == NODE_TYPE_RANGE && strcmp ( name , "metadata" ) == 0 ) {
m_value [ 0 ] = get_attribute ( atts , "type" );
node_type_new = NODE_TYPE_METADATA ;
}
2017-02-26 21:46:33 +01:00
break ;
case 5 :
if ( strcmp ( name , "coordinates" ) == 0 ) {
if ( m_path [ 4 ] == NODE_TYPE_VERTEX ) {
node_type_new = NODE_TYPE_COORDINATES ;
} else
this -> stop ();
} else if ( name [ 0 ] == 'v' && name [ 1 ] >= '1' && name [ 1 ] <= '3' && name [ 2 ] == 0 ) {
if ( m_path [ 4 ] == NODE_TYPE_TRIANGLE ) {
node_type_new = AMFNodeType ( NODE_TYPE_VERTEX1 + name [ 1 ] - '1' );
} else
this -> stop ();
}
break ;
case 6 :
if (( name [ 0 ] == 'x' || name [ 0 ] == 'y' || name [ 0 ] == 'z' ) && name [ 1 ] == 0 ) {
if ( m_path [ 5 ] == NODE_TYPE_COORDINATES )
node_type_new = AMFNodeType ( NODE_TYPE_COORDINATE_X + name [ 0 ] - 'x' );
else
this -> stop ();
}
break ;
default :
break ;
}
m_path . push_back ( node_type_new );
}
void AMFParserContext :: characters ( const XML_Char * s , int len )
{
if ( m_path . back () == NODE_TYPE_METADATA ) {
m_value [ 1 ]. append ( s , len );
}
else
{
switch ( m_path . size ()) {
case 4 :
2018-10-22 11:45:03 +02:00
if ( m_path . back () == NODE_TYPE_DELTAX ||
m_path . back () == NODE_TYPE_DELTAY ||
m_path . back () == NODE_TYPE_DELTAZ ||
2018-09-20 15:00:40 +02:00
m_path . back () == NODE_TYPE_RX ||
m_path . back () == NODE_TYPE_RY ||
m_path . back () == NODE_TYPE_RZ ||
2018-09-24 15:54:09 +02:00
m_path . back () == NODE_TYPE_SCALEX ||
m_path . back () == NODE_TYPE_SCALEY ||
m_path . back () == NODE_TYPE_SCALEZ ||
2018-10-22 11:45:03 +02:00
m_path . back () == NODE_TYPE_SCALE ||
m_path . back () == NODE_TYPE_MIRRORX ||
m_path . back () == NODE_TYPE_MIRRORY ||
2019-08-06 14:54:38 +02:00
m_path . back () == NODE_TYPE_MIRRORZ ||
m_path . back () == NODE_TYPE_PRINTABLE )
2017-02-26 21:46:33 +01:00
m_value [ 0 ]. append ( s , len );
break ;
case 6 :
switch ( m_path . back ()) {
case NODE_TYPE_VERTEX1 : m_value [ 0 ]. append ( s , len ); break ;
case NODE_TYPE_VERTEX2 : m_value [ 1 ]. append ( s , len ); break ;
case NODE_TYPE_VERTEX3 : m_value [ 2 ]. append ( s , len ); break ;
default : break ;
}
case 7 :
switch ( m_path . back ()) {
case NODE_TYPE_COORDINATE_X : m_value [ 0 ]. append ( s , len ); break ;
case NODE_TYPE_COORDINATE_Y : m_value [ 1 ]. append ( s , len ); break ;
case NODE_TYPE_COORDINATE_Z : m_value [ 2 ]. append ( s , len ); break ;
default : break ;
}
default :
break ;
}
}
}
2017-05-03 18:28:22 +02:00
void AMFParserContext :: endElement ( const char * /* name */ )
2017-02-26 21:46:33 +01:00
{
switch ( m_path . back ()) {
// Constellation transformation:
case NODE_TYPE_DELTAX :
assert ( m_instance );
m_instance -> deltax = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> deltax_set = true ;
m_value [ 0 ]. clear ();
break ;
case NODE_TYPE_DELTAY :
assert ( m_instance );
m_instance -> deltay = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> deltay_set = true ;
m_value [ 0 ]. clear ();
break ;
2018-09-17 15:12:13 +02:00
case NODE_TYPE_DELTAZ :
assert ( m_instance );
m_instance -> deltaz = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> deltaz_set = true ;
m_value [ 0 ]. clear ();
break ;
2018-09-20 15:00:40 +02:00
case NODE_TYPE_RX :
assert ( m_instance );
m_instance -> rx = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> rx_set = true ;
m_value [ 0 ]. clear ();
break ;
case NODE_TYPE_RY :
assert ( m_instance );
m_instance -> ry = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> ry_set = true ;
m_value [ 0 ]. clear ();
break ;
2017-02-26 21:46:33 +01:00
case NODE_TYPE_RZ :
assert ( m_instance );
m_instance -> rz = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> rz_set = true ;
m_value [ 0 ]. clear ();
break ;
2017-03-06 17:35:38 +01:00
case NODE_TYPE_SCALE :
assert ( m_instance );
2018-09-24 15:54:09 +02:00
m_instance -> scalex = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> scalex_set = true ;
m_instance -> scaley = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> scaley_set = true ;
m_instance -> scalez = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> scalez_set = true ;
2017-03-06 17:35:38 +01:00
m_value [ 0 ]. clear ();
break ;
2018-09-24 15:54:09 +02:00
case NODE_TYPE_SCALEX :
assert ( m_instance );
m_instance -> scalex = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> scalex_set = true ;
m_value [ 0 ]. clear ();
break ;
case NODE_TYPE_SCALEY :
assert ( m_instance );
m_instance -> scaley = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> scaley_set = true ;
m_value [ 0 ]. clear ();
break ;
case NODE_TYPE_SCALEZ :
assert ( m_instance );
m_instance -> scalez = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> scalez_set = true ;
m_value [ 0 ]. clear ();
break ;
2018-10-22 11:45:03 +02:00
case NODE_TYPE_MIRRORX :
assert ( m_instance );
m_instance -> mirrorx = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> mirrorx_set = true ;
m_value [ 0 ]. clear ();
break ;
case NODE_TYPE_MIRRORY :
assert ( m_instance );
m_instance -> mirrory = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> mirrory_set = true ;
m_value [ 0 ]. clear ();
break ;
case NODE_TYPE_MIRRORZ :
assert ( m_instance );
m_instance -> mirrorz = float ( atof ( m_value [ 0 ]. c_str ()));
m_instance -> mirrorz_set = true ;
m_value [ 0 ]. clear ();
break ;
2019-08-06 14:54:38 +02:00
case NODE_TYPE_PRINTABLE :
assert ( m_instance );
m_instance -> printable = bool ( atoi ( m_value [ 0 ]. c_str ()));
m_value [ 0 ]. clear ();
break ;
2017-02-26 21:46:33 +01:00
// Object vertices:
case NODE_TYPE_VERTEX :
assert ( m_object );
// Parse the vertex data
2018-04-30 12:03:06 +02:00
m_object_vertices . emplace_back (( float ) atof ( m_value [ 0 ]. c_str ()));
m_object_vertices . emplace_back (( float ) atof ( m_value [ 1 ]. c_str ()));
m_object_vertices . emplace_back (( float ) atof ( m_value [ 2 ]. c_str ()));
2017-02-26 21:46:33 +01:00
m_value [ 0 ]. clear ();
m_value [ 1 ]. clear ();
m_value [ 2 ]. clear ();
break ;
// Faces of the current volume:
case NODE_TYPE_TRIANGLE :
assert ( m_object && m_volume );
m_volume_facets . push_back ( atoi ( m_value [ 0 ]. c_str ()));
m_volume_facets . push_back ( atoi ( m_value [ 1 ]. c_str ()));
m_volume_facets . push_back ( atoi ( m_value [ 2 ]. c_str ()));
m_value [ 0 ]. clear ();
m_value [ 1 ]. clear ();
m_value [ 2 ]. clear ();
break ;
// Closing the current volume. Create an STL from m_volume_facets pointing to m_object_vertices.
case NODE_TYPE_VOLUME :
{
assert ( m_object && m_volume );
2019-06-11 17:08:47 +02:00
TriangleMesh mesh ;
stl_file & stl = mesh . stl ;
2017-02-26 21:46:33 +01:00
stl . stats . type = inmemory ;
stl . stats . number_of_facets = int ( m_volume_facets . size () / 3 );
stl . stats . original_num_facets = stl . stats . number_of_facets ;
stl_allocate ( & stl );
2019-09-23 13:54:49 +02:00
2019-12-19 10:22:46 +01:00
bool has_transform = ! m_volume_transform . isApprox ( Transform3d :: Identity (), 1e-10 );
2017-02-26 21:46:33 +01:00
for ( size_t i = 0 ; i < m_volume_facets . size ();) {
stl_facet & facet = stl . facet_start [ i / 3 ];
2019-09-23 13:54:49 +02:00
for ( unsigned int v = 0 ; v < 3 ; ++ v )
{
unsigned int tri_id = m_volume_facets [ i ++ ] * 3 ;
2019-12-20 12:11:58 +01:00
facet . vertex [ v ] = Vec3f ( m_object_vertices [ tri_id + 0 ], m_object_vertices [ tri_id + 1 ], m_object_vertices [ tri_id + 2 ]);
2019-09-23 13:54:49 +02:00
}
2019-12-19 10:22:46 +01:00
}
2017-02-26 21:46:33 +01:00
stl_get_size ( & stl );
2019-06-11 17:08:47 +02:00
mesh . repair ();
m_volume -> set_mesh ( std :: move ( mesh ));
2019-12-20 12:11:58 +01:00
// stores the volume matrix taken from the metadata, if present
if ( has_transform )
m_volume -> source . transform = Slic3r :: Geometry :: Transformation ( m_volume_transform );
2019-10-21 08:48:38 +02:00
if ( m_volume -> source . input_file . empty () && ( m_volume -> type () == ModelVolumeType :: MODEL_PART ))
{
m_volume -> source . object_idx = ( int ) m_model . objects . size () - 1 ;
m_volume -> source . volume_idx = ( int ) m_model . objects . back () -> volumes . size () - 1 ;
m_volume -> center_geometry_after_creation ();
}
else
// pass false if the mesh offset has been already taken from the data
m_volume -> center_geometry_after_creation ( m_volume -> source . input_file . empty ());
2018-08-15 12:50:06 +02:00
m_volume -> calculate_convex_hull ();
2017-02-26 21:46:33 +01:00
m_volume_facets . clear ();
m_volume = nullptr ;
break ;
}
case NODE_TYPE_OBJECT :
assert ( m_object );
m_object_vertices . clear ();
m_object = nullptr ;
break ;
case NODE_TYPE_MATERIAL :
assert ( m_material );
m_material = nullptr ;
break ;
case NODE_TYPE_INSTANCE :
assert ( m_instance );
m_instance = nullptr ;
break ;
2019-11-11 16:01:34 +01:00
case NODE_TYPE_GCODE_PER_HEIGHT : {
2020-01-23 12:49:39 +01:00
double print_z = double ( atof ( m_value [ 0 ]. c_str ()));
2020-06-03 10:42:47 +02:00
// const std::string& gcode = m_value[1];
CustomGCode :: Type type = static_cast < CustomGCode :: Type > ( atoi ( m_value [ 1 ]. c_str ()));
2019-11-11 16:01:34 +01:00
int extruder = atoi ( m_value [ 2 ]. c_str ());
const std :: string & color = m_value [ 3 ];
2020-06-03 10:42:47 +02:00
m_model . custom_gcode_per_print_z . gcodes . push_back ( CustomGCode :: Item { print_z , type , /*gcode, */ extruder , color });
2019-11-11 16:01:34 +01:00
for ( std :: string & val : m_value )
val . clear ();
break ;
}
2020-01-23 12:49:39 +01:00
case NODE_TYPE_CUSTOM_GCODE_MODE : {
const std :: string & mode = m_value [ 0 ];
m_model . custom_gcode_per_print_z . mode = mode == CustomGCode :: SingleExtruderMode ? CustomGCode :: Mode :: SingleExtruder :
mode == CustomGCode :: MultiAsSingleMode ? CustomGCode :: Mode :: MultiAsSingle :
CustomGCode :: Mode :: MultiExtruder ;
for ( std :: string & val : m_value )
val . clear ();
break ;
}
2017-02-26 21:46:33 +01:00
case NODE_TYPE_METADATA :
2018-09-25 11:53:05 +02:00
if (( m_config != nullptr ) && strncmp ( m_value [ 0 ]. c_str (), SLIC3R_CONFIG_TYPE , strlen ( SLIC3R_CONFIG_TYPE )) == 0 )
m_config -> load_from_gcode_string ( m_value [ 1 ]. c_str ());
2018-02-14 14:30:03 +01:00
else if ( strncmp ( m_value [ 0 ]. c_str (), "slic3r." , 7 ) == 0 ) {
2017-02-26 21:46:33 +01:00
const char * opt_key = m_value [ 0 ]. c_str () + 7 ;
if ( print_config_def . options . find ( opt_key ) != print_config_def . options . end ()) {
DynamicPrintConfig * config = nullptr ;
if ( m_path . size () == 3 ) {
if ( m_path [ 1 ] == NODE_TYPE_MATERIAL && m_material )
config = & m_material -> config ;
else if ( m_path [ 1 ] == NODE_TYPE_OBJECT && m_object )
config = & m_object -> config ;
2019-06-11 09:50:58 +02:00
}
else if ( m_path . size () == 5 && m_path [ 3 ] == NODE_TYPE_VOLUME && m_volume )
2017-02-26 21:46:33 +01:00
config = & m_volume -> config ;
2019-06-11 09:50:58 +02:00
else if ( m_path . size () == 5 && m_path [ 3 ] == NODE_TYPE_RANGE && m_object && ! m_object -> layer_config_ranges . empty ()) {
auto it = -- m_object -> layer_config_ranges . end ();
config = & it -> second ;
}
2017-02-26 21:46:33 +01:00
if ( config )
config -> set_deserialize ( opt_key , m_value [ 1 ]);
} else if ( m_path . size () == 3 && m_path [ 1 ] == NODE_TYPE_OBJECT && m_object && strcmp ( opt_key , "layer_height_profile" ) == 0 ) {
// Parse object's layer height profile, a semicolon separated list of floats.
2020-01-03 16:32:56 +01:00
char * p = m_value [ 1 ]. data ();
2017-02-26 21:46:33 +01:00
for (;;) {
char * end = strchr ( p , ';' );
if ( end != nullptr )
* end = 0 ;
m_object -> layer_height_profile . push_back ( float ( atof ( p )));
if ( end == nullptr )
break ;
p = end + 1 ;
}
2018-10-03 15:13:54 +02:00
}
else if ( m_path . size () == 3 && m_path [ 1 ] == NODE_TYPE_OBJECT && m_object && strcmp ( opt_key , "sla_support_points" ) == 0 ) {
// Parse object's layer height profile, a semicolon separated list of floats.
unsigned char coord_idx = 0 ;
2019-01-30 08:26:23 +01:00
Eigen :: Matrix < float , 5 , 1 , Eigen :: DontAlign > point ( Eigen :: Matrix < float , 5 , 1 , Eigen :: DontAlign >:: Zero ());
2020-01-03 16:32:56 +01:00
char * p = m_value [ 1 ]. data ();
2018-10-03 15:13:54 +02:00
for (;;) {
char * end = strchr ( p , ';' );
if ( end != nullptr )
* end = 0 ;
2019-06-20 16:15:09 +02:00
point ( coord_idx ) = float ( atof ( p ));
2019-01-30 08:26:23 +01:00
if ( ++ coord_idx == 5 ) {
m_object -> sla_support_points . push_back ( sla :: SupportPoint ( point ));
2018-10-03 15:13:54 +02:00
coord_idx = 0 ;
}
if ( end == nullptr )
break ;
p = end + 1 ;
}
2019-03-11 11:13:37 +01:00
m_object -> sla_points_status = sla :: PointsStatus :: UserModified ;
2018-10-03 15:13:54 +02:00
}
2019-06-11 09:50:58 +02:00
else if ( m_path . size () == 5 && m_path [ 1 ] == NODE_TYPE_OBJECT && m_path [ 3 ] == NODE_TYPE_RANGE &&
2019-06-20 16:15:09 +02:00
m_object && strcmp ( opt_key , "layer_height_range" ) == 0 ) {
// Parse object's layer_height_range, a semicolon separated doubles.
2020-01-03 16:32:56 +01:00
char * p = m_value [ 1 ]. data ();
2019-06-11 09:50:58 +02:00
char * end = strchr ( p , ';' );
* end = 0 ;
const t_layer_height_range range = { double ( atof ( p )), double ( atof ( end + 1 ))};
m_object -> layer_config_ranges [ range ];
}
2018-10-03 15:13:54 +02:00
else if ( m_path . size () == 5 && m_path [ 3 ] == NODE_TYPE_VOLUME && m_volume ) {
2018-09-17 15:12:13 +02:00
if ( strcmp ( opt_key , "modifier" ) == 0 ) {
// Is this volume a modifier volume?
// "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag.
2019-02-22 12:12:10 +01:00
m_volume -> set_type (( atoi ( m_value [ 1 ]. c_str ()) == 1 ) ? ModelVolumeType :: PARAMETER_MODIFIER : ModelVolumeType :: MODEL_PART );
2018-09-17 15:12:13 +02:00
} else if ( strcmp ( opt_key , "volume_type" ) == 0 ) {
m_volume -> set_type ( ModelVolume :: type_from_string ( m_value [ 1 ]));
}
2019-09-19 12:39:59 +02:00
else if ( strcmp ( opt_key , "matrix" ) == 0 ) {
2019-12-19 10:22:46 +01:00
m_volume_transform = Slic3r :: Geometry :: transform3d_from_string ( m_value [ 1 ]);
2019-09-19 12:39:59 +02:00
}
else if ( strcmp ( opt_key , "source_file" ) == 0 ) {
m_volume -> source . input_file = m_value [ 1 ];
}
else if ( strcmp ( opt_key , "source_object_id" ) == 0 ) {
m_volume -> source . object_idx = :: atoi ( m_value [ 1 ]. c_str ());
}
else if ( strcmp ( opt_key , "source_volume_id" ) == 0 ) {
m_volume -> source . volume_idx = :: atoi ( m_value [ 1 ]. c_str ());
}
else if ( strcmp ( opt_key , "source_offset_x" ) == 0 ) {
m_volume -> source . mesh_offset ( 0 ) = :: atof ( m_value [ 1 ]. c_str ());
}
else if ( strcmp ( opt_key , "source_offset_y" ) == 0 ) {
m_volume -> source . mesh_offset ( 1 ) = :: atof ( m_value [ 1 ]. c_str ());
}
else if ( strcmp ( opt_key , "source_offset_z" ) == 0 ) {
m_volume -> source . mesh_offset ( 2 ) = :: atof ( m_value [ 1 ]. c_str ());
}
2017-02-26 21:46:33 +01:00
}
} else if ( m_path . size () == 3 ) {
if ( m_path [ 1 ] == NODE_TYPE_MATERIAL ) {
if ( m_material )
m_material -> attributes [ m_value [ 0 ]] = m_value [ 1 ];
} else if ( m_path [ 1 ] == NODE_TYPE_OBJECT ) {
if ( m_object && m_value [ 0 ] == "name" )
m_object -> name = std :: move ( m_value [ 1 ]);
}
} else if ( m_path . size () == 5 && m_path [ 3 ] == NODE_TYPE_VOLUME ) {
if ( m_volume && m_value [ 0 ] == "name" )
m_volume -> name = std :: move ( m_value [ 1 ]);
}
2018-04-30 12:03:06 +02:00
else if ( strncmp ( m_value [ 0 ]. c_str (), SLIC3RPE_AMF_VERSION , strlen ( SLIC3RPE_AMF_VERSION )) == 0 ) {
m_version = ( unsigned int ) atoi ( m_value [ 1 ]. c_str ());
}
2017-02-26 21:46:33 +01:00
m_value [ 0 ]. clear ();
m_value [ 1 ]. clear ();
break ;
default :
break ;
}
m_path . pop_back ();
}
void AMFParserContext :: endDocument ()
{
for ( const auto & object : m_object_instances_map ) {
if ( object . second . idx == - 1 ) {
printf ( "Undefined object %s referenced in constellation \n " , object . first . c_str ());
continue ;
}
for ( const Instance & instance : object . second . instances )
2019-03-25 12:07:43 +01:00
if ( instance . anything_set ()) {
2017-02-26 21:46:33 +01:00
ModelInstance * mi = m_model . objects [ object . second . idx ] -> add_instance ();
2018-09-20 15:00:40 +02:00
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 ));
2018-09-25 10:42:11 +02:00
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 ));
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 ));
2018-10-22 11:45:03 +02:00
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 ));
2019-08-06 14:54:38 +02:00
mi -> printable = instance . printable ;
2019-01-18 14:40:10 +01:00
}
2017-02-26 21:46:33 +01:00
}
}
// Load an AMF file into a provided model.
2018-09-25 11:53:05 +02:00
bool load_amf_file ( const char * path , DynamicPrintConfig * config , Model * model )
2017-02-26 21:46:33 +01:00
{
2018-02-14 14:30:03 +01:00
if (( path == nullptr ) || ( model == nullptr ))
return false ;
2017-02-26 21:46:33 +01:00
XML_Parser parser = XML_ParserCreate ( nullptr ); // encoding
2018-02-14 14:30:03 +01:00
if ( ! parser ) {
2017-02-26 21:46:33 +01:00
printf ( "Couldn't allocate memory for parser \n " );
return false ;
}
2017-08-03 17:31:31 +02:00
FILE * pFile = boost :: nowide :: fopen ( path , "rt" );
2017-02-26 21:46:33 +01:00
if ( pFile == nullptr ) {
printf ( "Cannot open file %s \n " , path );
return false ;
}
2018-09-25 11:53:05 +02:00
AMFParserContext ctx ( parser , config , model );
2017-02-26 21:46:33 +01:00
XML_SetUserData ( parser , ( void * ) & ctx );
XML_SetElementHandler ( parser , AMFParserContext :: startElement , AMFParserContext :: endElement );
XML_SetCharacterDataHandler ( parser , AMFParserContext :: characters );
char buff [ 8192 ];
bool result = false ;
for (;;) {
int len = ( int ) fread ( buff , 1 , 8192 , pFile );
if ( ferror ( pFile )) {
printf ( "AMF parser: Read error \n " );
break ;
}
int done = feof ( pFile );
if ( XML_Parse ( parser , buff , len , done ) == XML_STATUS_ERROR ) {
2019-06-25 13:06:04 +02:00
printf ( "AMF parser: Parse error at line %d: \n %s \n " ,
( int ) XML_GetCurrentLineNumber ( parser ),
2017-02-26 21:46:33 +01:00
XML_ErrorString ( XML_GetErrorCode ( parser )));
break ;
}
if ( done ) {
result = true ;
break ;
}
}
XML_ParserFree ( parser );
:: fclose ( pFile );
if ( result )
ctx . endDocument ();
2018-02-14 14:30:03 +01:00
2019-10-21 08:48:38 +02:00
for ( ModelObject * o : model -> objects )
{
for ( ModelVolume * v : o -> volumes )
{
if ( v -> source . input_file . empty () && ( v -> type () == ModelVolumeType :: MODEL_PART ))
v -> source . input_file = path ;
}
}
2017-02-26 21:46:33 +01:00
return result ;
}
2019-08-23 13:12:31 +02:00
bool extract_model_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat , DynamicPrintConfig * config , Model * model , bool check_version )
2017-02-26 21:46:33 +01:00
{
2018-02-14 14:30:03 +01:00
if ( stat . m_uncomp_size == 0 )
{
printf ( "Found invalid size \n " );
2019-06-03 15:27:46 +02:00
close_zip_reader ( & archive );
2018-02-14 14:30:03 +01:00
return false ;
}
XML_Parser parser = XML_ParserCreate ( nullptr ); // encoding
if ( ! parser ) {
printf ( "Couldn't allocate memory for parser \n " );
2019-06-03 15:27:46 +02:00
close_zip_reader ( & archive );
2018-02-14 14:30:03 +01:00
return false ;
}
2018-09-25 11:53:05 +02:00
AMFParserContext ctx ( parser , config , model );
2018-02-14 14:30:03 +01:00
XML_SetUserData ( parser , ( void * ) & ctx );
XML_SetElementHandler ( parser , AMFParserContext :: startElement , AMFParserContext :: endElement );
XML_SetCharacterDataHandler ( parser , AMFParserContext :: characters );
2019-08-28 16:03:26 +02:00
struct CallbackData
2018-02-14 14:30:03 +01:00
{
2019-08-28 16:03:26 +02:00
XML_Parser & parser ;
const mz_zip_archive_file_stat & stat ;
CallbackData ( XML_Parser & parser , const mz_zip_archive_file_stat & stat ) : parser ( parser ), stat ( stat ) {}
};
CallbackData data ( parser , stat );
mz_bool res = 0 ;
try
{
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 {
CallbackData * data = ( CallbackData * ) pOpaque ;
if ( ! XML_Parse ( data -> parser , ( const char * ) pBuf , ( int ) n , ( file_ofs + n == data -> stat . m_uncomp_size ) ? 1 : 0 ))
{
char error_buf [ 1024 ];
:: sprintf ( error_buf , "Error (%s) while parsing '%s' at line %d" , XML_ErrorString ( XML_GetErrorCode ( data -> parser )), data -> stat . m_filename , ( int ) XML_GetCurrentLineNumber ( data -> parser ));
throw std :: runtime_error ( error_buf );
}
return n ;
}, & data , 0 );
}
catch ( std :: exception & e )
{
printf ( "%s \n " , e . what ());
2019-06-03 15:27:46 +02:00
close_zip_reader ( & archive );
2018-02-14 14:30:03 +01:00
return false ;
}
if ( res == 0 )
{
2019-08-28 16:03:26 +02:00
printf ( "Error while extracting model data from zip archive" );
2019-06-03 15:27:46 +02:00
close_zip_reader ( & archive );
2018-02-14 14:30:03 +01:00
return false ;
}
ctx . endDocument ();
2019-12-19 10:22:46 +01:00
if ( check_version && ( ctx . m_version > VERSION_AMF_COMPATIBLE ))
2019-08-23 13:12:31 +02:00
{
2019-12-06 08:59:25 +01:00
// 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."));
// throw std::runtime_error(msg.c_str());
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 ();
throw std :: runtime_error ( msg );
2019-08-23 13:12:31 +02:00
}
2018-04-30 12:03:06 +02:00
return true ;
}
// Load an AMF archive into a provided model.
2019-08-23 13:12:31 +02:00
bool load_amf_archive ( const char * path , DynamicPrintConfig * config , Model * model , bool check_version )
2018-04-30 12:03:06 +02:00
{
if (( path == nullptr ) || ( model == nullptr ))
return false ;
mz_zip_archive archive ;
mz_zip_zero_struct ( & archive );
2019-06-03 15:27:46 +02:00
if ( ! open_zip_reader ( & archive , path ))
2018-04-30 12:03:06 +02:00
{
printf ( "Unable to init zip reader \n " );
return false ;
}
mz_uint num_entries = mz_zip_reader_get_num_files ( & archive );
mz_zip_archive_file_stat stat ;
// we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
for ( mz_uint i = 0 ; i < num_entries ; ++ i )
{
if ( mz_zip_reader_file_stat ( & archive , i , & stat ))
{
if ( boost :: iends_with ( stat . m_filename , ".amf" ))
{
2019-08-23 13:12:31 +02:00
try
2018-04-30 12:03:06 +02:00
{
2019-08-23 13:12:31 +02:00
if ( ! extract_model_from_archive ( archive , stat , config , model , check_version ))
{
close_zip_reader ( & archive );
printf ( "Archive does not contain a valid model" );
return false ;
}
}
catch ( const std :: exception & e )
{
// ensure the zip archive is closed and rethrow the exception
2019-06-03 15:27:46 +02:00
close_zip_reader ( & archive );
2019-09-02 09:38:45 +02:00
throw std :: runtime_error ( e . what ());
2018-04-30 12:03:06 +02:00
}
break ;
}
}
}
#if 0 // forward compatibility
// we then loop again the entries to read other files stored in the archive
for (mz_uint i = 0; i < num_entries; ++i)
{
if (mz_zip_reader_file_stat(&archive, i, &stat))
{
// add code to extract the file
}
}
#endif // forward compatibility
2019-06-03 15:27:46 +02:00
close_zip_reader ( & archive );
2018-02-14 14:30:03 +01:00
return true ;
}
// Load an AMF file into a provided model.
2018-09-25 11:53:05 +02:00
// If config is not a null pointer, updates it if the amf file/archive contains config data
2019-08-23 13:12:31 +02:00
bool load_amf ( const char * path , DynamicPrintConfig * config , Model * model , bool check_version )
2018-02-14 14:30:03 +01:00
{
2018-05-24 09:57:12 +02:00
if ( boost :: iends_with ( path , ".amf.xml" ))
// backward compatibility with older slic3r output
2018-09-25 11:53:05 +02:00
return load_amf_file ( path , config , model );
2018-05-24 09:57:12 +02:00
else if ( boost :: iends_with ( path , ".amf" ))
{
boost :: nowide :: ifstream file ( path , boost :: nowide :: ifstream :: binary );
if ( ! file . good ())
return false ;
std :: string zip_mask ( 2 , '\0' );
2020-01-03 16:32:56 +01:00
file . read ( zip_mask . data (), 2 );
2018-05-24 09:57:12 +02:00
file . close ();
2019-08-23 13:12:31 +02:00
return ( zip_mask == "PK" ) ? load_amf_archive ( path , config , model , check_version ) : load_amf_file ( path , config , model );
2018-05-24 09:57:12 +02:00
}
2018-02-14 14:30:03 +01:00
else
return false ;
}
2020-01-08 11:11:38 +01:00
bool store_amf ( const char * path , Model * model , const DynamicPrintConfig * config , bool fullpath_sources )
2018-02-14 14:30:03 +01:00
{
2018-11-07 14:57:50 +01:00
if (( path == nullptr ) || ( model == nullptr ))
2018-02-14 14:30:03 +01:00
return false ;
2018-03-26 08:58:44 +02:00
// forces ".zip.amf" extension
std :: string export_path = path ;
if ( ! boost :: iends_with ( export_path , ".zip.amf" ))
export_path = boost :: filesystem :: path ( export_path ). replace_extension ( ".zip.amf" ). string ();
2018-02-14 14:30:03 +01:00
mz_zip_archive archive ;
mz_zip_zero_struct ( & archive );
2019-06-03 15:27:46 +02:00
if ( ! open_zip_writer ( & archive , export_path )) return false ;
2018-02-14 14:30:03 +01:00
std :: stringstream stream ;
2019-02-27 10:31:53 +01:00
// https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10
// 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).
// It is guaranteed to produce the same floating-point value, even though the intermediate text representation is not exact.
// The default value of std::stream precision is 6 digits only!
2019-02-27 11:08:58 +01:00
stream << std :: setprecision ( std :: numeric_limits < float >:: max_digits10 );
2018-02-14 14:30:03 +01:00
stream << "<?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> \n " ;
stream << "<amf unit= \" millimeter \" > \n " ;
stream << "<metadata type= \" cad \" >Slic3r " << SLIC3R_VERSION << "</metadata> \n " ;
2018-04-30 12:03:06 +02:00
stream << "<metadata type= \" " << SLIC3RPE_AMF_VERSION << " \" >" << VERSION_AMF << "</metadata> \n " ;
2018-02-14 14:30:03 +01:00
2018-11-07 14:57:50 +01:00
if ( config != nullptr )
2018-03-22 13:49:48 +01:00
{
2018-11-07 14:57:50 +01:00
std :: string str_config = " \n " ;
for ( const std :: string & key : config -> keys ())
if ( key != "compatible_printers" )
2019-06-26 13:26:49 +02:00
str_config += "; " + key + " = " + config -> opt_serialize ( key ) + " \n " ;
2018-11-07 14:57:50 +01:00
stream << "<metadata type= \" " << SLIC3R_CONFIG_TYPE << " \" >" << xml_escape ( str_config ) << "</metadata> \n " ;
2018-03-22 13:49:48 +01:00
}
2018-02-14 14:30:03 +01:00
2017-02-26 21:46:33 +01:00
for ( const auto & material : model -> materials ) {
if ( material . first . empty ())
continue ;
// note that material-id must never be 0 since it's reserved by the AMF spec
2018-02-14 14:30:03 +01:00
stream << " <material id= \" " << material . first << " \" > \n " ;
2017-02-26 21:46:33 +01:00
for ( const auto & attr : material . second -> attributes )
2018-02-14 14:30:03 +01:00
stream << " <metadata type= \" " << attr . first << " \" >" << attr . second << "</metadata> \n " ;
2017-02-26 21:46:33 +01:00
for ( const std :: string & key : material . second -> config . keys ())
2019-06-26 13:26:49 +02:00
stream << " <metadata type= \" slic3r." << key << " \" >" << material . second -> config . opt_serialize ( key ) << "</metadata> \n " ;
2018-02-14 14:30:03 +01:00
stream << " </material> \n " ;
2017-02-26 21:46:33 +01:00
}
std :: string instances ;
for ( size_t object_id = 0 ; object_id < model -> objects . size (); ++ object_id ) {
ModelObject * object = model -> objects [ object_id ];
2018-02-14 14:30:03 +01:00
stream << " <object id= \" " << object_id << " \" > \n " ;
2017-02-26 21:46:33 +01:00
for ( const std :: string & key : object -> config . keys ())
2019-06-26 13:26:49 +02:00
stream << " <metadata type= \" slic3r." << key << " \" >" << object -> config . opt_serialize ( key ) << "</metadata> \n " ;
2018-02-14 14:30:03 +01:00
if ( ! object -> name . empty ())
2018-07-23 10:58:39 +02:00
stream << " <metadata type= \" name \" >" << xml_escape ( object -> name ) << "</metadata> \n " ;
2019-01-21 10:06:51 +01:00
const std :: vector < double > & layer_height_profile = object -> layer_height_profile ;
2017-02-26 21:46:33 +01:00
if ( layer_height_profile . size () >= 4 && ( layer_height_profile . size () % 2 ) == 0 ) {
// Store the layer height profile as a single semicolon separated list.
2018-02-14 14:30:03 +01:00
stream << " <metadata type= \" slic3r.layer_height_profile \" >" ;
stream << layer_height_profile . front ();
for ( size_t i = 1 ; i < layer_height_profile . size (); ++ i )
stream << ";" << layer_height_profile [ i ];
2019-09-24 16:01:01 +02:00
stream << " \n </metadata> \n " ;
2017-02-26 21:46:33 +01:00
}
2018-10-03 15:13:54 +02:00
2019-07-04 20:49:46 +02:00
// Export layer height ranges including the layer range specific config overrides.
2019-06-11 09:50:58 +02:00
const t_layer_config_ranges & config_ranges = object -> layer_config_ranges ;
if ( ! config_ranges . empty ())
{
// Store the layer config range as a single semicolon separated list.
stream << " <layer_config_ranges> \n " ;
size_t layer_counter = 0 ;
for ( auto range : config_ranges ) {
stream << " <range id= \" " << layer_counter << " \" > \n " ;
2019-06-20 16:15:09 +02:00
stream << " <metadata type= \" slic3r.layer_height_range \" >" ;
2019-06-11 09:50:58 +02:00
stream << range . first . first << ";" << range . first . second << "</metadata> \n " ;
for ( const std :: string & key : range . second . keys ())
2019-07-04 20:49:46 +02:00
stream << " <metadata type= \" slic3r." << key << " \" >" << range . second . opt_serialize ( key ) << "</metadata> \n " ;
2019-06-11 09:50:58 +02:00
stream << " </range> \n " ;
layer_counter ++ ;
}
stream << " </layer_config_ranges> \n " ;
}
2018-10-03 15:13:54 +02:00
2019-01-30 08:26:23 +01:00
const std :: vector < sla :: SupportPoint >& sla_support_points = object -> sla_support_points ;
2018-10-03 15:13:54 +02:00
if ( ! sla_support_points . empty ()) {
// Store the SLA supports as a single semicolon separated list.
stream << " <metadata type= \" slic3r.sla_support_points \" >" ;
for ( size_t i = 0 ; i < sla_support_points . size (); ++ i ) {
if ( i != 0 )
stream << ";" ;
2019-01-30 08:26:23 +01:00
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 ;
2018-10-03 15:13:54 +02:00
}
stream << " \n </metadata> \n " ;
}
2018-02-14 14:30:03 +01:00
stream << " <mesh> \n " ;
stream << " <vertices> \n " ;
2017-02-26 21:46:33 +01:00
std :: vector < int > vertices_offsets ;
int num_vertices = 0 ;
for ( ModelVolume * volume : object -> volumes ) {
vertices_offsets . push_back ( num_vertices );
2019-06-11 17:08:47 +02:00
if ( ! volume -> mesh (). repaired )
2018-09-18 10:09:58 +02:00
throw std :: runtime_error ( "store_amf() requires repair()" );
2019-06-11 17:08:47 +02:00
if ( ! volume -> mesh (). has_shared_vertices ())
throw std :: runtime_error ( "store_amf() requires shared vertices" );
const indexed_triangle_set & its = volume -> mesh (). its ;
2019-01-24 13:32:45 +01:00
const Transform3d & matrix = volume -> get_matrix ();
2019-06-10 18:30:54 +02:00
for ( size_t i = 0 ; i < its . vertices . size (); ++ i ) {
2018-02-14 14:30:03 +01:00
stream << " <vertex> \n " ;
stream << " <coordinates> \n " ;
2019-06-10 18:30:54 +02:00
Vec3f v = ( matrix * its . vertices [ i ]. cast < double > ()). cast < float > ();
2019-01-24 13:32:45 +01:00
stream << " <x>" << v ( 0 ) << "</x> \n " ;
stream << " <y>" << v ( 1 ) << "</y> \n " ;
stream << " <z>" << v ( 2 ) << "</z> \n " ;
2018-02-14 14:30:03 +01:00
stream << " </coordinates> \n " ;
stream << " </vertex> \n " ;
2017-02-26 21:46:33 +01:00
}
2019-06-20 16:15:09 +02:00
num_vertices += ( int ) its . vertices . size ();
2017-02-26 21:46:33 +01:00
}
2018-02-14 14:30:03 +01:00
stream << " </vertices> \n " ;
for ( size_t i_volume = 0 ; i_volume < object -> volumes . size (); ++ i_volume ) {
2017-02-26 21:46:33 +01:00
ModelVolume * volume = object -> volumes [ i_volume ];
int vertices_offset = vertices_offsets [ i_volume ];
if ( volume -> material_id (). empty ())
2018-02-14 14:30:03 +01:00
stream << " <volume> \n " ;
2017-02-26 21:46:33 +01:00
else
2018-02-14 14:30:03 +01:00
stream << " <volume materialid= \" " << volume -> material_id () << " \" > \n " ;
2017-02-26 21:46:33 +01:00
for ( const std :: string & key : volume -> config . keys ())
2019-06-26 13:26:49 +02:00
stream << " <metadata type= \" slic3r." << key << " \" >" << volume -> config . opt_serialize ( key ) << "</metadata> \n " ;
2018-02-14 14:30:03 +01:00
if ( ! volume -> name . empty ())
2018-07-23 10:58:39 +02:00
stream << " <metadata type= \" name \" >" << xml_escape ( volume -> name ) << "</metadata> \n " ;
2018-09-17 15:12:13 +02:00
if ( volume -> is_modifier ())
2018-02-14 14:30:03 +01:00
stream << " <metadata type= \" slic3r.modifier \" >1</metadata> \n " ;
2018-09-17 15:12:13 +02:00
stream << " <metadata type= \" slic3r.volume_type \" >" << ModelVolume :: type_to_string ( volume -> type ()) << "</metadata> \n " ;
2019-09-19 12:39:59 +02:00
stream << " <metadata type= \" slic3r.matrix \" >" ;
2019-12-20 12:11:58 +01:00
const Transform3d & matrix = volume -> get_matrix () * volume -> source . transform . get_matrix ();
stream << std :: setprecision ( std :: numeric_limits < double >:: max_digits10 );
2019-09-19 12:39:59 +02:00
for ( int r = 0 ; r < 4 ; ++ r )
{
for ( int c = 0 ; c < 4 ; ++ c )
{
stream << matrix ( r , c );
if (( r != 3 ) || ( c != 3 ))
stream << " " ;
}
}
stream << "</metadata> \n " ;
if ( ! volume -> source . input_file . empty ())
{
2020-01-08 11:11:38 +01:00
std :: string input_file = xml_escape ( fullpath_sources ? volume -> source . input_file : boost :: filesystem :: path ( volume -> source . input_file ). filename (). string ());
stream << " <metadata type= \" slic3r.source_file \" >" << input_file << "</metadata> \n " ;
2019-09-19 12:39:59 +02:00
stream << " <metadata type= \" slic3r.source_object_id \" >" << volume -> source . object_idx << "</metadata> \n " ;
stream << " <metadata type= \" slic3r.source_volume_id \" >" << volume -> source . volume_idx << "</metadata> \n " ;
stream << " <metadata type= \" slic3r.source_offset_x \" >" << volume -> source . mesh_offset ( 0 ) << "</metadata> \n " ;
stream << " <metadata type= \" slic3r.source_offset_y \" >" << volume -> source . mesh_offset ( 1 ) << "</metadata> \n " ;
stream << " <metadata type= \" slic3r.source_offset_z \" >" << volume -> source . mesh_offset ( 2 ) << "</metadata> \n " ;
}
2019-12-19 10:22:46 +01:00
stream << std :: setprecision ( std :: numeric_limits < float >:: max_digits10 );
2019-09-19 12:39:59 +02:00
const indexed_triangle_set & its = volume -> mesh (). its ;
2019-06-25 13:06:04 +02:00
for ( size_t i = 0 ; i < its . indices . size (); ++ i ) {
2018-02-14 14:30:03 +01:00
stream << " <triangle> \n " ;
for ( int j = 0 ; j < 3 ; ++ j )
2019-06-11 17:08:47 +02:00
stream << " <v" << j + 1 << ">" << its . indices [ i ][ j ] + vertices_offset << "</v" << j + 1 << "> \n " ;
2018-02-14 14:30:03 +01:00
stream << " </triangle> \n " ;
2017-02-26 21:46:33 +01:00
}
2018-02-14 14:30:03 +01:00
stream << " </volume> \n " ;
2017-02-26 21:46:33 +01:00
}
2018-02-14 14:30:03 +01:00
stream << " </mesh> \n " ;
stream << " </object> \n " ;
if ( ! object -> instances . empty ()) {
2017-02-26 21:46:33 +01:00
for ( ModelInstance * instance : object -> instances ) {
char buf [ 512 ];
sprintf ( buf ,
2017-02-26 23:13:31 +01:00
" <instance objectid= \" " PRINTF_ZU " \" > \n "
" <deltax>%lf</deltax> \n "
" <deltay>%lf</deltay> \n "
2018-09-17 15:12:13 +02:00
" <deltaz>%lf</deltaz> \n "
2018-09-20 15:00:40 +02:00
" <rx>%lf</rx> \n "
" <ry>%lf</ry> \n "
2017-02-26 23:13:31 +01:00
" <rz>%lf</rz> \n "
2018-09-24 15:54:09 +02:00
" <scalex>%lf</scalex> \n "
" <scaley>%lf</scaley> \n "
" <scalez>%lf</scalez> \n "
2018-10-22 11:45:03 +02:00
" <mirrorx>%lf</mirrorx> \n "
" <mirrory>%lf</mirrory> \n "
" <mirrorz>%lf</mirrorz> \n "
2019-08-06 14:54:38 +02:00
" <printable>%d</printable> \n "
2017-02-26 21:46:33 +01:00
" </instance> \n " ,
object_id ,
2018-09-17 15:12:13 +02:00
instance -> get_offset ( X ),
instance -> get_offset ( Y ),
instance -> get_offset ( Z ),
2018-09-20 15:00:40 +02:00
instance -> get_rotation ( X ),
instance -> get_rotation ( Y ),
instance -> get_rotation ( Z ),
2018-09-24 15:54:09 +02:00
instance -> get_scaling_factor ( X ),
instance -> get_scaling_factor ( Y ),
2018-10-22 11:45:03 +02:00
instance -> get_scaling_factor ( Z ),
instance -> get_mirror ( X ),
instance -> get_mirror ( Y ),
2019-08-06 14:54:38 +02:00
instance -> get_mirror ( Z ),
instance -> printable );
2018-09-20 15:00:40 +02:00
2017-02-26 21:46:33 +01:00
//FIXME missing instance->scaling_factor
instances . append ( buf );
}
}
}
if ( ! instances . empty ()) {
2018-02-14 14:30:03 +01:00
stream << " <constellation id= \" 1 \" > \n " ;
stream << instances ;
stream << " </constellation> \n " ;
2017-02-26 21:46:33 +01:00
}
2019-11-11 16:01:34 +01:00
2020-01-16 16:01:19 +01:00
if ( ! model -> custom_gcode_per_print_z . gcodes . empty ())
2019-11-11 16:01:34 +01:00
{
std :: string out = "" ;
pt :: ptree tree ;
pt :: ptree & main_tree = tree . add ( "custom_gcodes_per_height" , "" );
2020-01-23 12:49:39 +01:00
for ( const CustomGCode :: Item & code : model -> custom_gcode_per_print_z . gcodes )
2019-11-11 16:01:34 +01:00
{
pt :: ptree & code_tree = main_tree . add ( "code" , "" );
2020-01-23 12:49:39 +01:00
// store custom_gcode_per_print_z gcodes information
2019-12-17 08:37:50 +01:00
code_tree . put ( "<xmlattr>.print_z" , code . print_z );
2020-06-03 10:42:47 +02:00
// code_tree.put("<xmlattr>.gcode" , code.gcode );
code_tree . put ( "<xmlattr>.type" , code . type );
2019-12-17 08:37:50 +01:00
code_tree . put ( "<xmlattr>.extruder" , code . extruder );
code_tree . put ( "<xmlattr>.color" , code . color );
2019-11-11 16:01:34 +01:00
}
2020-01-23 12:49:39 +01:00
pt :: ptree & mode_tree = main_tree . add ( "mode" , "" );
// store mode of a custom_gcode_per_print_z
mode_tree . put ( "<xmlattr>.value" ,
model -> custom_gcode_per_print_z . mode == CustomGCode :: Mode :: SingleExtruder ? CustomGCode :: SingleExtruderMode :
model -> custom_gcode_per_print_z . mode == CustomGCode :: Mode :: MultiAsSingle ?
CustomGCode :: MultiAsSingleMode : CustomGCode :: MultiExtruderMode );
2019-11-11 16:01:34 +01:00
if ( ! tree . empty ())
{
std :: ostringstream oss ;
pt :: write_xml ( oss , tree );
out = oss . str ();
2019-12-20 12:11:58 +01:00
size_t del_header_pos = out . find ( "<custom_gcodes_per_height" );
2019-11-11 16:01:34 +01:00
if ( del_header_pos != std :: string :: npos )
out . erase ( out . begin (), out . begin () + del_header_pos );
// Post processing("beautification") of the output string
boost :: replace_all ( out , "><code" , "> \n <code" );
2020-01-23 12:49:39 +01:00
boost :: replace_all ( out , "><mode" , "> \n <mode" );
2019-11-11 16:01:34 +01:00
boost :: replace_all ( out , "><" , "> \n <" );
stream << out << " \n " ;
}
}
2018-02-14 14:30:03 +01:00
stream << "</amf> \n " ;
2018-03-26 08:58:44 +02:00
std :: string internal_amf_filename = boost :: ireplace_last_copy ( boost :: filesystem :: path ( export_path ). filename (). string (), ".zip.amf" , ".amf" );
2018-02-14 14:30:03 +01:00
std :: string out = stream . str ();
2018-02-14 15:52:39 +01:00
if ( ! mz_zip_writer_add_mem ( & archive , internal_amf_filename . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
2018-02-14 14:30:03 +01:00
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-03-26 08:58:44 +02:00
boost :: filesystem :: remove ( export_path );
2018-02-14 14:30:03 +01:00
return false ;
}
if ( ! mz_zip_writer_finalize_archive ( & archive ))
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-03-26 08:58:44 +02:00
boost :: filesystem :: remove ( export_path );
2018-02-14 14:30:03 +01:00
return false ;
}
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-02-14 14:30:03 +01:00
2017-02-26 21:46:33 +01:00
return true ;
}
}; // namespace Slic3r