2022-07-15 15:37:19 +00:00
# include "../libslic3r.h"
# include "../Exception.hpp"
# include "../Model.hpp"
# include "../Utils.hpp"
# include "../LocalesUtils.hpp"
# include "../GCode.hpp"
# include "../Geometry.hpp"
# include "../GCode/ThumbnailData.hpp"
# include "../Semver.hpp"
# include "../Time.hpp"
# include "../I18N.hpp"
# include "3mf.hpp"
# include <limits>
# include <stdexcept>
# include <boost/algorithm/string/classification.hpp>
# include <boost/algorithm/string/split.hpp>
# include <boost/algorithm/string/predicate.hpp>
# include <boost/algorithm/string/replace.hpp>
# include <boost/filesystem/operations.hpp>
# include <boost/spirit/include/karma.hpp>
# include <boost/spirit/include/qi_int.hpp>
# include <boost/log/trivial.hpp>
# include <boost/property_tree/ptree.hpp>
# include <boost/property_tree/xml_parser.hpp>
# include <boost/foreach.hpp>
namespace pt = boost : : property_tree ;
# include <expat.h>
# include <Eigen/Dense>
# include "miniz_extension.hpp"
# include <fast_float/fast_float.h>
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
// https://github.com/boostorg/spirit/pull/586
// where the exported string is one digit shorter than it should be to guarantee lossless round trip.
// The code is left here for the ocasion boost guys improve.
# define EXPORT_3MF_USE_SPIRIT_KARMA_FP 0
// 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.
// 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, meshes transformed back to their coordinate system on loading.
// WARNING !! -> the version number has been rolled back to 1
// the next change should use 3
const unsigned int VERSION_3MF = 1 ;
// Allow loading version 2 file as well.
const unsigned int VERSION_3MF_COMPATIBLE = 2 ;
const char * SLIC3RPE_3MF_VERSION = " slic3rpe:Version3mf " ; // definition of the metadata name saved into .model file
// Painting gizmos data version numbers
// 0 : 3MF files saved by older PrusaSlicer or the painting gizmo wasn't used. No version definition in them.
// 1 : Introduction of painting gizmos data versioning. No other changes in painting gizmos data.
const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 1 ;
const unsigned int SEAM_PAINTING_VERSION = 1 ;
const unsigned int MM_PAINTING_VERSION = 1 ;
const std : : string SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION = " slic3rpe:FdmSupportsPaintingVersion " ;
const std : : string SLIC3RPE_SEAM_PAINTING_VERSION = " slic3rpe:SeamPaintingVersion " ;
const std : : string SLIC3RPE_MM_PAINTING_VERSION = " slic3rpe:MmPaintingVersion " ;
const std : : string MODEL_FOLDER = " 3D/ " ;
const std : : string MODEL_EXTENSION = " .model " ;
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 " ;
const std : : string THUMBNAIL_FILE = " Metadata/thumbnail.png " ;
const std : : string PRINT_CONFIG_FILE = " Metadata/Slic3r_PE.config " ;
const std : : string MODEL_CONFIG_FILE = " Metadata/Slic3r_PE_model.config " ;
const std : : string LAYER_HEIGHTS_PROFILE_FILE = " Metadata/Slic3r_PE_layer_heights_profile.txt " ;
const std : : string LAYER_CONFIG_RANGES_FILE = " Metadata/Prusa_Slicer_layer_config_ranges.xml " ;
const std : : string SLA_SUPPORT_POINTS_FILE = " Metadata/Slic3r_PE_sla_support_points.txt " ;
const std : : string SLA_DRAIN_HOLES_FILE = " Metadata/Slic3r_PE_sla_drain_holes.txt " ;
const std : : string CUSTOM_GCODE_PER_PRINT_Z_FILE = " Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml " ;
static constexpr const char * MODEL_TAG = " model " ;
static constexpr const char * RESOURCES_TAG = " resources " ;
static constexpr const char * OBJECT_TAG = " object " ;
static constexpr const char * MESH_TAG = " mesh " ;
static constexpr const char * VERTICES_TAG = " vertices " ;
static constexpr const char * VERTEX_TAG = " vertex " ;
static constexpr const char * TRIANGLES_TAG = " triangles " ;
static constexpr const char * TRIANGLE_TAG = " triangle " ;
static constexpr const char * COMPONENTS_TAG = " components " ;
static constexpr const char * COMPONENT_TAG = " component " ;
static constexpr const char * BUILD_TAG = " build " ;
static constexpr const char * ITEM_TAG = " item " ;
static constexpr const char * METADATA_TAG = " metadata " ;
static constexpr const char * CONFIG_TAG = " config " ;
static constexpr const char * VOLUME_TAG = " volume " ;
static constexpr const char * UNIT_ATTR = " unit " ;
static constexpr const char * NAME_ATTR = " name " ;
static constexpr const char * TYPE_ATTR = " type " ;
static constexpr const char * ID_ATTR = " id " ;
static constexpr const char * X_ATTR = " x " ;
static constexpr const char * Y_ATTR = " y " ;
static constexpr const char * Z_ATTR = " z " ;
static constexpr const char * V1_ATTR = " v1 " ;
static constexpr const char * V2_ATTR = " v2 " ;
static constexpr const char * V3_ATTR = " v3 " ;
static constexpr const char * OBJECTID_ATTR = " objectid " ;
static constexpr const char * TRANSFORM_ATTR = " transform " ;
static constexpr const char * PRINTABLE_ATTR = " printable " ;
static constexpr const char * INSTANCESCOUNT_ATTR = " instances_count " ;
static constexpr const char * CUSTOM_SUPPORTS_ATTR = " slic3rpe:custom_supports " ;
static constexpr const char * CUSTOM_SEAM_ATTR = " slic3rpe:custom_seam " ;
static constexpr const char * MMU_SEGMENTATION_ATTR = " slic3rpe:mmu_segmentation " ;
static constexpr const char * KEY_ATTR = " key " ;
static constexpr const char * VALUE_ATTR = " value " ;
static constexpr const char * FIRST_TRIANGLE_ID_ATTR = " firstid " ;
static constexpr const char * LAST_TRIANGLE_ID_ATTR = " lastid " ;
static constexpr const char * OBJECT_TYPE = " object " ;
static constexpr const char * VOLUME_TYPE = " volume " ;
static constexpr const char * NAME_KEY = " name " ;
static constexpr const char * MODIFIER_KEY = " modifier " ;
static constexpr const char * VOLUME_TYPE_KEY = " volume_type " ;
static constexpr const char * MATRIX_KEY = " matrix " ;
static constexpr const char * SOURCE_FILE_KEY = " source_file " ;
static constexpr const char * SOURCE_OBJECT_ID_KEY = " source_object_id " ;
static constexpr const char * SOURCE_VOLUME_ID_KEY = " source_volume_id " ;
static constexpr const char * SOURCE_OFFSET_X_KEY = " source_offset_x " ;
static constexpr const char * SOURCE_OFFSET_Y_KEY = " source_offset_y " ;
static constexpr const char * SOURCE_OFFSET_Z_KEY = " source_offset_z " ;
static constexpr const char * SOURCE_IN_INCHES = " source_in_inches " ;
static constexpr const char * SOURCE_IN_METERS = " source_in_meters " ;
static constexpr const char * MESH_STAT_EDGES_FIXED = " edges_fixed " ;
static constexpr const char * MESH_STAT_DEGENERATED_FACETS = " degenerate_facets " ;
static constexpr const char * MESH_STAT_FACETS_REMOVED = " facets_removed " ;
static constexpr const char * MESH_STAT_FACETS_RESERVED = " facets_reversed " ;
static constexpr const char * MESH_STAT_BACKWARDS_EDGES = " backwards_edges " ;
const unsigned int VALID_OBJECT_TYPES_COUNT = 1 ;
const char * VALID_OBJECT_TYPES [ ] =
{
" model "
} ;
const char * INVALID_OBJECT_TYPES [ ] =
{
" solidsupport " ,
" support " ,
" surface " ,
" other "
} ;
class version_error : public Slic3r : : FileIOError
{
public :
version_error ( const std : : string & what_arg ) : Slic3r : : FileIOError ( what_arg ) { }
version_error ( const char * what_arg ) : Slic3r : : FileIOError ( what_arg ) { }
} ;
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 )
{
float value = 0.0f ;
if ( const char * text = get_attribute_value_charptr ( attributes , attributes_size , attribute_key ) ; text ! = nullptr )
fast_float : : from_chars ( text , text + strlen ( text ) , value ) ;
return value ;
}
int get_attribute_value_int ( const char * * attributes , unsigned int attributes_size , const char * attribute_key )
{
int value = 0 ;
if ( const char * text = get_attribute_value_charptr ( attributes , attributes_size , attribute_key ) ; text ! = nullptr )
boost : : spirit : : qi : : parse ( text , text + strlen ( text ) , boost : : spirit : : qi : : int_ , value ) ;
return value ;
}
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 ;
}
Slic3r : : Transform3d get_transform_from_3mf_specs_string ( const std : : string & mat_str )
{
// 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 ( ) ;
if ( mat_str . empty ( ) )
// empty string means default identity matrix
return ret ;
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
return ret ;
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 ) {
ret ( r , c ) = : : atof ( mat_elements_str [ i + + ] . c_str ( ) ) ;
}
}
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 {
//! macro used to mark string used at localization,
//! return same string
# define L(s) (s)
# define _(s) Slic3r::I18N::translate(s)
2022-07-29 07:28:56 +00:00
void XMLCALL PrusaFileParser : : start_element_handler ( void * userData , const char * name , const char * * attributes )
{
PrusaFileParser * prusa_parser = ( PrusaFileParser * ) userData ;
if ( prusa_parser ! = nullptr ) { prusa_parser - > _start_element_handler ( name , attributes ) ; }
}
void XMLCALL PrusaFileParser : : characters_handler ( void * userData , const XML_Char * s , int len )
{
PrusaFileParser * prusa_parser = ( PrusaFileParser * ) userData ;
if ( prusa_parser ! = nullptr ) { prusa_parser - > _characters_handler ( s , len ) ; }
}
bool PrusaFileParser : : check_3mf_from_prusa ( const std : : string filename )
{
mz_zip_archive archive ;
mz_zip_zero_struct ( & archive ) ;
if ( ! open_zip_reader ( & archive , filename ) ) {
// throw Slic3r::RuntimeError("Loading 3mf file failed.");
return false ;
}
const std : : string sub_relationship_file = " 3D/_rels/3dmodel.model.rels " ;
int sub_index = mz_zip_reader_locate_file ( & archive , sub_relationship_file . c_str ( ) , nullptr , 0 ) ;
if ( sub_index = = - 1 ) {
const std : : string model_file = " 3D/3dmodel.model " ;
int model_file_index = mz_zip_reader_locate_file ( & archive , model_file . c_str ( ) , nullptr , 0 ) ;
if ( model_file_index ! = - 1 ) {
int depth = 0 ;
m_parser = XML_ParserCreate ( nullptr ) ;
XML_SetUserData ( m_parser , ( void * ) this ) ;
XML_SetElementHandler ( m_parser , start_element_handler , nullptr ) ;
XML_SetCharacterDataHandler ( m_parser , characters_handler ) ;
mz_zip_archive_file_stat stat ;
if ( ! mz_zip_reader_file_stat ( & archive , model_file_index , & stat ) ) goto EXIT ;
void * parser_buffer = XML_GetBuffer ( m_parser , ( int ) stat . m_uncomp_size ) ;
if ( parser_buffer = = nullptr ) goto EXIT ;
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 ) goto EXIT ;
XML_ParseBuffer ( m_parser , ( int ) stat . m_uncomp_size , 1 ) ;
}
}
EXIT :
close_zip_reader ( & archive ) ;
return m_from_prusa ;
}
void PrusaFileParser : : _characters_handler ( const XML_Char * s , int len )
{
if ( m_is_application_key ) {
std : : string str ( s , len ) ;
if ( ! str . empty ( ) & & str . find ( " PrusaSlicer " ) ! = std : : string : : npos ) m_from_prusa = true ;
}
}
void PrusaFileParser : : _start_element_handler ( const char * name , const char * * attributes )
{
if ( : : strcmp ( name , " metadata " ) = = 0 ) {
unsigned int num_attributes = ( unsigned int ) XML_GetSpecifiedAttributeCount ( m_parser ) ;
std : : string str_name = get_attribute_value_string ( attributes , num_attributes , " name " ) ;
if ( ! str_name . empty ( ) & & str_name . find ( " Application " ) ! = std : : string : : npos ) { m_is_application_key = true ; }
}
}
const char * PrusaFileParser : : 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 PrusaFileParser : : 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 : " " ;
}
ModelVolumeType type_from_string ( const std : : string & s )
{
// Legacy support
if ( s = = " 1 " ) return ModelVolumeType : : PARAMETER_MODIFIER ;
// New type (supporting the support enforcers & blockers)
if ( s = = " ModelPart " ) return ModelVolumeType : : MODEL_PART ;
if ( s = = " NegativeVolume " ) return ModelVolumeType : : NEGATIVE_VOLUME ;
if ( s = = " ParameterModifier " ) return ModelVolumeType : : PARAMETER_MODIFIER ;
if ( s = = " SupportEnforcer " ) return ModelVolumeType : : SUPPORT_ENFORCER ;
if ( s = = " SupportBlocker " ) return ModelVolumeType : : SUPPORT_BLOCKER ;
// Default value if invalud type string received.
return ModelVolumeType : : MODEL_PART ;
}
2022-07-15 15:37:19 +00: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 )
BOOST_LOG_TRIVIAL ( error ) < < error ;
}
} ;
class _3MF_Importer : public _3MF_Base
{
struct Component
{
int object_id ;
Transform3d transform ;
explicit Component ( int object_id )
: object_id ( object_id )
, transform ( Transform3d : : Identity ( ) )
{
}
Component ( int object_id , const Transform3d & transform )
: object_id ( object_id )
, transform ( transform )
{
}
} ;
typedef std : : vector < Component > ComponentsList ;
struct Geometry
{
std : : vector < Vec3f > vertices ;
std : : vector < Vec3i > triangles ;
std : : vector < std : : string > custom_supports ;
std : : vector < std : : string > custom_seam ;
std : : vector < std : : string > mmu_segmentation ;
bool empty ( ) { return vertices . empty ( ) | | triangles . empty ( ) ; }
void reset ( ) {
vertices . clear ( ) ;
triangles . clear ( ) ;
custom_supports . clear ( ) ;
custom_seam . clear ( ) ;
mmu_segmentation . clear ( ) ;
}
} ;
struct CurrentObject
{
// ID of the object inside the 3MF file, 1 based.
int id ;
// Index of the ModelObject in its respective Model, zero based.
int model_object_idx ;
Geometry geometry ;
ModelObject * object ;
ComponentsList components ;
CurrentObject ( ) { reset ( ) ; }
void reset ( ) {
id = - 1 ;
model_object_idx = - 1 ;
geometry . reset ( ) ;
object = nullptr ;
components . clear ( ) ;
}
} ;
struct CurrentConfig
{
int object_id ;
int volume_id ;
} ;
struct Instance
{
ModelInstance * instance ;
Transform3d transform ;
Instance ( ModelInstance * instance , const Transform3d & transform )
: instance ( instance )
, transform ( transform )
{
}
} ;
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 ;
RepairedMeshErrors mesh_stats ;
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 ;
} ;
// Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects.
typedef std : : map < int , int > IdToModelObjectMap ;
typedef std : : map < int , ComponentsList > IdToAliasesMap ;
typedef std : : vector < Instance > InstancesList ;
typedef std : : map < int , ObjectMetadata > IdToMetadataMap ;
typedef std : : map < int , Geometry > IdToGeometryMap ;
typedef std : : map < int , std : : vector < coordf_t > > IdToLayerHeightsProfileMap ;
typedef std : : map < int , t_layer_config_ranges > IdToLayerConfigRangesMap ;
typedef std : : map < int , std : : vector < sla : : SupportPoint > > IdToSlaSupportPointsMap ;
typedef std : : map < int , std : : vector < sla : : DrainHole > > IdToSlaDrainHolesMap ;
// Version of the 3mf file
unsigned int m_version ;
bool m_check_version ;
// Semantic version of PrusaSlicer, that generated this 3MF.
boost : : optional < Semver > m_prusaslicer_generator_version ;
unsigned int m_fdm_supports_painting_version = 0 ;
unsigned int m_seam_painting_version = 0 ;
unsigned int m_mm_painting_version = 0 ;
XML_Parser m_xml_parser ;
// Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state
// after returning from XML_Parse() function, thus we keep the error state here.
bool m_parse_error { false } ;
std : : string m_parse_error_message ;
Model * m_model ;
float m_unit_factor ;
CurrentObject m_curr_object ;
IdToModelObjectMap m_objects ;
IdToAliasesMap m_objects_aliases ;
InstancesList m_instances ;
IdToGeometryMap m_geometries ;
CurrentConfig m_curr_config ;
IdToMetadataMap m_objects_metadata ;
IdToLayerHeightsProfileMap m_layer_heights_profiles ;
IdToLayerConfigRangesMap m_layer_config_ranges ;
IdToSlaSupportPointsMap m_sla_support_points ;
IdToSlaDrainHolesMap m_sla_drain_holes ;
std : : string m_curr_metadata_name ;
std : : string m_curr_characters ;
std : : string m_name ;
public :
_3MF_Importer ( ) ;
~ _3MF_Importer ( ) ;
bool load_model_from_file ( const std : : string & filename , Model & model , DynamicPrintConfig & config , ConfigSubstitutionContext & config_substitutions , bool check_version ) ;
unsigned int version ( ) const { return m_version ; }
private :
void _destroy_xml_parser ( ) ;
void _stop_xml_parser ( const std : : string & msg = std : : string ( ) ) ;
bool parse_error ( ) const { return m_parse_error ; }
const char * parse_error_message ( ) const {
return m_parse_error ?
// The error was signalled by the user code, not the expat parser.
( m_parse_error_message . empty ( ) ? " Invalid 3MF format " : m_parse_error_message . c_str ( ) ) :
// The error was signalled by the expat parser.
XML_ErrorString ( XML_GetErrorCode ( m_xml_parser ) ) ;
}
bool _load_model_from_file ( const std : : string & filename , Model & model , DynamicPrintConfig & config , ConfigSubstitutionContext & config_substitutions ) ;
bool _extract_model_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat ) ;
void _extract_layer_heights_profile_config_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat ) ;
void _extract_layer_config_ranges_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat , ConfigSubstitutionContext & config_substitutions ) ;
void _extract_sla_support_points_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat ) ;
void _extract_sla_drain_holes_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat ) ;
void _extract_custom_gcode_per_print_z_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat ) ;
void _extract_print_config_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat , DynamicPrintConfig & config , ConfigSubstitutionContext & subs_context , const std : : string & archive_filename ) ;
bool _extract_model_config_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat , Model & model ) ;
// 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 ) ;
void _handle_model_xml_characters ( const XML_Char * s , int len ) ;
// 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 ) ;
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 ( ) ;
bool _handle_start_metadata ( const char * * attributes , unsigned int num_attributes ) ;
bool _handle_end_metadata ( ) ;
bool _create_object_instance ( int object_id , const Transform3d & transform , const bool printable , unsigned int recur_counter ) ;
void _apply_transform ( ModelInstance & instance , const Transform3d & transform ) ;
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_start_config_volume_mesh ( const char * * attributes , unsigned int num_attributes ) ;
bool _handle_end_config_volume ( ) ;
bool _handle_end_config_volume_mesh ( ) ;
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 , ConfigSubstitutionContext & config_substitutions ) ;
// 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 ) ;
static void XMLCALL _handle_model_xml_characters ( void * userData , const XML_Char * s , int len ) ;
// 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 ) ;
} ;
_3MF_Importer : : _3MF_Importer ( )
: m_version ( 0 )
, m_check_version ( false )
, m_xml_parser ( nullptr )
2022-11-29 14:10:10 +00:00
, m_model ( nullptr )
2022-07-15 15:37:19 +00:00
, m_unit_factor ( 1.0f )
, m_curr_metadata_name ( " " )
, m_curr_characters ( " " )
, m_name ( " " )
{
}
_3MF_Importer : : ~ _3MF_Importer ( )
{
_destroy_xml_parser ( ) ;
}
bool _3MF_Importer : : load_model_from_file ( const std : : string & filename , Model & model , DynamicPrintConfig & config , ConfigSubstitutionContext & config_substitutions , bool check_version )
{
m_version = 0 ;
m_fdm_supports_painting_version = 0 ;
m_seam_painting_version = 0 ;
m_mm_painting_version = 0 ;
m_check_version = check_version ;
m_model = & model ;
m_unit_factor = 1.0f ;
m_curr_object . reset ( ) ;
m_objects . clear ( ) ;
m_objects_aliases . clear ( ) ;
m_instances . clear ( ) ;
m_geometries . clear ( ) ;
m_curr_config . object_id = - 1 ;
m_curr_config . volume_id = - 1 ;
m_objects_metadata . clear ( ) ;
m_layer_heights_profiles . clear ( ) ;
m_layer_config_ranges . clear ( ) ;
m_sla_support_points . clear ( ) ;
m_curr_metadata_name . clear ( ) ;
m_curr_characters . clear ( ) ;
clear_errors ( ) ;
return _load_model_from_file ( filename , model , config , config_substitutions ) ;
}
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 ( const std : : string & msg )
{
assert ( ! m_parse_error ) ;
assert ( m_parse_error_message . empty ( ) ) ;
assert ( m_xml_parser ! = nullptr ) ;
m_parse_error = true ;
m_parse_error_message = msg ;
XML_StopParser ( m_xml_parser , false ) ;
}
bool _3MF_Importer : : _load_model_from_file ( const std : : string & filename , Model & model , DynamicPrintConfig & config , ConfigSubstitutionContext & config_substitutions )
{
mz_zip_archive archive ;
mz_zip_zero_struct ( & archive ) ;
if ( ! open_zip_reader ( & archive , filename ) ) {
add_error ( " Unable to open the file " ) ;
return false ;
}
mz_uint num_entries = mz_zip_reader_get_num_files ( & archive ) ;
mz_zip_archive_file_stat stat ;
m_name = boost : : filesystem : : path ( filename ) . stem ( ) . string ( ) ;
// we first loop the entries to read from the archive the .model file only, in order to extract the version from it
for ( mz_uint i = 0 ; i < num_entries ; + + i ) {
if ( mz_zip_reader_file_stat ( & archive , i , & stat ) ) {
std : : string name ( stat . m_filename ) ;
std : : replace ( name . begin ( ) , name . end ( ) , ' \\ ' , ' / ' ) ;
if ( boost : : algorithm : : istarts_with ( name , MODEL_FOLDER ) & & boost : : algorithm : : iends_with ( name , MODEL_EXTENSION ) ) {
try
{
// 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
close_zip_reader ( & archive ) ;
throw Slic3r : : FileIOError ( e . what ( ) ) ;
}
}
}
}
// 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 ( ) , ' \\ ' , ' / ' ) ;
2022-07-29 07:28:56 +00:00
/*
2022-07-15 15:37:19 +00:00
if ( boost : : algorithm : : iequals ( name , LAYER_HEIGHTS_PROFILE_FILE ) ) {
// extract slic3r layer heights profile file
_extract_layer_heights_profile_config_from_archive ( archive , stat ) ;
}
else if ( boost : : algorithm : : iequals ( name , LAYER_CONFIG_RANGES_FILE ) ) {
// extract slic3r layer config ranges file
_extract_layer_config_ranges_from_archive ( archive , stat , config_substitutions ) ;
}
else if ( boost : : algorithm : : iequals ( name , SLA_SUPPORT_POINTS_FILE ) ) {
// extract sla support points file
_extract_sla_support_points_from_archive ( archive , stat ) ;
}
else if ( boost : : algorithm : : iequals ( name , SLA_DRAIN_HOLES_FILE ) ) {
// extract sla support points file
_extract_sla_drain_holes_from_archive ( archive , stat ) ;
}
else if ( boost : : algorithm : : iequals ( name , PRINT_CONFIG_FILE ) ) {
// extract slic3r print config file
_extract_print_config_from_archive ( archive , stat , config , config_substitutions , filename ) ;
}
else if ( boost : : algorithm : : iequals ( name , CUSTOM_GCODE_PER_PRINT_Z_FILE ) ) {
// extract slic3r layer config ranges file
_extract_custom_gcode_per_print_z_from_archive ( archive , stat ) ;
}
2022-07-29 07:28:56 +00:00
*/
// only read the model config for Prusa 3mf
if ( boost : : algorithm : : iequals ( name , MODEL_CONFIG_FILE ) ) {
2022-07-15 15:37:19 +00:00
// extract slic3r model config file
if ( ! _extract_model_config_from_archive ( archive , stat , model ) ) {
close_zip_reader ( & archive ) ;
add_error ( " Archive does not contain a valid model config " ) ;
return false ;
}
}
}
}
close_zip_reader ( & archive ) ;
if ( m_version = = 0 ) {
// if the 3mf was not produced by PrusaSlicer and there is more than one instance,
// split the object in as many objects as instances
size_t curr_models_count = m_model - > objects . size ( ) ;
size_t i = 0 ;
while ( i < curr_models_count ) {
ModelObject * model_object = m_model - > objects [ i ] ;
if ( model_object - > instances . size ( ) > 1 ) {
// select the geometry associated with the original model object
const Geometry * geometry = nullptr ;
for ( const IdToModelObjectMap : : value_type & object : m_objects ) {
if ( object . second = = int ( i ) ) {
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 ;
}
geometry = & obj_geometry - > second ;
break ;
}
}
if ( geometry = = nullptr ) {
add_error ( " Unable to find object geometry " ) ;
return false ;
}
// use the geometry to create the volumes in the new model objects
ObjectMetadata : : VolumeMetadataList volumes ( 1 , { 0 , ( unsigned int ) geometry - > triangles . size ( ) - 1 } ) ;
// for each instance after the 1st, create a new model object containing only that instance
// and copy into it the geometry
while ( model_object - > instances . size ( ) > 1 ) {
ModelObject * new_model_object = m_model - > add_object ( * model_object ) ;
new_model_object - > clear_instances ( ) ;
new_model_object - > add_instance ( * model_object - > instances . back ( ) ) ;
model_object - > delete_last_instance ( ) ;
if ( ! _generate_volumes ( * new_model_object , * geometry , volumes , config_substitutions ) )
return false ;
}
}
+ + i ;
}
}
for ( const IdToModelObjectMap : : value_type & object : m_objects ) {
if ( object . second > = int ( m_model - > objects . size ( ) ) ) {
add_error ( " Unable to find object " ) ;
return false ;
}
ModelObject * model_object = m_model - > objects [ object . second ] ;
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 ;
}
// 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 ) ;
if ( obj_layer_heights_profile ! = m_layer_heights_profiles . end ( ) )
model_object - > layer_height_profile . set ( std : : move ( obj_layer_heights_profile - > second ) ) ;
// 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 = std : : move ( obj_layer_config_ranges - > second ) ;
// 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 ) ;
if ( obj_sla_support_points ! = m_sla_support_points . end ( ) & & ! obj_sla_support_points - > second . empty ( ) ) {
model_object - > sla_support_points = std : : move ( obj_sla_support_points - > second ) ;
model_object - > sla_points_status = sla : : PointsStatus : : UserModified ;
}
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 = std : : move ( obj_drain_holes - > second ) ;
}
ObjectMetadata : : VolumeMetadataList volumes ;
ObjectMetadata : : VolumeMetadataList * volumes_ptr = nullptr ;
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
// apply object's name and config data
for ( const Metadata & metadata : obj_metadata - > second . metadata ) {
if ( metadata . key = = " name " )
model_object - > name = metadata . value ;
else
model_object - > config . set_deserialize ( metadata . key , metadata . value , config_substitutions ) ;
}
// 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 ( ) - 1 ) ;
// select as volumes
volumes_ptr = & volumes ;
}
if ( ! _generate_volumes ( * model_object , obj_geometry - > second , * volumes_ptr , config_substitutions ) )
return false ;
}
int object_idx = 0 ;
for ( ModelObject * o : model . objects ) {
int volume_idx = 0 ;
for ( ModelVolume * v : o - > volumes ) {
if ( v - > source . input_file . empty ( ) & & v - > type ( ) = = ModelVolumeType : : MODEL_PART ) {
v - > source . input_file = filename ;
if ( v - > source . volume_idx = = - 1 )
v - > source . volume_idx = volume_idx ;
if ( v - > source . object_idx = = - 1 )
v - > source . object_idx = object_idx ;
}
+ + volume_idx ;
}
+ + object_idx ;
}
2022-11-29 14:10:10 +00:00
//BBS: copy object isteadof instance
int object_size = model . objects . size ( ) ;
for ( int obj_index = 0 ; obj_index < object_size ; obj_index + + ) {
ModelObject * object = model . objects [ obj_index ] ;
while ( object - > instances . size ( ) > 1 ) {
ModelObject * new_model_object = model . add_object ( * object ) ;
new_model_object - > clear_instances ( ) ;
new_model_object - > add_instance ( * object - > instances . back ( ) ) ;
object - > delete_last_instance ( ) ;
}
}
2022-07-15 15:37:19 +00:00
// // fixes the min z of the model if negative
// model.adjust_min_z();
return true ;
}
bool _3MF_Importer : : _extract_model_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat )
{
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_model_xml_element , _3MF_Importer : : _handle_end_model_xml_element ) ;
XML_SetCharacterDataHandler ( m_xml_parser , _3MF_Importer : : _handle_model_xml_characters ) ;
struct CallbackData
{
XML_Parser & parser ;
_3MF_Importer & importer ;
const mz_zip_archive_file_stat & stat ;
CallbackData ( XML_Parser & parser , _3MF_Importer & importer , const mz_zip_archive_file_stat & stat ) : parser ( parser ) , importer ( importer ) , stat ( stat ) { }
} ;
CallbackData data ( m_xml_parser , * this , 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 ) | | data - > importer . parse_error ( ) ) {
char error_buf [ 1024 ] ;
: : sprintf ( error_buf , " Error (%s) while parsing '%s' at line %d " , data - > importer . parse_error_message ( ) , data - > stat . m_filename , ( int ) XML_GetCurrentLineNumber ( data - > parser ) ) ;
throw Slic3r : : FileIOError ( error_buf ) ;
}
return n ;
} , & data , 0 ) ;
}
catch ( const version_error & e )
{
// rethrow the exception
throw Slic3r : : FileIOError ( e . what ( ) ) ;
}
catch ( std : : exception & e )
{
add_error ( e . what ( ) ) ;
return false ;
}
if ( res = = 0 ) {
add_error ( " Error while extracting model data from zip archive " ) ;
return false ;
}
return true ;
}
void _3MF_Importer : : _extract_print_config_from_archive (
2022-11-29 14:10:10 +00:00
mz_zip_archive & archive , const mz_zip_archive_file_stat & stat ,
DynamicPrintConfig & config , ConfigSubstitutionContext & config_substitutions ,
2022-07-15 15:37:19 +00:00
const std : : string & archive_filename )
{
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 config data to buffer " ) ;
return ;
}
//FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment.
// Each config line is prefixed with a semicolon (G-code comment), that is ugly.
// Replacing the legacy function with load_from_ini_string_commented leads to issues when
// parsing 3MFs from before PrusaSlicer 2.0.0 (which can have duplicated entries in the INI.
// See https://github.com/prusa3d/PrusaSlicer/issues/7155. We'll revert it for now.
//config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule);
ConfigBase : : load_from_gcode_string_legacy ( config , buffer . data ( ) , config_substitutions ) ;
}
}
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 ( { object_id , profile } ) ;
}
}
}
void _3MF_Importer : : _extract_layer_config_ranges_from_archive ( mz_zip_archive & archive , const mz_zip_archive_file_stat & stat , ConfigSubstitutionContext & config_substitutions )
{
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 ;
}
std : : istringstream iss ( buffer ) ; // wrap returned xml to istringstream
pt : : ptree objects_tree ;
pt : : read_xml ( iss , objects_tree ) ;
for ( const auto & object : objects_tree . get_child ( " objects " ) ) {
pt : : ptree object_tree = object . second ;
int obj_idx = object_tree . get < int > ( " <xmlattr>.id " , - 1 ) ;
if ( obj_idx < = 0 ) {
add_error ( " Found invalid object id " ) ;
continue ;
}
IdToLayerConfigRangesMap : : iterator object_item = m_layer_config_ranges . find ( obj_idx ) ;
if ( object_item ! = m_layer_config_ranges . end ( ) ) {
add_error ( " Found duplicated layer config range " ) ;
continue ;
}
t_layer_config_ranges config_ranges ;
for ( const auto & range : object_tree ) {
if ( range . first ! = " range " )
continue ;
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 " ) ;
// get Z range information
DynamicPrintConfig config ;
for ( const auto & option : range_tree ) {
if ( option . first ! = " option " )
continue ;
std : : string opt_key = option . second . get < std : : string > ( " <xmlattr>.opt_key " ) ;
std : : string value = option . second . data ( ) ;
config . set_deserialize ( opt_key , value , config_substitutions ) ;
}
config_ranges [ { min_z , max_z } ] . assign_config ( std : : move ( config ) ) ;
}
if ( ! config_ranges . empty ( ) )
m_layer_config_ranges . insert ( { obj_idx , std : : move ( config_ranges ) } ) ;
}
}
}
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 ) ;
// Info on format versioning - see 3mf.hpp
int version = 0 ;
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
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 ;
}
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 ) ;
std : : vector < sla : : SupportPoint > sla_support_points ;
if ( version = = 0 ) {
for ( unsigned int i = 0 ; i < object_data_points . size ( ) ; i + = 3 )
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 ( ) ) ) ,
0.4f ,
false ) ;
}
if ( version = = 1 ) {
for ( unsigned int i = 0 ; i < object_data_points . size ( ) ; i + = 5 )
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 ) ;
}
if ( ! sla_support_points . empty ( ) )
m_sla_support_points . insert ( { object_id , sla_support_points } ) ;
}
}
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00: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 ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
if ( buffer . back ( ) = = ' \n ' )
buffer . pop_back ( ) ;
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
std : : vector < std : : string > objects ;
boost : : split ( objects , buffer , boost : : is_any_of ( " \n " ) , boost : : token_compress_off ) ;
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
// 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
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00: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 ) ;
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
if ( object_data . size ( ) ! = 2 ) {
add_error ( " Error while reading object data " ) ;
continue ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
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 ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
int object_id = std : : atoi ( object_data_id [ 1 ] . c_str ( ) ) ;
if ( object_id = = 0 ) {
add_error ( " Found invalid object id " ) ;
continue ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
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 ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
std : : vector < std : : string > object_data_points ;
boost : : split ( object_data_points , object_data [ 1 ] , boost : : is_any_of ( " " ) , boost : : token_compress_off ) ;
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
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 ( ) ) ) ) ;
}
// The holes are saved elevated above the mesh and deeper (bad idea indeed).
// This is retained for compatibility.
// Place the hole to the mesh and make it shallower to compensate.
// The offset is 1 mm above the mesh.
for ( sla : : DrainHole & hole : sla_drain_holes ) {
hole . pos + = hole . normal . normalized ( ) ;
hole . height - = 1.f ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
if ( ! sla_drain_holes . empty ( ) )
m_sla_drain_holes . insert ( { object_id , sla_drain_holes } ) ;
}
}
}
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 ] ;
: : 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 ) ) ;
add_error ( error_buf ) ;
return false ;
}
return true ;
}
void _3MF_Importer : : _extract_custom_gcode_per_print_z_from_archive ( : : mz_zip_archive & archive , const mz_zip_archive_file_stat & stat )
{
2023-02-01 09:38:02 +00: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);
// if (main_tree.front().first != "custom_gcodes_per_print_z")
// return;
// pt::ptree code_tree = main_tree.front().second;
// m_model->custom_gcode_per_print_z.gcodes.clear();
// for (const auto& code : code_tree) {
// 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;
// }
// if (code.first != "code")
// continue;
// 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;
// pt::ptree attr_tree = tree.find("<xmlattr>")->second;
// if (attr_tree.find("type") == attr_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}) ;
// }
//}
2022-07-15 15:37:19 +00:00
}
void _3MF_Importer : : _handle_start_model_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 ( 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 ) ;
else if ( : : strcmp ( METADATA_TAG , name ) = = 0 )
res = _handle_start_metadata ( attributes , num_attributes ) ;
if ( ! res )
_stop_xml_parser ( ) ;
}
void _3MF_Importer : : _handle_end_model_xml_element ( const char * name )
{
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 ( ) ;
else if ( : : strcmp ( METADATA_TAG , name ) = = 0 )
res = _handle_end_metadata ( ) ;
if ( ! res )
_stop_xml_parser ( ) ;
}
void _3MF_Importer : : _handle_model_xml_characters ( const XML_Char * s , int len )
{
m_curr_characters . append ( s , len ) ;
}
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 ( MESH_TAG , name ) = = 0 )
res = _handle_start_config_volume_mesh ( 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 ( MESH_TAG , name ) = = 0 )
res = _handle_end_config_volume_mesh ( ) ;
else if ( : : strcmp ( METADATA_TAG , name ) = = 0 )
res = _handle_end_config_metadata ( ) ;
if ( ! res )
_stop_xml_parser ( ) ;
}
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 ) {
if ( object . second > = int ( m_model - > objects . size ( ) ) ) {
add_error ( " Unable to find object " ) ;
return false ;
}
ModelObject * model_object = m_model - > objects [ object . second ] ;
if ( model_object ! = nullptr & & model_object - > instances . size ( ) = = 0 )
m_model - > delete_object ( model_object ) ;
}
if ( m_version = = 0 ) {
// if the 3mf was not produced by PrusaSlicer and there is only one object,
// set the object name to match the filename
if ( m_model - > objects . size ( ) = = 1 )
m_model - > objects . front ( ) - > name = m_name ;
}
// applies instances' matrices
for ( Instance & instance : m_instances ) {
if ( instance . instance ! = nullptr & & instance . instance - > get_object ( ) ! = nullptr )
// apply the transform to the instance
_apply_transform ( * instance . instance , instance . transform ) ;
}
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)
m_curr_object . model_object_idx = ( int ) m_model - > objects . size ( ) ;
m_curr_object . object = m_model - > add_object ( ) ;
if ( m_curr_object . object = = nullptr ) {
add_error ( " Unable to create object " ) ;
return false ;
}
// set object data
m_curr_object . object - > name = get_attribute_value_string ( attributes , num_attributes , NAME_ATTR ) ;
if ( m_curr_object . object - > name . empty ( ) )
m_curr_object . object - > name = m_name + " _ " + std : : to_string ( m_model - > objects . size ( ) ) ;
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 ( { m_curr_object . id , m_curr_object . components } ) ;
}
else {
// geometry defined, store it for later use
m_geometries . insert ( { m_curr_object . id , std : : move ( m_curr_object . geometry ) } ) ;
// stores the object for later use
if ( m_objects . find ( m_curr_object . id ) = = m_objects . end ( ) ) {
m_objects . insert ( { m_curr_object . id , m_curr_object . model_object_idx } ) ;
m_objects_aliases . insert ( { m_curr_object . id , { 1 , Component ( m_curr_object . id ) } } ) ; // aliases itself
}
else {
add_error ( " Found object with duplicate id " ) ;
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 . emplace_back (
m_unit_factor * get_attribute_value_float ( attributes , num_attributes , X_ATTR ) ,
m_unit_factor * get_attribute_value_float ( attributes , num_attributes , Y_ATTR ) ,
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 . emplace_back (
get_attribute_value_int ( attributes , num_attributes , V1_ATTR ) ,
get_attribute_value_int ( attributes , num_attributes , V2_ATTR ) ,
get_attribute_value_int ( attributes , num_attributes , V3_ATTR ) ) ;
m_curr_object . geometry . custom_supports . push_back ( get_attribute_value_string ( attributes , num_attributes , CUSTOM_SUPPORTS_ATTR ) ) ;
m_curr_object . geometry . custom_seam . push_back ( get_attribute_value_string ( attributes , num_attributes , CUSTOM_SEAM_ATTR ) ) ;
m_curr_object . geometry . mmu_segmentation . push_back ( get_attribute_value_string ( attributes , num_attributes , MMU_SEGMENTATION_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 ) ;
Transform3d transform = get_transform_from_3mf_specs_string ( get_attribute_value_string ( attributes , num_attributes , TRANSFORM_ATTR ) ) ;
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 ( ) ) {
add_error ( " Found component with invalid object id " ) ;
return false ;
}
}
m_curr_object . components . emplace_back ( object_id , transform ) ;
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 ) ;
Transform3d transform = get_transform_from_3mf_specs_string ( get_attribute_value_string ( attributes , num_attributes , TRANSFORM_ATTR ) ) ;
int printable = get_attribute_value_bool ( attributes , num_attributes , PRINTABLE_ATTR ) ;
return _create_object_instance ( object_id , transform , printable , 1 ) ;
}
bool _3MF_Importer : : _handle_end_item ( )
{
// do nothing
return true ;
}
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 ;
}
inline static void check_painting_version ( unsigned int loaded_version , unsigned int highest_supported_version , const std : : string & error_msg )
{
if ( loaded_version > highest_supported_version )
throw version_error ( error_msg ) ;
}
bool _3MF_Importer : : _handle_end_metadata ( )
{
if ( m_curr_metadata_name = = SLIC3RPE_3MF_VERSION ) {
m_version = ( unsigned int ) atoi ( m_curr_characters . c_str ( ) ) ;
if ( m_check_version & & ( m_version > VERSION_3MF_COMPATIBLE ) ) {
// 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 ) ;
}
} else if ( m_curr_metadata_name = = " Application " ) {
// Generator application of the 3MF.
// SLIC3R_APP_KEY - SLIC3R_VERSION
if ( boost : : starts_with ( m_curr_characters , " PrusaSlicer- " ) )
m_prusaslicer_generator_version = Semver : : parse ( m_curr_characters . substr ( 12 ) ) ;
} else if ( m_curr_metadata_name = = SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION ) {
m_fdm_supports_painting_version = ( unsigned int ) atoi ( m_curr_characters . c_str ( ) ) ;
check_painting_version ( m_fdm_supports_painting_version , FDM_SUPPORTS_PAINTING_VERSION ,
_ ( L ( " The selected 3MF contains FDM supports painted object using a newer version of PrusaSlicer and is not compatible. " ) ) ) ;
} else if ( m_curr_metadata_name = = SLIC3RPE_SEAM_PAINTING_VERSION ) {
m_seam_painting_version = ( unsigned int ) atoi ( m_curr_characters . c_str ( ) ) ;
check_painting_version ( m_seam_painting_version , SEAM_PAINTING_VERSION ,
_ ( L ( " The selected 3MF contains seam painted object using a newer version of PrusaSlicer and is not compatible. " ) ) ) ;
} else if ( m_curr_metadata_name = = SLIC3RPE_MM_PAINTING_VERSION ) {
m_mm_painting_version = ( unsigned int ) atoi ( m_curr_characters . c_str ( ) ) ;
check_painting_version ( m_mm_painting_version , MM_PAINTING_VERSION ,
_ ( L ( " The selected 3MF contains multi-material painted object using a newer version of PrusaSlicer and is not compatible. " ) ) ) ;
}
return true ;
}
bool _3MF_Importer : : _create_object_instance ( int object_id , const Transform3d & transform , const bool printable , unsigned int recur_counter )
{
static const unsigned int MAX_RECURSIONS = 10 ;
// escape from circular aliasing
if ( recur_counter > MAX_RECURSIONS ) {
add_error ( " Too many recursions " ) ;
return false ;
}
IdToAliasesMap : : iterator it = m_objects_aliases . find ( object_id ) ;
if ( it = = m_objects_aliases . end ( ) ) {
add_error ( " Found item with invalid object id " ) ;
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 ) ;
if ( object_item = = m_objects . end ( ) | | object_item - > second = = - 1 ) {
add_error ( " Found invalid object " ) ;
return false ;
}
else {
ModelInstance * instance = m_model - > objects [ object_item - > second ] - > add_instance ( ) ;
if ( instance = = nullptr ) {
add_error ( " Unable to add object instance " ) ;
return false ;
}
instance - > printable = printable ;
m_instances . emplace_back ( instance , transform ) ;
}
}
else {
// recursively process nested components
for ( const Component & component : it - > second ) {
if ( ! _create_object_instance ( component . object_id , transform * component . transform , printable , recur_counter + 1 ) )
return false ;
}
}
return true ;
}
void _3MF_Importer : : _apply_transform ( ModelInstance & instance , const Transform3d & transform )
{
Slic3r : : Geometry : : Transformation t ( transform ) ;
// invalid scale value, return
if ( ! t . get_scaling_factor ( ) . all ( ) )
return ;
instance . set_transformation ( t ) ;
}
bool _3MF_Importer : : _handle_start_config ( const char * * attributes , unsigned int num_attributes )
{
// do nothing
return true ;
}
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 ;
}
// Added because of github #3435, currently not used by PrusaSlicer
// int instances_count_id = get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR);
m_objects_metadata . insert ( { 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 ;
}
m_curr_config . volume_id = ( int ) object - > second . volumes . size ( ) ;
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_start_config_volume_mesh ( 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 mesh to a valid object " ) ;
return false ;
}
if ( object - > second . volumes . empty ( ) ) {
add_error ( " Cannot assign mesh to a valid olume " ) ;
return false ;
}
ObjectMetadata : : VolumeMetadata & volume = object - > second . volumes . back ( ) ;
int edges_fixed = get_attribute_value_int ( attributes , num_attributes , MESH_STAT_EDGES_FIXED ) ;
int degenerate_facets = get_attribute_value_int ( attributes , num_attributes , MESH_STAT_DEGENERATED_FACETS ) ;
int facets_removed = get_attribute_value_int ( attributes , num_attributes , MESH_STAT_FACETS_REMOVED ) ;
int facets_reversed = get_attribute_value_int ( attributes , num_attributes , MESH_STAT_FACETS_RESERVED ) ;
int backwards_edges = get_attribute_value_int ( attributes , num_attributes , MESH_STAT_BACKWARDS_EDGES ) ;
volume . mesh_stats = { edges_fixed , degenerate_facets , facets_removed , facets_reversed , backwards_edges } ;
return true ;
}
bool _3MF_Importer : : _handle_end_config_volume ( )
{
// do nothing
return true ;
}
bool _3MF_Importer : : _handle_end_config_volume_mesh ( )
{
// 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 ) ;
2022-07-29 07:28:56 +00:00
// filter the prusa model config keys
std : : vector < std : : string > valid_keys = {
" name " ,
" volume_type " ,
" matrix " ,
" source_file " ,
" source_object_id " ,
" source_volume_id " ,
" source_offset_x " ,
" source_offset_y " ,
" source_offset_z " ,
" extruder " ,
" modifier "
} ;
auto itor = std : : find ( valid_keys . begin ( ) , valid_keys . end ( ) , key ) ;
if ( itor = = valid_keys . end ( ) ) {
// do nothing if not valid keys
return true ;
}
2022-07-15 15:37:19 +00:00
if ( type = = OBJECT_TYPE )
object - > second . metadata . emplace_back ( key , value ) ;
else if ( type = = VOLUME_TYPE ) {
if ( size_t ( m_curr_config . volume_id ) < object - > second . volumes . size ( ) )
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 , ConfigSubstitutionContext & config_substitutions )
{
if ( ! object . volumes . empty ( ) ) {
add_error ( " Found invalid volumes count " ) ;
return false ;
}
unsigned int geo_tri_count = ( unsigned int ) geometry . triangles . size ( ) ;
unsigned int renamed_volumes_count = 0 ;
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 ;
}
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 ) {
if ( metadata . key = = MATRIX_KEY ) {
volume_matrix_to_object = Slic3r : : Geometry : : transform3d_from_string ( metadata . value ) ;
has_transform = ! volume_matrix_to_object . isApprox ( Transform3d : : Identity ( ) , 1e-10 ) ;
break ;
}
}
// splits volume out of imported geometry
indexed_triangle_set its ;
its . indices . assign ( geometry . triangles . begin ( ) + volume_data . first_triangle_id , geometry . triangles . begin ( ) + volume_data . last_triangle_id + 1 ) ;
const size_t triangles_count = its . indices . size ( ) ;
if ( triangles_count = = 0 ) {
add_error ( " An empty triangle mesh found " ) ;
return false ;
}
{
int min_id = its . indices . front ( ) [ 0 ] ;
int max_id = min_id ;
for ( const Vec3i & face : its . indices ) {
for ( const int tri_id : face ) {
if ( tri_id < 0 | | tri_id > = int ( geometry . vertices . size ( ) ) ) {
add_error ( " Found invalid vertex id " ) ;
return false ;
}
min_id = std : : min ( min_id , tri_id ) ;
max_id = std : : max ( max_id , tri_id ) ;
}
}
its . vertices . assign ( geometry . vertices . begin ( ) + min_id , geometry . vertices . begin ( ) + max_id + 1 ) ;
// rebase indices to the current vertices list
for ( Vec3i & face : its . indices )
for ( int & tri_id : face )
tri_id - = min_id ;
}
2022-11-29 14:10:10 +00:00
if ( m_prusaslicer_generator_version & &
2022-07-15 15:37:19 +00:00
* m_prusaslicer_generator_version > = * Semver : : parse ( " 2.4.0-alpha1 " ) & &
* m_prusaslicer_generator_version < * Semver : : parse ( " 2.4.0-alpha3 " ) )
// PrusaSlicer 2.4.0-alpha2 contained a bug, where all vertices of a single object were saved for each volume the object contained.
// Remove the vertices, that are not referenced by any face.
its_compactify_vertices ( its , true ) ;
TriangleMesh triangle_mesh ( std : : move ( its ) , volume_data . mesh_stats ) ;
if ( m_version = = 0 ) {
// if the 3mf was not produced by PrusaSlicer and there is only one instance,
// bake the transformation into the geometry to allow the reload from disk command
// to work properly
if ( object . instances . size ( ) = = 1 ) {
triangle_mesh . transform ( object . instances . front ( ) - > get_transformation ( ) . get_matrix ( ) , false ) ;
object . instances . front ( ) - > set_transformation ( Slic3r : : Geometry : : Transformation ( ) ) ;
//FIXME do the mesh fixing?
}
}
if ( triangle_mesh . volume ( ) < 0 )
triangle_mesh . flip_triangles ( ) ;
ModelVolume * volume = object . add_volume ( std : : move ( triangle_mesh ) ) ;
// stores the volume matrix taken from the metadata, if present
if ( has_transform )
volume - > source . transform = Slic3r : : Geometry : : Transformation ( volume_matrix_to_object ) ;
// recreate custom supports, seam and mmu segmentation from previously loaded attribute
volume - > supported_facets . reserve ( triangles_count ) ;
volume - > seam_facets . reserve ( triangles_count ) ;
volume - > mmu_segmentation_facets . reserve ( triangles_count ) ;
for ( size_t i = 0 ; i < triangles_count ; + + i ) {
size_t index = volume_data . first_triangle_id + i ;
assert ( index < geometry . custom_supports . size ( ) ) ;
assert ( index < geometry . custom_seam . size ( ) ) ;
assert ( index < geometry . mmu_segmentation . size ( ) ) ;
if ( ! geometry . custom_supports [ index ] . empty ( ) )
volume - > supported_facets . set_triangle_from_string ( i , geometry . custom_supports [ index ] ) ;
if ( ! geometry . custom_seam [ index ] . empty ( ) )
volume - > seam_facets . set_triangle_from_string ( i , geometry . custom_seam [ index ] ) ;
if ( ! geometry . mmu_segmentation [ index ] . empty ( ) )
volume - > mmu_segmentation_facets . set_triangle_from_string ( i , geometry . mmu_segmentation [ index ] ) ;
}
volume - > supported_facets . shrink_to_fit ( ) ;
volume - > seam_facets . shrink_to_fit ( ) ;
volume - > mmu_segmentation_facets . shrink_to_fit ( ) ;
// apply the remaining volume's metadata
for ( const Metadata & metadata : volume_data . metadata ) {
if ( metadata . key = = NAME_KEY )
volume - > name = metadata . value ;
else if ( ( metadata . key = = MODIFIER_KEY ) & & ( metadata . value = = " 1 " ) )
volume - > set_type ( ModelVolumeType : : PARAMETER_MODIFIER ) ;
else if ( metadata . key = = VOLUME_TYPE_KEY )
2022-07-29 07:28:56 +00:00
volume - > set_type ( type_from_string ( metadata . value ) ) ;
2022-07-15 15:37:19 +00: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 ( ) ) ;
else if ( metadata . key = = SOURCE_IN_INCHES )
volume - > source . is_converted_from_inches = metadata . value = = " 1 " ;
else if ( metadata . key = = SOURCE_IN_METERS )
volume - > source . is_converted_from_meters = metadata . value = = " 1 " ;
else
volume - > config . set_deserialize ( metadata . key , metadata . value , config_substitutions ) ;
}
// this may happen for 3mf saved by 3rd part softwares
if ( volume - > name . empty ( ) ) {
volume - > name = object . name ;
if ( renamed_volumes_count > 0 )
volume - > name + = " _ " + std : : to_string ( renamed_volumes_count + 1 ) ;
+ + renamed_volumes_count ;
}
}
return true ;
}
void XMLCALL _3MF_Importer : : _handle_start_model_xml_element ( void * userData , const char * name , const char * * attributes )
{
_3MF_Importer * importer = ( _3MF_Importer * ) userData ;
if ( importer ! = nullptr )
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 ) ;
}
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 ) ;
}
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 ) ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
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 ) ;
}
class _3MF_Exporter : public _3MF_Base
{
struct BuildItem
{
unsigned int id ;
Transform3d transform ;
bool printable ;
BuildItem ( unsigned int id , const Transform3d & transform , const bool printable )
: id ( id )
, transform ( transform )
, printable ( printable )
{
}
} ;
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 )
{
}
} ;
typedef std : : vector < BuildItem > BuildItemsList ;
typedef std : : map < int , ObjectData > IdToObjectDataMap ;
bool m_fullpath_sources { true } ;
bool m_zip64 { true } ;
public :
bool save_model_to_file ( const std : : string & filename , Model & model , const DynamicPrintConfig * config , bool fullpath_sources , const ThumbnailData * thumbnail_data , bool zip64 ) ;
private :
bool _save_model_to_file ( const std : : string & filename , Model & model , const DynamicPrintConfig * config , const ThumbnailData * thumbnail_data ) ;
bool _add_content_types_file_to_archive ( mz_zip_archive & archive ) ;
bool _add_thumbnail_file_to_archive ( mz_zip_archive & archive , const ThumbnailData & thumbnail_data ) ;
bool _add_relationships_file_to_archive ( mz_zip_archive & archive ) ;
bool _add_model_file_to_archive ( const std : : string & filename , mz_zip_archive & archive , const Model & model , IdToObjectDataMap & objects_data ) ;
bool _add_object_to_model_stream ( mz_zip_writer_staged_context & context , unsigned int & object_id , ModelObject & object , BuildItemsList & build_items , VolumeToOffsetsMap & volumes_offsets ) ;
bool _add_mesh_to_object_stream ( mz_zip_writer_staged_context & context , ModelObject & object , VolumeToOffsetsMap & volumes_offsets ) ;
bool _add_build_to_model_stream ( std : : stringstream & stream , const BuildItemsList & build_items ) ;
bool _add_layer_height_profile_file_to_archive ( mz_zip_archive & archive , Model & model ) ;
bool _add_layer_config_ranges_file_to_archive ( mz_zip_archive & archive , Model & model ) ;
bool _add_sla_support_points_file_to_archive ( mz_zip_archive & archive , Model & model ) ;
bool _add_sla_drain_holes_file_to_archive ( mz_zip_archive & archive , Model & model ) ;
bool _add_print_config_file_to_archive ( mz_zip_archive & archive , const DynamicPrintConfig & config ) ;
bool _add_model_config_file_to_archive ( mz_zip_archive & archive , const Model & model , const IdToObjectDataMap & objects_data ) ;
bool _add_custom_gcode_per_print_z_file_to_archive ( mz_zip_archive & archive , Model & model , const DynamicPrintConfig * config ) ;
} ;
bool _3MF_Exporter : : save_model_to_file ( const std : : string & filename , Model & model , const DynamicPrintConfig * config , bool fullpath_sources , const ThumbnailData * thumbnail_data , bool zip64 )
{
clear_errors ( ) ;
m_fullpath_sources = fullpath_sources ;
m_zip64 = zip64 ;
return _save_model_to_file ( filename , model , config , thumbnail_data ) ;
}
bool _3MF_Exporter : : _save_model_to_file ( const std : : string & filename , Model & model , const DynamicPrintConfig * config , const ThumbnailData * thumbnail_data )
{
mz_zip_archive archive ;
mz_zip_zero_struct ( & archive ) ;
if ( ! open_zip_writer ( & archive , filename ) ) {
add_error ( " Unable to open the file " ) ;
return false ;
}
// Adds content types file ("[Content_Types].xml";).
// The content of this file is the same for each PrusaSlicer 3mf.
if ( ! _add_content_types_file_to_archive ( archive ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
if ( thumbnail_data ! = nullptr & & thumbnail_data - > is_valid ( ) ) {
// Adds the file Metadata/thumbnail.png.
if ( ! _add_thumbnail_file_to_archive ( archive , * thumbnail_data ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
}
2022-11-29 14:10:10 +00:00
// Adds relationships file ("_rels/.rels").
2022-07-15 15:37:19 +00:00
// 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.
if ( ! _add_relationships_file_to_archive ( archive ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
// 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 ( filename , archive , model , objects_data ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
// 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!
if ( ! _add_layer_height_profile_file_to_archive ( archive , model ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
// 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 ;
}
// 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!
if ( ! _add_sla_support_points_file_to_archive ( archive , model ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
if ( ! _add_sla_drain_holes_file_to_archive ( archive , model ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
// Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
// All custom gcode per height of whole Model are stored here
if ( ! _add_custom_gcode_per_print_z_file_to_archive ( archive , model , config ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
// Adds slic3r print config file ("Metadata/Slic3r_PE.config").
// This file contains the content of FullPrintConfing / SLAFullPrintConfig.
if ( config ! = nullptr ) {
if ( ! _add_print_config_file_to_archive ( archive , * config ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
}
// 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 ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
return false ;
}
if ( ! mz_zip_writer_finalize_archive ( & archive ) ) {
close_zip_writer ( & archive ) ;
boost : : filesystem : : remove ( filename ) ;
add_error ( " Unable to finalize the archive " ) ;
return false ;
}
close_zip_writer ( & archive ) ;
return true ;
}
bool _3MF_Exporter : : _add_content_types_file_to_archive ( mz_zip_archive & archive )
{
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 " ;
stream < < " <Default Extension= \" png \" ContentType= \" image/png \" /> \n " ;
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 ) ) {
add_error ( " Unable to add content types file to archive " ) ;
return false ;
}
return true ;
}
bool _3MF_Exporter : : _add_thumbnail_file_to_archive ( mz_zip_archive & archive , const ThumbnailData & thumbnail_data )
{
bool res = false ;
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 ;
}
bool _3MF_Exporter : : _add_relationships_file_to_archive ( mz_zip_archive & archive )
{
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 " ;
stream < < " <Relationship Target= \" / " < < THUMBNAIL_FILE < < " \" Id= \" rel-2 \" Type= \" http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail \" /> \n " ;
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 ) ) {
add_error ( " Unable to add relationships file to archive " ) ;
return false ;
}
return true ;
}
static void reset_stream ( std : : stringstream & stream )
{
stream . str ( " " ) ;
stream . clear ( ) ;
// 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!
stream < < std : : setprecision ( std : : numeric_limits < float > : : max_digits10 ) ;
}
bool _3MF_Exporter : : _add_model_file_to_archive ( const std : : string & filename , mz_zip_archive & archive , const Model & model , IdToObjectDataMap & objects_data )
{
mz_zip_writer_staged_context context ;
2022-11-29 14:10:10 +00:00
if ( ! mz_zip_writer_add_staged_open ( & archive , & context , MODEL_FILE . c_str ( ) ,
m_zip64 ?
2022-07-15 15:37:19 +00:00
// Maximum expected and allowed 3MF file size is 16GiB.
// This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records.
2022-11-29 14:10:10 +00:00
( uint64_t ( 1 ) < < 30 ) * 16 :
2022-07-15 15:37:19 +00:00
// Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see
// GH issue #6193.
( uint64_t ( 1 ) < < 32 ) - 1 ,
nullptr , nullptr , 0 , MZ_DEFAULT_COMPRESSION , nullptr , 0 , nullptr , 0 ) ) {
add_error ( " Unable to add model file to archive " ) ;
return false ;
}
{
std : : stringstream stream ;
reset_stream ( stream ) ;
stream < < " <?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> \n " ;
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 " ;
if ( model . is_fdm_support_painted ( ) )
stream < < " < " < < METADATA_TAG < < " name= \" " < < SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION < < " \" > " < < FDM_SUPPORTS_PAINTING_VERSION < < " </ " < < METADATA_TAG < < " > \n " ;
if ( model . is_seam_painted ( ) )
stream < < " < " < < METADATA_TAG < < " name= \" " < < SLIC3RPE_SEAM_PAINTING_VERSION < < " \" > " < < SEAM_PAINTING_VERSION < < " </ " < < METADATA_TAG < < " > \n " ;
if ( model . is_mm_painted ( ) )
stream < < " < " < < METADATA_TAG < < " name= \" " < < SLIC3RPE_MM_PAINTING_VERSION < < " \" > " < < MM_PAINTING_VERSION < < " </ " < < METADATA_TAG < < " > \n " ;
std : : string name = xml_escape ( boost : : filesystem : : path ( filename ) . stem ( ) . string ( ) ) ;
stream < < " < " < < METADATA_TAG < < " name= \" Title \" > " < < name < < " </ " < < METADATA_TAG < < " > \n " ;
stream < < " < " < < METADATA_TAG < < " name= \" Designer \" > " < < " </ " < < METADATA_TAG < < " > \n " ;
stream < < " < " < < METADATA_TAG < < " name= \" Description \" > " < < name < < " </ " < < METADATA_TAG < < " > \n " ;
stream < < " < " < < METADATA_TAG < < " name= \" Copyright \" > " < < " </ " < < METADATA_TAG < < " > \n " ;
stream < < " < " < < METADATA_TAG < < " name= \" LicenseTerms \" > " < < " </ " < < METADATA_TAG < < " > \n " ;
stream < < " < " < < METADATA_TAG < < " name= \" Rating \" > " < < " </ " < < METADATA_TAG < < " > \n " ;
std : : string date = Slic3r : : Utils : : utc_timestamp ( Slic3r : : Utils : : get_current_time_utc ( ) ) ;
// keep only the date part of the string
date = date . substr ( 0 , 10 ) ;
stream < < " < " < < METADATA_TAG < < " name= \" CreationDate \" > " < < date < < " </ " < < METADATA_TAG < < " > \n " ;
stream < < " < " < < METADATA_TAG < < " name= \" ModificationDate \" > " < < date < < " </ " < < METADATA_TAG < < " > \n " ;
stream < < " < " < < METADATA_TAG < < " name= \" Application \" > " < < SLIC3R_APP_KEY < < " - " < < SLIC3R_VERSION < < " </ " < < METADATA_TAG < < " > \n " ;
stream < < " < " < < RESOURCES_TAG < < " > \n " ;
std : : string buf = stream . str ( ) ;
if ( ! buf . empty ( ) & & ! mz_zip_writer_add_staged_data ( & context , buf . data ( ) , buf . size ( ) ) ) {
add_error ( " Unable to add model file to archive " ) ;
return false ;
}
}
// Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects).
BuildItemsList build_items ;
// 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.
unsigned int object_id = 1 ;
for ( ModelObject * obj : model . objects ) {
if ( obj = = nullptr )
continue ;
// Index of an object in the 3MF file corresponding to the 1st instance of a ModelObject.
unsigned int curr_id = object_id ;
IdToObjectDataMap : : iterator object_it = objects_data . insert ( { 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.
if ( ! _add_object_to_model_stream ( context , object_id , * obj , build_items , object_it - > second . volumes_offsets ) ) {
add_error ( " Unable to add object to archive " ) ;
mz_zip_writer_add_staged_finish ( & context ) ;
return false ;
}
}
{
std : : stringstream stream ;
reset_stream ( stream ) ;
stream < < " </ " < < RESOURCES_TAG < < " > \n " ;
// Store the transformations of all the ModelInstances of all ModelObjects, indexed in a linear fashion.
if ( ! _add_build_to_model_stream ( stream , build_items ) ) {
add_error ( " Unable to add build to archive " ) ;
mz_zip_writer_add_staged_finish ( & context ) ;
return false ;
}
stream < < " </ " < < MODEL_TAG < < " > \n " ;
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
std : : string buf = stream . str ( ) ;
if ( ( ! buf . empty ( ) & & ! mz_zip_writer_add_staged_data ( & context , buf . data ( ) , buf . size ( ) ) ) | |
! mz_zip_writer_add_staged_finish ( & context ) ) {
add_error ( " Unable to add model file to archive " ) ;
return false ;
}
}
return true ;
}
bool _3MF_Exporter : : _add_object_to_model_stream ( mz_zip_writer_staged_context & context , unsigned int & object_id , ModelObject & object , BuildItemsList & build_items , VolumeToOffsetsMap & volumes_offsets )
{
std : : stringstream stream ;
reset_stream ( stream ) ;
unsigned int id = 0 ;
for ( const ModelInstance * instance : object . instances ) {
assert ( instance ! = nullptr ) ;
if ( instance = = nullptr )
continue ;
unsigned int instance_id = object_id + id ;
stream < < " < " < < OBJECT_TAG < < " id= \" " < < instance_id < < " \" type= \" model \" > \n " ;
if ( id = = 0 ) {
std : : string buf = stream . str ( ) ;
reset_stream ( stream ) ;
if ( ( ! buf . empty ( ) & & ! mz_zip_writer_add_staged_data ( & context , buf . data ( ) , buf . size ( ) ) ) | |
! _add_mesh_to_object_stream ( context , object , volumes_offsets ) ) {
add_error ( " Unable to add mesh to archive " ) ;
return false ;
}
}
else {
stream < < " < " < < COMPONENTS_TAG < < " > \n " ;
stream < < " < " < < COMPONENT_TAG < < " objectid= \" " < < object_id < < " \" /> \n " ;
stream < < " </ " < < COMPONENTS_TAG < < " > \n " ;
}
Transform3d t = instance - > get_matrix ( ) ;
// instance_id is just a 1 indexed index in build_items.
assert ( instance_id = = build_items . size ( ) + 1 ) ;
build_items . emplace_back ( instance_id , t , instance - > printable ) ;
stream < < " </ " < < OBJECT_TAG < < " > \n " ;
+ + id ;
}
object_id + = id ;
std : : string buf = stream . str ( ) ;
return buf . empty ( ) | | mz_zip_writer_add_staged_data ( & context , buf . data ( ) , buf . size ( ) ) ;
}
# if EXPORT_3MF_USE_SPIRIT_KARMA_FP
template < typename Num >
struct coordinate_policy_fixed : boost : : spirit : : karma : : real_policies < Num >
{
static int floatfield ( Num n ) { return fmtflags : : fixed ; }
// Number of decimal digits to maintain float accuracy when storing into a text file and parsing back.
static unsigned precision ( Num /* n */ ) { return std : : numeric_limits < Num > : : max_digits10 + 1 ; }
// No trailing zeros, thus for fmtflags::fixed usually much less than max_digits10 decimal numbers will be produced.
static bool trailing_zeros ( Num /* n */ ) { return false ; }
} ;
template < typename Num >
struct coordinate_policy_scientific : coordinate_policy_fixed < Num >
{
static int floatfield ( Num n ) { return fmtflags : : scientific ; }
} ;
// Define a new generator type based on the new coordinate policy.
using coordinate_type_fixed = boost : : spirit : : karma : : real_generator < float , coordinate_policy_fixed < float > > ;
using coordinate_type_scientific = boost : : spirit : : karma : : real_generator < float , coordinate_policy_scientific < float > > ;
# endif // EXPORT_3MF_USE_SPIRIT_KARMA_FP
bool _3MF_Exporter : : _add_mesh_to_object_stream ( mz_zip_writer_staged_context & context , ModelObject & object , VolumeToOffsetsMap & volumes_offsets )
{
std : : string output_buffer ;
output_buffer + = " < " ;
output_buffer + = MESH_TAG ;
output_buffer + = " > \n < " ;
output_buffer + = VERTICES_TAG ;
output_buffer + = " > \n " ;
auto flush = [ this , & output_buffer , & context ] ( bool force = false ) {
if ( ( force & & ! output_buffer . empty ( ) ) | | output_buffer . size ( ) > = 65536 * 16 ) {
if ( ! mz_zip_writer_add_staged_data ( & context , output_buffer . data ( ) , output_buffer . size ( ) ) ) {
add_error ( " Error during writing or compression " ) ;
return false ;
}
output_buffer . clear ( ) ;
}
return true ;
} ;
auto format_coordinate = [ ] ( float f , char * buf ) - > char * {
assert ( is_decimal_separator_point ( ) ) ;
# if EXPORT_3MF_USE_SPIRIT_KARMA_FP
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
// https://github.com/boostorg/spirit/pull/586
// where the exported string is one digit shorter than it should be to guarantee lossless round trip.
// The code is left here for the ocasion boost guys improve.
coordinate_type_fixed const coordinate_fixed = coordinate_type_fixed ( ) ;
coordinate_type_scientific const coordinate_scientific = coordinate_type_scientific ( ) ;
// Format "f" in a fixed format.
char * ptr = buf ;
boost : : spirit : : karma : : generate ( ptr , coordinate_fixed , f ) ;
// Format "f" in a scientific format.
char * ptr2 = ptr ;
boost : : spirit : : karma : : generate ( ptr2 , coordinate_scientific , f ) ;
// Return end of the shorter string.
auto len2 = ptr2 - ptr ;
if ( ptr - buf > len2 ) {
// Move the shorter scientific form to the front.
memcpy ( buf , ptr , len2 ) ;
ptr = buf + len2 ;
}
// Return pointer to the end.
return ptr ;
# else
// Round-trippable float, shortest possible.
return buf + sprintf ( buf , " %.9g " , f ) ;
# endif
} ;
char buf [ 256 ] ;
unsigned int vertices_count = 0 ;
for ( ModelVolume * volume : object . volumes ) {
if ( volume = = nullptr )
continue ;
volumes_offsets . insert ( { volume , Offsets ( vertices_count ) } ) ;
const indexed_triangle_set & its = volume - > mesh ( ) . its ;
if ( its . vertices . empty ( ) ) {
add_error ( " Found invalid mesh " ) ;
return false ;
}
vertices_count + = ( int ) its . vertices . size ( ) ;
const Transform3d & matrix = volume - > get_matrix ( ) ;
for ( size_t i = 0 ; i < its . vertices . size ( ) ; + + i ) {
Vec3f v = ( matrix * its . vertices [ i ] . cast < double > ( ) ) . cast < float > ( ) ;
char * ptr = buf ;
boost : : spirit : : karma : : generate ( ptr , boost : : spirit : : lit ( " < " ) < < VERTEX_TAG < < " x= \" " ) ;
ptr = format_coordinate ( v . x ( ) , ptr ) ;
boost : : spirit : : karma : : generate ( ptr , " \" y= \" " ) ;
ptr = format_coordinate ( v . y ( ) , ptr ) ;
boost : : spirit : : karma : : generate ( ptr , " \" z= \" " ) ;
ptr = format_coordinate ( v . z ( ) , ptr ) ;
boost : : spirit : : karma : : generate ( ptr , " \" /> \n " ) ;
* ptr = ' \0 ' ;
output_buffer + = buf ;
if ( ! flush ( ) )
return false ;
}
}
output_buffer + = " </ " ;
output_buffer + = VERTICES_TAG ;
output_buffer + = " > \n < " ;
output_buffer + = TRIANGLES_TAG ;
output_buffer + = " > \n " ;
unsigned int triangles_count = 0 ;
for ( ModelVolume * volume : object . volumes ) {
if ( volume = = nullptr )
continue ;
bool is_left_handed = volume - > is_left_handed ( ) ;
VolumeToOffsetsMap : : iterator volume_it = volumes_offsets . find ( volume ) ;
assert ( volume_it ! = volumes_offsets . end ( ) ) ;
const indexed_triangle_set & its = volume - > mesh ( ) . its ;
// updates triangle offsets
volume_it - > second . first_triangle_id = triangles_count ;
triangles_count + = ( int ) its . indices . size ( ) ;
volume_it - > second . last_triangle_id = triangles_count - 1 ;
for ( int i = 0 ; i < int ( its . indices . size ( ) ) ; + + i ) {
{
const Vec3i & idx = its . indices [ i ] ;
char * ptr = buf ;
boost : : spirit : : karma : : generate ( ptr , boost : : spirit : : lit ( " < " ) < < TRIANGLE_TAG < <
" v1= \" " < < boost : : spirit : : int_ < <
" \" v2= \" " < < boost : : spirit : : int_ < <
" \" v3= \" " < < boost : : spirit : : int_ < < " \" " ,
idx [ is_left_handed ? 2 : 0 ] + volume_it - > second . first_vertex_id ,
idx [ 1 ] + volume_it - > second . first_vertex_id ,
idx [ is_left_handed ? 0 : 2 ] + volume_it - > second . first_vertex_id ) ;
* ptr = ' \0 ' ;
output_buffer + = buf ;
}
std : : string custom_supports_data_string = volume - > supported_facets . get_triangle_as_string ( i ) ;
if ( ! custom_supports_data_string . empty ( ) ) {
output_buffer + = " " ;
output_buffer + = CUSTOM_SUPPORTS_ATTR ;
output_buffer + = " = \" " ;
output_buffer + = custom_supports_data_string ;
output_buffer + = " \" " ;
}
std : : string custom_seam_data_string = volume - > seam_facets . get_triangle_as_string ( i ) ;
if ( ! custom_seam_data_string . empty ( ) ) {
output_buffer + = " " ;
output_buffer + = CUSTOM_SEAM_ATTR ;
output_buffer + = " = \" " ;
output_buffer + = custom_seam_data_string ;
output_buffer + = " \" " ;
}
std : : string mmu_painting_data_string = volume - > mmu_segmentation_facets . get_triangle_as_string ( i ) ;
if ( ! mmu_painting_data_string . empty ( ) ) {
output_buffer + = " " ;
output_buffer + = MMU_SEGMENTATION_ATTR ;
output_buffer + = " = \" " ;
output_buffer + = mmu_painting_data_string ;
output_buffer + = " \" " ;
}
output_buffer + = " /> \n " ;
if ( ! flush ( ) )
return false ;
}
}
output_buffer + = " </ " ;
output_buffer + = TRIANGLES_TAG ;
output_buffer + = " > \n </ " ;
output_buffer + = MESH_TAG ;
output_buffer + = " > \n " ;
// Force flush.
return flush ( true ) ;
}
bool _3MF_Exporter : : _add_build_to_model_stream ( std : : stringstream & stream , const BuildItemsList & build_items )
{
// This happens for empty projects
if ( build_items . size ( ) = = 0 ) {
add_error ( " No build item found " ) ;
return true ;
}
stream < < " < " < < BUILD_TAG < < " > \n " ;
for ( const BuildItem & item : build_items ) {
stream < < " < " < < ITEM_TAG < < " " < < OBJECTID_ATTR < < " = \" " < < item . id < < " \" " < < TRANSFORM_ATTR < < " = \" " ;
for ( unsigned c = 0 ; c < 4 ; + + c ) {
for ( unsigned r = 0 ; r < 3 ; + + r ) {
stream < < item . transform ( r , c ) ;
if ( r ! = 2 | | c ! = 3 )
stream < < " " ;
}
}
stream < < " \" " < < PRINTABLE_ATTR < < " = \" " < < item . printable < < " \" /> \n " ;
}
stream < < " </ " < < BUILD_TAG < < " > \n " ;
return true ;
}
bool _3MF_Exporter : : _add_layer_height_profile_file_to_archive ( mz_zip_archive & archive , Model & model )
{
assert ( is_decimal_separator_point ( ) ) ;
std : : string out = " " ;
char buffer [ 1024 ] ;
unsigned int count = 0 ;
for ( const ModelObject * object : model . objects ) {
+ + count ;
const std : : vector < double > & layer_height_profile = object - > layer_height_profile . get ( ) ;
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 ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
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 ;
}
bool _3MF_Exporter : : _add_layer_config_ranges_file_to_archive ( mz_zip_archive & archive , Model & model )
{
std : : string out = " " ;
pt : : ptree tree ;
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 ( ) )
{
pt : : ptree & obj_tree = tree . add ( " objects.object " , " " ) ;
obj_tree . put ( " <xmlattr>.id " , object_cnt ) ;
// Store the layer config ranges.
for ( const auto & range : ranges ) {
pt : : ptree & range_tree = obj_tree . add ( " range " , " " ) ;
// store minX and maxZ
range_tree . put ( " <xmlattr>.min_z " , range . first . first ) ;
range_tree . put ( " <xmlattr>.max_z " , range . first . second ) ;
// store range configuration
const ModelConfig & config = range . second ;
for ( const std : : string & opt_key : config . keys ( ) ) {
pt : : ptree & opt_tree = range_tree . add ( " option " , config . opt_serialize ( opt_key ) ) ;
opt_tree . put ( " <xmlattr>.opt_key " , opt_key ) ;
}
}
}
}
if ( ! tree . empty ( ) ) {
std : : ostringstream oss ;
pt : : write_xml ( oss , tree ) ;
out = oss . str ( ) ;
// Post processing("beautification") of the output string for a better preview
boost : : replace_all ( out , " ><object " , " > \n <object " ) ;
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> " ) ;
2022-11-29 14:10:10 +00:00
// OR just
boost : : replace_all ( out , " >< " , " > \n < " ) ;
2022-07-15 15:37:19 +00: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 ;
}
bool _3MF_Exporter : : _add_sla_support_points_file_to_archive ( mz_zip_archive & archive , Model & model )
{
assert ( is_decimal_separator_point ( ) ) ;
std : : string out = " " ;
char buffer [ 1024 ] ;
unsigned int count = 0 ;
for ( const ModelObject * object : model . objects ) {
+ + count ;
const std : : vector < sla : : SupportPoint > & sla_support_points = object - > sla_support_points ;
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 ) {
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 ) ;
out + = buffer ;
}
out + = " \n " ;
}
}
if ( ! out . empty ( ) ) {
// Adds version header at the beginning:
out = std : : string ( " support_points_format_version= " ) + std : : to_string ( support_points_format_version ) + std : : string ( " \n " ) + out ;
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 ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
bool _3MF_Exporter : : _add_sla_drain_holes_file_to_archive ( mz_zip_archive & archive , Model & model )
{
assert ( is_decimal_separator_point ( ) ) ;
const char * const fmt = " object_id=%d| " ;
std : : string out ;
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
unsigned int count = 0 ;
for ( const ModelObject * object : model . objects ) {
+ + count ;
sla : : DrainHoles drain_holes = object - > sla_drain_holes ;
// The holes were placed 1mm above the mesh in the first implementation.
// This was a bad idea and the reference point was changed in 2.3 so
// to be on the mesh exactly. The elevated position is still saved
// in 3MFs for compatibility reasons.
for ( sla : : DrainHole & hole : drain_holes ) {
hole . pos - = hole . normal . normalized ( ) ;
hole . height + = 1.f ;
}
if ( ! drain_holes . empty ( ) ) {
out + = string_printf ( fmt , count ) ;
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
// 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 ) ;
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
out + = " \n " ;
}
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
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 ;
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
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 ;
}
bool _3MF_Exporter : : _add_print_config_file_to_archive ( mz_zip_archive & archive , const DynamicPrintConfig & config )
{
assert ( is_decimal_separator_point ( ) ) ;
char buffer [ 1024 ] ;
sprintf ( buffer , " ; %s \n \n " , header_slic3r_generated ( ) . c_str ( ) ) ;
std : : string out = buffer ;
for ( const std : : string & key : config . keys ( ) )
if ( key ! = " compatible_printers " )
out + = " ; " + key + " = " + config . opt_serialize ( key ) + " \n " ;
if ( ! out . empty ( ) ) {
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 ;
}
}
return true ;
}
bool _3MF_Exporter : : _add_model_config_file_to_archive ( mz_zip_archive & archive , const Model & model , const IdToObjectDataMap & objects_data )
{
std : : stringstream stream ;
// 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 ) ;
stream < < " <?xml version= \" 1.0 \" encoding= \" UTF-8 \" ?> \n " ;
stream < < " < " < < CONFIG_TAG < < " > \n " ;
for ( const IdToObjectDataMap : : value_type & obj_metadata : objects_data ) {
const ModelObject * obj = obj_metadata . second . object ;
if ( obj ! = nullptr ) {
// 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 " ;
// stores object's name
if ( ! obj - > name . empty ( ) )
stream < < " < " < < METADATA_TAG < < " " < < TYPE_ATTR < < " = \" " < < OBJECT_TYPE < < " \" " < < KEY_ATTR < < " = \" name \" " < < VALUE_ATTR < < " = \" " < < xml_escape ( obj - > name ) < < " \" /> \n " ;
// stores object's config data
for ( const std : : string & key : obj - > config . keys ( ) ) {
stream < < " < " < < METADATA_TAG < < " " < < TYPE_ATTR < < " = \" " < < OBJECT_TYPE < < " \" " < < KEY_ATTR < < " = \" " < < key < < " \" " < < VALUE_ATTR < < " = \" " < < obj - > config . opt_serialize ( key ) < < " \" /> \n " ;
}
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 " ;
// stores volume's name
if ( ! volume - > name . empty ( ) )
stream < < " < " < < METADATA_TAG < < " " < < TYPE_ATTR < < " = \" " < < VOLUME_TYPE < < " \" " < < KEY_ATTR < < " = \" " < < NAME_KEY < < " \" " < < VALUE_ATTR < < " = \" " < < xml_escape ( volume - > name ) < < " \" /> \n " ;
// stores volume's modifier field (legacy, to support old slicers)
if ( volume - > is_modifier ( ) )
stream < < " < " < < METADATA_TAG < < " " < < TYPE_ATTR < < " = \" " < < VOLUME_TYPE < < " \" " < < KEY_ATTR < < " = \" " < < MODIFIER_KEY < < " \" " < < VALUE_ATTR < < " = \" 1 \" /> \n " ;
// stores volume's type (overrides the modifier field above)
2022-11-29 14:10:10 +00:00
stream < < " < " < < METADATA_TAG < < " " < < TYPE_ATTR < < " = \" " < < VOLUME_TYPE < < " \" " < < KEY_ATTR < < " = \" " < < VOLUME_TYPE_KEY < < " \" " < <
2022-07-15 15:37:19 +00:00
VALUE_ATTR < < " = \" " < < ModelVolume : : type_to_string ( volume - > type ( ) ) < < " \" /> \n " ;
// stores volume's local matrix
stream < < " < " < < METADATA_TAG < < " " < < TYPE_ATTR < < " = \" " < < VOLUME_TYPE < < " \" " < < KEY_ATTR < < " = \" " < < MATRIX_KEY < < " \" " < < VALUE_ATTR < < " = \" " ;
Transform3d matrix = volume - > get_matrix ( ) * volume - > source . transform . get_matrix ( ) ;
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
{
std : : string input_file = xml_escape ( m_fullpath_sources ? volume - > source . input_file : boost : : filesystem : : path ( volume - > source . input_file ) . filename ( ) . string ( ) ) ;
std : : string prefix = std : : string ( " < " ) + METADATA_TAG + " " + TYPE_ATTR + " = \" " + VOLUME_TYPE + " \" " + KEY_ATTR + " = \" " ;
if ( ! volume - > source . input_file . empty ( ) ) {
stream < < prefix < < SOURCE_FILE_KEY < < " \" " < < VALUE_ATTR < < " = \" " < < input_file < < " \" /> \n " ;
stream < < prefix < < SOURCE_OBJECT_ID_KEY < < " \" " < < VALUE_ATTR < < " = \" " < < volume - > source . object_idx < < " \" /> \n " ;
stream < < prefix < < SOURCE_VOLUME_ID_KEY < < " \" " < < VALUE_ATTR < < " = \" " < < volume - > source . volume_idx < < " \" /> \n " ;
stream < < prefix < < SOURCE_OFFSET_X_KEY < < " \" " < < VALUE_ATTR < < " = \" " < < volume - > source . mesh_offset ( 0 ) < < " \" /> \n " ;
stream < < prefix < < SOURCE_OFFSET_Y_KEY < < " \" " < < VALUE_ATTR < < " = \" " < < volume - > source . mesh_offset ( 1 ) < < " \" /> \n " ;
stream < < prefix < < SOURCE_OFFSET_Z_KEY < < " \" " < < VALUE_ATTR < < " = \" " < < volume - > source . mesh_offset ( 2 ) < < " \" /> \n " ;
}
assert ( ! volume - > source . is_converted_from_inches | | ! volume - > source . is_converted_from_meters ) ;
if ( volume - > source . is_converted_from_inches )
stream < < prefix < < SOURCE_IN_INCHES < < " \" " < < VALUE_ATTR < < " = \" 1 \" /> \n " ;
else if ( volume - > source . is_converted_from_meters )
stream < < prefix < < SOURCE_IN_METERS < < " \" " < < VALUE_ATTR < < " = \" 1 \" /> \n " ;
}
// stores volume's config data
for ( const std : : string & key : volume - > config . keys ( ) ) {
stream < < " < " < < METADATA_TAG < < " " < < TYPE_ATTR < < " = \" " < < VOLUME_TYPE < < " \" " < < KEY_ATTR < < " = \" " < < key < < " \" " < < VALUE_ATTR < < " = \" " < < volume - > config . opt_serialize ( key ) < < " \" /> \n " ;
}
2022-11-29 14:10:10 +00:00
2022-07-15 15:37:19 +00:00
// stores mesh's statistics
const RepairedMeshErrors & stats = volume - > mesh ( ) . stats ( ) . repaired_errors ;
stream < < " < " < < MESH_TAG < < " " ;
stream < < MESH_STAT_EDGES_FIXED < < " = \" " < < stats . edges_fixed < < " \" " ;
stream < < MESH_STAT_DEGENERATED_FACETS < < " = \" " < < stats . degenerate_facets < < " \" " ;
stream < < MESH_STAT_FACETS_REMOVED < < " = \" " < < stats . facets_removed < < " \" " ;
stream < < MESH_STAT_FACETS_RESERVED < < " = \" " < < stats . facets_reversed < < " \" " ;
stream < < MESH_STAT_BACKWARDS_EDGES < < " = \" " < < stats . backwards_edges < < " \" /> \n " ;
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 " ) ;
return false ;
}
return true ;
}
bool _3MF_Exporter : : _add_custom_gcode_per_print_z_file_to_archive ( mz_zip_archive & archive , Model & model , const DynamicPrintConfig * config )
{
2023-02-16 12:27:27 +00:00
return true ;
2023-02-01 09:38:02 +00:00
//std::string out = "";
//if (!model.custom_gcode_per_print_z.gcodes.empty()) {
// pt::ptree tree;
// pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", "");
// for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) {
// pt::ptree& code_tree = main_tree.add("code", "");
// // store data of custom_gcode_per_print_z
// code_tree.put("<xmlattr>.print_z" , code.print_z );
// code_tree.put("<xmlattr>.type" , static_cast<int>(code.type));
// code_tree.put("<xmlattr>.extruder" , code.extruder );
// code_tree.put("<xmlattr>.color" , code.color );
// code_tree.put("<xmlattr>.extra" , code.extra );
// //BBS
// std::string gcode = //code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") :
// code.type == CustomGCode::PausePrint ? config->opt_string("machine_pause_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 );
// }
// 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);
// 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()) {
// if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
// add_error("Unable to add custom Gcodes per print_z file to archive");
// return false;
// }
//}
//return true;
2022-07-15 15:37:19 +00:00
}
// Perform conversions based on the config values available.
//FIXME provide a version of PrusaSlicer that stored the project file (3MF).
static void handle_legacy_project_loaded ( unsigned int version_project_file , DynamicPrintConfig & config )
{
if ( ! config . has ( " brim_object_gap " ) ) {
if ( auto * opt_elephant_foot = config . option < ConfigOptionFloat > ( " elefant_foot_compensation " , false ) ; opt_elephant_foot ) {
// Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation.
auto * opt_brim_separation = config . option < ConfigOptionFloat > ( " brim_object_gap " , true ) ;
opt_brim_separation - > value = opt_elephant_foot - > value ;
}
}
}
bool load_3mf ( const char * path , DynamicPrintConfig & config , ConfigSubstitutionContext & config_substitutions , Model * model , bool check_version )
{
if ( path = = nullptr | | model = = nullptr )
return false ;
// All import should use "C" locales for number formatting.
CNumericLocalesSetter locales_setter ;
_3MF_Importer importer ;
bool res = importer . load_model_from_file ( path , * model , config , config_substitutions , check_version ) ;
importer . log_errors ( ) ;
handle_legacy_project_loaded ( importer . version ( ) , config ) ;
return res ;
}
bool store_3mf ( const char * path , Model * model , const DynamicPrintConfig * config , bool fullpath_sources , const ThumbnailData * thumbnail_data , bool zip64 )
{
// All export should use "C" locales for number formatting.
CNumericLocalesSetter locales_setter ;
if ( path = = nullptr | | model = = nullptr )
return false ;
_3MF_Exporter exporter ;
bool res = exporter . save_model_to_file ( path , * model , config , fullpath_sources , thumbnail_data , zip64 ) ;
if ( ! res )
exporter . log_errors ( ) ;
return res ;
}
} // namespace Slic3r