diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16c113b34..0398e8566 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -123,6 +123,7 @@ if(NOT DEFINED MESHLAB_PLUGINS) # it may be already defined in parent directory set(MESHLAB_PLUGINS # IO plugins meshlabplugins/io_3ds + meshlabplugins/io_3mf meshlabplugins/io_base meshlabplugins/io_bre meshlabplugins/io_collada diff --git a/src/external/CMakeLists.txt b/src/external/CMakeLists.txt index d82d49ecd..9df7f126b 100644 --- a/src/external/CMakeLists.txt +++ b/src/external/CMakeLists.txt @@ -55,6 +55,9 @@ if ((NOT MESHLAB_BUILD_MINI) AND MESHLAB_ALLOW_OPTIONAL_EXTERNAL_LIBRARIES) # lib3ds - optional, for io_3ds include(${CMAKE_CURRENT_SOURCE_DIR}/lib3ds.cmake) + # lib3mf - optional, for io_3mf + include(${CMAKE_CURRENT_SOURCE_DIR}/lib3mf.cmake) + # libigl - optional for filter_mesh_booleans include(${CMAKE_CURRENT_SOURCE_DIR}/libigl.cmake) diff --git a/src/external/lib3mf.cmake b/src/external/lib3mf.cmake new file mode 100644 index 000000000..f4cf06c4d --- /dev/null +++ b/src/external/lib3mf.cmake @@ -0,0 +1,73 @@ +############################################################################# +# MeshLab o o # +# A versatile mesh processing toolbox o o # +# _ O _ # +# Copyright(C) 2023 - 2024 \/)\/ # +# Visual Computing Lab /\/| # +# ISTI - Italian National Research Council | # +# \ # +# All rights reserved. # +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License (http://www.gnu.org/licenses/gpl.txt) # +# for more details. # +# # +############################################################################# + +option(MESHLAB_ALLOW_DOWNLOAD_SOURCE_LIB3MF "Allow download and use of lib3MF source" ON) + +if(MESHLAB_ALLOW_DOWNLOAD_SOURCE_LIB3MF) + set(LIB3MF_DIR ${MESHLAB_EXTERNAL_DOWNLOAD_DIR}/lib3mf-2.3.2) + set(LIB3MF_CHECK ${LIB3MF_DIR}/CMakeLists.txt) + + if(NOT EXISTS ${LIB3MF_CHECK}) + set(LIB3MF_LINK https://github.com/3MFConsortium/lib3mf/releases/download/v2.3.2/lib3mf-2.3.2-source-with-submodules.zip) + set(LIB3MF_MD5 e9f3f40de2bd58c3f9109d657c86f3a8) + download_and_unzip( + NAME "Lib3MF" + MD5 ${LIB3MF_MD5} + LINK ${LIB3MF_LINK} + DIR ${MESHLAB_EXTERNAL_DOWNLOAD_DIR}) + if(NOT download_and_unzip_SUCCESS) + message(STATUS "- Lib3MF - download failed") + endif() + endif() + + if(EXISTS ${LIB3MF_CHECK}) + message(STATUS "- Lib3MF - Using downloaded Lib3MF sources") + set(MESSAGE_QUIET ON) + set(LIB3MF_TESTS OFF) + add_subdirectory(${LIB3MF_DIR} EXCLUDE_FROM_ALL) + + # Well, this is extremely ugly + # But due to some bug in lib3mf CMake function `generate_product_version`, + # it is not possible to build lib3mf with ninja on Windows, because the following + # error message will appear when processing VersionResource.rc + # + # fatal error RC1106: invalid option: -3 + # + # I don't know what causes the bug. A workaround is to just simply exclude VersionResource.rc from the list + # of sources associated to the lib3mf target. + if( WIN32 AND CMAKE_GENERATOR STREQUAL "Ninja" ) + get_target_property(LIB3MF_SRCS lib3mf SOURCES) + LIST(FILTER LIB3MF_SRCS EXCLUDE REGEX "VersionResource.rc") + SET_TARGET_PROPERTIES(lib3mf PROPERTIES SOURCES "${LIB3MF_SRCS}") + endif() + unset(MESSAGE_QUIET) + else() + message(FATAL " - Lib3MF - Could not add lib3mf to source tree ") + endif() + + add_library(external-lib3mf INTERFACE) + target_link_libraries(external-lib3mf INTERFACE lib3mf) + target_include_directories(external-lib3mf INTERFACE ${LIB3MF_DIR}/Autogenerated/Bindings/Cpp) + install(TARGETS lib3mf DESTINATION ${MESHLAB_LIB_INSTALL_DIR}) + +endif() diff --git a/src/meshlabplugins/io_3mf/CMakeLists.txt b/src/meshlabplugins/io_3mf/CMakeLists.txt new file mode 100644 index 000000000..9b76f7323 --- /dev/null +++ b/src/meshlabplugins/io_3mf/CMakeLists.txt @@ -0,0 +1,34 @@ +############################################################################# +# MeshLab o o # +# A versatile mesh processing toolbox o o # +# _ O _ # +# Copyright(C) 2023 - 2024 \/)\/ # +# Visual Computing Lab /\/| # +# ISTI - Italian National Research Council | # +# \ # +# All rights reserved. # +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License (http://www.gnu.org/licenses/gpl.txt) # +# for more details. # +# # +############################################################################# + +set(HEADERS + io_3mf.h + ) + +set(SOURCES + io_3mf.cpp + ) + +add_meshlab_plugin(io_3mf ${SOURCES} ${HEADERS}) + +target_link_libraries(io_3mf PUBLIC external-lib3mf) diff --git a/src/meshlabplugins/io_3mf/io_3mf.cpp b/src/meshlabplugins/io_3mf/io_3mf.cpp new file mode 100644 index 000000000..a43c0cb5b --- /dev/null +++ b/src/meshlabplugins/io_3mf/io_3mf.cpp @@ -0,0 +1,553 @@ +/**************************************************************************** + * MeshLab o o * + * A versatile mesh processing toolbox o o * + * _ O _ * + * Copyright(C) 2023 - 2024 \/)\/ * + * Visual Computing Lab /\/| * + * ISTI - Italian National Research Council | * + * \ * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * + * for more details. * + * * + ****************************************************************************/ + +#include "io_3mf.h" + +#include "common/ml_document/cmesh.h" +#include "common/ml_document/mesh_model.h" +#include "common/parameters/rich_parameter/rich_bool.h" +#include "common/parameters/rich_parameter_list.h" +#include "lib3mf_implicit.hpp" +#include "lib3mf_types.hpp" +#include "vcg/complex/allocate.h" +#include "vcg/space/color4.h" +#include "wrap/io_trimesh/io_mask.h" + +#include +#include +#include +#include +#include + +namespace { +Lib3MF::PModel get_model_from_file(const QString& fileName) +{ + const QString errorMsgFormat = + "Error encountered while loading file:\n\"%1\"\n\nError details: %2"; + + if (!QFile::exists(fileName)) { + throw MLException(errorMsgFormat.arg(fileName, "File does not exist")); + } + + const auto& wrapper = Lib3MF::CWrapper::loadLibrary(); + if (wrapper == nullptr) { + throw MLException(errorMsgFormat.arg(fileName, "Failed to initialize 3MF library")); + } + + const auto& model = wrapper->CreateModel(); + if (model == nullptr) { + throw MLException(errorMsgFormat.arg(fileName, "Failed to create 3MF internal model")); + } + + const auto& reader = model->QueryReader("3mf"); + if (model == nullptr) { + throw MLException(errorMsgFormat.arg(fileName, "Failed to create 3MF reader object")); + } + + reader->ReadFromFile(fileName.toStdString()); + + return model; +} + +Lib3MF::PLib3MFMeshObjectIterator get_mesh_iterator(const QString& fileName) +{ + const QString errorMsgFormat = + "Error encountered while loading file:\n\"%1\"\n\nError details: %2"; + + const auto& model = get_model_from_file(fileName); + + return model->GetMeshObjects(); +} + +Lib3MF::PLib3MFBuildItemIterator get_build_item_iterator(const Lib3MF::PModel& model) +{ + return model->GetBuildItems(); +} + +// Loads all the textures from the model into QImage-s and returns them in a map +// where the key is the unique resource ID of the texture +std::map load_textures(const Lib3MF::PModel& model) +{ + std::map result; + auto textures = model->GetTexture2Ds(); + if (textures == nullptr) { + throw std::runtime_error("Could not get iterator to textures"); + } + + while (textures->MoveNext()) { + auto current_texture = textures->GetCurrentTexture2D(); + auto id = current_texture->GetUniqueResourceID(); + auto attachment = current_texture->GetAttachment(); + if (attachment == nullptr) { + throw std::runtime_error("Attachment to texture returned a nullptr"); + } + std::vector buffer; + attachment->WriteToBuffer(buffer); + QImage image; + image.loadFromData(buffer.data(), buffer.size()); + result.insert({std::to_string(id), image}); + } + + return result; +} + +} // namespace + +Lib3MFPlugin::Lib3MFPlugin() +{ +} + +QString Lib3MFPlugin::pluginName() const +{ + return "3MF importer and exporter"; +} + +std::list Lib3MFPlugin::importFormats() const +{ + return {FileFormat {"3MF File Format", tr("3MF")}}; +} + +std::list Lib3MFPlugin::exportFormats() const +{ + return {FileFormat {"3MF File Format", tr("3MF")}}; +} + +RichParameterList Lib3MFPlugin::initPreOpenParameter(const QString& /*format*/) const +{ + RichParameterList result; + result.addParam(RichBool( + "usecolors", + false, + "Load colors and textures", + "When turned on, loads color and texture information from the file. Turn off if you " + "experience slow rendering performance.")); + result.addParam(RichBool( + "forcetransform", + false, + "Transform vertices instead of using transformation matrix", + "When turned on, transform the vertices directly, instead of creating " + "a transformation " + "matrix")); + return result; +} + +unsigned int Lib3MFPlugin::numberMeshesContainedInFile( + const QString& format, + const QString& fileName, + const RichParameterList& preParams) const +{ + const QString errorMsgFormat = + "Error encountered while loading file:\n\"%1\"\n\nError details: %2"; + + try { + const auto& model = get_model_from_file(fileName); + const auto& build_item_iterator = get_build_item_iterator(model); + + if (build_item_iterator == nullptr) { + throw MLException( + errorMsgFormat.arg(fileName, "Failed to iterate over build items in file")); + } + + if (build_item_iterator->Count() == 0) { + throw MLException( + errorMsgFormat.arg(fileName, "The file does not contain any models!")); + } + + return build_item_iterator->Count(); + } + catch (const Lib3MF::ELib3MFException& e) { + std::stringstream message_stream; + message_stream << "An exception occurred while opening the 3MF file.\n" << e.what(); + log(message_stream.str()); + throw MLException( + errorMsgFormat.arg(fileName, QString::fromStdString((message_stream.str())))); + } + catch (const std::exception& e) { + std::stringstream message_stream; + message_stream << "An exception occurred while opening the 3MF file.\n" << e.what(); + log(message_stream.str()); + throw MLException( + errorMsgFormat.arg(fileName, QString::fromStdString((message_stream.str())))); + } + catch (...) { + std::stringstream message_stream; + message_stream << "An unkown error occurred while opening the 3MF file.\n"; + log(message_stream.str()); + throw MLException( + errorMsgFormat.arg(fileName, QString::fromStdString((message_stream.str())))); + } +} + +void to_cmesh(const Lib3MF::PMeshObject& mesh_object, CMeshO& target) +{ + auto n_vertices = mesh_object->GetVertexCount(); + auto n_triangles = mesh_object->GetTriangleCount(); + + auto vertex_iterator = + vcg::tri::Allocator>::AddVertices( + target, n_vertices); + auto face_iterator = vcg::tri::Allocator>::AddFaces( + target, n_triangles); + + for (int i = 0; i < n_vertices; ++i) { + const auto& pos = mesh_object->GetVertex(i).m_Coordinates; + (*vertex_iterator).P()[0] = pos[0]; + (*vertex_iterator).P()[1] = pos[1]; + (*vertex_iterator).P()[2] = pos[2]; + ++vertex_iterator; + } + + for (size_t i = 0; i < n_triangles; ++i) { + const auto& tri = mesh_object->GetTriangle(i).m_Indices; + (*face_iterator).V(0) = &target.vert[tri[0]]; + (*face_iterator).V(1) = &target.vert[tri[1]]; + (*face_iterator).V(2) = &target.vert[tri[2]]; + ++face_iterator; + } +} + +bool append_props(const Lib3MF::PModel model, const Lib3MF::PMeshObject mesh_object, CMeshO& cmesh) +{ + auto n_triangles = mesh_object->GetTriangleCount(); + + bool result = false; + + for (int i = 0; i < n_triangles; ++i) { + Lib3MF::sTriangleProperties props; + mesh_object->GetTriangleProperties(i, props); + if (props.m_ResourceID == 0) { + continue; + } + + switch (model->GetPropertyTypeByID(props.m_ResourceID)) { + case Lib3MF::ePropertyType::BaseMaterial: { + result = true; + auto baseMaterial = model->GetBaseMaterialGroupByID(props.m_ResourceID); + auto color = baseMaterial->GetDisplayColor(props.m_PropertyIDs[0]); + cmesh.face[i].C() = + vcg::Color4b {color.m_Red, color.m_Green, color.m_Blue, color.m_Alpha}; + break; + } + case Lib3MF::ePropertyType::TexCoord: { + auto group = model->GetTexture2DGroupByID(props.m_ResourceID); + auto texture_id = std::distance( + cmesh.textures.begin(), + std::find( + cmesh.textures.begin(), + cmesh.textures.end(), + std::to_string(group->GetTexture2D()->GetUniqueResourceID()))); + auto coord0 = group->GetTex2Coord(props.m_PropertyIDs[0]); + auto coord1 = group->GetTex2Coord(props.m_PropertyIDs[1]); + auto coord2 = group->GetTex2Coord(props.m_PropertyIDs[2]); + + cmesh.face[i].WT(0).U() = coord0.m_U; + cmesh.face[i].WT(0).V() = coord0.m_V; + cmesh.face[i].WT(0).N() = texture_id; + + cmesh.face[i].WT(1).U() = coord1.m_U; + cmesh.face[i].WT(1).V() = coord1.m_V; + cmesh.face[i].WT(1).N() = texture_id; + + cmesh.face[i].WT(2).U() = coord2.m_U; + cmesh.face[i].WT(2).V() = coord2.m_V; + cmesh.face[i].WT(2).N() = texture_id; + break; + } + case Lib3MF::ePropertyType::Colors: { + // mesh_model.enable(vcg::tri::io::Mask::IOM_FACECOLOR); + result = true; + auto colorGroup = model->GetColorGroupByID(props.m_ResourceID); + auto color0 = colorGroup->GetColor(props.m_PropertyIDs[0]); + cmesh.face[i].C() = + vcg::Color4b {color0.m_Red, color0.m_Green, color0.m_Blue, color0.m_Alpha}; + break; + } + default: break; + }; + } + + return result; +} + +void read_components( + int level, + const Lib3MF::PModel& model, + const Lib3MF::PComponentsObject& componentsObject, + MeshModel& meshModel, + Matrix44m T) +{ + for (int iComponent = 0; iComponent < componentsObject->GetComponentCount(); ++iComponent) { + auto component = componentsObject->GetComponent(iComponent); + auto objectResource = component->GetObjectResource(); + auto currentTransform = T; + if (component->HasTransform()) { + auto transform = component->GetTransform(); + Matrix44m componentT; + componentT.ElementAt(0, 0) = transform.m_Fields[0][0]; + componentT.ElementAt(0, 1) = transform.m_Fields[0][1]; + componentT.ElementAt(0, 2) = transform.m_Fields[0][2]; + componentT.ElementAt(1, 0) = transform.m_Fields[1][0]; + componentT.ElementAt(1, 1) = transform.m_Fields[1][1]; + componentT.ElementAt(1, 2) = transform.m_Fields[1][2]; + componentT.ElementAt(2, 0) = transform.m_Fields[2][0]; + componentT.ElementAt(2, 1) = transform.m_Fields[2][1]; + componentT.ElementAt(2, 2) = transform.m_Fields[2][2]; + componentT.ElementAt(3, 0) = transform.m_Fields[3][0]; + componentT.ElementAt(3, 1) = transform.m_Fields[3][1]; + componentT.ElementAt(3, 2) = transform.m_Fields[3][2]; + componentT.ElementAt(3, 3) = 1.0; + currentTransform = currentTransform * componentT.transpose(); + } + if (objectResource->IsMeshObject()) { + auto meshObject = model->GetMeshObjectByID(objectResource->GetUniqueResourceID()); + auto n_vertices = meshObject->GetVertexCount(); + auto n_triangles = meshObject->GetTriangleCount(); + CMeshO new_cmesh; + for (const auto& texture : meshModel.getTextures()) { + new_cmesh.textures.push_back(texture.first); + } + to_cmesh(meshObject, new_cmesh); + new_cmesh.face.EnableColor(); + new_cmesh.face.EnableWedgeTexCoord(); + meshModel.enable( + vcg::tri::io::Mask::IOM_FACECOLOR | vcg::tri::io::Mask::IOM_WEDGTEXCOORD); + append_props(model, meshObject, new_cmesh); + vcg::tri::UpdatePosition::Matrix(new_cmesh, currentTransform); + vcg::tri::Append::Mesh(meshModel.cm, new_cmesh); + } + else if (objectResource->IsComponentsObject()) { + std::cout << "Component " << objectResource->GetUniqueResourceID() << std::endl; + read_components( + level + 1, + model, + model->GetComponentsObjectByID(objectResource->GetUniqueResourceID()), + meshModel, + currentTransform); + } + } +} + +void Lib3MFPlugin::open( + const QString& format, + const QString& fileName, + const std::list& meshModelList, + std::list& maskList, + const RichParameterList& par, + vcg::CallBackPos* cb) +{ + const QString errorMsgFormat = + "Error encountered while loading file:\n\"%1\"\n\nError details: %2"; + + // Lib3MF doesn't seem to account for the fact that an object may contain a + // sequence of components and meshes, we can only access either a single + // mesh or a single component! + // Go over every build item + // Get the object that the build item refers to + // If it's a mesh, load it into a cmesh and return it + // If it's a components object, visit the children components recursively, keep track of the + // transformations until we find a mesh and then load it. + + try { + using namespace vcg::tri::io; + + if (cb != nullptr) { + (*cb)(0, std::string("Loading " + fileName.toStdString()).c_str()); + } + + auto lib3mf_model = get_model_from_file(fileName); + auto build_item_iterator = get_build_item_iterator(lib3mf_model); + auto mesh_model_iterator = meshModelList.begin(); + auto textures = load_textures(lib3mf_model); + + auto build_item_count = 0; + + auto delta_progress = 100 / build_item_iterator->Count(); + + while (build_item_iterator->MoveNext()) { + if (cb != nullptr) { + (*cb)( + delta_progress * build_item_count, + std::string("Loading mesh " + std::to_string(build_item_count)).c_str()); + } + auto& mesh_model = *(mesh_model_iterator++); + auto current_build_item = build_item_iterator->GetCurrent(); + auto object_resource = current_build_item->GetObjectResource(); + for (const auto& texture : textures) { + const auto& id = texture.first; + const auto& image = texture.second; + mesh_model->addTexture(id, image); + } + if (object_resource->IsMeshObject()) { + auto mesh_object = + lib3mf_model->GetMeshObjectByID(object_resource->GetUniqueResourceID()); + to_cmesh(mesh_object, mesh_model->cm); + mesh_model->enable( + vcg::tri::io::Mask::IOM_FACECOLOR | vcg::tri::io::Mask::IOM_WEDGTEXCOORD); + append_props(lib3mf_model, mesh_object, mesh_model->cm); + } + else if (object_resource->IsComponentsObject()) { + read_components( + 1, + lib3mf_model, + lib3mf_model->GetComponentsObjectByID(object_resource->GetUniqueResourceID()), + *mesh_model, + Matrix44m::Identity()); + } + + if (current_build_item->HasObjectTransform()) { + auto transform = current_build_item->GetObjectTransform(); + Matrix44m T; + T.ElementAt(0, 0) = transform.m_Fields[0][0]; + T.ElementAt(0, 1) = transform.m_Fields[0][1]; + T.ElementAt(0, 2) = transform.m_Fields[0][2]; + T.ElementAt(1, 0) = transform.m_Fields[1][0]; + T.ElementAt(1, 1) = transform.m_Fields[1][1]; + T.ElementAt(1, 2) = transform.m_Fields[1][2]; + T.ElementAt(2, 0) = transform.m_Fields[2][0]; + T.ElementAt(2, 1) = transform.m_Fields[2][1]; + T.ElementAt(2, 2) = transform.m_Fields[2][2]; + T.ElementAt(3, 0) = transform.m_Fields[3][0]; + T.ElementAt(3, 1) = transform.m_Fields[3][1]; + T.ElementAt(3, 2) = transform.m_Fields[3][2]; + T.ElementAt(3, 3) = 1.0; + if (par.getBool("forcetransform")) { + vcg::tri::UpdatePositioncm)>::Matrix( + mesh_model->cm, T.transpose(), true); + } + else { + mesh_model->cm.Tr = T.transpose(); + } + } + build_item_count++; + } + } + catch (const Lib3MF::ELib3MFException& e) { + std::stringstream message_stream; + message_stream << "An exception occurred while opening the 3MF file.\n" << e.what(); + log(message_stream.str()); + throw MLException( + errorMsgFormat.arg(fileName, QString::fromStdString((message_stream.str())))); + } + catch (const std::exception& e) { + std::stringstream message_stream; + message_stream << "An exception occurred while opening the 3MF file.\n" << e.what(); + log(message_stream.str()); + throw MLException( + errorMsgFormat.arg(fileName, QString::fromStdString((message_stream.str())))); + } + catch (...) { + std::stringstream message_stream; + message_stream << "An unkown error occurred while opening the 3MF file.\n"; + log(message_stream.str()); + throw MLException( + errorMsgFormat.arg(fileName, QString::fromStdString((message_stream.str())))); + } +} + +void Lib3MFPlugin::open( + const QString& formatName, + const QString& fileName, + MeshModel& m, + int& mask, + const RichParameterList& par, + vcg::CallBackPos* cb) +{ + wrongOpenFormat("This should have not happened!"); +} + +void Lib3MFPlugin::save( + const QString& formatName, + const QString& fileName, + MeshModel& m, + const int mask, + const RichParameterList& par, + vcg::CallBackPos* cb) +{ + const QString errorMsgFormat = + "Error encountered while saving file:\n\"%1\"\n\nError details: %2"; + + const auto& wrapper = Lib3MF::CWrapper::loadLibrary(); + if (wrapper == nullptr) { + throw MLException(errorMsgFormat.arg(fileName, "Could not init 3mf library")); + } + + const auto& model = wrapper->CreateModel(); + if (model == nullptr) { + throw MLException(errorMsgFormat.arg(fileName, "Could not create model for writing")); + } + + const auto& metadata_group = model->GetMetaDataGroup(); + metadata_group->AddMetaData("", "Application", "Meshlab", "string", false); + + const auto& mesh = model->AddMeshObject(); + mesh->SetName(m.label().toStdString()); + + std::vector vertex_buffer; + vertex_buffer.reserve(m.cm.vert.size()); + + std::vector vertex_ids(m.cm.vert.size()); + + std::vector triangle_buffer; + triangle_buffer.reserve(m.cm.face.size()); + + int number_of_vertices = 0; + for (auto vertex_it = m.cm.vert.begin(); vertex_it != m.cm.vert.end(); ++vertex_it) { + if (vertex_it->IsD()) + continue; + vertex_ids[vertex_it - m.cm.vert.begin()] = number_of_vertices; + Lib3MF::sPosition pos; + pos.m_Coordinates[0] = vertex_it->P()[0]; + pos.m_Coordinates[1] = vertex_it->P()[1]; + pos.m_Coordinates[2] = vertex_it->P()[2]; + vertex_buffer.push_back(pos); + number_of_vertices++; + } + + for (auto face_it = m.cm.face.begin(); face_it != m.cm.face.end(); ++face_it) { + if (face_it->IsD()) + continue; + if (face_it->VN() != 3) { + throw MLException( + errorMsgFormat.arg(fileName, "Only triangular meshes can be written to 3mf files")); + } + Lib3MF::sTriangle triangle; + triangle.m_Indices[0] = vertex_ids[vcg::tri::Index(m.cm, face_it->V(0))]; + triangle.m_Indices[1] = vertex_ids[vcg::tri::Index(m.cm, face_it->V(1))]; + triangle.m_Indices[2] = vertex_ids[vcg::tri::Index(m.cm, face_it->V(2))]; + triangle_buffer.push_back(triangle); + } + + mesh->SetGeometry(vertex_buffer, triangle_buffer); + model->AddBuildItem(mesh.get(), wrapper->GetIdentityTransform()); + const auto& writer = model->QueryWriter("3mf"); + writer->WriteToFile(fileName.toStdString()); +} + +void Lib3MFPlugin::exportMaskCapability(const QString& format, int& capability, int& defaultBits) + const +{ + capability = 0; + defaultBits = 0; +} + +MESHLAB_PLUGIN_NAME_EXPORTER(Lib3MFPlugin) diff --git a/src/meshlabplugins/io_3mf/io_3mf.h b/src/meshlabplugins/io_3mf/io_3mf.h new file mode 100644 index 000000000..d31a0e1f0 --- /dev/null +++ b/src/meshlabplugins/io_3mf/io_3mf.h @@ -0,0 +1,74 @@ +/**************************************************************************** + * MeshLab o o * + * A versatile mesh processing toolbox o o * + * _ O _ * + * Copyright(C) 2023 - 2024 \/)\/ * + * Visual Computing Lab /\/| * + * ISTI - Italian National Research Council | * + * \ * + * All rights reserved. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * + * for more details. * + * * + ****************************************************************************/ + +#pragma once + +#include "common/plugins/interfaces/io_plugin.h" + +class Lib3MFPlugin : public QObject, public IOPlugin +{ + Q_OBJECT + MESHLAB_PLUGIN_IID_EXPORTER(IO_PLUGIN_IID) + Q_INTERFACES(IOPlugin) + +public: + Lib3MFPlugin(); + + QString pluginName() const override; + std::list importFormats() const override; + std::list exportFormats() const override; + + RichParameterList initPreOpenParameter(const QString& /*format*/) const override; + + unsigned int numberMeshesContainedInFile( + const QString& format, + const QString& fileName, + const RichParameterList& preParams) const override; + + void open( + const QString& format, + const QString& fileName, + const std::list& meshModelList, + std::list& maskList, + const RichParameterList& par, + vcg::CallBackPos* cb = nullptr) override; + + void open( + const QString& formatName, + const QString& fileName, + MeshModel& m, + int& mask, + const RichParameterList& par, + vcg::CallBackPos* cb) override; + + void save( + const QString& formatName, + const QString& fileName, + MeshModel& m, + const int mask, + const RichParameterList& par, + vcg::CallBackPos* cb) override; + + virtual void + exportMaskCapability(const QString& format, int& capability, int& defaultBits) const override; +};