mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-10 00:07:52 -06:00
FIX: mesh boolean may take forever with CGAL
Only use MCut to do boolean, with a little modification to allow input mesh to have multiple components. Jira: STUDIO-3167 Change-Id: I4f53010f76fbcacfe966c2ef48e465f97d6516fe (cherry picked from commit 92d0eb11744820466ee2f67c02d4764e496b7e60)
This commit is contained in:
parent
fe7a96ef15
commit
fcda0e5f2c
3 changed files with 117 additions and 11 deletions
|
@ -2,6 +2,7 @@
|
||||||
#include "MeshBoolean.hpp"
|
#include "MeshBoolean.hpp"
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
#include "libslic3r/TryCatchSignal.hpp"
|
#include "libslic3r/TryCatchSignal.hpp"
|
||||||
|
#include "libslic3r/format.hpp"
|
||||||
#undef PI
|
#undef PI
|
||||||
|
|
||||||
// Include igl first. It defines "L" macro which then clashes with our localization
|
// Include igl first. It defines "L" macro which then clashes with our localization
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
#include <CGAL/boost/graph/Face_filtered_graph.h>
|
#include <CGAL/boost/graph/Face_filtered_graph.h>
|
||||||
// BBS: for boolean using mcut
|
// BBS: for boolean using mcut
|
||||||
#include "mcut/include/mcut/mcut.h"
|
#include "mcut/include/mcut/mcut.h"
|
||||||
|
#include "boost/log/trivial.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace MeshBoolean {
|
namespace MeshBoolean {
|
||||||
|
@ -554,12 +556,76 @@ TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh)
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void merge_mcut_meshes(McutMesh& src, const McutMesh& cut) {
|
||||||
|
indexed_triangle_set all_its;
|
||||||
|
TriangleMesh tri_src = mcut_to_triangle_mesh(src);
|
||||||
|
TriangleMesh tri_cut = mcut_to_triangle_mesh(cut);
|
||||||
|
its_merge(all_its, tri_src.its);
|
||||||
|
its_merge(all_its, tri_cut.its);
|
||||||
|
src = *triangle_mesh_to_mcut(all_its);
|
||||||
|
}
|
||||||
|
|
||||||
|
MCAPI_ATTR void MCAPI_CALL mcDebugOutput(McDebugSource source,
|
||||||
|
McDebugType type,
|
||||||
|
unsigned int id,
|
||||||
|
McDebugSeverity severity,
|
||||||
|
size_t length,
|
||||||
|
const char* message,
|
||||||
|
const void* userParam)
|
||||||
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<Slic3r::format("mcut mcDebugOutput message ( %d ): %s ", id, message);
|
||||||
|
|
||||||
|
switch (source) {
|
||||||
|
case MC_DEBUG_SOURCE_API:
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<("Source: API");
|
||||||
|
break;
|
||||||
|
case MC_DEBUG_SOURCE_KERNEL:
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<("Source: Kernel");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case MC_DEBUG_TYPE_ERROR:
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<("Type: Error");
|
||||||
|
break;
|
||||||
|
case MC_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<("Type: Deprecated Behaviour");
|
||||||
|
break;
|
||||||
|
case MC_DEBUG_TYPE_OTHER:
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<("Type: Other");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (severity) {
|
||||||
|
case MC_DEBUG_SEVERITY_HIGH:
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<("Severity: high");
|
||||||
|
break;
|
||||||
|
case MC_DEBUG_SEVERITY_MEDIUM:
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<("Severity: medium");
|
||||||
|
break;
|
||||||
|
case MC_DEBUG_SEVERITY_LOW:
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<("Severity: low");
|
||||||
|
break;
|
||||||
|
case MC_DEBUG_SEVERITY_NOTIFICATION:
|
||||||
|
BOOST_LOG_TRIVIAL(debug)<<("Severity: notification");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
// create context
|
// create context
|
||||||
McContext context = MC_NULL_HANDLE;
|
McContext context = MC_NULL_HANDLE;
|
||||||
McResult err = mcCreateContext(&context, static_cast<McFlags>(MC_DEBUG));
|
McResult err = mcCreateContext(&context, static_cast<McFlags>(MC_DEBUG));
|
||||||
|
// 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_ALL,
|
||||||
|
MC_DEBUG_SEVERITY_ALL,
|
||||||
|
true);
|
||||||
// We can either let MCUT compute all possible meshes (including patches etc.), or we can
|
// 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
|
// constrain the library to compute exactly the boolean op mesh we want. This 'constrained' case
|
||||||
// is done with the following flags.
|
// is done with the following flags.
|
||||||
|
@ -577,6 +643,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);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,10 +657,43 @@ void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &b
|
||||||
// cut mesh
|
// cut mesh
|
||||||
reinterpret_cast<const void *>(cutMesh.vertexCoordsArray.data()), cutMesh.faceIndicesArray.data(), cutMesh.faceSizesArray.data(),
|
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()));
|
static_cast<uint32_t>(cutMesh.vertexCoordsArray.size() / 3), static_cast<uint32_t>(cutMesh.faceSizesArray.size()));
|
||||||
|
if (err != MC_NO_ERROR) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "MCUT mcDispatch fails! err=" << err;
|
||||||
|
mcReleaseContext(context);
|
||||||
|
if (boolean_opts == "UNION") {
|
||||||
|
merge_mcut_meshes(srcMesh, cutMesh);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
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
|
||||||
uint32_t numConnComps;
|
uint32_t numConnComps;
|
||||||
err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, 0, NULL, &numConnComps);
|
err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, 0, NULL, &numConnComps);
|
||||||
|
if (err != MC_NO_ERROR || numConnComps==0) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "MCUT mcGetConnectedComponents fails! err=" << err << ", numConnComps" << numConnComps;
|
||||||
|
mcReleaseContext(context);
|
||||||
|
if (numConnComps == 0 && boolean_opts == "UNION") {
|
||||||
|
merge_mcut_meshes(srcMesh, cutMesh);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<McConnectedComponent> connectedComponents(numConnComps, MC_NULL_HANDLE);
|
std::vector<McConnectedComponent> connectedComponents(numConnComps, MC_NULL_HANDLE);
|
||||||
err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, (uint32_t) connectedComponents.size(), connectedComponents.data(), NULL);
|
err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, (uint32_t) connectedComponents.size(), connectedComponents.data(), NULL);
|
||||||
|
@ -658,7 +758,7 @@ void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &b
|
||||||
}
|
}
|
||||||
|
|
||||||
// free connected component data
|
// free connected component data
|
||||||
err = mcReleaseConnectedComponents(context, (uint32_t) connectedComponents.size(), connectedComponents.data());
|
err = mcReleaseConnectedComponents(context, 0, NULL);
|
||||||
|
|
||||||
// destroy context
|
// destroy context
|
||||||
err = mcReleaseContext(context);
|
err = mcReleaseContext(context);
|
||||||
|
@ -674,7 +774,14 @@ std::vector<TriangleMesh> make_boolean(const McutMesh &srcMesh, const McutMesh &
|
||||||
// create context
|
// create context
|
||||||
McContext context = MC_NULL_HANDLE;
|
McContext context = MC_NULL_HANDLE;
|
||||||
McResult err = mcCreateContext(&context, static_cast<McFlags>(MC_DEBUG));
|
McResult err = mcCreateContext(&context, static_cast<McFlags>(MC_DEBUG));
|
||||||
|
// 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_ALL,
|
||||||
|
MC_DEBUG_SEVERITY_ALL,
|
||||||
|
true);
|
||||||
// We can either let MCUT compute all possible meshes (including patches etc.), or we can
|
// 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
|
// constrain the library to compute exactly the boolean op mesh we want. This 'constrained' case
|
||||||
// is done with the following flags.
|
// is done with the following flags.
|
||||||
|
@ -779,7 +886,9 @@ void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, st
|
||||||
McutMesh srcMesh, cutMesh;
|
McutMesh srcMesh, cutMesh;
|
||||||
triangle_mesh_to_mcut(src_mesh, srcMesh);
|
triangle_mesh_to_mcut(src_mesh, srcMesh);
|
||||||
triangle_mesh_to_mcut(cut_mesh, cutMesh);
|
triangle_mesh_to_mcut(cut_mesh, cutMesh);
|
||||||
dst_mesh = make_boolean(srcMesh, cutMesh, boolean_opts);
|
//dst_mesh = make_boolean(srcMesh, cutMesh, boolean_opts);
|
||||||
|
do_boolean(srcMesh, cutMesh, boolean_opts);
|
||||||
|
dst_mesh.push_back(mcut_to_triangle_mesh(srcMesh));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mcut
|
} // namespace mcut
|
||||||
|
|
|
@ -808,7 +808,6 @@ public:
|
||||||
}
|
}
|
||||||
//BBS
|
//BBS
|
||||||
static StringObjectException sequential_print_clearance_valid(const Print &print, Polygons *polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr);
|
static StringObjectException sequential_print_clearance_valid(const Print &print, Polygons *polygons = nullptr, std::vector<std::pair<Polygon, float>>* height_polygons = nullptr);
|
||||||
ConflictResultOpt get_conflict_result() const { return m_conflict_result; }
|
|
||||||
|
|
||||||
// Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim.
|
// Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim.
|
||||||
std::vector<Point> first_layer_wipe_tower_corners(bool check_wipe_tower_existance=true) const;
|
std::vector<Point> first_layer_wipe_tower_corners(bool check_wipe_tower_existance=true) const;
|
||||||
|
|
|
@ -9988,14 +9988,11 @@ TriangleMesh Plater::combine_mesh_fff(const ModelObject& mo, int instance_id, st
|
||||||
|
|
||||||
if (csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }) == csgmesh.end()) {
|
if (csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }) == csgmesh.end()) {
|
||||||
try {
|
try {
|
||||||
// mcut can't handle splitable positive volumes
|
MeshBoolean::mcut::McutMeshPtr meshPtr = csg::perform_csgmesh_booleans_mcut(Range{ std::begin(csgmesh), std::end(csgmesh) });
|
||||||
if (!has_splitable_volume) {
|
mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*meshPtr);
|
||||||
MeshBoolean::mcut::McutMeshPtr meshPtr = csg::perform_csgmesh_booleans_mcut(Range{ std::begin(csgmesh), std::end(csgmesh) });
|
|
||||||
mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*meshPtr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (...) {}
|
catch (...) {}
|
||||||
|
#if 0
|
||||||
// if mcut fails, try again with CGAL
|
// if mcut fails, try again with CGAL
|
||||||
if (mesh.empty()) {
|
if (mesh.empty()) {
|
||||||
try {
|
try {
|
||||||
|
@ -10004,6 +10001,7 @@ TriangleMesh Plater::combine_mesh_fff(const ModelObject& mo, int instance_id, st
|
||||||
}
|
}
|
||||||
catch (...) {}
|
catch (...) {}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mesh.empty()) {
|
if (mesh.empty()) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue