From 3eb2147fa3902df811bb8127728d2b0869c719a1 Mon Sep 17 00:00:00 2001 From: lvk88 <15655519+lvk88@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:51:09 +0200 Subject: [PATCH] Enable 3mf reader to read component objects --- src/meshlabplugins/io_3mf/io_3mf.cpp | 383 +++++++++++++++++++++------ 1 file changed, 299 insertions(+), 84 deletions(-) diff --git a/src/meshlabplugins/io_3mf/io_3mf.cpp b/src/meshlabplugins/io_3mf/io_3mf.cpp index e6967ad14..cb7f0afda 100644 --- a/src/meshlabplugins/io_3mf/io_3mf.cpp +++ b/src/meshlabplugins/io_3mf/io_3mf.cpp @@ -84,22 +84,101 @@ Lib3MF::PLib3MFBuildItemIterator get_build_item_iterator(const Lib3MF::PModel& m 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; +} + +std::map load_meshes(const Lib3MF::PModel& model) +{ + std::map result; + if (model == nullptr) { + throw std::runtime_error("Got a null model!"); + } + + auto meshes = model->GetMeshObjects(); + if (meshes == nullptr) { + throw std::runtime_error("Could not access mesh iterators!"); + } + + while (meshes->MoveNext()) { + auto current_mesh = meshes->GetCurrentMeshObject(); + if (current_mesh == nullptr) { + throw std::runtime_error("Error accessing mesh from iterator"); + } + + auto id = current_mesh->GetUniqueResourceID(); + CMeshO cmesh; + + auto n_vertices = current_mesh->GetVertexCount(); + auto n_triangles = current_mesh->GetTriangleCount(); + + auto vertex_iterator = vcg::tri::Allocator::AddVertices(cmesh, n_vertices); + auto face_iterator = vcg::tri::Allocator::AddFaces(cmesh, n_triangles); + + for (size_t i = 0; i < n_vertices; ++i) { + const auto& pos = current_mesh->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 = current_mesh->GetTriangle(i).m_Indices; + (*face_iterator).V(0) = &cmesh.vert[tri[0]]; + (*face_iterator).V(1) = &cmesh.vert[tri[1]]; + (*face_iterator).V(2) = &cmesh.vert[tri[2]]; + ++face_iterator; + } + + result.insert({id, cmesh}); + } + + return result; +} + void load_mesh_to_meshmodel( - const Lib3MF::PModel& model, - const Lib3MF::PMeshObject& lib3mf_mesh_object, - MeshModel& mesh_model, - const std::string& name_postfix, - bool load_material_data) + const Lib3MF::PModel& model, + const Lib3MF::PMeshObject& lib3mf_mesh_object, + MeshModel& mesh_model, + const std::string& name_postfix, + const std::map& textures, + bool load_material_data) { - auto load_or_get_texture_id = [&mesh_model, &model](const int id) { + auto load_or_get_texture_id = [&mesh_model, &model, &textures](const int id) { const std::string string_id = std::to_string(id); - const auto& texture = mesh_model.getTexture(string_id); - if (texture.isNull()) { - std::vector buffer; - model->GetTexture2DByID(id)->GetAttachment()->WriteToBuffer(buffer); - QImage image; - image.loadFromData(buffer.data(), buffer.size()); - mesh_model.addTexture(string_id, image); + + // If the mesh model doesn't have this texture yet, load it + if (mesh_model.getTexture(string_id).isNull()) { + if (textures.find(string_id) == textures.end()) { + throw std::runtime_error( + "One of the meshes uses a texture that does not exist in the model!"); + } + auto texture = textures.at(string_id); + mesh_model.addTexture(string_id, texture); } auto texture_id = std::distance( @@ -302,6 +381,155 @@ unsigned int Lib3MFPlugin::numberMeshesContainedInFile( } } +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, @@ -313,6 +541,15 @@ void Lib3MFPlugin::open( 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; @@ -323,91 +560,69 @@ void Lib3MFPlugin::open( auto lib3mf_model = get_model_from_file(fileName); auto build_item_iterator = get_build_item_iterator(lib3mf_model); auto mesh_model_iterator = meshModelList.begin(); - if (meshModelList.size() != build_item_iterator->Count()) { - throw MLException(errorMsgFormat.arg( - fileName, - "Internal error while loading mesh objects: inconsistent number of meshes " - "encontered")); - } + auto textures = load_textures(lib3mf_model); + auto meshes = load_meshes(lib3mf_model); - auto delta_percent = 100 / meshModelList.size(); + auto build_item_count = 0; - for (size_t i_mesh = 0; i_mesh < meshModelList.size(); ++i_mesh) { - build_item_iterator->MoveNext(); - const auto& current_build_item = build_item_iterator->GetCurrent(); - if (current_build_item == nullptr) { - throw MLException(errorMsgFormat.arg(fileName, "Failed to access build item")); - } + auto delta_progress = 100 / build_item_iterator->Count(); - const auto& object = current_build_item->GetObjectResource(); - if (!object->IsMeshObject()) { - throw MLException(errorMsgFormat.arg( - fileName, "Error while loading mesh object: build item is not a mesh")); + 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()); } - - const auto& current_mesh_object = - lib3mf_model->GetMeshObjectByID(object->GetResourceID()); - - if (current_mesh_object == nullptr) { - throw MLException(errorMsgFormat.arg( - fileName, "Internal error while loading mesh objects: invalid mesh object")); + 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); } - - auto current_mesh_model = (*mesh_model_iterator); - if (current_mesh_model == nullptr) { - throw MLException(errorMsgFormat.arg( - fileName, "Internal error while loading mesh objects: invalid mesh model")); + 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()); } - - // TODO (lvk88): even if enable WEDGCOLOR, meshlab will crash when trying to add a - // per vertex wedge color later on maskList.push_back(Mask::IOM_VERTCOORD | - // Mask::IOM_FACEINDEX | Mask::IOM_WEDGCOLOR); current_mesh_model->enable( - // Mask::IOM_VERTCOORD | Mask::IOM_FACEINDEX | Mask::Mask::IOM_WEDGCOLOR); - - maskList.push_back(Mask::IOM_VERTCOORD | Mask::IOM_FACEINDEX); - current_mesh_model->enable(Mask::IOM_VERTCOORD | Mask::IOM_FACEINDEX); - - load_mesh_to_meshmodel( - lib3mf_model, - current_mesh_object, - *current_mesh_model, - "_" + std::to_string(i_mesh), - par.getBool("usecolors")); if (current_build_item->HasObjectTransform()) { auto transform = current_build_item->GetObjectTransform(); - Matrix44m tr; - tr.SetZero(); - tr.V()[0] = transform.m_Fields[0][0]; - tr.V()[1] = transform.m_Fields[0][1]; - tr.V()[2] = transform.m_Fields[0][2]; - tr.V()[4] = transform.m_Fields[1][0]; - tr.V()[5] = transform.m_Fields[1][1]; - tr.V()[6] = transform.m_Fields[1][2]; - tr.V()[8] = transform.m_Fields[2][0]; - tr.V()[9] = transform.m_Fields[2][1]; - tr.V()[10] = transform.m_Fields[2][2]; - tr.V()[12] = transform.m_Fields[3][0]; - tr.V()[13] = transform.m_Fields[3][1]; - tr.V()[14] = transform.m_Fields[3][2]; - tr.V()[15] = 1.0; + 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( - current_mesh_model->cm, tr.transpose(), true); + vcg::tri::UpdatePositioncm)>::Matrix( + mesh_model->cm, T.transpose(), true); } else { - current_mesh_model->cm.Tr = tr.transpose(); + mesh_model->cm.Tr = T.transpose(); } } - - mesh_model_iterator++; - if (cb != nullptr) { - cb(i_mesh * delta_percent, - std::string( - "Finished reading mesh " + std::to_string(i_mesh + 1) + " of " + - std::to_string(meshModelList.size())) - .c_str()); - } + build_item_count++; } } catch (const Lib3MF::ELib3MFException& e) {