From 343f43ede87a30fad76ee17ac26ce0eb4095f402 Mon Sep 17 00:00:00 2001 From: Arthur Date: Thu, 21 Dec 2023 17:43:47 +0800 Subject: [PATCH] FIX: several problems with mesh boolean 1. Cut with multiple volumes are OK now. 2. Close mesh boolean fail error with new object or open object 3. Fix wrong name and config of boolean resulting object github: #3118 jira: none Change-Id: If2c9dbfb36cbdfe4917a2371217923891bb7909c (cherry picked from commit 982c0ecb92cf7c2b5ae5972ab900a6b10e7dda50) --- src/libslic3r/MeshBoolean.cpp | 182 ++++++++---------------------- src/libslic3r/MeshBoolean.hpp | 5 +- src/slic3r/GUI/GUI_ObjectList.cpp | 15 +-- src/slic3r/GUI/Plater.cpp | 10 ++ 4 files changed, 68 insertions(+), 144 deletions(-) diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 50bbc099e..f3b9cfb27 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -619,7 +619,7 @@ MCAPI_ATTR void MCAPI_CALL mcDebugOutput(McDebugSource source, } -void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts) +bool do_boolean_single(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts) { // create context McContext context = MC_NULL_HANDLE; @@ -650,7 +650,7 @@ void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &b if (srcMesh.vertexCoordsArray.empty() && (boolean_opts == "UNION" || boolean_opts == "B_NOT_A")) { srcMesh = cutMesh; mcReleaseContext(context); - return; + return true; } err = mcDispatch(context, @@ -668,31 +668,9 @@ void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &b mcReleaseContext(context); if (boolean_opts == "UNION") { merge_mcut_meshes(srcMesh, cutMesh); + return true; } - else { - // when src mesh has multiple connected components, mcut refuses to work. - // But we can force it to work by spliting the src mesh into disconnected components, - // and do booleans seperately, then merge all the results. - indexed_triangle_set all_its; - TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); - std::vector src_parts = its_split(tri_src.its); - if (src_parts.size() == 1) - { - //can not split, return error directly - BOOST_LOG_TRIVIAL(error) << boost::format("bool operation %1% failed, also can not split")%boolean_opts; - return; - } - for (size_t i = 0; i < src_parts.size(); i++) - { - auto part = triangle_mesh_to_mcut(src_parts[i]); - do_boolean(*part, cutMesh, boolean_opts); - TriangleMesh tri_part = mcut_to_triangle_mesh(*part); - its_merge(all_its, tri_part.its); - } - srcMesh = *triangle_mesh_to_mcut(all_its); - } - - return; + return false; } // query the number of available connected component @@ -703,8 +681,9 @@ void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &b mcReleaseContext(context); if (numConnComps == 0 && boolean_opts == "UNION") { merge_mcut_meshes(srcMesh, cutMesh); + return true; } - return; + return false; } std::vector connectedComponents(numConnComps, MC_NULL_HANDLE); @@ -771,126 +750,57 @@ void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &b // free connected component data err = mcReleaseConnectedComponents(context, 0, NULL); - // destroy context err = mcReleaseContext(context); srcMesh = outMesh; + + return true; } -/* BBS: Musang King - * mcut for Mesh Boolean which provides C-style syntax API - */ -std::vector make_boolean(const McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts) +void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& boolean_opts) { - // create context - McContext context = MC_NULL_HANDLE; - McResult err = mcCreateContext(&context, 0); - // add debug callback according to https://cutdigital.github.io/mcut.site/tutorials/debugging/ - mcDebugMessageCallback(context, mcDebugOutput, nullptr); - mcDebugMessageControl( - context, - MC_DEBUG_SOURCE_ALL, - MC_DEBUG_TYPE_ERROR, - MC_DEBUG_SEVERITY_MEDIUM, - true); - // We can either let MCUT compute all possible meshes (including patches etc.), or we can - // constrain the library to compute exactly the boolean op mesh we want. This 'constrained' case - // is done with the following flags. - // NOTE#1: you can extend these flags by bitwise ORing with additional flags (see `McDispatchFlags' in mcut.h) - // NOTE#2: below order of columns MATTERS - const std::map booleanOpts = { - {"A_NOT_B", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, - {"B_NOT_A", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, - {"UNION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE}, - {"INTERSECTION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW}, - }; + TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh); + std::vector src_parts = its_split(tri_src.its); - std::map::const_iterator it = booleanOpts.find(boolean_opts); - McFlags boolOpFlags = it->second; + TriangleMesh tri_cut = mcut_to_triangle_mesh(cutMesh); + std::vector cut_parts = its_split(tri_cut.its); - err = mcDispatch(context, - MC_DISPATCH_VERTEX_ARRAY_DOUBLE | // vertices are in array of doubles - MC_DISPATCH_ENFORCE_GENERAL_POSITION | // perturb if necessary - boolOpFlags, // filter flags which specify the type of output we want - // source mesh - reinterpret_cast(srcMesh.vertexCoordsArray.data()), reinterpret_cast(srcMesh.faceIndicesArray.data()), - srcMesh.faceSizesArray.data(), static_cast(srcMesh.vertexCoordsArray.size() / 3), static_cast(srcMesh.faceSizesArray.size()), - // cut mesh - reinterpret_cast(cutMesh.vertexCoordsArray.data()), cutMesh.faceIndicesArray.data(), cutMesh.faceSizesArray.data(), - static_cast(cutMesh.vertexCoordsArray.size() / 3), static_cast(cutMesh.faceSizesArray.size())); - - // query the number of available connected component - uint32_t numConnComps; - err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, 0, NULL, &numConnComps); - - std::vector connectedComponents(numConnComps, MC_NULL_HANDLE); - err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, (uint32_t) connectedComponents.size(), connectedComponents.data(), NULL); - - std::vector outs; - // traversal of all connected components - for (int n = 0; n < numConnComps; ++n) { - // query the data of each connected component from MCUT - McConnectedComponent connComp = connectedComponents[n]; - - // query the vertices - McSize numBytes = 0; - err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, 0, NULL, &numBytes); - uint32_t ccVertexCount = (uint32_t) (numBytes / (sizeof(double) * 3)); - std::vector ccVertices((uint64_t) ccVertexCount * 3u, 0); - err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, numBytes, (void *) ccVertices.data(), NULL); - - // query the faces - numBytes = 0; - err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, 0, NULL, &numBytes); - std::vector ccFaceIndices(numBytes / sizeof(uint32_t), 0); - err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, numBytes, ccFaceIndices.data(), NULL); - std::vector faceSizes(ccFaceIndices.size() / 3, 3); - - const uint32_t ccFaceCount = static_cast(faceSizes.size()); - - // Here we show, how to know when connected components, pertain particular boolean operations. - McPatchLocation patchLocation = (McPatchLocation) 0; - err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_PATCH_LOCATION, sizeof(McPatchLocation), &patchLocation, NULL); - - McFragmentLocation fragmentLocation = (McFragmentLocation) 0; - err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FRAGMENT_LOCATION, sizeof(McFragmentLocation), &fragmentLocation, NULL); - - // rearrange vertices/faces and save into result mesh - std::vector vertices(ccVertexCount); - for (uint32_t i = 0; i < ccVertexCount; ++i) { - vertices[i][0] = (float) ccVertices[(uint64_t) i * 3 + 0]; - vertices[i][1] = (float) ccVertices[(uint64_t) i * 3 + 1]; - vertices[i][2] = (float) ccVertices[(uint64_t) i * 3 + 2]; - } - - // output faces - int faceVertexOffsetBase = 0; - - // for each face in CC - std::vector faces(ccFaceCount); - for (uint32_t f = 0; f < ccFaceCount; ++f) { - bool reverseWindingOrder = (fragmentLocation == MC_FRAGMENT_LOCATION_BELOW) && (patchLocation == MC_PATCH_LOCATION_OUTSIDE); - int faceSize = faceSizes.at(f); - - // for each vertex in face - for (int v = (reverseWindingOrder ? (faceSize - 1) : 0); (reverseWindingOrder ? (v >= 0) : (v < faceSize)); v += (reverseWindingOrder ? -1 : 1)) { - faces[f][v] = ccFaceIndices[(uint64_t) faceVertexOffsetBase + v]; - } - faceVertexOffsetBase += faceSize; - } - - TriangleMesh out(vertices, faces); - outs.emplace_back(out); + if (src_parts.empty() && boolean_opts == "UNION") { + srcMesh = cutMesh; + return; } + if(cut_parts.empty()) return; - // free connected component data - err = mcReleaseConnectedComponents(context, (uint32_t) connectedComponents.size(), connectedComponents.data()); - - // destroy context - err = mcReleaseContext(context); - - return outs; + // when src mesh has multiple connected components, mcut refuses to work. + // But we can force it to work by spliting the src mesh into disconnected components, + // and do booleans seperately, then merge all the results. + indexed_triangle_set all_its; + if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") { + for (size_t i = 0; i < src_parts.size(); i++) { + auto src_part = triangle_mesh_to_mcut(src_parts[i]); + for (size_t j = 0; j < cut_parts.size(); j++) { + auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); + bool success = do_boolean_single(*src_part, *cut_part, boolean_opts); + } + TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); + its_merge(all_its, tri_part.its); + } + } + else if (boolean_opts == "INTERSECTION") { + for (size_t i = 0; i < src_parts.size(); i++) { + for (size_t j = 0; j < cut_parts.size(); j++) { + auto src_part = triangle_mesh_to_mcut(src_parts[i]); + auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); + bool success = do_boolean_single(*src_part, *cut_part, boolean_opts); + if (success) { + TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); + its_merge(all_its, tri_part.its); + } + } + } + } + srcMesh = *triangle_mesh_to_mcut(all_its); } void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts) diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index 520d2f189..92592f5f6 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -85,9 +85,12 @@ McutMeshPtr triangle_mesh_to_mcut(const indexed_triangle_set &M); TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh); // do boolean and save result to srcMesh +// return true if sucessful +bool do_boolean_single(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& boolean_opts); +// do boolean of mesh with multiple volumes and save result to srcMesh +// Both srcMesh and cutMesh may have multiple volumes. void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts); -std::vector make_boolean(const McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts); // do boolean and convert result to TriangleMesh void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector &dst_mesh, const std::string &boolean_opts); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8429cb7ad..208b6c4db 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2947,15 +2947,16 @@ void ObjectList::boolean() Plater::TakeSnapshot snapshot(wxGetApp().plater(), "boolean"); - Model* model = (*m_objects)[0]->get_model(); - ModelObject* new_object = model->add_object(); - new_object->name = (*m_objects)[0]->name; - new_object->config.assign_config((*m_objects)[0]->config); - if (new_object->instances.empty()) - new_object->add_instance(); - ModelObject* object = (*m_objects)[obj_idxs.front()]; TriangleMesh mesh = Plater::combine_mesh_fff(*object, -1, [this](const std::string& msg) {return wxGetApp().notification_manager()->push_plater_error_notification(msg); }); + + // add mesh to model as a new object, keep the original object's name and config + Model* model = object->get_model(); + ModelObject* new_object = model->add_object(); + new_object->name = object->name; + new_object->config.assign_config(object->config); + if (new_object->instances.empty()) + new_object->add_instance(); ModelVolume* new_volume = new_object->add_volume(mesh); // BBS: ensure on bed but no need to ensure locate in the center around origin diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index db1040d68..66664d399 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -8228,6 +8228,11 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString &project_ get_notification_manager()->bbl_close_plateinfo_notification(); get_notification_manager()->bbl_close_preview_only_notification(); get_notification_manager()->bbl_close_3mf_warn_notification(); + get_notification_manager()->close_notification_of_type(NotificationType::PlaterError); + get_notification_manager()->close_notification_of_type(NotificationType::PlaterWarning); + get_notification_manager()->close_notification_of_type(NotificationType::SlicingError); + get_notification_manager()->close_notification_of_type(NotificationType::SlicingSeriousWarning); + get_notification_manager()->close_notification_of_type(NotificationType::SlicingWarning); if (!silent) wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor); @@ -8317,6 +8322,11 @@ void Plater::load_project(wxString const& filename2, get_notification_manager()->bbl_close_plateinfo_notification(); get_notification_manager()->bbl_close_preview_only_notification(); get_notification_manager()->bbl_close_3mf_warn_notification(); + get_notification_manager()->close_notification_of_type(NotificationType::PlaterError); + get_notification_manager()->close_notification_of_type(NotificationType::PlaterWarning); + get_notification_manager()->close_notification_of_type(NotificationType::SlicingError); + get_notification_manager()->close_notification_of_type(NotificationType::SlicingSeriousWarning); + get_notification_manager()->close_notification_of_type(NotificationType::SlicingWarning); auto path = into_path(filename);