diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 5d6d8e4a6..4063824e5 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -41,16 +41,17 @@ use Wx::Event qw(EVT_IDLE EVT_COMMAND EVT_MENU); use base 'Wx::App'; use constant FILE_WILDCARDS => { - known => 'Known files (*.stl, *.obj, *.amf, *.xml, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.prusa;*.PRUSA', + known => 'Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA', stl => 'STL files (*.stl)|*.stl;*.STL', obj => 'OBJ files (*.obj)|*.obj;*.OBJ', amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML', + threemf => '3MF files (*.3mf)|*.3mf;*.3MF', prusa => 'Prusa Control files (*.prusa)|*.prusa;*.PRUSA', ini => 'INI files *.ini|*.ini;*.INI', gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC', svg => 'SVG files *.svg|*.svg;*.SVG', }; -use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf prusa)}; +use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf threemf prusa)}; # Datadir provided on the command line. our $datadir; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index c91c59ea7..3b82ee157 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -374,7 +374,7 @@ sub quick_slice { # select input file my $input_file; if (!$params{reslice}) { - my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/PRUSA):', + my $dialog = Wx::FileDialog->new($self, 'Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):', wxTheApp->{app_config}->get_last_dir, "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST); if ($dialog->ShowModal != wxID_OK) { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 10d65475c..a5a98d649 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -2063,8 +2063,8 @@ sub OnDropFiles { # stop scalars leaking on older perl # https://rt.perl.org/rt3/Public/Bug/Display.html?id=70602 @_ = (); - # only accept STL, OBJ and AMF files - return 0 if grep !/\.(?:[sS][tT][lL]|[oO][bB][jJ]|[aA][mM][fF](?:\.[xX][mM][lL])?|[pP][rR][uU][sS][aA])$/, @$filenames; + # only accept STL, OBJ, AMF and 3MF files + return 0 if grep !/\.(?:[sS][tT][lL]|[oO][bB][jJ]|[aA][mM][fF]|[3][mM][fF](?:\.[xX][mM][lL])?|[pP][rR][uU][sS][aA])$/, @$filenames; $self->{window}->load_files($filenames); } diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 4c1f2bb8c..fbddc31e6 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -74,6 +74,8 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/Fill/FillRectilinear3.hpp ${LIBDIR}/libslic3r/Flow.cpp ${LIBDIR}/libslic3r/Flow.hpp + ${LIBDIR}/libslic3r/Format/3MF.cpp + ${LIBDIR}/libslic3r/Format/3MF.hpp ${LIBDIR}/libslic3r/Format/AMF.cpp ${LIBDIR}/libslic3r/Format/AMF.hpp ${LIBDIR}/libslic3r/Format/OBJ.cpp diff --git a/xs/src/libslic3r/Format/3mf.cpp b/xs/src/libslic3r/Format/3mf.cpp new file mode 100644 index 000000000..6057e1c11 --- /dev/null +++ b/xs/src/libslic3r/Format/3mf.cpp @@ -0,0 +1,990 @@ +#include "../libslic3r.h" +#include "../Model.hpp" + +#include "3mf.hpp" + +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/split.hpp> + +#include <expat.h> +#include <eigen/dense> +#include <miniz/miniz_zip.h> + +const std::string MODEL_FOLDER = "3d\\"; +const std::string MODEL_EXTENSION = ".model"; + +const char* MODEL_TAG = "model"; +const char* RESOURCES_TAG = "resources"; +const char* OBJECT_TAG = "object"; +const char* MESH_TAG = "mesh"; +const char* VERTICES_TAG = "vertices"; +const char* VERTEX_TAG = "vertex"; +const char* TRIANGLES_TAG = "triangles"; +const char* TRIANGLE_TAG = "triangle"; +const char* COMPONENTS_TAG = "components"; +const char* COMPONENT_TAG = "component"; +const char* BUILD_TAG = "build"; +const char* ITEM_TAG = "item"; + +const char* UNIT_ATTR = "unit"; +const char* NAME_ATTR = "name"; +const char* TYPE_ATTR = "type"; +const char* ID_ATTR = "id"; +const char* X_ATTR = "x"; +const char* Y_ATTR = "y"; +const char* Z_ATTR = "z"; +const char* V1_ATTR = "v1"; +const char* V2_ATTR = "v2"; +const char* V3_ATTR = "v3"; +const char* OBJECTID_ATTR = "objectid"; +const char* TRANSFORM_ATTR = "transform"; + +const unsigned int VALID_OBJECT_TYPES_COUNT = 1; +const char* VALID_OBJECT_TYPES[] = +{ + "model" +}; + +const unsigned int INVALID_OBJECT_TYPES_COUNT = 4; +const char* INVALID_OBJECT_TYPES[] = +{ + "solidsupport", + "support", + "surface", + "other" +}; + +typedef Eigen::Matrix<float, 4, 4, Eigen::RowMajor> Matrix4x4; + +const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + if ((attributes == nullptr) || (attributes_size == 0) || (attributes_size % 2 != 0) || (attribute_key == nullptr)) + return nullptr; + + for (unsigned int a = 0; a < attributes_size; a += 2) + { + if (::strcmp(attributes[a], attribute_key) == 0) + return attributes[a + 1]; + } + + return nullptr; +} + +std::string get_attribute_value_string(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? text : ""; +} + +float get_attribute_value_float(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? (float)::atof(text) : 0.0f; +} + +int get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? ::atoi(text) : 0; +} + +Matrix4x4 get_matrix_from_string(const std::string& mat_str) +{ + if (mat_str.empty()) + // empty string means default identity matrix + return Matrix4x4::Identity(); + + 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 Matrix4x4::Identity(); + + Matrix4x4 ret = Matrix4x4::Identity(); + 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) = (float)::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 { + + class _3MF_Importer + { + struct Component + { + int object_id; + Matrix4x4 matrix; + + explicit Component(int object_id); + Component(int object_id, const Matrix4x4& matrix); + }; + + typedef std::vector<Component> ComponentsList; + + struct CurrentObject + { + struct Geometry + { + std::vector<float> vertices; + std::vector<unsigned int> triangles; + + bool empty(); + void reset(); + }; + + int id; + Geometry geometry; + ModelObject* object; + TriangleMesh mesh; + ComponentsList components; + + CurrentObject(); + + void reset(); + }; + + struct Instance + { + ModelInstance* instance; + Matrix4x4 matrix; + + Instance(ModelInstance* instance, const Matrix4x4& matrix); + }; + + typedef std::map<int, ModelObject*> IdToModelObjectMap; + typedef std::map<int, ComponentsList> IdToAliasesMap; + typedef std::vector<Instance> InstancesList; + + XML_Parser m_xml_parser; + Model* m_model; + float m_unit_factor; + CurrentObject m_curr_object; + IdToModelObjectMap m_objects; + IdToAliasesMap m_objects_aliases; + InstancesList m_instances; + std::vector<std::string> m_errors; + + public: + _3MF_Importer(); + ~_3MF_Importer(); + + bool load_model_from_file(const std::string& filename, Model& model); + + const std::vector<std::string>& get_errors() const; + + private: + void _destroy_xml_parser(); + void _stop_xml_parser(); + + bool _load_model_from_file_miniz(const std::string& filename, Model& model); + bool _extract_model_from_archive_miniz(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + + void _handle_start_xml_element(const char* name, const char** attributes); + void _handle_end_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 _create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter); + + void _apply_transform(ModelObject& object, const Matrix4x4& matrix); + void _apply_transform(ModelInstance& instance, const Matrix4x4& matrix); + + static void XMLCALL _handle_start_xml_element(void* userData, const char* name, const char** attributes); + static void XMLCALL _handle_end_xml_element(void* userData, const char* name); + }; + + _3MF_Importer::Component::Component(int object_id) + : object_id(object_id) + , matrix(Matrix4x4::Identity()) + { + } + + _3MF_Importer::Component::Component(int object_id, const Matrix4x4& matrix) + : object_id(object_id) + , matrix(matrix) + { + } + + bool _3MF_Importer::CurrentObject::Geometry::empty() + { + return vertices.empty() || triangles.empty(); + } + + void _3MF_Importer::CurrentObject::Geometry::reset() + { + vertices.clear(); + triangles.clear(); + } + + _3MF_Importer::CurrentObject::CurrentObject() + { + reset(); + } + + void _3MF_Importer::CurrentObject::reset() + { + id = -1; + geometry.reset(); + object = nullptr; + mesh = TriangleMesh(); + components.clear(); + } + + _3MF_Importer::Instance::Instance(ModelInstance* instance, const Matrix4x4& matrix) + : instance(instance) + , matrix(matrix) + { + } + + _3MF_Importer::_3MF_Importer() + : m_xml_parser(nullptr) + , m_model(nullptr) + , m_unit_factor(1.0f) + { + } + + _3MF_Importer::~_3MF_Importer() + { + _destroy_xml_parser(); + } + + bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model) + { + m_model = &model; + m_unit_factor = 1.0f; + m_curr_object.reset(); + m_objects.clear(); + m_objects_aliases.clear(); + m_instances.clear(); + m_errors.clear(); + + return _load_model_from_file_miniz(filename, model); + } + + const std::vector<std::string>& _3MF_Importer::get_errors() const + { + return m_errors; + } + + void _3MF_Importer::_destroy_xml_parser() + { + if (m_xml_parser != nullptr) + { + XML_ParserFree(m_xml_parser); + m_xml_parser = nullptr; + } + } + + void _3MF_Importer::_stop_xml_parser() + { + if (m_xml_parser != nullptr) + XML_StopParser(m_xml_parser, false); + } + + bool _3MF_Importer::_load_model_from_file_miniz(const std::string& filename, Model& model) + { + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + mz_bool res = mz_zip_reader_init_file(&archive, filename.c_str(), 0); + if (res == 0) + { + m_errors.push_back("Unable to open the file"); + return false; + } + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + mz_zip_archive_file_stat stat; + 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::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) -> unsigned char { return std::tolower(c); }); + std::replace(name.begin(), name.end(), '/', '\\'); + + if ((name.find(MODEL_FOLDER) == 0) && (name.rfind(MODEL_EXTENSION) == name.length() - MODEL_EXTENSION.length())) + { + if (!_extract_model_from_archive_miniz(archive, stat)) + { + mz_zip_reader_end(&archive); + m_errors.push_back("Archive does not contain a valid model"); + return false; + } + } + } + } + + mz_zip_reader_end(&archive); + return true; + } + + bool _3MF_Importer::_extract_model_from_archive_miniz(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + _destroy_xml_parser(); + + m_xml_parser = XML_ParserCreate(nullptr); + if (m_xml_parser == nullptr) + { + m_errors.push_back("Unable to create parser"); + return false; + } + + XML_SetUserData(m_xml_parser, (void*)this); + XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_xml_element, _3MF_Importer::_handle_end_xml_element); + + void* parser_buffer = XML_GetBuffer(m_xml_parser, (int)stat.m_uncomp_size); + if (parser_buffer == nullptr) + { + m_errors.push_back("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) + { + m_errors.push_back("Error while reading 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)), XML_GetCurrentLineNumber(m_xml_parser)); + m_errors.push_back(error_buf); + return false; + } + + return true; + } + + void _3MF_Importer::_handle_start_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); + + if (!res) + _stop_xml_parser(); + } + + void _3MF_Importer::_handle_end_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(); + + 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 != nullptr) && (object.second->instances.size() == 0)) + m_model->delete_object(object.second); + } + + // applies instances' matrices + for (Instance& instance : m_instances) + { + if (instance.instance != nullptr) + { + ModelObject* object = instance.instance->get_object(); + if (object != nullptr) + { + if (object->instances.size() == 1) + { + // single instance -> apply the matrix to object geometry + _apply_transform(*object, instance.matrix); + } + else + { + // multiple instances -> apply the matrix to the instance + _apply_transform(*instance.instance, instance.matrix); + } + } + } + } + + 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.object = m_model->add_object(); + if (m_curr_object.object == nullptr) + { + m_errors.push_back("Unable to create object"); + return false; + } + + // set object data + m_curr_object.object->name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); + m_curr_object.id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); + } + + return true; + } + + bool _3MF_Importer::_handle_end_object() + { + if (m_curr_object.object != nullptr) + { + if (m_curr_object.geometry.empty()) + { + // no geometry defined + // remove the object from the model + m_model->delete_object(m_curr_object.object); + + if (m_curr_object.components.empty()) + { + // no components defined -> invalid object, delete it + IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id); + if (object_item != m_objects.end()) + m_objects.erase(object_item); + + IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id); + if (alias_item != m_objects_aliases.end()) + m_objects_aliases.erase(alias_item); + } + else + // adds components to aliases + m_objects_aliases.insert(IdToAliasesMap::value_type(m_curr_object.id, m_curr_object.components)); + } + else + { + // geometry defined, add it to the object + + ModelVolume* volume = m_curr_object.object->add_volume(m_curr_object.mesh); + if (volume == nullptr) + { + m_errors.push_back("Unable to add volume"); + return false; + } + + stl_file& stl = volume->mesh.stl; + stl.stats.type = inmemory; + stl.stats.number_of_facets = (uint32_t)m_curr_object.geometry.triangles.size() / 3; + stl.stats.original_num_facets = (int)stl.stats.number_of_facets; + stl_allocate(&stl); + for (size_t i = 0; i < m_curr_object.geometry.triangles.size(); /*nothing*/) + { + stl_facet& facet = stl.facet_start[i / 3]; + for (unsigned int v = 0; v < 3; ++v) + { + ::memcpy((void*)&facet.vertex[v].x, (const void*)&m_curr_object.geometry.vertices[m_curr_object.geometry.triangles[i++] * 3], 3 * sizeof(float)); + } + } + stl_get_size(&stl); + volume->mesh.repair(); + + // stores the object for later use + if (m_objects.find(m_curr_object.id) == m_objects.end()) + { + m_objects.insert(IdToModelObjectMap::value_type(m_curr_object.id, m_curr_object.object)); + m_objects_aliases.insert(IdToAliasesMap::value_type(m_curr_object.id, ComponentsList(1, Component(m_curr_object.id)))); // aliases itself + } + else + { + m_errors.push_back("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.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR)); + m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR)); + m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR)); + return true; + } + + bool _3MF_Importer::_handle_end_vertex() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes) + { + // reset current triangles + m_curr_object.geometry.triangles.clear(); + return true; + } + + bool _3MF_Importer::_handle_end_triangles() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_triangle(const char** attributes, unsigned int num_attributes) + { + // we are ignoring the following attributes: + // p1 + // p2 + // p3 + // pid + // see specifications + + // appends the triangle's vertices indices + // missing values are set equal to ZERO + m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR)); + m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR)); + m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR)); + return true; + } + + bool _3MF_Importer::_handle_end_triangle() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes) + { + // reset current components + m_curr_object.components.clear(); + return true; + } + + bool _3MF_Importer::_handle_end_components() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes) + { + int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); + Matrix4x4 matrix = get_matrix_from_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()) + { + m_errors.push_back("Found component with invalid object id"); + return false; + } + } + + m_curr_object.components.emplace_back(object_id, matrix); + + 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); + Matrix4x4 matrix = get_matrix_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + + return _create_object_instance(object_id, matrix, 1); + } + + bool _3MF_Importer::_handle_end_item() + { + // do nothing + return true; + } + + bool _3MF_Importer::_create_object_instance(int object_id, const Matrix4x4& matrix, unsigned int recur_counter) + { + static const unsigned int MAX_RECURSIONS = 10; + + // escape from circular aliasing + if (recur_counter > MAX_RECURSIONS) + { + m_errors.push_back("Too many recursions"); + return false; + } + + IdToAliasesMap::iterator it = m_objects_aliases.find(object_id); + if (it == m_objects_aliases.end()) + { + m_errors.push_back("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 == nullptr)) + { + m_errors.push_back("Found invalid object"); + return false; + } + else + { + ModelInstance* instance = object_item->second->add_instance(); + if (instance == nullptr) + { + m_errors.push_back("Unable to add object instance"); + return false; + } + + m_instances.emplace_back(instance, matrix); + } + } + else + { + // recursively process nested components + for (const Component& component : it->second) + { + if (!_create_object_instance(component.object_id, matrix * component.matrix, recur_counter + 1)) + return false; + } + } + + return true; + } + + void _3MF_Importer::_apply_transform(ModelObject& object, const Matrix4x4& matrix) + { + float matrix3x4[12] = { matrix(0, 0), matrix(0, 1), matrix(0, 2), matrix(0, 3), + matrix(1, 0), matrix(1, 1), matrix(1, 2), matrix(1, 3), + matrix(2, 0), matrix(2, 1), matrix(2, 2), matrix(2, 3) }; + + object.transform(matrix3x4); + } + + void _3MF_Importer::_apply_transform(ModelInstance& instance, const Matrix4x4& matrix) + { + // slic3r ModelInstance cannot be transformed using a matrix + // we extract from the given matrix only the values currently used + + // translation + double offset_x = (double)matrix(0, 3); + double offset_y = (double)matrix(1, 3); + double offset_z = (double)matrix(2, 3); + + // scale + double sx = ::sqrt(sqr((double)matrix(0, 0)) + sqr((double)matrix(1, 0)) + sqr((double)matrix(2, 0))); + double sy = ::sqrt(sqr((double)matrix(0, 1)) + sqr((double)matrix(1, 1)) + sqr((double)matrix(2, 1))); + double sz = ::sqrt(sqr((double)matrix(0, 2)) + sqr((double)matrix(1, 2)) + sqr((double)matrix(2, 2))); + + // invalid scale value, return + if ((sx == 0.0) || (sy == 0.0) || (sz == 0.0)) + return; + + // non-uniform scale value, return + if ((std::abs(sx - sy) > 0.00001) || (std::abs(sx - sz) > 0.00001)) + return; + + // rotations (extracted using quaternion) + double inv_sx = 1.0 / sx; + double inv_sy = 1.0 / sy; + double inv_sz = 1.0 / sz; + + Eigen::Matrix<double, 3, 3, Eigen::RowMajor> m3x3; + m3x3 << (double)matrix(0, 0) * inv_sx, (double)matrix(0, 1) * inv_sy, (double)matrix(0, 2) * inv_sz, + (double)matrix(1, 0) * inv_sx, (double)matrix(1, 1) * inv_sy, (double)matrix(1, 2) * inv_sz, + (double)matrix(2, 0) * inv_sx, (double)matrix(2, 1) * inv_sy, (double)matrix(2, 2) * inv_sz; + + double qw = 0.5 * ::sqrt(std::max(0.0, 1.0 + m3x3(0, 0) + m3x3(1, 1) + m3x3(2, 2))); + double qx = 0.5 * ::sqrt(std::max(0.0, 1.0 + m3x3(0, 0) - m3x3(1, 1) - m3x3(2, 2))); + double qy = 0.5 * ::sqrt(std::max(0.0, 1.0 - m3x3(0, 0) + m3x3(1, 1) - m3x3(2, 2))); + double qz = 0.5 * ::sqrt(std::max(0.0, 1.0 - m3x3(0, 0) - m3x3(1, 1) + m3x3(2, 2))); + + double q_magnitude = ::sqrt(sqr(qw) + sqr(qx) + sqr(qy) + sqr(qz)); + + // invalid length, return + if (q_magnitude == 0.0) + return; + + double inv_q_magnitude = 1.0 / q_magnitude; + + qw *= inv_q_magnitude; + qx *= inv_q_magnitude; + qy *= inv_q_magnitude; + qz *= inv_q_magnitude; + + double test = qx * qy + qz * qw; + double angle_x, angle_y, angle_z; + + if (test > 0.499) + { + // singularity at north pole + angle_x = 0.0; + angle_y = 2.0 * ::atan2(qx, qw); + angle_z = 0.5 * PI; + } + else if (test < -0.499) + { + // singularity at south pole + angle_x = 0.0; + angle_y = -2.0 * ::atan2(qx, qw); + angle_z = -0.5 * PI; + } + else + { + angle_x = ::atan2(2.0 * qx * qw - 2.0 * qy * qz, 1.0 - 2.0 * sqr(qx) - 2.0 * sqr(qz)); + angle_y = ::atan2(2.0 * qy * qw - 2.0 * qx * qz, 1.0 - 2.0 * sqr(qy) - 2.0 * sqr(qz)); + angle_z = ::asin(2.0 * qx * qy + 2.0 * qz * qw); + + if (angle_x < 0.0) + angle_x += 2.0 * PI; + + if (angle_y < 0.0) + angle_y += 2.0 * PI; + + if (angle_z < 0.0) + angle_z += 2.0 * PI; + } + + instance.offset.x = offset_x; + instance.offset.y = offset_y; + instance.scaling_factor = sx; + instance.rotation = angle_z; + } + + void XMLCALL _3MF_Importer::_handle_start_xml_element(void* userData, const char* name, const char** attributes) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_start_xml_element(name, attributes); + } + + void XMLCALL _3MF_Importer::_handle_end_xml_element(void* userData, const char* name) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_end_xml_element(name); + } + + bool load_3mf(const char* path, Model* model, const char* object_name) + { + if ((path == nullptr) || (model == nullptr)) + return false; + + _3MF_Importer importer; + bool res = importer.load_model_from_file(path, *model); + + if (!res) + { + const std::vector<std::string>& errors = importer.get_errors(); + int a = 0; + } + + return res; + } +} // namespace Slic3r diff --git a/xs/src/libslic3r/Format/3mf.hpp b/xs/src/libslic3r/Format/3mf.hpp new file mode 100644 index 000000000..11eb4c388 --- /dev/null +++ b/xs/src/libslic3r/Format/3mf.hpp @@ -0,0 +1,12 @@ +#ifndef slic3r_Format_3mf_hpp_ +#define slic3r_Format_3mf_hpp_ + +namespace Slic3r { + + class Model; + + // Load an 3mf file into a provided model. + extern bool load_3mf(const char* path, Model* model, const char* object_name = nullptr); +}; // namespace Slic3r + +#endif /* slic3r_Format_3mf_hpp_ */ diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 88a0b8279..b86b7e55e 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -5,6 +5,7 @@ #include "Format/OBJ.hpp" #include "Format/PRUS.hpp" #include "Format/STL.hpp" +#include "Format/3mf.hpp" #include <float.h> @@ -53,9 +54,11 @@ Model Model::read_from_file(const std::string &input_file, bool add_default_inst else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); #endif /* SLIC3R_PRUS */ + else if (boost::algorithm::iends_with(input_file, ".3mf")) + result = load_3mf(input_file.c_str(), &model); else - throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); - + throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml), .3mf or .prusa extension."); + if (! result) throw std::runtime_error("Loading of a model file failed."); @@ -115,6 +118,23 @@ void Model::delete_object(size_t idx) this->objects.erase(i); } +void Model::delete_object(ModelObject* object) +{ + if (object == nullptr) + return; + + for (ModelObjectPtrs::iterator it = objects.begin(); it != objects.end(); ++it) + { + ModelObject* obj = *it; + if (obj == object) + { + delete obj; + objects.erase(it); + return; + } + } +} + void Model::clear_objects() { for (ModelObject *o : this->objects) @@ -607,6 +627,20 @@ void ModelObject::rotate(float angle, const Axis &axis) this->invalidate_bounding_box(); } +void ModelObject::transform(const float* matrix3x4) +{ + if (matrix3x4 == nullptr) + return; + + for (ModelVolume* v : volumes) + { + v->mesh.transform(matrix3x4); + } + + origin_translation = Pointf3(0.0f, 0.0f, 0.0f); + invalidate_bounding_box(); +} + void ModelObject::mirror(const Axis &axis) { for (ModelVolume *v : this->volumes) diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 636dfa1f4..317d7ccbb 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -119,6 +119,7 @@ public: void translate(coordf_t x, coordf_t y, coordf_t z); void scale(const Pointf3 &versor); void rotate(float angle, const Axis &axis); + void transform(const float* matrix3x4); void mirror(const Axis &axis); size_t materials_count() const; size_t facets_count() const; @@ -244,6 +245,7 @@ public: ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); ModelObject* add_object(const ModelObject &other, bool copy_volumes = true); void delete_object(size_t idx); + void delete_object(ModelObject* object); void clear_objects(); ModelMaterial* add_material(t_model_material_id material_id); diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 7afa4871c..c2c64e427 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -375,6 +375,15 @@ void TriangleMesh::mirror_z() this->mirror(Z); } +void TriangleMesh::transform(const float* matrix3x4) +{ + if (matrix3x4 == nullptr) + return; + + stl_transform(&stl, const_cast<float*>(matrix3x4)); + stl_invalidate_shared_vertices(&stl); +} + void TriangleMesh::align_to_origin() { this->translate( diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index b28f3a310..3192c90b6 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -47,6 +47,7 @@ public: void mirror_x(); void mirror_y(); void mirror_z(); + void transform(const float* matrix3x4); void align_to_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split() const;