2018-01-30 09:27:10 +01:00
#include "../libslic3r.h"
#include "../Model.hpp"
2018-02-13 15:19:55 +01:00
#include "../Utils.hpp"
#include "../GCode.hpp"
2018-10-12 12:19:57 +02:00
#include "../Geometry.hpp"
2019-10-23 13:31:24 +02:00
#include "../GCode/ThumbnailData.hpp"
2018-01-30 09:27:10 +01:00
2019-08-26 09:35:04 +02:00
#include "../I18N.hpp"
2018-01-30 09:27:10 +01:00
#include "3mf.hpp"
2019-02-27 10:31:53 +01:00
#include <limits>
2019-09-02 11:55:14 +02:00
#include <stdexcept>
2019-02-27 10:31:53 +01:00
2018-01-30 09:27:10 +01:00
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
2018-02-13 15:19:55 +01:00
#include <boost/algorithm/string/predicate.hpp>
2019-06-11 14:54:31 +02:00
#include <boost/algorithm/string/replace.hpp>
2018-02-08 13:26:50 +01:00
#include <boost/filesystem/operations.hpp>
#include <boost/nowide/fstream.hpp>
2019-06-02 22:27:35 +02:00
#include <boost/nowide/cstdio.hpp>
2018-01-30 09:27:10 +01:00
2019-06-27 13:54:51 +02:00
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/foreach.hpp>
namespace pt = boost :: property_tree ;
2018-01-30 09:27:10 +01:00
#include <expat.h>
2018-02-13 15:33:45 +01:00
#include <Eigen/Dense>
2019-06-03 15:27:46 +02:00
#include "miniz_extension.hpp"
2018-01-30 09:27:10 +01:00
2018-04-30 15:27:01 +02:00
// VERSION NUMBERS
// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them.
// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files.
2019-12-19 10:22:46 +01:00
// 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, 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 1
// the next change should use 3
const unsigned int VERSION_3MF = 1 ;
2019-12-19 10:22:46 +01:00
// Allow loading version 2 file as well.
const unsigned int VERSION_3MF_COMPATIBLE = 2 ;
2018-04-30 15:27:01 +02:00
const char * SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf" ; // definition of the metadata name saved into .model file
2018-02-08 13:26:50 +01:00
const std :: string MODEL_FOLDER = "3D/" ;
2018-01-30 09:27:10 +01:00
const std :: string MODEL_EXTENSION = ".model" ;
2018-02-08 13:26:50 +01:00
const std :: string MODEL_FILE = "3D/3dmodel.model" ; // << this is the only format of the string which works with CURA
const std :: string CONTENT_TYPES_FILE = "[Content_Types].xml" ;
const std :: string RELATIONSHIPS_FILE = "_rels/.rels" ;
2019-10-23 13:31:24 +02:00
const std :: string THUMBNAIL_FILE = "Metadata/thumbnail.png" ;
2018-02-27 10:49:51 +01:00
const std :: string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config" ;
const std :: string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config" ;
2018-04-27 12:56:35 +02:00
const std :: string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt" ;
2019-06-27 13:54:51 +02:00
const std :: string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml" ;
2018-10-05 10:13:21 +02:00
const std :: string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt" ;
2019-12-06 15:47:58 +01:00
const std :: string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt" ;
2019-12-17 08:37:50 +01:00
const std :: string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml" ;
2018-01-30 09:27:10 +01:00
const char * MODEL_TAG = "model" ;
const char * RESOURCES_TAG = "resources" ;
const char * OBJECT_TAG = "object" ;
const char * MESH_TAG = "mesh" ;
const char * VERTICES_TAG = "vertices" ;
const char * VERTEX_TAG = "vertex" ;
const char * TRIANGLES_TAG = "triangles" ;
const char * TRIANGLE_TAG = "triangle" ;
const char * COMPONENTS_TAG = "components" ;
const char * COMPONENT_TAG = "component" ;
const char * BUILD_TAG = "build" ;
const char * ITEM_TAG = "item" ;
2018-04-30 15:27:01 +02:00
const char * METADATA_TAG = "metadata" ;
2018-01-30 09:27:10 +01:00
2018-02-27 10:49:51 +01:00
const char * CONFIG_TAG = "config" ;
const char * VOLUME_TAG = "volume" ;
2018-01-30 09:27:10 +01:00
const char * UNIT_ATTR = "unit" ;
const char * NAME_ATTR = "name" ;
const char * TYPE_ATTR = "type" ;
const char * ID_ATTR = "id" ;
const char * X_ATTR = "x" ;
const char * Y_ATTR = "y" ;
const char * Z_ATTR = "z" ;
const char * V1_ATTR = "v1" ;
const char * V2_ATTR = "v2" ;
const char * V3_ATTR = "v3" ;
const char * OBJECTID_ATTR = "objectid" ;
const char * TRANSFORM_ATTR = "transform" ;
2019-08-06 14:54:38 +02:00
const char * PRINTABLE_ATTR = "printable" ;
2020-01-02 11:37:50 +01:00
const char * INSTANCESCOUNT_ATTR = "instances_count" ;
2018-01-30 09:27:10 +01:00
2018-02-27 10:49:51 +01:00
const char * KEY_ATTR = "key" ;
const char * VALUE_ATTR = "value" ;
const char * FIRST_TRIANGLE_ID_ATTR = "firstid" ;
const char * LAST_TRIANGLE_ID_ATTR = "lastid" ;
const char * OBJECT_TYPE = "object" ;
const char * VOLUME_TYPE = "volume" ;
2018-02-28 12:11:41 +01:00
const char * NAME_KEY = "name" ;
const char * MODIFIER_KEY = "modifier" ;
2018-09-17 15:12:13 +02:00
const char * VOLUME_TYPE_KEY = "volume_type" ;
2019-09-19 09:09:11 +02:00
const char * MATRIX_KEY = "matrix" ;
const char * SOURCE_FILE_KEY = "source_file" ;
const char * SOURCE_OBJECT_ID_KEY = "source_object_id" ;
const char * SOURCE_VOLUME_ID_KEY = "source_volume_id" ;
const char * SOURCE_OFFSET_X_KEY = "source_offset_x" ;
const char * SOURCE_OFFSET_Y_KEY = "source_offset_y" ;
const char * SOURCE_OFFSET_Z_KEY = "source_offset_z" ;
2018-02-28 12:11:41 +01:00
2018-01-30 09:27:10 +01:00
const unsigned int VALID_OBJECT_TYPES_COUNT = 1 ;
const char * VALID_OBJECT_TYPES [] =
{
"model"
};
const unsigned int INVALID_OBJECT_TYPES_COUNT = 4 ;
const char * INVALID_OBJECT_TYPES [] =
{
"solidsupport" ,
"support" ,
"surface" ,
"other"
};
2019-09-02 11:55:14 +02:00
class version_error : public std :: runtime_error
{
public :
version_error ( const std :: string & what_arg ) : std :: runtime_error ( what_arg ) {}
version_error ( const char * what_arg ) : std :: runtime_error ( what_arg ) {}
};
2018-01-30 09:27:10 +01:00
const char * get_attribute_value_charptr ( const char ** attributes , unsigned int attributes_size , const char * attribute_key )
{
if (( attributes == nullptr ) || ( attributes_size == 0 ) || ( attributes_size % 2 != 0 ) || ( attribute_key == nullptr ))
return nullptr ;
for ( unsigned int a = 0 ; a < attributes_size ; a += 2 )
{
if ( :: strcmp ( attributes [ a ], attribute_key ) == 0 )
return attributes [ a + 1 ];
}
return nullptr ;
}
std :: string get_attribute_value_string ( const char ** attributes , unsigned int attributes_size , const char * attribute_key )
{
const char * text = get_attribute_value_charptr ( attributes , attributes_size , attribute_key );
return ( text != nullptr ) ? text : "" ;
}
float get_attribute_value_float ( const char ** attributes , unsigned int attributes_size , const char * attribute_key )
{
const char * text = get_attribute_value_charptr ( attributes , attributes_size , attribute_key );
return ( text != nullptr ) ? ( float ) :: atof ( text ) : 0.0f ;
}
int get_attribute_value_int ( const char ** attributes , unsigned int attributes_size , const char * attribute_key )
{
const char * text = get_attribute_value_charptr ( attributes , attributes_size , attribute_key );
return ( text != nullptr ) ? :: atoi ( text ) : 0 ;
}
2019-08-06 14:54:38 +02:00
bool get_attribute_value_bool ( const char ** attributes , unsigned int attributes_size , const char * attribute_key )
{
const char * text = get_attribute_value_charptr ( attributes , attributes_size , attribute_key );
return ( text != nullptr ) ? ( bool ) :: atoi ( text ) : true ;
}
2019-09-19 09:09:11 +02:00
Slic3r :: Transform3d get_transform_from_3mf_specs_string ( const std :: string & mat_str )
2018-01-30 09:27:10 +01:00
{
2019-09-19 09:09:11 +02:00
// check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md
// to see how matrices are stored inside 3mf according to specifications
Slic3r :: Transform3d ret = Slic3r :: Transform3d :: Identity ();
2018-01-30 09:27:10 +01:00
if ( mat_str . empty ())
// empty string means default identity matrix
2019-09-19 09:09:11 +02:00
return ret ;
2018-01-30 09:27:10 +01:00
std :: vector < std :: string > mat_elements_str ;
boost :: split ( mat_elements_str , mat_str , boost :: is_any_of ( " " ), boost :: token_compress_on );
unsigned int size = ( unsigned int ) mat_elements_str . size ();
if ( size != 12 )
// invalid data, return identity matrix
2019-09-19 09:09:11 +02:00
return ret ;
2018-01-30 09:27:10 +01:00
unsigned int i = 0 ;
// matrices are stored into 3mf files as 4x3
// we need to transpose them
for ( unsigned int c = 0 ; c < 4 ; ++ c )
{
for ( unsigned int r = 0 ; r < 3 ; ++ r )
{
2018-08-28 14:30:36 +02:00
ret ( r , c ) = :: atof ( mat_elements_str [ i ++ ]. c_str ());
2018-01-30 09:27:10 +01:00
}
}
return ret ;
}
float get_unit_factor ( const std :: string & unit )
{
const char * text = unit . c_str ();
if ( :: strcmp ( text , "micron" ) == 0 )
return 0.001f ;
else if ( :: strcmp ( text , "centimeter" ) == 0 )
return 10.0f ;
else if ( :: strcmp ( text , "inch" ) == 0 )
return 25.4f ;
else if ( :: strcmp ( text , "foot" ) == 0 )
return 304.8f ;
else if ( :: strcmp ( text , "meter" ) == 0 )
return 1000.0f ;
else
// default "millimeters" (see specification)
return 1.0f ;
}
bool is_valid_object_type ( const std :: string & type )
{
// if the type is empty defaults to "model" (see specification)
if ( type . empty ())
return true ;
for ( unsigned int i = 0 ; i < VALID_OBJECT_TYPES_COUNT ; ++ i )
{
if ( :: strcmp ( type . c_str (), VALID_OBJECT_TYPES [ i ]) == 0 )
return true ;
}
return false ;
}
namespace Slic3r {
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)
2018-02-14 14:30:03 +01:00
// Base class with error messages management
class _3MF_Base
{
std :: vector < std :: string > m_errors ;
protected :
void add_error ( const std :: string & error ) { m_errors . push_back ( error ); }
void clear_errors () { m_errors . clear (); }
public :
void log_errors ()
{
for ( const std :: string & error : m_errors )
{
printf ( "%s \n " , error . c_str ());
}
}
};
class _3MF_Importer : public _3MF_Base
2018-01-30 09:27:10 +01:00
{
struct Component
{
int object_id ;
2018-08-28 14:30:36 +02:00
Transform3d transform ;
2018-01-30 09:27:10 +01:00
2018-02-27 10:49:51 +01:00
explicit Component ( int object_id )
: object_id ( object_id )
2018-08-28 14:30:36 +02:00
, transform ( Transform3d :: Identity ())
2018-02-27 10:49:51 +01:00
{
}
2018-08-28 14:30:36 +02:00
Component ( int object_id , const Transform3d & transform )
2018-02-27 10:49:51 +01:00
: object_id ( object_id )
2018-08-28 14:30:36 +02:00
, transform ( transform )
2018-02-27 10:49:51 +01:00
{
}
2018-01-30 09:27:10 +01:00
};
typedef std :: vector < Component > ComponentsList ;
2018-02-27 10:49:51 +01:00
struct Geometry
{
std :: vector < float > vertices ;
std :: vector < unsigned int > triangles ;
bool empty ()
{
return vertices . empty () || triangles . empty ();
}
void reset ()
{
vertices . clear ();
triangles . clear ();
}
};
2018-01-30 09:27:10 +01:00
struct CurrentObject
{
2019-05-24 12:17:54 +02:00
// ID of the object inside the 3MF file, 1 based.
2018-01-30 09:27:10 +01:00
int id ;
2019-05-24 12:17:54 +02:00
// Index of the ModelObject in its respective Model, zero based.
int model_object_idx ;
2018-01-30 09:27:10 +01:00
Geometry geometry ;
ModelObject * object ;
ComponentsList components ;
2018-02-27 10:49:51 +01:00
CurrentObject ()
{
reset ();
}
2018-01-30 09:27:10 +01:00
2018-02-27 10:49:51 +01:00
void reset ()
{
id = - 1 ;
2019-05-24 12:17:54 +02:00
model_object_idx = - 1 ;
2018-02-27 10:49:51 +01:00
geometry . reset ();
object = nullptr ;
components . clear ();
}
};
struct CurrentConfig
{
int object_id ;
int volume_id ;
2018-01-30 09:27:10 +01:00
};
struct Instance
{
ModelInstance * instance ;
2018-08-28 14:30:36 +02:00
Transform3d transform ;
2018-01-30 09:27:10 +01:00
2018-08-28 14:30:36 +02:00
Instance ( ModelInstance * instance , const Transform3d & transform )
2018-02-27 10:49:51 +01:00
: instance ( instance )
2018-08-28 14:30:36 +02:00
, transform ( transform )
2018-02-27 10:49:51 +01:00
{
}
};
struct Metadata
{
std :: string key ;
std :: string value ;
Metadata ( const std :: string & key , const std :: string & value )
: key ( key )
, value ( value )
{
}
};
typedef std :: vector < Metadata > MetadataList ;
struct ObjectMetadata
{
struct VolumeMetadata
{
unsigned int first_triangle_id ;
unsigned int last_triangle_id ;
MetadataList metadata ;
VolumeMetadata ( unsigned int first_triangle_id , unsigned int last_triangle_id )
: first_triangle_id ( first_triangle_id )
, last_triangle_id ( last_triangle_id )
{
}
};
typedef std :: vector < VolumeMetadata > VolumeMetadataList ;
MetadataList metadata ;
VolumeMetadataList volumes ;
2018-01-30 09:27:10 +01:00
};
2019-05-24 12:17:54 +02:00
// Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects.
typedef std :: map < int , int > IdToModelObjectMap ;
2018-01-30 09:27:10 +01:00
typedef std :: map < int , ComponentsList > IdToAliasesMap ;
typedef std :: vector < Instance > InstancesList ;
2018-02-27 10:49:51 +01:00
typedef std :: map < int , ObjectMetadata > IdToMetadataMap ;
typedef std :: map < int , Geometry > IdToGeometryMap ;
2018-04-27 12:56:35 +02:00
typedef std :: map < int , std :: vector < coordf_t >> IdToLayerHeightsProfileMap ;
2019-06-11 14:39:41 +02:00
typedef std :: map < int , t_layer_config_ranges > IdToLayerConfigRangesMap ;
2019-01-30 08:26:23 +01:00
typedef std :: map < int , std :: vector < sla :: SupportPoint >> IdToSlaSupportPointsMap ;
2019-12-06 15:47:58 +01:00
typedef std :: map < int , std :: vector < sla :: DrainHole >> IdToSlaDrainHolesMap ;
2018-01-30 09:27:10 +01:00
2018-04-30 15:27:01 +02:00
// Version of the 3mf file
unsigned int m_version ;
2019-08-23 13:12:31 +02:00
bool m_check_version ;
2018-04-30 15:27:01 +02:00
2018-01-30 09:27:10 +01:00
XML_Parser m_xml_parser ;
Model * m_model ;
float m_unit_factor ;
CurrentObject m_curr_object ;
IdToModelObjectMap m_objects ;
IdToAliasesMap m_objects_aliases ;
InstancesList m_instances ;
2018-02-27 10:49:51 +01:00
IdToGeometryMap m_geometries ;
CurrentConfig m_curr_config ;
IdToMetadataMap m_objects_metadata ;
2018-04-27 12:56:35 +02:00
IdToLayerHeightsProfileMap m_layer_heights_profiles ;
2019-06-11 14:39:41 +02:00
IdToLayerConfigRangesMap m_layer_config_ranges ;
2018-10-05 10:13:21 +02:00
IdToSlaSupportPointsMap m_sla_support_points ;
2019-12-06 15:47:58 +01:00
IdToSlaDrainHolesMap m_sla_drain_holes ;
2018-04-30 15:27:01 +02:00
std :: string m_curr_metadata_name ;
std :: string m_curr_characters ;
2018-11-08 10:18:19 +01:00
std :: string m_name ;
2018-01-30 09:27:10 +01:00
public :
_3MF_Importer ();
~ _3MF_Importer ();
2019-08-23 13:12:31 +02:00
bool load_model_from_file ( const std :: string & filename , Model & model , DynamicPrintConfig & config , bool check_version );
2018-01-30 09:27:10 +01:00
private :
void _destroy_xml_parser ();
void _stop_xml_parser ();
2018-09-25 11:53:05 +02:00
bool _load_model_from_file ( const std :: string & filename , Model & model , DynamicPrintConfig & config );
2018-02-13 15:19:55 +01:00
bool _extract_model_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat );
2018-04-27 12:56:35 +02:00
void _extract_layer_heights_profile_config_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat );
2019-06-11 14:39:41 +02:00
void _extract_layer_config_ranges_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat );
2018-10-05 10:13:21 +02:00
void _extract_sla_support_points_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat );
2019-12-06 15:47:58 +01:00
void _extract_sla_drain_holes_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat );
2018-10-26 15:45:52 +02:00
2019-12-17 08:37:50 +01:00
void _extract_custom_gcode_per_print_z_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat );
2019-11-11 16:01:34 +01:00
2018-09-25 11:53:05 +02:00
void _extract_print_config_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat , DynamicPrintConfig & config , const std :: string & archive_filename );
2018-02-27 10:49:51 +01:00
bool _extract_model_config_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat , Model & model );
2018-01-30 09:27:10 +01:00
2018-02-27 10:49:51 +01:00
// handlers to parse the .model file
void _handle_start_model_xml_element ( const char * name , const char ** attributes );
void _handle_end_model_xml_element ( const char * name );
2018-04-30 15:27:01 +02:00
void _handle_model_xml_characters ( const XML_Char * s , int len );
2018-02-27 10:49:51 +01:00
// handlers to parse the MODEL_CONFIG_FILE file
void _handle_start_config_xml_element ( const char * name , const char ** attributes );
void _handle_end_config_xml_element ( const char * name );
2018-01-30 09:27:10 +01:00
bool _handle_start_model ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_model ();
bool _handle_start_resources ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_resources ();
bool _handle_start_object ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_object ();
bool _handle_start_mesh ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_mesh ();
bool _handle_start_vertices ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_vertices ();
bool _handle_start_vertex ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_vertex ();
bool _handle_start_triangles ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_triangles ();
bool _handle_start_triangle ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_triangle ();
bool _handle_start_components ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_components ();
bool _handle_start_component ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_component ();
bool _handle_start_build ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_build ();
bool _handle_start_item ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_item ();
2018-04-30 15:27:01 +02:00
bool _handle_start_metadata ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_metadata ();
2019-08-06 14:54:38 +02:00
bool _create_object_instance ( int object_id , const Transform3d & transform , const bool printable , unsigned int recur_counter );
2018-01-30 09:27:10 +01:00
2018-08-28 14:30:36 +02:00
void _apply_transform ( ModelInstance & instance , const Transform3d & transform );
2018-01-30 09:27:10 +01:00
2018-02-27 10:49:51 +01:00
bool _handle_start_config ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_config ();
bool _handle_start_config_object ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_config_object ();
bool _handle_start_config_volume ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_config_volume ();
bool _handle_start_config_metadata ( const char ** attributes , unsigned int num_attributes );
bool _handle_end_config_metadata ();
bool _generate_volumes ( ModelObject & object , const Geometry & geometry , const ObjectMetadata :: VolumeMetadataList & volumes );
// callbacks to parse the .model file
static void XMLCALL _handle_start_model_xml_element ( void * userData , const char * name , const char ** attributes );
static void XMLCALL _handle_end_model_xml_element ( void * userData , const char * name );
2018-04-30 15:27:01 +02:00
static void XMLCALL _handle_model_xml_characters ( void * userData , const XML_Char * s , int len );
2018-02-27 10:49:51 +01:00
// callbacks to parse the MODEL_CONFIG_FILE file
static void XMLCALL _handle_start_config_xml_element ( void * userData , const char * name , const char ** attributes );
static void XMLCALL _handle_end_config_xml_element ( void * userData , const char * name );
2018-01-30 09:27:10 +01:00
};
_3MF_Importer :: _3MF_Importer ()
2018-04-30 15:27:01 +02:00
: m_version ( 0 )
2019-08-23 13:12:31 +02:00
, m_check_version ( false )
2018-04-30 15:27:01 +02:00
, m_xml_parser ( nullptr )
2018-01-30 09:27:10 +01:00
, m_model ( nullptr )
, m_unit_factor ( 1.0f )
2018-04-30 15:27:01 +02:00
, m_curr_metadata_name ( "" )
, m_curr_characters ( "" )
2018-11-08 10:18:19 +01:00
, m_name ( "" )
2018-01-30 09:27:10 +01:00
{
}
_3MF_Importer ::~ _3MF_Importer ()
{
_destroy_xml_parser ();
}
2019-08-23 13:12:31 +02:00
bool _3MF_Importer :: load_model_from_file ( const std :: string & filename , Model & model , DynamicPrintConfig & config , bool check_version )
2018-01-30 09:27:10 +01:00
{
2018-04-30 15:27:01 +02:00
m_version = 0 ;
2019-08-23 13:12:31 +02:00
m_check_version = check_version ;
2018-01-30 09:27:10 +01:00
m_model = & model ;
m_unit_factor = 1.0f ;
m_curr_object . reset ();
m_objects . clear ();
m_objects_aliases . clear ();
m_instances . clear ();
2018-02-27 10:49:51 +01:00
m_geometries . clear ();
m_curr_config . object_id = - 1 ;
m_curr_config . volume_id = - 1 ;
m_objects_metadata . clear ();
2018-04-27 12:56:35 +02:00
m_layer_heights_profiles . clear ();
2019-06-11 14:39:41 +02:00
m_layer_config_ranges . clear ();
2018-10-05 10:13:21 +02:00
m_sla_support_points . clear ();
2018-04-30 15:27:01 +02:00
m_curr_metadata_name . clear ();
m_curr_characters . clear ();
2018-02-14 14:30:03 +01:00
clear_errors ();
2018-01-30 09:27:10 +01:00
2018-09-25 11:53:05 +02:00
return _load_model_from_file ( filename , model , config );
2018-01-30 09:27:10 +01:00
}
void _3MF_Importer :: _destroy_xml_parser ()
{
if ( m_xml_parser != nullptr )
{
XML_ParserFree ( m_xml_parser );
m_xml_parser = nullptr ;
}
}
void _3MF_Importer :: _stop_xml_parser ()
{
if ( m_xml_parser != nullptr )
XML_StopParser ( m_xml_parser , false );
}
2018-09-25 11:53:05 +02:00
bool _3MF_Importer :: _load_model_from_file ( const std :: string & filename , Model & model , DynamicPrintConfig & config )
2018-01-30 09:27:10 +01:00
{
mz_zip_archive archive ;
mz_zip_zero_struct ( & archive );
2019-06-02 22:27:35 +02:00
2019-06-03 15:27:46 +02:00
if ( ! open_zip_reader ( & archive , filename )) {
2018-02-14 14:30:03 +01:00
add_error ( "Unable to open the file" );
2018-01-30 09:27:10 +01:00
return false ;
}
mz_uint num_entries = mz_zip_reader_get_num_files ( & archive );
mz_zip_archive_file_stat stat ;
2018-04-30 15:27:01 +02:00
2018-11-08 10:18:19 +01:00
m_name = boost :: filesystem :: path ( filename ). filename (). stem (). string ();
2018-04-30 15:27:01 +02:00
// we first loop the entries to read from the archive the .model file only, in order to extract the version from it
2018-01-30 09:27:10 +01:00
for ( mz_uint i = 0 ; i < num_entries ; ++ i )
{
if ( mz_zip_reader_file_stat ( & archive , i , & stat ))
{
std :: string name ( stat . m_filename );
2018-02-08 13:26:50 +01:00
std :: replace ( name . begin (), name . end (), '\\' , '/' );
2018-01-30 09:27:10 +01:00
2018-02-13 15:19:55 +01:00
if ( boost :: algorithm :: istarts_with ( name , MODEL_FOLDER ) && boost :: algorithm :: iends_with ( name , MODEL_EXTENSION ))
2018-01-30 09:27:10 +01:00
{
2019-08-23 13:12:31 +02:00
try
2018-01-30 09:27:10 +01:00
{
2019-08-23 13:12:31 +02:00
// valid model name -> extract model
if ( ! _extract_model_from_archive ( archive , stat ))
{
close_zip_reader ( & archive );
add_error ( "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-01-30 09:27:10 +01:00
}
}
2018-04-30 15:27:01 +02:00
}
}
// we then loop again the entries to read other files stored in the archive
for ( mz_uint i = 0 ; i < num_entries ; ++ i )
{
if ( mz_zip_reader_file_stat ( & archive , i , & stat ))
{
std :: string name ( stat . m_filename );
std :: replace ( name . begin (), name . end (), '\\' , '/' );
if ( boost :: algorithm :: iequals ( name , LAYER_HEIGHTS_PROFILE_FILE ))
2018-04-27 12:56:35 +02:00
{
2019-06-11 14:39:41 +02:00
// extract slic3r layer heights profile file
2018-04-27 12:56:35 +02:00
_extract_layer_heights_profile_config_from_archive ( archive , stat );
}
2019-06-11 14:39:41 +02:00
if ( boost :: algorithm :: iequals ( name , LAYER_CONFIG_RANGES_FILE ))
{
// extract slic3r layer config ranges file
_extract_layer_config_ranges_from_archive ( archive , stat );
}
2018-10-05 10:13:21 +02:00
else if ( boost :: algorithm :: iequals ( name , SLA_SUPPORT_POINTS_FILE ))
{
// extract sla support points file
_extract_sla_support_points_from_archive ( archive , stat );
}
2019-12-06 15:47:58 +01:00
else if ( boost :: algorithm :: iequals ( name , SLA_DRAIN_HOLES_FILE ))
{
// extract sla support points file
_extract_sla_drain_holes_from_archive ( archive , stat );
}
2018-02-27 10:49:51 +01:00
else if ( boost :: algorithm :: iequals ( name , PRINT_CONFIG_FILE ))
2018-02-13 15:19:55 +01:00
{
2018-02-27 10:49:51 +01:00
// extract slic3r print config file
2018-09-25 11:53:05 +02:00
_extract_print_config_from_archive ( archive , stat , config , filename );
2018-02-27 10:49:51 +01:00
}
2019-12-17 08:37:50 +01:00
if ( boost :: algorithm :: iequals ( name , CUSTOM_GCODE_PER_PRINT_Z_FILE ))
2019-11-11 16:01:34 +01:00
{
// extract slic3r layer config ranges file
2019-12-17 08:37:50 +01:00
_extract_custom_gcode_per_print_z_from_archive ( archive , stat );
2019-11-11 16:01:34 +01:00
}
2018-02-27 10:49:51 +01:00
else if ( boost :: algorithm :: iequals ( name , MODEL_CONFIG_FILE ))
{
// extract slic3r model config file
if ( ! _extract_model_config_from_archive ( archive , stat , model ))
{
2019-06-03 15:27:46 +02:00
close_zip_reader ( & archive );
2018-02-27 10:49:51 +01:00
add_error ( "Archive does not contain a valid model config" );
2018-02-13 15:19:55 +01:00
return false ;
}
}
2018-01-30 09:27:10 +01:00
}
}
2019-06-03 15:27:46 +02:00
close_zip_reader ( & archive );
2018-02-27 10:49:51 +01:00
for ( const IdToModelObjectMap :: value_type & object : m_objects )
{
2019-05-24 12:17:54 +02:00
ModelObject * model_object = m_model -> objects [ object . second ];
2018-02-27 10:49:51 +01:00
ObjectMetadata :: VolumeMetadataList volumes ;
ObjectMetadata :: VolumeMetadataList * volumes_ptr = nullptr ;
IdToGeometryMap :: const_iterator obj_geometry = m_geometries . find ( object . first );
if ( obj_geometry == m_geometries . end ())
{
add_error ( "Unable to find object geometry" );
return false ;
}
2019-05-24 12:17:54 +02:00
// m_layer_heights_profiles are indexed by a 1 based model object index.
IdToLayerHeightsProfileMap :: iterator obj_layer_heights_profile = m_layer_heights_profiles . find ( object . second + 1 );
2018-04-27 12:56:35 +02:00
if ( obj_layer_heights_profile != m_layer_heights_profiles . end ())
2019-05-24 12:17:54 +02:00
model_object -> layer_height_profile = obj_layer_heights_profile -> second ;
2018-04-27 12:56:35 +02:00
2019-06-11 14:39:41 +02:00
// m_layer_config_ranges are indexed by a 1 based model object index.
IdToLayerConfigRangesMap :: iterator obj_layer_config_ranges = m_layer_config_ranges . find ( object . second + 1 );
if ( obj_layer_config_ranges != m_layer_config_ranges . end ())
model_object -> layer_config_ranges = obj_layer_config_ranges -> second ;
2019-05-24 12:17:54 +02:00
// m_sla_support_points are indexed by a 1 based model object index.
IdToSlaSupportPointsMap :: iterator obj_sla_support_points = m_sla_support_points . find ( object . second + 1 );
2019-03-11 11:13:37 +01:00
if ( obj_sla_support_points != m_sla_support_points . end () && ! obj_sla_support_points -> second . empty ()) {
2019-05-24 12:17:54 +02:00
model_object -> sla_support_points = obj_sla_support_points -> second ;
model_object -> sla_points_status = sla :: PointsStatus :: UserModified ;
2019-03-11 11:13:37 +01:00
}
2019-12-06 15:47:58 +01:00
IdToSlaDrainHolesMap :: iterator obj_drain_holes = m_sla_drain_holes . find ( object . second + 1 );
if ( obj_drain_holes != m_sla_drain_holes . end () && ! obj_drain_holes -> second . empty ()) {
model_object -> sla_drain_holes = obj_drain_holes -> second ;
}
2018-10-05 10:13:21 +02:00
2018-02-27 10:49:51 +01:00
IdToMetadataMap :: iterator obj_metadata = m_objects_metadata . find ( object . first );
if ( obj_metadata != m_objects_metadata . end ())
{
// config data has been found, this model was saved using slic3r pe
2018-02-27 15:46:54 +01:00
// apply object's name and config data
2018-02-27 10:49:51 +01:00
for ( const Metadata & metadata : obj_metadata -> second . metadata )
{
2018-02-27 15:46:54 +01:00
if ( metadata . key == "name" )
2019-05-24 12:17:54 +02:00
model_object -> name = metadata . value ;
2018-02-27 15:46:54 +01:00
else
2019-05-24 12:17:54 +02:00
model_object -> config . set_deserialize ( metadata . key , metadata . value );
2018-02-27 10:49:51 +01:00
}
// select object's detected volumes
volumes_ptr = & obj_metadata -> second . volumes ;
}
else
{
// config data not found, this model was not saved using slic3r pe
// add the entire geometry as the single volume to generate
volumes . emplace_back ( 0 , ( int ) obj_geometry -> second . triangles . size () / 3 - 1 );
// select as volumes
volumes_ptr = & volumes ;
}
2019-05-24 12:17:54 +02:00
if ( ! _generate_volumes ( * model_object , obj_geometry -> second , * volumes_ptr ))
2018-02-27 10:49:51 +01:00
return false ;
}
2020-01-06 12:10:57 +01:00
// // fixes the min z of the model if negative
// model.adjust_min_z();
2018-03-06 10:26:39 +01:00
2018-01-30 09:27:10 +01:00
return true ;
}
2018-02-13 15:19:55 +01:00
bool _3MF_Importer :: _extract_model_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat )
2018-01-30 09:27:10 +01:00
{
2018-02-14 14:30:03 +01:00
if ( stat . m_uncomp_size == 0 )
{
add_error ( "Found invalid size" );
return false ;
}
2018-01-30 09:27:10 +01:00
_destroy_xml_parser ();
m_xml_parser = XML_ParserCreate ( nullptr );
if ( m_xml_parser == nullptr )
{
2018-02-14 14:30:03 +01:00
add_error ( "Unable to create parser" );
2018-01-30 09:27:10 +01:00
return false ;
}
XML_SetUserData ( m_xml_parser , ( void * ) this );
2018-02-27 10:49:51 +01:00
XML_SetElementHandler ( m_xml_parser , _3MF_Importer :: _handle_start_model_xml_element , _3MF_Importer :: _handle_end_model_xml_element );
2018-04-30 15:27:01 +02:00
XML_SetCharacterDataHandler ( m_xml_parser , _3MF_Importer :: _handle_model_xml_characters );
2018-01-30 09:27:10 +01:00
2019-08-28 16:03:26 +02:00
struct CallbackData
2018-01-30 09:27:10 +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 ( m_xml_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 );
}
2019-09-02 11:55:14 +02:00
catch ( const version_error & e )
{
// rethrow the exception
throw std :: runtime_error ( e . what ());
}
2019-08-28 16:03:26 +02:00
catch ( std :: exception & e )
{
add_error ( e . what ());
2018-01-30 09:27:10 +01:00
return false ;
}
if ( res == 0 )
{
2019-08-28 16:03:26 +02:00
add_error ( "Error while extracting model data from zip archive" );
2018-01-30 09:27:10 +01:00
return false ;
}
return true ;
}
2018-09-25 11:53:05 +02:00
void _3MF_Importer :: _extract_print_config_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat , DynamicPrintConfig & config , const std :: string & archive_filename )
2018-02-13 15:19:55 +01:00
{
if ( stat . m_uncomp_size > 0 )
{
2018-04-27 12:56:35 +02:00
std :: string buffer (( size_t ) stat . m_uncomp_size , 0 );
2018-02-13 15:19:55 +01:00
mz_bool res = mz_zip_reader_extract_file_to_mem ( & archive , stat . m_filename , ( void * ) buffer . data (), ( size_t ) stat . m_uncomp_size , 0 );
if ( res == 0 )
{
2018-02-14 14:30:03 +01:00
add_error ( "Error while reading config data to buffer" );
2018-04-27 12:56:35 +02:00
return ;
2018-02-13 15:19:55 +01:00
}
2018-09-25 11:53:05 +02:00
config . load_from_gcode_string ( buffer . data ());
2018-02-13 15:19:55 +01:00
}
2018-04-27 12:56:35 +02:00
}
2018-02-13 15:19:55 +01:00
2018-04-27 12:56:35 +02:00
void _3MF_Importer :: _extract_layer_heights_profile_config_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat )
{
if ( stat . m_uncomp_size > 0 )
{
std :: string buffer (( size_t ) stat . m_uncomp_size , 0 );
mz_bool res = mz_zip_reader_extract_file_to_mem ( & archive , stat . m_filename , ( void * ) buffer . data (), ( size_t ) stat . m_uncomp_size , 0 );
if ( res == 0 )
{
add_error ( "Error while reading layer heights profile data to buffer" );
return ;
}
if ( buffer . back () == '\n' )
buffer . pop_back ();
std :: vector < std :: string > objects ;
boost :: split ( objects , buffer , boost :: is_any_of ( " \n " ), boost :: token_compress_off );
for ( const std :: string & object : objects )
{
std :: vector < std :: string > object_data ;
boost :: split ( object_data , object , boost :: is_any_of ( "|" ), boost :: token_compress_off );
if ( object_data . size () != 2 )
{
add_error ( "Error while reading object data" );
continue ;
}
std :: vector < std :: string > object_data_id ;
boost :: split ( object_data_id , object_data [ 0 ], boost :: is_any_of ( "=" ), boost :: token_compress_off );
if ( object_data_id . size () != 2 )
{
add_error ( "Error while reading object id" );
continue ;
}
int object_id = std :: atoi ( object_data_id [ 1 ]. c_str ());
if ( object_id == 0 )
{
add_error ( "Found invalid object id" );
continue ;
}
IdToLayerHeightsProfileMap :: iterator object_item = m_layer_heights_profiles . find ( object_id );
if ( object_item != m_layer_heights_profiles . end ())
{
add_error ( "Found duplicated layer heights profile" );
continue ;
}
std :: vector < std :: string > object_data_profile ;
boost :: split ( object_data_profile , object_data [ 1 ], boost :: is_any_of ( ";" ), boost :: token_compress_off );
if (( object_data_profile . size () <= 4 ) || ( object_data_profile . size () % 2 != 0 ))
{
add_error ( "Found invalid layer heights profile" );
continue ;
}
std :: vector < coordf_t > profile ;
profile . reserve ( object_data_profile . size ());
for ( const std :: string & value : object_data_profile )
{
profile . push_back (( coordf_t ) std :: atof ( value . c_str ()));
}
m_layer_heights_profiles . insert ( IdToLayerHeightsProfileMap :: value_type ( object_id , profile ));
}
}
2018-02-13 15:19:55 +01:00
}
2019-06-11 14:39:41 +02:00
void _3MF_Importer :: _extract_layer_config_ranges_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat )
{
if ( stat . m_uncomp_size > 0 )
{
std :: string buffer (( size_t ) stat . m_uncomp_size , 0 );
mz_bool res = mz_zip_reader_extract_file_to_mem ( & archive , stat . m_filename , ( void * ) buffer . data (), ( size_t ) stat . m_uncomp_size , 0 );
if ( res == 0 ) {
add_error ( "Error while reading layer config ranges data to buffer" );
return ;
}
2019-06-27 13:54:51 +02:00
std :: istringstream iss ( buffer ); // wrap returned xml to istringstream
pt :: ptree objects_tree ;
pt :: read_xml ( iss , objects_tree );
2019-06-11 14:39:41 +02:00
2019-06-27 13:54:51 +02:00
for ( const auto & object : objects_tree . get_child ( "objects" ))
2019-06-11 14:39:41 +02:00
{
2019-06-27 13:54:51 +02:00
pt :: ptree object_tree = object . second ;
int obj_idx = object_tree . get < int > ( "<xmlattr>.id" , - 1 );
if ( obj_idx <= 0 ) {
2019-06-11 14:39:41 +02:00
add_error ( "Found invalid object id" );
continue ;
}
2019-06-27 13:54:51 +02:00
IdToLayerConfigRangesMap :: iterator object_item = m_layer_config_ranges . find ( obj_idx );
2019-06-11 14:39:41 +02:00
if ( object_item != m_layer_config_ranges . end ()) {
add_error ( "Found duplicated layer config range" );
continue ;
}
t_layer_config_ranges config_ranges ;
2019-06-27 15:57:29 +02:00
for ( const auto & range : object_tree )
2019-06-11 14:39:41 +02:00
{
2019-06-27 15:57:29 +02:00
if ( range . first != "range" )
continue ;
2019-06-27 13:54:51 +02:00
pt :: ptree range_tree = range . second ;
double min_z = range_tree . get < double > ( "<xmlattr>.min_z" );
double max_z = range_tree . get < double > ( "<xmlattr>.max_z" );
2019-06-11 14:39:41 +02:00
// get Z range information
2019-06-27 13:54:51 +02:00
DynamicPrintConfig & config = config_ranges [{ min_z , max_z }];
2019-06-11 14:39:41 +02:00
2019-06-27 15:57:29 +02:00
for ( const auto & option : range_tree )
2019-06-11 14:39:41 +02:00
{
2019-06-27 15:57:29 +02:00
if ( option . first != "option" )
continue ;
2019-06-27 13:54:51 +02:00
std :: string opt_key = option . second . get < std :: string > ( "<xmlattr>.opt_key" );
std :: string value = option . second . data ();
config . set_deserialize ( opt_key , value );
2019-06-11 14:39:41 +02:00
}
}
if ( ! config_ranges . empty ())
2019-06-27 13:54:51 +02:00
m_layer_config_ranges . insert ( IdToLayerConfigRangesMap :: value_type ( obj_idx , config_ranges ));
2019-06-11 14:39:41 +02:00
}
}
}
2018-10-05 10:13:21 +02:00
void _3MF_Importer :: _extract_sla_support_points_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat )
{
if ( stat . m_uncomp_size > 0 )
{
std :: string buffer (( size_t ) stat . m_uncomp_size , 0 );
mz_bool res = mz_zip_reader_extract_file_to_mem ( & archive , stat . m_filename , ( void * ) buffer . data (), ( size_t ) stat . m_uncomp_size , 0 );
if ( res == 0 )
{
add_error ( "Error while reading sla support points data to buffer" );
return ;
}
if ( buffer . back () == '\n' )
buffer . pop_back ();
std :: vector < std :: string > objects ;
boost :: split ( objects , buffer , boost :: is_any_of ( " \n " ), boost :: token_compress_off );
2019-02-01 16:12:00 +01:00
// Info on format versioning - see 3mf.hpp
int version = 0 ;
2019-12-06 15:47:58 +01:00
std :: string key ( "support_points_format_version=" );
if ( ! objects . empty () && objects [ 0 ]. find ( key ) != std :: string :: npos ) {
objects [ 0 ]. erase ( objects [ 0 ]. begin (), objects [ 0 ]. begin () + long ( key . size ())); // removes the string
2019-02-01 16:12:00 +01:00
version = std :: stoi ( objects [ 0 ]);
objects . erase ( objects . begin ()); // pop the header
}
2018-10-05 10:13:21 +02:00
for ( const std :: string & object : objects )
{
std :: vector < std :: string > object_data ;
boost :: split ( object_data , object , boost :: is_any_of ( "|" ), boost :: token_compress_off );
2019-02-01 16:12:00 +01:00
2018-10-05 10:13:21 +02:00
if ( object_data . size () != 2 )
{
add_error ( "Error while reading object data" );
continue ;
}
std :: vector < std :: string > object_data_id ;
boost :: split ( object_data_id , object_data [ 0 ], boost :: is_any_of ( "=" ), boost :: token_compress_off );
if ( object_data_id . size () != 2 )
{
add_error ( "Error while reading object id" );
continue ;
}
int object_id = std :: atoi ( object_data_id [ 1 ]. c_str ());
if ( object_id == 0 )
{
add_error ( "Found invalid object id" );
continue ;
}
IdToSlaSupportPointsMap :: iterator object_item = m_sla_support_points . find ( object_id );
if ( object_item != m_sla_support_points . end ())
{
add_error ( "Found duplicated SLA support points" );
continue ;
}
std :: vector < std :: string > object_data_points ;
boost :: split ( object_data_points , object_data [ 1 ], boost :: is_any_of ( " " ), boost :: token_compress_off );
2019-01-30 08:26:23 +01:00
std :: vector < sla :: SupportPoint > sla_support_points ;
2018-10-05 10:13:21 +02:00
2019-02-01 16:12:00 +01:00
if ( version == 0 ) {
for ( unsigned int i = 0 ; i < object_data_points . size (); i += 3 )
2019-05-24 12:17:54 +02:00
sla_support_points . emplace_back ( float ( std :: atof ( object_data_points [ i + 0 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 1 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 2 ]. c_str ())),
2019-02-01 16:12:00 +01:00
0.4f ,
false );
}
if ( version == 1 ) {
for ( unsigned int i = 0 ; i < object_data_points . size (); i += 5 )
2019-05-24 12:17:54 +02:00
sla_support_points . emplace_back ( float ( std :: atof ( object_data_points [ i + 0 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 1 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 2 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 3 ]. c_str ())),
//FIXME storing boolean as 0 / 1 and importing it as float.
std :: abs ( std :: atof ( object_data_points [ i + 4 ]. c_str ()) - 1. ) < EPSILON );
2019-02-01 16:12:00 +01:00
}
2018-10-05 10:13:21 +02:00
if ( ! sla_support_points . empty ())
m_sla_support_points . insert ( IdToSlaSupportPointsMap :: value_type ( object_id , sla_support_points ));
}
}
}
2019-12-06 15:47:58 +01:00
void _3MF_Importer :: _extract_sla_drain_holes_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat )
{
if ( stat . m_uncomp_size > 0 )
{
std :: string buffer ( size_t ( stat . m_uncomp_size ), 0 );
mz_bool res = mz_zip_reader_extract_file_to_mem ( & archive , stat . m_filename , ( void * ) buffer . data (), ( size_t ) stat . m_uncomp_size , 0 );
if ( res == 0 )
{
add_error ( "Error while reading sla support points data to buffer" );
return ;
}
if ( buffer . back () == '\n' )
buffer . pop_back ();
std :: vector < std :: string > objects ;
boost :: split ( objects , buffer , boost :: is_any_of ( " \n " ), boost :: token_compress_off );
// Info on format versioning - see 3mf.hpp
int version = 0 ;
std :: string key ( "drain_holes_format_version=" );
if ( ! objects . empty () && objects [ 0 ]. find ( key ) != std :: string :: npos ) {
objects [ 0 ]. erase ( objects [ 0 ]. begin (), objects [ 0 ]. begin () + long ( key . size ())); // removes the string
version = std :: stoi ( objects [ 0 ]);
objects . erase ( objects . begin ()); // pop the header
}
for ( const std :: string & object : objects )
{
std :: vector < std :: string > object_data ;
boost :: split ( object_data , object , boost :: is_any_of ( "|" ), boost :: token_compress_off );
if ( object_data . size () != 2 )
{
add_error ( "Error while reading object data" );
continue ;
}
std :: vector < std :: string > object_data_id ;
boost :: split ( object_data_id , object_data [ 0 ], boost :: is_any_of ( "=" ), boost :: token_compress_off );
if ( object_data_id . size () != 2 )
{
add_error ( "Error while reading object id" );
continue ;
}
int object_id = std :: atoi ( object_data_id [ 1 ]. c_str ());
if ( object_id == 0 )
{
add_error ( "Found invalid object id" );
continue ;
}
IdToSlaDrainHolesMap :: iterator object_item = m_sla_drain_holes . find ( object_id );
if ( object_item != m_sla_drain_holes . end ())
{
add_error ( "Found duplicated SLA drain holes" );
continue ;
}
std :: vector < std :: string > object_data_points ;
boost :: split ( object_data_points , object_data [ 1 ], boost :: is_any_of ( " " ), boost :: token_compress_off );
sla :: DrainHoles sla_drain_holes ;
if ( version == 1 ) {
for ( unsigned int i = 0 ; i < object_data_points . size (); i += 8 )
sla_drain_holes . emplace_back ( Vec3f { float ( std :: atof ( object_data_points [ i + 0 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 1 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 2 ]. c_str ()))},
Vec3f { float ( std :: atof ( object_data_points [ i + 3 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 4 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 5 ]. c_str ()))},
float ( std :: atof ( object_data_points [ i + 6 ]. c_str ())),
float ( std :: atof ( object_data_points [ i + 7 ]. c_str ())));
}
if ( ! sla_drain_holes . empty ())
m_sla_drain_holes . insert ( IdToSlaDrainHolesMap :: value_type ( object_id , sla_drain_holes ));
}
}
}
2018-10-05 10:13:21 +02:00
2018-02-27 10:49:51 +01:00
bool _3MF_Importer :: _extract_model_config_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat , Model & model )
{
if ( stat . m_uncomp_size == 0 )
{
add_error ( "Found invalid size" );
return false ;
}
_destroy_xml_parser ();
m_xml_parser = XML_ParserCreate ( nullptr );
if ( m_xml_parser == nullptr )
{
add_error ( "Unable to create parser" );
return false ;
}
XML_SetUserData ( m_xml_parser , ( void * ) this );
XML_SetElementHandler ( m_xml_parser , _3MF_Importer :: _handle_start_config_xml_element , _3MF_Importer :: _handle_end_config_xml_element );
void * parser_buffer = XML_GetBuffer ( m_xml_parser , ( int ) stat . m_uncomp_size );
if ( parser_buffer == nullptr )
{
add_error ( "Unable to create buffer" );
return false ;
}
mz_bool res = mz_zip_reader_extract_file_to_mem ( & archive , stat . m_filename , parser_buffer , ( size_t ) stat . m_uncomp_size , 0 );
if ( res == 0 )
{
add_error ( "Error while reading config data to buffer" );
return false ;
}
if ( ! XML_ParseBuffer ( m_xml_parser , ( int ) stat . m_uncomp_size , 1 ))
{
char error_buf [ 1024 ];
2019-06-25 13:06:04 +02:00
:: sprintf ( error_buf , "Error (%s) while parsing xml file at line %d" , XML_ErrorString ( XML_GetErrorCode ( m_xml_parser )), ( int ) XML_GetCurrentLineNumber ( m_xml_parser ));
2018-02-27 10:49:51 +01:00
add_error ( error_buf );
return false ;
}
return true ;
}
2019-12-17 08:37:50 +01:00
void _3MF_Importer :: _extract_custom_gcode_per_print_z_from_archive ( :: mz_zip_archive & archive , const mz_zip_archive_file_stat & stat )
2019-11-11 16:01:34 +01:00
{
if ( stat . m_uncomp_size > 0 )
{
std :: string buffer (( size_t ) stat . m_uncomp_size , 0 );
mz_bool res = mz_zip_reader_extract_file_to_mem ( & archive , stat . m_filename , ( void * ) buffer . data (), ( size_t ) stat . m_uncomp_size , 0 );
if ( res == 0 ) {
add_error ( "Error while reading custom Gcodes per height data to buffer" );
return ;
}
std :: istringstream iss ( buffer ); // wrap returned xml to istringstream
pt :: ptree main_tree ;
pt :: read_xml ( iss , main_tree );
2019-12-17 08:37:50 +01:00
if ( main_tree . front (). first != "custom_gcodes_per_print_z" )
2019-11-11 16:01:34 +01:00
return ;
pt :: ptree code_tree = main_tree . front (). second ;
2020-01-16 16:01:19 +01:00
m_model -> custom_gcode_per_print_z . gcodes . clear ();
2019-11-11 16:01:34 +01:00
for ( const auto & code : code_tree )
{
2020-01-23 12:49:39 +01:00
if ( code . first == "mode" )
{
pt :: ptree tree = code . second ;
std :: string mode = tree . get < std :: string > ( "<xmlattr>.value" );
m_model -> custom_gcode_per_print_z . mode = mode == CustomGCode :: SingleExtruderMode ? CustomGCode :: Mode :: SingleExtruder :
mode == CustomGCode :: MultiAsSingleMode ? CustomGCode :: Mode :: MultiAsSingle :
CustomGCode :: Mode :: MultiExtruder ;
}
2019-11-11 16:01:34 +01:00
if ( code . first != "code" )
continue ;
2020-06-08 12:25:29 +02:00
pt :: ptree tree = code . second ;
double print_z = tree . get < double > ( "<xmlattr>.print_z" );
int extruder = tree . get < int > ( "<xmlattr>.extruder" );
std :: string color = tree . get < std :: string > ( "<xmlattr>.color" );
CustomGCode :: Type type ;
std :: string extra ;
if ( tree . find ( "type" ) == tree . not_found ())
{
// It means that data was saved in old version (2.2.0 and older) of PrusaSlicer
// read old data ...
std :: string gcode = tree . get < std :: string > ( "<xmlattr>.gcode" );
// ... and interpret them to the new data
type = gcode == "M600" ? CustomGCode :: ColorChange :
gcode == "M601" ? CustomGCode :: PausePrint :
gcode == "tool_change" ? CustomGCode :: ToolChange : CustomGCode :: Custom ;
extra = type == CustomGCode :: PausePrint ? color :
type == CustomGCode :: Custom ? gcode : "" ;
}
else
{
type = static_cast < CustomGCode :: Type > ( tree . get < int > ( "<xmlattr>.type" ));
extra = tree . get < std :: string > ( "<xmlattr>.extra" );
}
m_model -> custom_gcode_per_print_z . gcodes . push_back ( CustomGCode :: Item { print_z , type , extruder , color , extra }) ;
2019-11-11 16:01:34 +01:00
}
}
}
2018-02-27 10:49:51 +01:00
void _3MF_Importer :: _handle_start_model_xml_element ( const char * name , const char ** attributes )
2018-01-30 09:27:10 +01:00
{
if ( m_xml_parser == nullptr )
return ;
bool res = true ;
unsigned int num_attributes = ( unsigned int ) XML_GetSpecifiedAttributeCount ( m_xml_parser );
if ( :: strcmp ( MODEL_TAG , name ) == 0 )
res = _handle_start_model ( attributes , num_attributes );
else if ( :: strcmp ( RESOURCES_TAG , name ) == 0 )
res = _handle_start_resources ( attributes , num_attributes );
else if ( :: strcmp ( OBJECT_TAG , name ) == 0 )
res = _handle_start_object ( attributes , num_attributes );
else if ( :: strcmp ( MESH_TAG , name ) == 0 )
res = _handle_start_mesh ( attributes , num_attributes );
else if ( :: strcmp ( VERTICES_TAG , name ) == 0 )
res = _handle_start_vertices ( attributes , num_attributes );
else if ( :: strcmp ( VERTEX_TAG , name ) == 0 )
res = _handle_start_vertex ( attributes , num_attributes );
else if ( :: strcmp ( TRIANGLES_TAG , name ) == 0 )
res = _handle_start_triangles ( attributes , num_attributes );
else if ( :: strcmp ( TRIANGLE_TAG , name ) == 0 )
res = _handle_start_triangle ( attributes , num_attributes );
else if ( :: strcmp ( COMPONENTS_TAG , name ) == 0 )
res = _handle_start_components ( attributes , num_attributes );
else if ( :: strcmp ( COMPONENT_TAG , name ) == 0 )
res = _handle_start_component ( attributes , num_attributes );
else if ( :: strcmp ( BUILD_TAG , name ) == 0 )
res = _handle_start_build ( attributes , num_attributes );
else if ( :: strcmp ( ITEM_TAG , name ) == 0 )
res = _handle_start_item ( attributes , num_attributes );
2018-04-30 15:27:01 +02:00
else if ( :: strcmp ( METADATA_TAG , name ) == 0 )
res = _handle_start_metadata ( attributes , num_attributes );
2018-01-30 09:27:10 +01:00
if ( ! res )
_stop_xml_parser ();
}
2018-02-27 10:49:51 +01:00
void _3MF_Importer :: _handle_end_model_xml_element ( const char * name )
2018-01-30 09:27:10 +01:00
{
if ( m_xml_parser == nullptr )
return ;
bool res = true ;
if ( :: strcmp ( MODEL_TAG , name ) == 0 )
res = _handle_end_model ();
else if ( :: strcmp ( RESOURCES_TAG , name ) == 0 )
res = _handle_end_resources ();
else if ( :: strcmp ( OBJECT_TAG , name ) == 0 )
res = _handle_end_object ();
else if ( :: strcmp ( MESH_TAG , name ) == 0 )
res = _handle_end_mesh ();
else if ( :: strcmp ( VERTICES_TAG , name ) == 0 )
res = _handle_end_vertices ();
else if ( :: strcmp ( VERTEX_TAG , name ) == 0 )
res = _handle_end_vertex ();
else if ( :: strcmp ( TRIANGLES_TAG , name ) == 0 )
res = _handle_end_triangles ();
else if ( :: strcmp ( TRIANGLE_TAG , name ) == 0 )
res = _handle_end_triangle ();
else if ( :: strcmp ( COMPONENTS_TAG , name ) == 0 )
res = _handle_end_components ();
else if ( :: strcmp ( COMPONENT_TAG , name ) == 0 )
res = _handle_end_component ();
else if ( :: strcmp ( BUILD_TAG , name ) == 0 )
res = _handle_end_build ();
else if ( :: strcmp ( ITEM_TAG , name ) == 0 )
res = _handle_end_item ();
2018-04-30 15:27:01 +02:00
else if ( :: strcmp ( METADATA_TAG , name ) == 0 )
res = _handle_end_metadata ();
2018-01-30 09:27:10 +01:00
if ( ! res )
_stop_xml_parser ();
}
2018-04-30 15:27:01 +02:00
void _3MF_Importer :: _handle_model_xml_characters ( const XML_Char * s , int len )
{
m_curr_characters . append ( s , len );
}
2018-02-27 10:49:51 +01:00
void _3MF_Importer :: _handle_start_config_xml_element ( const char * name , const char ** attributes )
{
if ( m_xml_parser == nullptr )
return ;
bool res = true ;
unsigned int num_attributes = ( unsigned int ) XML_GetSpecifiedAttributeCount ( m_xml_parser );
if ( :: strcmp ( CONFIG_TAG , name ) == 0 )
res = _handle_start_config ( attributes , num_attributes );
else if ( :: strcmp ( OBJECT_TAG , name ) == 0 )
res = _handle_start_config_object ( attributes , num_attributes );
else if ( :: strcmp ( VOLUME_TAG , name ) == 0 )
res = _handle_start_config_volume ( attributes , num_attributes );
else if ( :: strcmp ( METADATA_TAG , name ) == 0 )
res = _handle_start_config_metadata ( attributes , num_attributes );
if ( ! res )
_stop_xml_parser ();
}
void _3MF_Importer :: _handle_end_config_xml_element ( const char * name )
{
if ( m_xml_parser == nullptr )
return ;
bool res = true ;
if ( :: strcmp ( CONFIG_TAG , name ) == 0 )
res = _handle_end_config ();
else if ( :: strcmp ( OBJECT_TAG , name ) == 0 )
res = _handle_end_config_object ();
else if ( :: strcmp ( VOLUME_TAG , name ) == 0 )
res = _handle_end_config_volume ();
else if ( :: strcmp ( METADATA_TAG , name ) == 0 )
res = _handle_end_config_metadata ();
if ( ! res )
_stop_xml_parser ();
}
2018-01-30 09:27:10 +01:00
bool _3MF_Importer :: _handle_start_model ( const char ** attributes , unsigned int num_attributes )
{
m_unit_factor = get_unit_factor ( get_attribute_value_string ( attributes , num_attributes , UNIT_ATTR ));
return true ;
}
bool _3MF_Importer :: _handle_end_model ()
{
// deletes all non-built or non-instanced objects
for ( const IdToModelObjectMap :: value_type & object : m_objects )
{
2019-05-24 12:17:54 +02:00
ModelObject * model_object = m_model -> objects [ object . second ];
if (( model_object != nullptr ) && ( model_object -> instances . size () == 0 ))
m_model -> delete_object ( model_object );
2018-01-30 09:27:10 +01:00
}
// applies instances' matrices
for ( Instance & instance : m_instances )
{
if ( instance . instance != nullptr )
{
ModelObject * object = instance . instance -> get_object ();
2018-04-12 10:10:15 +02:00
if ( object != nullptr )
2018-01-30 09:27:10 +01:00
{
2018-08-28 14:30:36 +02:00
// apply the transform to the instance
_apply_transform ( * instance . instance , instance . transform );
2018-01-30 09:27:10 +01:00
}
}
}
return true ;
}
bool _3MF_Importer :: _handle_start_resources ( const char ** attributes , unsigned int num_attributes )
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_end_resources ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_object ( const char ** attributes , unsigned int num_attributes )
{
// reset current data
m_curr_object . reset ();
if ( is_valid_object_type ( get_attribute_value_string ( attributes , num_attributes , TYPE_ATTR )))
{
// create new object (it may be removed later if no instances are generated from it)
2019-05-24 12:17:54 +02:00
m_curr_object . model_object_idx = ( int ) m_model -> objects . size ();
2018-01-30 09:27:10 +01:00
m_curr_object . object = m_model -> add_object ();
if ( m_curr_object . object == nullptr )
{
2018-02-14 14:30:03 +01:00
add_error ( "Unable to create object" );
2018-01-30 09:27:10 +01:00
return false ;
}
// set object data
m_curr_object . object -> name = get_attribute_value_string ( attributes , num_attributes , NAME_ATTR );
2018-11-08 10:18:19 +01:00
if ( m_curr_object . object -> name . empty ())
m_curr_object . object -> name = m_name + "_" + std :: to_string ( m_model -> objects . size ());
2018-01-30 09:27:10 +01:00
m_curr_object . id = get_attribute_value_int ( attributes , num_attributes , ID_ATTR );
}
return true ;
}
bool _3MF_Importer :: _handle_end_object ()
{
if ( m_curr_object . object != nullptr )
{
if ( m_curr_object . geometry . empty ())
{
// no geometry defined
// remove the object from the model
m_model -> delete_object ( m_curr_object . object );
if ( m_curr_object . components . empty ())
{
// no components defined -> invalid object, delete it
IdToModelObjectMap :: iterator object_item = m_objects . find ( m_curr_object . id );
if ( object_item != m_objects . end ())
m_objects . erase ( object_item );
IdToAliasesMap :: iterator alias_item = m_objects_aliases . find ( m_curr_object . id );
if ( alias_item != m_objects_aliases . end ())
m_objects_aliases . erase ( alias_item );
}
else
// adds components to aliases
m_objects_aliases . insert ( IdToAliasesMap :: value_type ( m_curr_object . id , m_curr_object . components ));
}
else
{
2018-02-27 10:49:51 +01:00
// geometry defined, store it for later use
m_geometries . insert ( IdToGeometryMap :: value_type ( m_curr_object . id , std :: move ( m_curr_object . geometry )));
2018-01-30 09:27:10 +01:00
// stores the object for later use
if ( m_objects . find ( m_curr_object . id ) == m_objects . end ())
{
2019-05-24 12:17:54 +02:00
m_objects . insert ( IdToModelObjectMap :: value_type ( m_curr_object . id , m_curr_object . model_object_idx ));
2018-01-30 09:27:10 +01:00
m_objects_aliases . insert ( IdToAliasesMap :: value_type ( m_curr_object . id , ComponentsList ( 1 , Component ( m_curr_object . id )))); // aliases itself
}
else
{
2018-02-14 14:30:03 +01:00
add_error ( "Found object with duplicate id" );
2018-01-30 09:27:10 +01:00
return false ;
}
}
}
return true ;
}
bool _3MF_Importer :: _handle_start_mesh ( const char ** attributes , unsigned int num_attributes )
{
// reset current geometry
m_curr_object . geometry . reset ();
return true ;
}
bool _3MF_Importer :: _handle_end_mesh ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_vertices ( const char ** attributes , unsigned int num_attributes )
{
// reset current vertices
m_curr_object . geometry . vertices . clear ();
return true ;
}
bool _3MF_Importer :: _handle_end_vertices ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_vertex ( const char ** attributes , unsigned int num_attributes )
{
// appends the vertex coordinates
// missing values are set equal to ZERO
m_curr_object . geometry . vertices . push_back ( m_unit_factor * get_attribute_value_float ( attributes , num_attributes , X_ATTR ));
m_curr_object . geometry . vertices . push_back ( m_unit_factor * get_attribute_value_float ( attributes , num_attributes , Y_ATTR ));
m_curr_object . geometry . vertices . push_back ( m_unit_factor * get_attribute_value_float ( attributes , num_attributes , Z_ATTR ));
return true ;
}
bool _3MF_Importer :: _handle_end_vertex ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_triangles ( const char ** attributes , unsigned int num_attributes )
{
// reset current triangles
m_curr_object . geometry . triangles . clear ();
return true ;
}
bool _3MF_Importer :: _handle_end_triangles ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_triangle ( const char ** attributes , unsigned int num_attributes )
{
// we are ignoring the following attributes:
// p1
// p2
// p3
// pid
// see specifications
// appends the triangle's vertices indices
// missing values are set equal to ZERO
m_curr_object . geometry . triangles . push_back (( unsigned int ) get_attribute_value_int ( attributes , num_attributes , V1_ATTR ));
m_curr_object . geometry . triangles . push_back (( unsigned int ) get_attribute_value_int ( attributes , num_attributes , V2_ATTR ));
m_curr_object . geometry . triangles . push_back (( unsigned int ) get_attribute_value_int ( attributes , num_attributes , V3_ATTR ));
return true ;
}
bool _3MF_Importer :: _handle_end_triangle ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_components ( const char ** attributes , unsigned int num_attributes )
{
// reset current components
m_curr_object . components . clear ();
return true ;
}
bool _3MF_Importer :: _handle_end_components ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_component ( const char ** attributes , unsigned int num_attributes )
{
int object_id = get_attribute_value_int ( attributes , num_attributes , OBJECTID_ATTR );
2019-09-19 09:09:11 +02:00
Transform3d transform = get_transform_from_3mf_specs_string ( get_attribute_value_string ( attributes , num_attributes , TRANSFORM_ATTR ));
2018-01-30 09:27:10 +01:00
IdToModelObjectMap :: iterator object_item = m_objects . find ( object_id );
if ( object_item == m_objects . end ())
{
IdToAliasesMap :: iterator alias_item = m_objects_aliases . find ( object_id );
if ( alias_item == m_objects_aliases . end ())
{
2018-02-14 14:30:03 +01:00
add_error ( "Found component with invalid object id" );
2018-01-30 09:27:10 +01:00
return false ;
}
}
2018-08-28 14:30:36 +02:00
m_curr_object . components . emplace_back ( object_id , transform );
2018-01-30 09:27:10 +01:00
return true ;
}
bool _3MF_Importer :: _handle_end_component ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_build ( const char ** attributes , unsigned int num_attributes )
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_end_build ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_item ( const char ** attributes , unsigned int num_attributes )
{
// we are ignoring the following attributes
// thumbnail
// partnumber
// pid
// pindex
// see specifications
int object_id = get_attribute_value_int ( attributes , num_attributes , OBJECTID_ATTR );
2019-09-19 09:09:11 +02:00
Transform3d transform = get_transform_from_3mf_specs_string ( get_attribute_value_string ( attributes , num_attributes , TRANSFORM_ATTR ));
2019-08-06 14:54:38 +02:00
int printable = get_attribute_value_bool ( attributes , num_attributes , PRINTABLE_ATTR );
2018-01-30 09:27:10 +01:00
2019-08-06 14:54:38 +02:00
return _create_object_instance ( object_id , transform , printable , 1 );
2018-01-30 09:27:10 +01:00
}
bool _3MF_Importer :: _handle_end_item ()
{
// do nothing
return true ;
}
2018-04-30 15:27:01 +02:00
bool _3MF_Importer :: _handle_start_metadata ( const char ** attributes , unsigned int num_attributes )
{
m_curr_characters . clear ();
std :: string name = get_attribute_value_string ( attributes , num_attributes , NAME_ATTR );
if ( ! name . empty ())
m_curr_metadata_name = name ;
return true ;
}
bool _3MF_Importer :: _handle_end_metadata ()
{
if ( m_curr_metadata_name == SLIC3RPE_3MF_VERSION )
2019-08-23 13:12:31 +02:00
{
2018-04-30 15:27:01 +02:00
m_version = ( unsigned int ) atoi ( m_curr_characters . c_str ());
2019-12-19 10:22:46 +01:00
if ( m_check_version && ( m_version > VERSION_3MF_COMPATIBLE ))
2019-08-23 13:12:31 +02:00
{
2019-12-06 08:59:25 +01:00
// std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible."));
// throw version_error(msg.c_str());
const std :: string msg = ( boost :: format ( _ ( L ( "The selected 3mf file has been saved with a newer version of %1% and is not compatible." ))) % std :: string ( SLIC3R_APP_NAME )). str ();
throw version_error ( msg );
2019-08-23 13:12:31 +02:00
}
}
2018-04-30 15:27:01 +02:00
return true ;
}
2019-08-06 14:54:38 +02:00
bool _3MF_Importer :: _create_object_instance ( int object_id , const Transform3d & transform , const bool printable , unsigned int recur_counter )
2018-01-30 09:27:10 +01:00
{
static const unsigned int MAX_RECURSIONS = 10 ;
// escape from circular aliasing
if ( recur_counter > MAX_RECURSIONS )
{
2018-02-14 14:30:03 +01:00
add_error ( "Too many recursions" );
2018-01-30 09:27:10 +01:00
return false ;
}
IdToAliasesMap :: iterator it = m_objects_aliases . find ( object_id );
if ( it == m_objects_aliases . end ())
{
2018-02-14 14:30:03 +01:00
add_error ( "Found item with invalid object id" );
2018-01-30 09:27:10 +01:00
return false ;
}
if (( it -> second . size () == 1 ) && ( it -> second [ 0 ]. object_id == object_id ))
{
// aliasing to itself
IdToModelObjectMap :: iterator object_item = m_objects . find ( object_id );
2019-05-24 12:17:54 +02:00
if (( object_item == m_objects . end ()) || ( object_item -> second == - 1 ))
2018-01-30 09:27:10 +01:00
{
2018-02-14 14:30:03 +01:00
add_error ( "Found invalid object" );
2018-01-30 09:27:10 +01:00
return false ;
}
else
{
2019-05-24 12:17:54 +02:00
ModelInstance * instance = m_model -> objects [ object_item -> second ] -> add_instance ();
2018-01-30 09:27:10 +01:00
if ( instance == nullptr )
{
2018-02-14 14:30:03 +01:00
add_error ( "Unable to add object instance" );
2018-01-30 09:27:10 +01:00
return false ;
}
2019-08-06 14:54:38 +02:00
instance -> printable = printable ;
2018-01-30 09:27:10 +01:00
2018-08-28 14:30:36 +02:00
m_instances . emplace_back ( instance , transform );
2018-01-30 09:27:10 +01:00
}
}
else
{
// recursively process nested components
for ( const Component & component : it -> second )
{
2019-08-06 14:54:38 +02:00
if ( ! _create_object_instance ( component . object_id , transform * component . transform , printable , recur_counter + 1 ))
2018-01-30 09:27:10 +01:00
return false ;
}
}
return true ;
}
2018-08-28 14:30:36 +02:00
void _3MF_Importer :: _apply_transform ( ModelInstance & instance , const Transform3d & transform )
2018-01-30 09:27:10 +01:00
{
2018-11-01 13:42:07 +01:00
Slic3r :: Geometry :: Transformation t ( transform );
// invalid scale value, return
if ( ! t . get_scaling_factor (). all ())
return ;
instance . set_transformation ( t );
2018-01-30 09:27:10 +01:00
}
2018-02-27 10:49:51 +01:00
bool _3MF_Importer :: _handle_start_config ( const char ** attributes , unsigned int num_attributes )
2018-01-30 09:27:10 +01:00
{
2018-02-27 10:49:51 +01:00
// do nothing
return true ;
2018-01-30 09:27:10 +01:00
}
2018-02-27 10:49:51 +01:00
bool _3MF_Importer :: _handle_end_config ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_config_object ( const char ** attributes , unsigned int num_attributes )
{
int object_id = get_attribute_value_int ( attributes , num_attributes , ID_ATTR );
IdToMetadataMap :: iterator object_item = m_objects_metadata . find ( object_id );
if ( object_item != m_objects_metadata . end ())
{
add_error ( "Found duplicated object id" );
return false ;
}
2020-01-02 11:37:50 +01:00
// Added because of github #3435, currently not used by PrusaSlicer
2020-03-03 14:52:16 +01:00
// int instances_count_id = get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR);
2020-01-02 11:37:50 +01:00
2018-02-27 10:49:51 +01:00
m_objects_metadata . insert ( IdToMetadataMap :: value_type ( object_id , ObjectMetadata ()));
m_curr_config . object_id = object_id ;
return true ;
}
bool _3MF_Importer :: _handle_end_config_object ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_config_volume ( const char ** attributes , unsigned int num_attributes )
{
IdToMetadataMap :: iterator object = m_objects_metadata . find ( m_curr_config . object_id );
if ( object == m_objects_metadata . end ())
{
add_error ( "Cannot assign volume to a valid object" );
return false ;
}
2018-10-08 15:17:36 +02:00
m_curr_config . volume_id = ( int ) object -> second . volumes . size ();
2018-02-27 10:49:51 +01:00
unsigned int first_triangle_id = ( unsigned int ) get_attribute_value_int ( attributes , num_attributes , FIRST_TRIANGLE_ID_ATTR );
unsigned int last_triangle_id = ( unsigned int ) get_attribute_value_int ( attributes , num_attributes , LAST_TRIANGLE_ID_ATTR );
object -> second . volumes . emplace_back ( first_triangle_id , last_triangle_id );
return true ;
}
bool _3MF_Importer :: _handle_end_config_volume ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _handle_start_config_metadata ( const char ** attributes , unsigned int num_attributes )
{
IdToMetadataMap :: iterator object = m_objects_metadata . find ( m_curr_config . object_id );
if ( object == m_objects_metadata . end ())
{
add_error ( "Cannot assign metadata to valid object id" );
return false ;
}
std :: string type = get_attribute_value_string ( attributes , num_attributes , TYPE_ATTR );
std :: string key = get_attribute_value_string ( attributes , num_attributes , KEY_ATTR );
std :: string value = get_attribute_value_string ( attributes , num_attributes , VALUE_ATTR );
if ( type == OBJECT_TYPE )
object -> second . metadata . emplace_back ( key , value );
else if ( type == VOLUME_TYPE )
{
2019-06-25 13:06:04 +02:00
if ( size_t ( m_curr_config . volume_id ) < object -> second . volumes . size ())
2018-02-27 10:49:51 +01:00
object -> second . volumes [ m_curr_config . volume_id ]. metadata . emplace_back ( key , value );
}
else
{
add_error ( "Found invalid metadata type" );
return false ;
}
return true ;
}
bool _3MF_Importer :: _handle_end_config_metadata ()
{
// do nothing
return true ;
}
bool _3MF_Importer :: _generate_volumes ( ModelObject & object , const Geometry & geometry , const ObjectMetadata :: VolumeMetadataList & volumes )
{
if ( ! object . volumes . empty ())
{
add_error ( "Found invalid volumes count" );
return false ;
}
2018-10-08 15:17:36 +02:00
unsigned int geo_tri_count = ( unsigned int ) geometry . triangles . size () / 3 ;
2018-02-27 10:49:51 +01:00
for ( const ObjectMetadata :: VolumeMetadata & volume_data : volumes )
{
if (( geo_tri_count <= volume_data . first_triangle_id ) || ( geo_tri_count <= volume_data . last_triangle_id ) || ( volume_data . last_triangle_id < volume_data . first_triangle_id ))
{
add_error ( "Found invalid triangle id" );
return false ;
}
2019-12-19 10:22:46 +01:00
Transform3d volume_matrix_to_object = Transform3d :: Identity ();
bool has_transform = false ;
// extract the volume transformation from the volume's metadata, if present
for ( const Metadata & metadata : volume_data . metadata )
2019-09-23 12:33:21 +02:00
{
2019-12-19 10:22:46 +01:00
if ( metadata . key == MATRIX_KEY )
2019-09-23 12:33:21 +02:00
{
2019-12-19 10:22:46 +01:00
volume_matrix_to_object = Slic3r :: Geometry :: transform3d_from_string ( metadata . value );
has_transform = ! volume_matrix_to_object . isApprox ( Transform3d :: Identity (), 1e-10 );
break ;
2019-09-23 12:33:21 +02:00
}
}
2018-02-27 10:49:51 +01:00
// splits volume out of imported geometry
2019-06-11 17:08:47 +02:00
TriangleMesh triangle_mesh ;
stl_file & stl = triangle_mesh . stl ;
unsigned int triangles_count = volume_data . last_triangle_id - volume_data . first_triangle_id + 1 ;
stl . stats . type = inmemory ;
2018-02-27 10:49:51 +01:00
stl . stats . number_of_facets = ( uint32_t ) triangles_count ;
stl . stats . original_num_facets = ( int ) stl . stats . number_of_facets ;
stl_allocate ( & stl );
unsigned int src_start_id = volume_data . first_triangle_id * 3 ;
2018-10-08 15:17:36 +02:00
for ( unsigned int i = 0 ; i < triangles_count ; ++ i )
2018-02-27 10:49:51 +01:00
{
unsigned int ii = i * 3 ;
stl_facet & facet = stl . facet_start [ i ];
for ( unsigned int v = 0 ; v < 3 ; ++ v )
{
2019-09-23 12:33:21 +02:00
unsigned int tri_id = geometry . triangles [ src_start_id + ii + v ] * 3 ;
2019-12-20 12:11:58 +01:00
facet . vertex [ v ] = Vec3f ( geometry . vertices [ tri_id + 0 ], geometry . vertices [ tri_id + 1 ], geometry . vertices [ tri_id + 2 ]);
2018-02-27 10:49:51 +01:00
}
}
2019-06-11 17:08:47 +02:00
stl_get_size ( & stl );
triangle_mesh . repair ();
ModelVolume * volume = object . add_volume ( std :: move ( triangle_mesh ));
2019-12-20 12:11:58 +01:00
// stores the volume matrix taken from the metadata, if present
if ( has_transform )
volume -> source . transform = Slic3r :: Geometry :: Transformation ( volume_matrix_to_object );
2018-08-15 12:50:06 +02:00
volume -> calculate_convex_hull ();
2018-02-27 10:49:51 +01:00
2019-09-23 12:33:21 +02:00
// apply the remaining volume's metadata
2018-02-27 10:49:51 +01:00
for ( const Metadata & metadata : volume_data . metadata )
{
2018-02-28 12:11:41 +01:00
if ( metadata . key == NAME_KEY )
2018-02-27 15:46:54 +01:00
volume -> name = metadata . value ;
2018-02-28 12:11:41 +01:00
else if (( metadata . key == MODIFIER_KEY ) && ( metadata . value == "1" ))
2019-02-22 12:12:10 +01:00
volume -> set_type ( ModelVolumeType :: PARAMETER_MODIFIER );
2018-09-17 15:12:13 +02:00
else if ( metadata . key == VOLUME_TYPE_KEY )
volume -> set_type ( ModelVolume :: type_from_string ( metadata . value ));
2019-09-19 09:09:11 +02:00
else if ( metadata . key == SOURCE_FILE_KEY )
volume -> source . input_file = metadata . value ;
else if ( metadata . key == SOURCE_OBJECT_ID_KEY )
volume -> source . object_idx = :: atoi ( metadata . value . c_str ());
else if ( metadata . key == SOURCE_VOLUME_ID_KEY )
volume -> source . volume_idx = :: atoi ( metadata . value . c_str ());
else if ( metadata . key == SOURCE_OFFSET_X_KEY )
volume -> source . mesh_offset ( 0 ) = :: atof ( metadata . value . c_str ());
else if ( metadata . key == SOURCE_OFFSET_Y_KEY )
volume -> source . mesh_offset ( 1 ) = :: atof ( metadata . value . c_str ());
else if ( metadata . key == SOURCE_OFFSET_Z_KEY )
volume -> source . mesh_offset ( 2 ) = :: atof ( metadata . value . c_str ());
2018-02-27 15:46:54 +01:00
else
volume -> config . set_deserialize ( metadata . key , metadata . value );
2018-02-27 10:49:51 +01:00
}
}
return true ;
}
void XMLCALL _3MF_Importer :: _handle_start_model_xml_element ( void * userData , const char * name , const char ** attributes )
2018-01-30 09:27:10 +01:00
{
_3MF_Importer * importer = ( _3MF_Importer * ) userData ;
if ( importer != nullptr )
2018-02-27 10:49:51 +01:00
importer -> _handle_start_model_xml_element ( name , attributes );
}
void XMLCALL _3MF_Importer :: _handle_end_model_xml_element ( void * userData , const char * name )
{
_3MF_Importer * importer = ( _3MF_Importer * ) userData ;
if ( importer != nullptr )
importer -> _handle_end_model_xml_element ( name );
}
2018-04-30 15:27:01 +02:00
void XMLCALL _3MF_Importer :: _handle_model_xml_characters ( void * userData , const XML_Char * s , int len )
{
_3MF_Importer * importer = ( _3MF_Importer * ) userData ;
if ( importer != nullptr )
importer -> _handle_model_xml_characters ( s , len );
}
2018-02-27 10:49:51 +01:00
void XMLCALL _3MF_Importer :: _handle_start_config_xml_element ( void * userData , const char * name , const char ** attributes )
{
_3MF_Importer * importer = ( _3MF_Importer * ) userData ;
if ( importer != nullptr )
importer -> _handle_start_config_xml_element ( name , attributes );
}
void XMLCALL _3MF_Importer :: _handle_end_config_xml_element ( void * userData , const char * name )
{
_3MF_Importer * importer = ( _3MF_Importer * ) userData ;
if ( importer != nullptr )
importer -> _handle_end_config_xml_element ( name );
2018-01-30 09:27:10 +01:00
}
2018-02-14 14:30:03 +01:00
class _3MF_Exporter : public _3MF_Base
2018-02-08 13:26:50 +01:00
{
struct BuildItem
{
unsigned int id ;
2018-08-28 14:30:36 +02:00
Transform3d transform ;
2019-08-06 14:54:38 +02:00
bool printable ;
2018-02-08 13:26:50 +01:00
2019-08-06 14:54:38 +02:00
BuildItem ( unsigned int id , const Transform3d & transform , const bool printable )
2018-02-27 10:49:51 +01:00
: id ( id )
2018-08-28 14:30:36 +02:00
, transform ( transform )
2019-08-06 14:54:38 +02:00
, printable ( printable )
2018-02-27 10:49:51 +01:00
{
}
};
struct Offsets
{
unsigned int first_vertex_id ;
unsigned int first_triangle_id ;
unsigned int last_triangle_id ;
Offsets ( unsigned int first_vertex_id )
: first_vertex_id ( first_vertex_id )
, first_triangle_id ( - 1 )
, last_triangle_id ( - 1 )
{
}
};
typedef std :: map < const ModelVolume * , Offsets > VolumeToOffsetsMap ;
struct ObjectData
{
ModelObject * object ;
VolumeToOffsetsMap volumes_offsets ;
explicit ObjectData ( ModelObject * object )
: object ( object )
{
}
2018-02-08 13:26:50 +01:00
};
typedef std :: vector < BuildItem > BuildItemsList ;
2018-02-27 10:49:51 +01:00
typedef std :: map < int , ObjectData > IdToObjectDataMap ;
2020-01-08 11:11:38 +01:00
bool m_fullpath_sources { true };
2018-02-08 13:26:50 +01:00
public :
2020-01-08 11:11:38 +01:00
bool save_model_to_file ( const std :: string & filename , Model & model , const DynamicPrintConfig * config , bool fullpath_sources , const ThumbnailData * thumbnail_data = nullptr );
2018-02-08 13:26:50 +01:00
private :
2019-10-24 09:20:33 +02:00
bool _save_model_to_file ( const std :: string & filename , Model & model , const DynamicPrintConfig * config , const ThumbnailData * thumbnail_data );
2018-02-13 15:19:55 +01:00
bool _add_content_types_file_to_archive ( mz_zip_archive & archive );
2019-10-24 09:20:33 +02:00
bool _add_thumbnail_file_to_archive ( mz_zip_archive & archive , const ThumbnailData & thumbnail_data );
2018-02-13 15:19:55 +01:00
bool _add_relationships_file_to_archive ( mz_zip_archive & archive );
2019-05-24 12:17:54 +02:00
bool _add_model_file_to_archive ( mz_zip_archive & archive , const Model & model , IdToObjectDataMap & objects_data );
2018-02-27 10:49:51 +01:00
bool _add_object_to_model_stream ( std :: stringstream & stream , unsigned int & object_id , ModelObject & object , BuildItemsList & build_items , VolumeToOffsetsMap & volumes_offsets );
bool _add_mesh_to_object_stream ( std :: stringstream & stream , ModelObject & object , VolumeToOffsetsMap & volumes_offsets );
2018-02-08 13:26:50 +01:00
bool _add_build_to_model_stream ( std :: stringstream & stream , const BuildItemsList & build_items );
2018-04-27 12:56:35 +02:00
bool _add_layer_height_profile_file_to_archive ( mz_zip_archive & archive , Model & model );
2019-06-11 14:39:41 +02:00
bool _add_layer_config_ranges_file_to_archive ( mz_zip_archive & archive , Model & model );
2018-10-05 10:13:21 +02:00
bool _add_sla_support_points_file_to_archive ( mz_zip_archive & archive , Model & model );
2019-12-06 15:47:58 +01:00
bool _add_sla_drain_holes_file_to_archive ( mz_zip_archive & archive , Model & model );
2018-11-07 14:57:50 +01:00
bool _add_print_config_file_to_archive ( mz_zip_archive & archive , const DynamicPrintConfig & config );
2019-05-24 12:17:54 +02:00
bool _add_model_config_file_to_archive ( mz_zip_archive & archive , const Model & model , const IdToObjectDataMap & objects_data );
2020-06-08 12:25:29 +02:00
bool _add_custom_gcode_per_print_z_file_to_archive ( mz_zip_archive & archive , Model & model , const DynamicPrintConfig * config );
2018-02-08 13:26:50 +01:00
};
2020-01-08 11:11:38 +01:00
bool _3MF_Exporter :: save_model_to_file ( const std :: string & filename , Model & model , const DynamicPrintConfig * config , bool fullpath_sources , const ThumbnailData * thumbnail_data )
{
clear_errors ();
m_fullpath_sources = fullpath_sources ;
return _save_model_to_file ( filename , model , config , thumbnail_data );
}
2018-02-08 13:26:50 +01:00
2019-10-24 09:20:33 +02:00
bool _3MF_Exporter :: _save_model_to_file ( const std :: string & filename , Model & model , const DynamicPrintConfig * config , const ThumbnailData * thumbnail_data )
2018-02-08 13:26:50 +01:00
{
mz_zip_archive archive ;
mz_zip_zero_struct ( & archive );
2019-06-03 15:27:46 +02:00
if ( ! open_zip_writer ( & archive , filename )) {
2018-02-14 14:30:03 +01:00
add_error ( "Unable to open the file" );
2018-02-08 13:26:50 +01:00
return false ;
}
2019-05-24 12:17:54 +02:00
// Adds content types file ("[Content_Types].xml";).
// The content of this file is the same for each PrusaSlicer 3mf.
2018-02-13 15:19:55 +01:00
if ( ! _add_content_types_file_to_archive ( archive ))
2018-02-08 13:26:50 +01:00
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-02-08 13:26:50 +01:00
boost :: filesystem :: remove ( filename );
return false ;
}
2019-10-24 09:20:33 +02:00
if (( thumbnail_data != nullptr ) && thumbnail_data -> is_valid ())
2019-10-23 13:31:24 +02:00
{
// Adds the file Metadata/thumbnail.png.
2019-10-24 09:20:33 +02:00
if ( ! _add_thumbnail_file_to_archive ( archive , * thumbnail_data ))
2019-10-23 13:31:24 +02:00
{
close_zip_writer ( & archive );
boost :: filesystem :: remove ( filename );
return false ;
}
}
2019-05-24 12:17:54 +02:00
// Adds relationships file ("_rels/.rels").
// The content of this file is the same for each PrusaSlicer 3mf.
// The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA.
2018-02-13 15:19:55 +01:00
if ( ! _add_relationships_file_to_archive ( archive ))
2018-02-08 13:26:50 +01:00
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-02-08 13:26:50 +01:00
boost :: filesystem :: remove ( filename );
return false ;
}
2019-05-24 12:17:54 +02:00
// Adds model file ("3D/3dmodel.model").
// This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes.
IdToObjectDataMap objects_data ;
if ( ! _add_model_file_to_archive ( archive , model , objects_data ))
2018-02-13 15:19:55 +01:00
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-02-13 15:19:55 +01:00
boost :: filesystem :: remove ( filename );
return false ;
}
2019-05-24 12:17:54 +02:00
// Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt").
// All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
// The index differes from the index of an object ID of an object instance of a 3MF file!
2018-04-27 12:56:35 +02:00
if ( ! _add_layer_height_profile_file_to_archive ( archive , model ))
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-04-27 12:56:35 +02:00
boost :: filesystem :: remove ( filename );
return false ;
}
2019-06-11 14:39:41 +02:00
// Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt").
// All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
// The index differes from the index of an object ID of an object instance of a 3MF file!
if ( ! _add_layer_config_ranges_file_to_archive ( archive , model ))
{
close_zip_writer ( & archive );
boost :: filesystem :: remove ( filename );
return false ;
}
2019-05-24 12:17:54 +02:00
// Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt").
// All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model.
// The index differes from the index of an object ID of an object instance of a 3MF file!
2018-10-05 10:13:21 +02:00
if ( ! _add_sla_support_points_file_to_archive ( archive , model ))
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-10-05 10:13:21 +02:00
boost :: filesystem :: remove ( filename );
return false ;
}
2019-12-06 15:47:58 +01:00
if ( ! _add_sla_drain_holes_file_to_archive ( archive , model ))
{
close_zip_writer ( & archive );
boost :: filesystem :: remove ( filename );
return false ;
}
2018-10-05 10:13:21 +02:00
2019-12-17 08:37:50 +01:00
// Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
2019-11-11 16:01:34 +01:00
// All custom gcode per height of whole Model are stored here
2020-06-08 12:25:29 +02:00
if ( ! _add_custom_gcode_per_print_z_file_to_archive ( archive , model , config ))
2019-11-11 16:01:34 +01:00
{
close_zip_writer ( & archive );
boost :: filesystem :: remove ( filename );
return false ;
}
2019-05-24 12:17:54 +02:00
// Adds slic3r print config file ("Metadata/Slic3r_PE.config").
// This file contains the content of FullPrintConfing / SLAFullPrintConfig.
2018-11-07 14:57:50 +01:00
if ( config != nullptr )
2018-02-27 10:49:51 +01:00
{
2018-11-07 14:57:50 +01:00
if ( ! _add_print_config_file_to_archive ( archive , * config ))
2018-03-22 13:49:48 +01:00
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-03-22 13:49:48 +01:00
boost :: filesystem :: remove ( filename );
return false ;
}
2018-02-27 10:49:51 +01:00
}
2019-05-24 12:17:54 +02:00
// Adds slic3r model config file ("Metadata/Slic3r_PE_model.config").
// This file contains all the attributes of all ModelObjects and their ModelVolumes (names, parameter overrides).
// As there is just a single Indexed Triangle Set data stored per ModelObject, offsets of volumes into their respective Indexed Triangle Set data
// is stored here as well.
if ( ! _add_model_config_file_to_archive ( archive , model , objects_data ))
2018-02-08 13:26:50 +01:00
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-02-08 13:26:50 +01:00
boost :: filesystem :: remove ( filename );
return false ;
}
if ( ! mz_zip_writer_finalize_archive ( & archive ))
{
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-02-08 13:26:50 +01:00
boost :: filesystem :: remove ( filename );
2018-02-14 14:30:03 +01:00
add_error ( "Unable to finalize the archive" );
2018-02-08 13:26:50 +01:00
return false ;
}
2019-06-03 15:27:46 +02:00
close_zip_writer ( & archive );
2018-02-08 13:26:50 +01:00
return true ;
}
2018-02-13 15:19:55 +01:00
bool _3MF_Exporter :: _add_content_types_file_to_archive ( mz_zip_archive & archive )
2018-02-08 13:26:50 +01:00
{
std :: stringstream stream ;
stream << "<?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> \n " ;
stream << "<Types xmlns= \" http://schemas.openxmlformats.org/package/2006/content-types \" > \n " ;
stream << " <Default Extension= \" rels \" ContentType= \" application/vnd.openxmlformats-package.relationships+xml \" /> \n " ;
stream << " <Default Extension= \" model \" ContentType= \" application/vnd.ms-package.3dmanufacturing-3dmodel+xml \" /> \n " ;
2019-10-23 13:31:24 +02:00
stream << " <Default Extension= \" png \" ContentType= \" image/png \" /> \n " ;
2018-02-08 13:26:50 +01:00
stream << "</Types>" ;
std :: string out = stream . str ();
if ( ! mz_zip_writer_add_mem ( & archive , CONTENT_TYPES_FILE . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
{
2018-02-14 14:30:03 +01:00
add_error ( "Unable to add content types file to archive" );
2018-02-08 13:26:50 +01:00
return false ;
}
return true ;
}
2019-10-24 09:20:33 +02:00
bool _3MF_Exporter :: _add_thumbnail_file_to_archive ( mz_zip_archive & archive , const ThumbnailData & thumbnail_data )
2019-10-23 13:31:24 +02:00
{
2019-10-24 09:20:33 +02:00
bool res = false ;
2019-10-23 13:31:24 +02:00
2019-10-24 09:20:33 +02:00
size_t png_size = 0 ;
void * png_data = tdefl_write_image_to_png_file_in_memory_ex (( const void * ) thumbnail_data . pixels . data (), thumbnail_data . width , thumbnail_data . height , 4 , & png_size , MZ_DEFAULT_LEVEL , 1 );
if ( png_data != nullptr )
{
res = mz_zip_writer_add_mem ( & archive , THUMBNAIL_FILE . c_str (), ( const void * ) png_data , png_size , MZ_DEFAULT_COMPRESSION );
mz_free ( png_data );
}
if ( ! res )
add_error ( "Unable to add thumbnail file to archive" );
return res ;
2019-10-23 13:31:24 +02:00
}
2018-02-13 15:19:55 +01:00
bool _3MF_Exporter :: _add_relationships_file_to_archive ( mz_zip_archive & archive )
2018-02-08 13:26:50 +01:00
{
std :: stringstream stream ;
stream << "<?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> \n " ;
stream << "<Relationships xmlns= \" http://schemas.openxmlformats.org/package/2006/relationships \" > \n " ;
stream << " <Relationship Target= \" /" << MODEL_FILE << " \" Id= \" rel-1 \" Type= \" http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel \" /> \n " ;
2019-10-23 13:31:24 +02:00
stream << " <Relationship Target= \" /" << THUMBNAIL_FILE << " \" Id= \" rel-2 \" Type= \" http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail \" /> \n " ;
2018-02-08 13:26:50 +01:00
stream << "</Relationships>" ;
std :: string out = stream . str ();
if ( ! mz_zip_writer_add_mem ( & archive , RELATIONSHIPS_FILE . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
{
2018-02-14 14:30:03 +01:00
add_error ( "Unable to add relationships file to archive" );
2018-02-08 13:26:50 +01:00
return false ;
}
return true ;
}
2019-05-24 12:17:54 +02:00
bool _3MF_Exporter :: _add_model_file_to_archive ( mz_zip_archive & archive , const Model & model , IdToObjectDataMap & objects_data )
2018-02-08 13:26:50 +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-08 13:26:50 +01:00
stream << "<?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> \n " ;
2018-04-30 15:27:01 +02:00
stream << "<" << MODEL_TAG << " unit= \" millimeter \" xml:lang= \" en-US \" xmlns= \" http://schemas.microsoft.com/3dmanufacturing/core/2015/02 \" xmlns:slic3rpe= \" http://schemas.slic3r.org/3mf/2017/06 \" > \n " ;
stream << " <" << METADATA_TAG << " name= \" " << SLIC3RPE_3MF_VERSION << " \" >" << VERSION_3MF << "</" << METADATA_TAG << "> \n " ;
2018-02-27 10:49:51 +01:00
stream << " <" << RESOURCES_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
2019-05-24 12:17:54 +02:00
// Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects).
2018-02-08 13:26:50 +01:00
BuildItemsList build_items ;
2019-05-24 12:17:54 +02:00
// The object_id here is a one based identifier of the first instance of a ModelObject in the 3MF file, where
// all the object instances of all ModelObjects are stored and indexed in a 1 based linear fashion.
// Therefore the list of object_ids here may not be continuous.
2018-02-08 13:26:50 +01:00
unsigned int object_id = 1 ;
for ( ModelObject * obj : model . objects )
{
if ( obj == nullptr )
continue ;
2019-05-24 12:17:54 +02:00
// Index of an object in the 3MF file corresponding to the 1st instance of a ModelObject.
2018-02-27 10:49:51 +01:00
unsigned int curr_id = object_id ;
2019-05-24 12:17:54 +02:00
IdToObjectDataMap :: iterator object_it = objects_data . insert ( IdToObjectDataMap :: value_type ( curr_id , ObjectData ( obj ))). first ;
// Store geometry of all ModelVolumes contained in a single ModelObject into a single 3MF indexed triangle set object.
// object_it->second.volumes_offsets will contain the offsets of the ModelVolumes in that single indexed triangle set.
// object_id will be increased to point to the 1st instance of the next ModelObject.
2018-02-27 10:49:51 +01:00
if ( ! _add_object_to_model_stream ( stream , object_id , * obj , build_items , object_it -> second . volumes_offsets ))
2018-02-08 13:26:50 +01:00
{
2018-02-14 14:30:03 +01:00
add_error ( "Unable to add object to archive" );
2018-02-08 13:26:50 +01:00
return false ;
}
}
2018-02-27 10:49:51 +01:00
stream << " </" << RESOURCES_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
2019-05-24 12:17:54 +02:00
// Store the transformations of all the ModelInstances of all ModelObjects, indexed in a linear fashion.
2018-02-08 13:26:50 +01:00
if ( ! _add_build_to_model_stream ( stream , build_items ))
{
2018-02-14 14:30:03 +01:00
add_error ( "Unable to add build to archive" );
2018-02-08 13:26:50 +01:00
return false ;
}
2018-02-27 10:49:51 +01:00
stream << "</" << MODEL_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
std :: string out = stream . str ();
if ( ! mz_zip_writer_add_mem ( & archive , MODEL_FILE . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
{
2018-02-14 14:30:03 +01:00
add_error ( "Unable to add model file to archive" );
2018-02-08 13:26:50 +01:00
return false ;
}
return true ;
}
2018-02-27 10:49:51 +01:00
bool _3MF_Exporter :: _add_object_to_model_stream ( std :: stringstream & stream , unsigned int & object_id , ModelObject & object , BuildItemsList & build_items , VolumeToOffsetsMap & volumes_offsets )
2018-02-08 13:26:50 +01:00
{
unsigned int id = 0 ;
for ( const ModelInstance * instance : object . instances )
{
2019-05-24 12:17:54 +02:00
assert ( instance != nullptr );
2018-02-08 13:26:50 +01:00
if ( instance == nullptr )
continue ;
unsigned int instance_id = object_id + id ;
2018-02-27 10:49:51 +01:00
stream << " <" << OBJECT_TAG << " id= \" " << instance_id << " \" type= \" model \" > \n " ;
2018-02-08 13:26:50 +01:00
if ( id == 0 )
{
2018-02-27 10:49:51 +01:00
if ( ! _add_mesh_to_object_stream ( stream , object , volumes_offsets ))
2018-02-08 13:26:50 +01:00
{
2018-02-14 14:30:03 +01:00
add_error ( "Unable to add mesh to archive" );
2018-02-08 13:26:50 +01:00
return false ;
}
}
else
{
2018-02-27 10:49:51 +01:00
stream << " <" << COMPONENTS_TAG << "> \n " ;
stream << " <" << COMPONENT_TAG << " objectid= \" " << object_id << " \" /> \n " ;
stream << " </" << COMPONENTS_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
}
2018-11-01 14:25:10 +01:00
Transform3d t = instance -> get_matrix ();
2019-05-24 12:17:54 +02:00
// instance_id is just a 1 indexed index in build_items.
assert ( instance_id == build_items . size () + 1 );
2019-08-06 14:54:38 +02:00
build_items . emplace_back ( instance_id , t , instance -> printable );
2018-02-08 13:26:50 +01:00
2018-02-27 10:49:51 +01:00
stream << " </" << OBJECT_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
++ id ;
}
object_id += id ;
return true ;
}
2018-02-27 10:49:51 +01:00
bool _3MF_Exporter :: _add_mesh_to_object_stream ( std :: stringstream & stream , ModelObject & object , VolumeToOffsetsMap & volumes_offsets )
2018-02-08 13:26:50 +01:00
{
2018-02-27 10:49:51 +01:00
stream << " <" << MESH_TAG << "> \n " ;
stream << " <" << VERTICES_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
unsigned int vertices_count = 0 ;
for ( ModelVolume * volume : object . volumes )
{
if ( volume == nullptr )
continue ;
2019-06-11 17:08:47 +02:00
if ( ! volume -> mesh (). repaired )
throw std :: runtime_error ( "store_3mf() requires repair()" );
if ( ! volume -> mesh (). has_shared_vertices ())
throw std :: runtime_error ( "store_3mf() requires shared vertices" );
2018-11-19 15:34:34 +01:00
volumes_offsets . insert ( VolumeToOffsetsMap :: value_type ( volume , Offsets ( vertices_count ))). first ;
2018-02-08 13:26:50 +01:00
2019-06-11 17:08:47 +02:00
const indexed_triangle_set & its = volume -> mesh (). its ;
2019-06-10 18:30:54 +02:00
if ( its . vertices . empty ())
2018-02-08 13:26:50 +01:00
{
2018-02-14 14:30:03 +01:00
add_error ( "Found invalid mesh" );
2018-02-08 13:26:50 +01:00
return false ;
}
2019-06-20 16:15:09 +02:00
vertices_count += ( int ) its . vertices . size ();
2018-02-08 13:26:50 +01:00
2018-12-14 09:52:15 +01:00
const Transform3d & matrix = volume -> get_matrix ();
2018-11-19 15:49:59 +01:00
2019-06-10 18:30:54 +02:00
for ( size_t i = 0 ; i < its . vertices . size (); ++ i )
2018-02-08 13:26:50 +01:00
{
2018-02-27 10:49:51 +01:00
stream << " <" << VERTEX_TAG << " " ;
2019-06-10 18:30:54 +02:00
Vec3f v = ( matrix * its . vertices [ i ]. cast < double > ()). cast < float > ();
2018-11-19 15:34:34 +01:00
stream << "x= \" " << v ( 0 ) << " \" " ;
stream << "y= \" " << v ( 1 ) << " \" " ;
stream << "z= \" " << v ( 2 ) << " \" /> \n " ;
2018-02-08 13:26:50 +01:00
}
}
2018-02-27 10:49:51 +01:00
stream << " </" << VERTICES_TAG << "> \n " ;
stream << " <" << TRIANGLES_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
2018-02-27 10:49:51 +01:00
unsigned int triangles_count = 0 ;
2018-02-08 13:26:50 +01:00
for ( ModelVolume * volume : object . volumes )
{
if ( volume == nullptr )
continue ;
2018-02-27 10:49:51 +01:00
VolumeToOffsetsMap :: iterator volume_it = volumes_offsets . find ( volume );
assert ( volume_it != volumes_offsets . end ());
2018-02-08 13:26:50 +01:00
2019-06-11 17:08:47 +02:00
const indexed_triangle_set & its = volume -> mesh (). its ;
2018-02-08 13:26:50 +01:00
2018-02-27 10:49:51 +01:00
// updates triangle offsets
volume_it -> second . first_triangle_id = triangles_count ;
2019-06-20 16:15:09 +02:00
triangles_count += ( int ) its . indices . size ();
2018-02-27 10:49:51 +01:00
volume_it -> second . last_triangle_id = triangles_count - 1 ;
2019-06-10 18:30:54 +02:00
for ( size_t i = 0 ; i < its . indices . size (); ++ i )
2018-02-08 13:26:50 +01:00
{
2018-02-27 10:49:51 +01:00
stream << " <" << TRIANGLE_TAG << " " ;
2018-02-08 13:26:50 +01:00
for ( int j = 0 ; j < 3 ; ++ j )
{
2019-06-10 21:14:58 +02:00
stream << "v" << j + 1 << "= \" " << its . indices [ i ][ j ] + volume_it -> second . first_vertex_id << " \" " ;
2018-02-08 13:26:50 +01:00
}
stream << "/> \n " ;
}
}
2018-02-27 10:49:51 +01:00
stream << " </" << TRIANGLES_TAG << "> \n " ;
stream << " </" << MESH_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
return true ;
}
bool _3MF_Exporter :: _add_build_to_model_stream ( std :: stringstream & stream , const BuildItemsList & build_items )
{
if ( build_items . size () == 0 )
{
2018-02-14 14:30:03 +01:00
add_error ( "No build item found" );
2018-02-08 13:26:50 +01:00
return false ;
}
2018-02-27 10:49:51 +01:00
stream << " <" << BUILD_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
for ( const BuildItem & item : build_items )
{
2019-09-19 09:09:11 +02:00
stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "= \" " << item . id << " \" " << TRANSFORM_ATTR << "= \" " ;
2018-02-08 13:26:50 +01:00
for ( unsigned c = 0 ; c < 4 ; ++ c )
{
for ( unsigned r = 0 ; r < 3 ; ++ r )
{
2018-08-28 14:30:36 +02:00
stream << item . transform ( r , c );
2018-02-08 13:26:50 +01:00
if (( r != 2 ) || ( c != 3 ))
stream << " " ;
}
}
2019-09-19 09:09:11 +02:00
stream << " \" " << PRINTABLE_ATTR << "= \" " << item . printable << " \" /> \n " ;
2018-02-08 13:26:50 +01:00
}
2018-02-27 10:49:51 +01:00
stream << " </" << BUILD_TAG << "> \n " ;
2018-02-08 13:26:50 +01:00
return true ;
}
2018-04-27 12:56:35 +02:00
bool _3MF_Exporter :: _add_layer_height_profile_file_to_archive ( mz_zip_archive & archive , Model & model )
{
std :: string out = "" ;
char buffer [ 1024 ];
unsigned int count = 0 ;
for ( const ModelObject * object : model . objects )
{
++ count ;
2019-01-21 10:06:51 +01:00
const std :: vector < double > & layer_height_profile = object -> layer_height_profile ;
2018-04-27 12:56:35 +02:00
if (( layer_height_profile . size () >= 4 ) && (( layer_height_profile . size () % 2 ) == 0 ))
{
sprintf ( buffer , "object_id=%d|" , count );
out += buffer ;
// Store the layer height profile as a single semicolon separated list.
for ( size_t i = 0 ; i < layer_height_profile . size (); ++ i )
{
sprintf ( buffer , ( i == 0 ) ? "%f" : ";%f" , layer_height_profile [ i ]);
out += buffer ;
}
out += " \n " ;
}
}
if ( ! out . empty ())
{
if ( ! mz_zip_writer_add_mem ( & archive , LAYER_HEIGHTS_PROFILE_FILE . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
{
add_error ( "Unable to add layer heights profile file to archive" );
return false ;
}
}
return true ;
}
2019-06-11 14:39:41 +02:00
bool _3MF_Exporter :: _add_layer_config_ranges_file_to_archive ( mz_zip_archive & archive , Model & model )
{
std :: string out = "" ;
2019-06-27 13:54:51 +02:00
pt :: ptree tree ;
2019-06-11 14:39:41 +02:00
unsigned int object_cnt = 0 ;
for ( const ModelObject * object : model . objects )
{
object_cnt ++ ;
const t_layer_config_ranges & ranges = object -> layer_config_ranges ;
if ( ! ranges . empty ())
{
2019-06-27 13:54:51 +02:00
pt :: ptree & obj_tree = tree . add ( "objects.object" , "" );
obj_tree . put ( "<xmlattr>.id" , object_cnt );
2019-06-11 14:39:41 +02:00
// Store the layer config ranges.
for ( const auto & range : ranges )
{
2019-06-27 15:57:29 +02:00
pt :: ptree & range_tree = obj_tree . add ( "range" , "" );
2019-06-27 13:54:51 +02:00
2019-06-11 14:39:41 +02:00
// store minX and maxZ
2019-06-27 13:54:51 +02:00
range_tree . put ( "<xmlattr>.min_z" , range . first . first );
range_tree . put ( "<xmlattr>.max_z" , range . first . second );
2019-06-11 14:39:41 +02:00
// store range configuration
const DynamicPrintConfig & config = range . second ;
2019-06-27 13:54:51 +02:00
for ( const std :: string & opt_key : config . keys ())
{
2019-07-04 20:49:46 +02:00
pt :: ptree & opt_tree = range_tree . add ( "option" , config . opt_serialize ( opt_key ));
2019-06-27 13:54:51 +02:00
opt_tree . put ( "<xmlattr>.opt_key" , opt_key );
}
2019-06-11 14:39:41 +02:00
}
}
}
2019-06-27 13:54:51 +02:00
if ( ! tree . empty ())
{
std :: ostringstream oss ;
2019-11-11 16:01:34 +01:00
pt :: write_xml ( oss , tree );
2019-06-27 13:54:51 +02:00
out = oss . str ();
2019-06-27 15:57:29 +02:00
// Post processing("beautification") of the output string for a better preview
2019-06-27 13:54:51 +02:00
boost :: replace_all ( out , "><object" , "> \n <object" );
2019-06-27 15:57:29 +02:00
boost :: replace_all ( out , "><range" , "> \n <range" );
boost :: replace_all ( out , "><option" , "> \n <option" );
boost :: replace_all ( out , "></range>" , "> \n </range>" );
boost :: replace_all ( out , "></object>" , "> \n </object>" );
// OR just
boost :: replace_all ( out , "><" , "> \n <" );
2019-06-27 13:54:51 +02:00
}
2019-06-11 14:39:41 +02:00
if ( ! out . empty ())
{
if ( ! mz_zip_writer_add_mem ( & archive , LAYER_CONFIG_RANGES_FILE . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
{
add_error ( "Unable to add layer heights profile file to archive" );
return false ;
}
}
return true ;
}
2018-10-05 10:13:21 +02:00
bool _3MF_Exporter :: _add_sla_support_points_file_to_archive ( mz_zip_archive & archive , Model & model )
{
std :: string out = "" ;
char buffer [ 1024 ];
unsigned int count = 0 ;
for ( const ModelObject * object : model . objects )
{
++ count ;
2019-01-30 08:26:23 +01:00
const std :: vector < sla :: SupportPoint >& sla_support_points = object -> sla_support_points ;
2018-10-05 10:13:21 +02:00
if ( ! sla_support_points . empty ())
{
sprintf ( buffer , "object_id=%d|" , count );
out += buffer ;
// Store the layer height profile as a single space separated list.
for ( size_t i = 0 ; i < sla_support_points . size (); ++ i )
{
2019-01-30 08:26:23 +01:00
sprintf ( buffer , ( i == 0 ? "%f %f %f %f %f" : " %f %f %f %f %f" ), 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 , ( float ) sla_support_points [ i ]. is_new_island );
2018-10-05 10:13:21 +02:00
out += buffer ;
}
out += " \n " ;
}
}
if ( ! out . empty ())
{
2019-02-01 16:12:00 +01:00
// Adds version header at the beginning:
out = std :: string ( "support_points_format_version=" ) + std :: to_string ( support_points_format_version ) + std :: string ( " \n " ) + out ;
2018-10-05 10:13:21 +02:00
if ( ! mz_zip_writer_add_mem ( & archive , SLA_SUPPORT_POINTS_FILE . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
{
add_error ( "Unable to add sla support points file to archive" );
return false ;
}
}
return true ;
}
2019-12-06 15:47:58 +01:00
bool _3MF_Exporter :: _add_sla_drain_holes_file_to_archive ( mz_zip_archive & archive , Model & model )
{
const char * const fmt = "object_id=%d|" ;
std :: string out ;
unsigned int count = 0 ;
for ( const ModelObject * object : model . objects )
{
++ count ;
auto & drain_holes = object -> sla_drain_holes ;
if ( ! drain_holes . empty ())
{
out += string_printf ( fmt , count );
// Store the layer height profile as a single space separated list.
for ( size_t i = 0 ; i < drain_holes . size (); ++ i )
out += string_printf (( i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f" ),
drain_holes [ i ]. pos ( 0 ),
drain_holes [ i ]. pos ( 1 ),
drain_holes [ i ]. pos ( 2 ),
drain_holes [ i ]. normal ( 0 ),
drain_holes [ i ]. normal ( 1 ),
drain_holes [ i ]. normal ( 2 ),
drain_holes [ i ]. radius ,
drain_holes [ i ]. height );
out += " \n " ;
}
}
if ( ! out . empty ())
{
// Adds version header at the beginning:
out = std :: string ( "drain_holes_format_version=" ) + std :: to_string ( drain_holes_format_version ) + std :: string ( " \n " ) + out ;
if ( ! mz_zip_writer_add_mem ( & archive , SLA_DRAIN_HOLES_FILE . c_str (), static_cast < const void *> ( out . data ()), out . length (), mz_uint ( MZ_DEFAULT_COMPRESSION )))
{
add_error ( "Unable to add sla support points file to archive" );
return false ;
}
}
return true ;
}
2018-10-05 10:13:21 +02:00
2018-11-07 14:57:50 +01:00
bool _3MF_Exporter :: _add_print_config_file_to_archive ( mz_zip_archive & archive , const DynamicPrintConfig & config )
2018-01-30 09:27:10 +01:00
{
2018-02-13 15:19:55 +01:00
char buffer [ 1024 ];
sprintf ( buffer , "; %s \n\n " , header_slic3r_generated (). c_str ());
std :: string out = buffer ;
2018-11-07 14:57:50 +01:00
for ( const std :: string & key : config . keys ())
if ( key != "compatible_printers" )
2019-06-26 13:26:49 +02:00
out += "; " + key + " = " + config . opt_serialize ( key ) + " \n " ;
2018-02-13 15:19:55 +01:00
2018-04-27 12:56:35 +02:00
if ( ! out . empty ())
2018-02-13 15:19:55 +01:00
{
2018-04-27 12:56:35 +02:00
if ( ! mz_zip_writer_add_mem ( & archive , PRINT_CONFIG_FILE . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
{
add_error ( "Unable to add print config file to archive" );
return false ;
}
2018-02-27 10:49:51 +01:00
}
return true ;
}
2019-05-24 12:17:54 +02:00
bool _3MF_Exporter :: _add_model_config_file_to_archive ( mz_zip_archive & archive , const Model & model , const IdToObjectDataMap & objects_data )
2018-02-27 10:49:51 +01:00
{
std :: stringstream stream ;
2019-12-19 10:22:46 +01:00
// Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back
// when loaded as accurately as possible.
stream << std :: setprecision ( std :: numeric_limits < double >:: max_digits10 );
2018-02-27 10:49:51 +01:00
stream << "<?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> \n " ;
stream << "<" << CONFIG_TAG << "> \n " ;
2019-05-24 12:17:54 +02:00
for ( const IdToObjectDataMap :: value_type & obj_metadata : objects_data )
2018-02-27 10:49:51 +01:00
{
const ModelObject * obj = obj_metadata . second . object ;
if ( obj != nullptr )
{
2020-01-02 11:37:50 +01:00
// Output of instances count added because of github #3435, currently not used by PrusaSlicer
stream << " <" << OBJECT_TAG << " " << ID_ATTR << "= \" " << obj_metadata . first << " \" " << INSTANCESCOUNT_ATTR << "= \" " << obj -> instances . size () << " \" > \n " ;
2018-02-27 10:49:51 +01:00
2018-02-27 15:46:54 +01:00
// stores object's name
if ( ! obj -> name . empty ())
2018-07-23 14:39:50 +02:00
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << OBJECT_TYPE << " \" " << KEY_ATTR << "= \" name \" " << VALUE_ATTR << "= \" " << xml_escape ( obj -> name ) << " \" /> \n " ;
2018-02-27 15:46:54 +01:00
2018-02-27 10:49:51 +01:00
// stores object's config data
for ( const std :: string & key : obj -> config . keys ())
{
2019-06-26 13:26:49 +02:00
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << OBJECT_TYPE << " \" " << KEY_ATTR << "= \" " << key << " \" " << VALUE_ATTR << "= \" " << obj -> config . opt_serialize ( key ) << " \" /> \n " ;
2018-02-27 10:49:51 +01:00
}
for ( const ModelVolume * volume : obj_metadata . second . object -> volumes )
{
if ( volume != nullptr )
{
const VolumeToOffsetsMap & offsets = obj_metadata . second . volumes_offsets ;
VolumeToOffsetsMap :: const_iterator it = offsets . find ( volume );
if ( it != offsets . end ())
{
// stores volume's offsets
stream << " <" << VOLUME_TAG << " " ;
stream << FIRST_TRIANGLE_ID_ATTR << "= \" " << it -> second . first_triangle_id << " \" " ;
stream << LAST_TRIANGLE_ID_ATTR << "= \" " << it -> second . last_triangle_id << " \" > \n " ;
2018-02-27 15:46:54 +01:00
// stores volume's name
if ( ! volume -> name . empty ())
2018-07-23 14:39:50 +02:00
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << NAME_KEY << " \" " << VALUE_ATTR << "= \" " << xml_escape ( volume -> name ) << " \" /> \n " ;
2018-02-28 12:11:41 +01:00
2018-09-17 15:12:13 +02:00
// stores volume's modifier field (legacy, to support old slicers)
if ( volume -> is_modifier ())
2018-02-28 12:11:41 +01:00
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << MODIFIER_KEY << " \" " << VALUE_ATTR << "= \" 1 \" /> \n " ;
2018-09-17 15:12:13 +02:00
// stores volume's type (overrides the modifier field above)
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << VOLUME_TYPE_KEY << " \" " <<
VALUE_ATTR << "= \" " << ModelVolume :: type_to_string ( volume -> type ()) << " \" /> \n " ;
2018-02-27 15:46:54 +01:00
2019-09-19 09:09:11 +02:00
// stores volume's local matrix
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << MATRIX_KEY << " \" " << VALUE_ATTR << "= \" " ;
2019-12-20 12:11:58 +01:00
Transform3d matrix = volume -> get_matrix () * volume -> source . transform . get_matrix ();
2019-09-19 09:09:11 +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 << " \" /> \n " ;
// stores volume's source data
if ( ! volume -> source . input_file . empty ())
{
2020-01-08 11:11:38 +01:00
std :: string input_file = xml_escape ( m_fullpath_sources ? volume -> source . input_file : boost :: filesystem :: path ( volume -> source . input_file ). filename (). string ());
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << SOURCE_FILE_KEY << " \" " << VALUE_ATTR << "= \" " << input_file << " \" /> \n " ;
2019-09-19 09:09:11 +02:00
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << SOURCE_OBJECT_ID_KEY << " \" " << VALUE_ATTR << "= \" " << volume -> source . object_idx << " \" /> \n " ;
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << SOURCE_VOLUME_ID_KEY << " \" " << VALUE_ATTR << "= \" " << volume -> source . volume_idx << " \" /> \n " ;
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << SOURCE_OFFSET_X_KEY << " \" " << VALUE_ATTR << "= \" " << volume -> source . mesh_offset ( 0 ) << " \" /> \n " ;
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << SOURCE_OFFSET_Y_KEY << " \" " << VALUE_ATTR << "= \" " << volume -> source . mesh_offset ( 1 ) << " \" /> \n " ;
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << SOURCE_OFFSET_Z_KEY << " \" " << VALUE_ATTR << "= \" " << volume -> source . mesh_offset ( 2 ) << " \" /> \n " ;
}
2018-02-27 10:49:51 +01:00
// stores volume's config data
for ( const std :: string & key : volume -> config . keys ())
{
2019-06-26 13:26:49 +02:00
stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "= \" " << VOLUME_TYPE << " \" " << KEY_ATTR << "= \" " << key << " \" " << VALUE_ATTR << "= \" " << volume -> config . opt_serialize ( key ) << " \" /> \n " ;
2018-02-27 10:49:51 +01:00
}
2018-02-27 15:46:54 +01:00
2018-02-27 10:49:51 +01:00
stream << " </" << VOLUME_TAG << "> \n " ;
}
}
}
stream << " </" << OBJECT_TAG << "> \n " ;
}
}
stream << "</" << CONFIG_TAG << "> \n " ;
std :: string out = stream . str ();
if ( ! mz_zip_writer_add_mem ( & archive , MODEL_CONFIG_FILE . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
{
add_error ( "Unable to add model config file to archive" );
2018-02-13 15:19:55 +01:00
return false ;
}
return true ;
}
2020-06-08 12:25:29 +02:00
bool _3MF_Exporter :: _add_custom_gcode_per_print_z_file_to_archive ( mz_zip_archive & archive , Model & model , const DynamicPrintConfig * config )
2019-11-11 16:01:34 +01:00
{
std :: string out = "" ;
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
{
pt :: ptree tree ;
2019-12-17 08:37:50 +01:00
pt :: ptree & main_tree = tree . add ( "custom_gcodes_per_print_z" , "" );
2019-11-11 16:01:34 +01:00
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-06-08 12:25:29 +02:00
// store data of custom_gcode_per_print_z
2019-12-17 08:37:50 +01:00
code_tree . put ( "<xmlattr>.print_z" , code . print_z );
2020-06-08 12:25:29 +02:00
code_tree . put ( "<xmlattr>.type" , static_cast < int > ( code . type ));
2019-11-11 16:01:34 +01:00
code_tree . put ( "<xmlattr>.extruder" , code . extruder );
code_tree . put ( "<xmlattr>.color" , code . color );
2020-06-08 12:25:29 +02:00
code_tree . put ( "<xmlattr>.extra" , code . extra );
// add gcode field data for the old version of the PrusaSlicer
std :: string gcode = code . type == CustomGCode :: ColorChange ? config -> opt_string ( "color_change_gcode" ) :
code . type == CustomGCode :: PausePrint ? config -> opt_string ( "pause_print_gcode" ) :
code . type == CustomGCode :: Template ? config -> opt_string ( "template_custom_gcode" ) :
code . type == CustomGCode :: ToolChange ? "tool_change" : code . extra ;
code_tree . put ( "<xmlattr>.gcode" , gcode );
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 ;
boost :: property_tree :: write_xml ( oss , tree );
out = oss . str ();
// Post processing("beautification") of the output string
boost :: replace_all ( out , "><" , "> \n <" );
}
}
if ( ! out . empty ())
{
2019-12-17 08:37:50 +01:00
if ( ! mz_zip_writer_add_mem ( & archive , CUSTOM_GCODE_PER_PRINT_Z_FILE . c_str (), ( const void * ) out . data (), out . length (), MZ_DEFAULT_COMPRESSION ))
2019-11-11 16:01:34 +01:00
{
2019-12-17 08:37:50 +01:00
add_error ( "Unable to add custom Gcodes per print_z file to archive" );
2019-11-11 16:01:34 +01:00
return false ;
}
}
return true ;
}
bool load_3mf ( const char * path , DynamicPrintConfig * config , Model * model , bool check_version )
2018-02-13 15:19:55 +01:00
{
2018-09-25 11:53:05 +02:00
if (( path == nullptr ) || ( config == nullptr ) || ( model == nullptr ))
2018-01-30 09:27:10 +01:00
return false ;
_3MF_Importer importer ;
2019-08-23 13:12:31 +02:00
bool res = importer . load_model_from_file ( path , * model , * config , check_version );
2018-04-27 12:56:35 +02:00
importer . log_errors ();
2018-01-30 09:27:10 +01:00
return res ;
}
2018-02-08 13:26:50 +01:00
2020-01-08 11:11:38 +01:00
bool store_3mf ( const char * path , Model * model , const DynamicPrintConfig * config , bool fullpath_sources , const ThumbnailData * thumbnail_data )
2018-02-08 13:26:50 +01:00
{
2018-11-07 14:57:50 +01:00
if (( path == nullptr ) || ( model == nullptr ))
2018-02-08 13:26:50 +01:00
return false ;
_3MF_Exporter exporter ;
2020-01-08 11:11:38 +01:00
bool res = exporter . save_model_to_file ( path , * model , config , fullpath_sources , thumbnail_data );
2018-02-08 13:26:50 +01:00
if ( ! res )
2018-02-14 14:30:03 +01:00
exporter . log_errors ();
2018-02-08 13:26:50 +01:00
return res ;
}
2018-01-30 09:27:10 +01:00
} // namespace Slic3r