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)
This commit is contained in:
Arthur 2023-12-21 17:43:47 +08:00 committed by Lane.Wei
parent 6c24a477cd
commit 343f43ede8
4 changed files with 68 additions and 144 deletions

View File

@ -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 // create context
McContext context = MC_NULL_HANDLE; 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")) { if (srcMesh.vertexCoordsArray.empty() && (boolean_opts == "UNION" || boolean_opts == "B_NOT_A")) {
srcMesh = cutMesh; srcMesh = cutMesh;
mcReleaseContext(context); mcReleaseContext(context);
return; return true;
} }
err = mcDispatch(context, err = mcDispatch(context,
@ -668,31 +668,9 @@ void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &b
mcReleaseContext(context); mcReleaseContext(context);
if (boolean_opts == "UNION") { if (boolean_opts == "UNION") {
merge_mcut_meshes(srcMesh, cutMesh); merge_mcut_meshes(srcMesh, cutMesh);
return true;
} }
else { return false;
// 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<indexed_triangle_set> 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;
} }
// query the number of available connected component // 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); mcReleaseContext(context);
if (numConnComps == 0 && boolean_opts == "UNION") { if (numConnComps == 0 && boolean_opts == "UNION") {
merge_mcut_meshes(srcMesh, cutMesh); merge_mcut_meshes(srcMesh, cutMesh);
return true;
} }
return; return false;
} }
std::vector<McConnectedComponent> connectedComponents(numConnComps, MC_NULL_HANDLE); std::vector<McConnectedComponent> 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 // free connected component data
err = mcReleaseConnectedComponents(context, 0, NULL); err = mcReleaseConnectedComponents(context, 0, NULL);
// destroy context // destroy context
err = mcReleaseContext(context); err = mcReleaseContext(context);
srcMesh = outMesh; srcMesh = outMesh;
return true;
} }
/* BBS: Musang King void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& boolean_opts)
* mcut for Mesh Boolean which provides C-style syntax API
*/
std::vector<TriangleMesh> make_boolean(const McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts)
{ {
// create context TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh);
McContext context = MC_NULL_HANDLE; std::vector<indexed_triangle_set> src_parts = its_split(tri_src.its);
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<std::string, McFlags> 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},
};
std::map<std::string, McFlags>::const_iterator it = booleanOpts.find(boolean_opts); TriangleMesh tri_cut = mcut_to_triangle_mesh(cutMesh);
McFlags boolOpFlags = it->second; std::vector<indexed_triangle_set> cut_parts = its_split(tri_cut.its);
err = mcDispatch(context, if (src_parts.empty() && boolean_opts == "UNION") {
MC_DISPATCH_VERTEX_ARRAY_DOUBLE | // vertices are in array of doubles srcMesh = cutMesh;
MC_DISPATCH_ENFORCE_GENERAL_POSITION | // perturb if necessary return;
boolOpFlags, // filter flags which specify the type of output we want
// source mesh
reinterpret_cast<const void *>(srcMesh.vertexCoordsArray.data()), reinterpret_cast<const uint32_t *>(srcMesh.faceIndicesArray.data()),
srcMesh.faceSizesArray.data(), static_cast<uint32_t>(srcMesh.vertexCoordsArray.size() / 3), static_cast<uint32_t>(srcMesh.faceSizesArray.size()),
// cut mesh
reinterpret_cast<const void *>(cutMesh.vertexCoordsArray.data()), cutMesh.faceIndicesArray.data(), cutMesh.faceSizesArray.data(),
static_cast<uint32_t>(cutMesh.vertexCoordsArray.size() / 3), static_cast<uint32_t>(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<McConnectedComponent> connectedComponents(numConnComps, MC_NULL_HANDLE);
err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, (uint32_t) connectedComponents.size(), connectedComponents.data(), NULL);
std::vector<TriangleMesh> 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<double> 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<uint32_t> ccFaceIndices(numBytes / sizeof(uint32_t), 0);
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, numBytes, ccFaceIndices.data(), NULL);
std::vector<uint32_t> faceSizes(ccFaceIndices.size() / 3, 3);
const uint32_t ccFaceCount = static_cast<uint32_t>(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<Vec3f> 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<Vec3i> 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(cut_parts.empty()) return;
// free connected component data // when src mesh has multiple connected components, mcut refuses to work.
err = mcReleaseConnectedComponents(context, (uint32_t) connectedComponents.size(), connectedComponents.data()); // But we can force it to work by spliting the src mesh into disconnected components,
// and do booleans seperately, then merge all the results.
// destroy context indexed_triangle_set all_its;
err = mcReleaseContext(context); if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") {
for (size_t i = 0; i < src_parts.size(); i++) {
return outs; 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<TriangleMesh> &dst_mesh, const std::string &boolean_opts) void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector<TriangleMesh> &dst_mesh, const std::string &boolean_opts)

View File

@ -85,9 +85,12 @@ McutMeshPtr triangle_mesh_to_mcut(const indexed_triangle_set &M);
TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh); TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh);
// do boolean and save result to srcMesh // 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); void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts);
std::vector<TriangleMesh> make_boolean(const McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts);
// do boolean and convert result to TriangleMesh // do boolean and convert result to TriangleMesh
void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector<TriangleMesh> &dst_mesh, const std::string &boolean_opts); void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector<TriangleMesh> &dst_mesh, const std::string &boolean_opts);

View File

@ -2947,15 +2947,16 @@ void ObjectList::boolean()
Plater::TakeSnapshot snapshot(wxGetApp().plater(), "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()]; 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); }); 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); ModelVolume* new_volume = new_object->add_volume(mesh);
// BBS: ensure on bed but no need to ensure locate in the center around origin // BBS: ensure on bed but no need to ensure locate in the center around origin

View File

@ -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_plateinfo_notification();
get_notification_manager()->bbl_close_preview_only_notification(); get_notification_manager()->bbl_close_preview_only_notification();
get_notification_manager()->bbl_close_3mf_warn_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) if (!silent)
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor); 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_plateinfo_notification();
get_notification_manager()->bbl_close_preview_only_notification(); get_notification_manager()->bbl_close_preview_only_notification();
get_notification_manager()->bbl_close_3mf_warn_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); auto path = into_path(filename);