Skip to content

Commit

Permalink
FIX: several problems with mesh boolean
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
ArthurBambulab authored and SoftFever committed Jan 25, 2024
1 parent 0490e1b commit ae0012e
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 140 deletions.
176 changes: 43 additions & 133 deletions src/libslic3r/MeshBoolean.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,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;
Expand Down Expand Up @@ -651,7 +651,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,
Expand All @@ -669,31 +669,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<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;
return false;
}

// query the number of available connected component
Expand All @@ -704,8 +682,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<McConnectedComponent> connectedComponents(numConnComps, MC_NULL_HANDLE);
Expand Down Expand Up @@ -772,126 +751,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<TriangleMesh> 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<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},
};
TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh);
std::vector<indexed_triangle_set> src_parts = its_split(tri_src.its);

std::map<std::string, McFlags>::const_iterator it = booleanOpts.find(boolean_opts);
McFlags boolOpFlags = it->second;
TriangleMesh tri_cut = mcut_to_triangle_mesh(cutMesh);
std::vector<indexed_triangle_set> 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<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);
if (src_parts.empty() && boolean_opts == "UNION") {
srcMesh = cutMesh;
return;
}
if(cut_parts.empty()) return;

// 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];
// 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);
}

// 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];
}
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);
}
}
faceVertexOffsetBase += faceSize;
}

TriangleMesh out(vertices, faces);
outs.emplace_back(out);
}

// free connected component data
err = mcReleaseConnectedComponents(context, (uint32_t) connectedComponents.size(), connectedComponents.data());

// destroy context
err = mcReleaseContext(context);

return outs;
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)
Expand Down
5 changes: 4 additions & 1 deletion src/libslic3r/MeshBoolean.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TriangleMesh> 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<TriangleMesh> &dst_mesh, const std::string &boolean_opts);
Expand Down
13 changes: 7 additions & 6 deletions src/slic3r/GUI/GUI_ObjectList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2892,15 +2892,16 @@ void ObjectList::boolean()

Plater::TakeSnapshot snapshot(wxGetApp().plater(), "boolean");

Model* model = (*m_objects)[0]->get_model();
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 = (*m_objects)[0]->name;
new_object->config.assign_config((*m_objects)[0]->config);
new_object->name = object->name;
new_object->config.assign_config(object->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); });
ModelVolume* new_volume = new_object->add_volume(mesh);

// BBS: ensure on bed but no need to ensure locate in the center around origin
Expand Down
10 changes: 10 additions & 0 deletions src/slic3r/GUI/Plater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8315,6 +8315,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);
Expand Down Expand Up @@ -8400,6 +8405,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);

Expand Down

0 comments on commit ae0012e

Please sign in to comment.