ENH: give exaxt reason of boolean fail

jira: none
Change-Id: Ied645373a4516be7466078ace73c69ef584a743a
(cherry picked from commit f21819b4f6718357984650e7eb89a2e496dfa03c)
This commit is contained in:
Arthur 2024-01-17 15:26:12 +08:00 committed by Lane.Wei
parent cb1a384593
commit 72ef0a4d2a
2 changed files with 40 additions and 35 deletions

View File

@ -11,6 +11,7 @@
#include "libslic3r/MeshBoolean.hpp" #include "libslic3r/MeshBoolean.hpp"
namespace Slic3r { namespace csg { namespace Slic3r { namespace csg {
enum class BooleanFailReason { OK, MeshEmpty, NotBoundAVolume, SelfIntersect, NoIntersection};
// This method can be overriden when a specific CSGPart type supports caching // This method can be overriden when a specific CSGPart type supports caching
// of the voxel grid // of the voxel grid
@ -256,12 +257,12 @@ void perform_csgmesh_booleans_mcut(MeshBoolean::mcut::McutMeshPtr& mcutm,
template<class It, class Visitor> template<class It, class Visitor>
It check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn) BooleanFailReason check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
{ {
using namespace detail_cgal; using namespace detail_cgal;
BooleanFailReason fail_reason = BooleanFailReason::OK;
std::vector<CGALMeshPtr> cgalmeshes(csgrange.size()); std::vector<CGALMeshPtr> cgalmeshes(csgrange.size());
auto check_part = [&csgrange, &cgalmeshes](size_t i) auto check_part = [&csgrange, &cgalmeshes,&fail_reason](size_t i)
{ {
auto it = csgrange.begin(); auto it = csgrange.begin();
std::advance(it, i); std::advance(it, i);
@ -277,16 +278,19 @@ It check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
try { try {
if (!m || MeshBoolean::cgal::empty(*m)) { if (!m || MeshBoolean::cgal::empty(*m)) {
BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh " << i << "/" << csgrange.size() << " is empty, cannot do boolean!"; BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh " << i << "/" << csgrange.size() << " is empty, cannot do boolean!";
fail_reason= BooleanFailReason::MeshEmpty;
return; return;
} }
if (!MeshBoolean::cgal::does_bound_a_volume(*m)) { if (!MeshBoolean::cgal::does_bound_a_volume(*m)) {
BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh "<<i<<"/"<<csgrange.size()<<" does_bound_a_volume is false, cannot do boolean!"; BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh "<<i<<"/"<<csgrange.size()<<" does_bound_a_volume is false, cannot do boolean!";
fail_reason= BooleanFailReason::NotBoundAVolume;
return; return;
} }
if (MeshBoolean::cgal::does_self_intersect(*m)) { if (MeshBoolean::cgal::does_self_intersect(*m)) {
BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh " << i << "/" << csgrange.size() << " does_self_intersect is true, cannot do boolean!"; BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh " << i << "/" << csgrange.size() << " does_self_intersect is true, cannot do boolean!";
fail_reason= BooleanFailReason::SelfIntersect;
return; return;
} }
} }
@ -296,31 +300,32 @@ It check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
}; };
execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part); execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part);
It ret = csgrange.end(); //It ret = csgrange.end();
for (size_t i = 0; i < csgrange.size(); ++i) { //for (size_t i = 0; i < csgrange.size(); ++i) {
if (!cgalmeshes[i]) { // if (!cgalmeshes[i]) {
auto it = csgrange.begin(); // auto it = csgrange.begin();
std::advance(it, i); // std::advance(it, i);
vfn(it); // vfn(it);
if (ret == csgrange.end()) // if (ret == csgrange.end())
ret = it; // ret = it;
} // }
} //}
return ret; return fail_reason;
} }
template<class It> template<class It>
It check_csgmesh_booleans(const Range<It> &csgrange, bool use_mcut=false) BooleanFailReason check_csgmesh_booleans(const Range<It> &csgrange, bool use_mcut=false)
{ {
if(!use_mcut) if(!use_mcut)
return check_csgmesh_booleans(csgrange, [](auto &) {}); return check_csgmesh_booleans(csgrange, [](auto &) {});
else { else {
using namespace detail_mcut; using namespace detail_mcut;
BooleanFailReason fail_reason = BooleanFailReason::OK;
std::vector<McutMeshPtr> McutMeshes(csgrange.size()); std::vector<McutMeshPtr> McutMeshes(csgrange.size());
auto check_part = [&csgrange, &McutMeshes](size_t i) { auto check_part = [&csgrange, &McutMeshes,&fail_reason](size_t i) {
auto it = csgrange.begin(); auto it = csgrange.begin();
std::advance(it, i); std::advance(it, i);
auto& csgpart = *it; auto& csgpart = *it;
@ -333,27 +338,17 @@ It check_csgmesh_booleans(const Range<It> &csgrange, bool use_mcut=false)
} }
try { try {
if (!m || MeshBoolean::mcut::empty(*m)) if (!m || MeshBoolean::mcut::empty(*m)) {
fail_reason=BooleanFailReason::MeshEmpty;
return; return;
} }
}
catch (...) { return; } catch (...) { return; }
McutMeshes[i] = std::move(m); McutMeshes[i] = std::move(m);
}; };
execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part); execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part);
return fail_reason;
It ret = csgrange.end();
for (size_t i = 0; i < csgrange.size(); ++i) {
if (!McutMeshes[i]) {
auto it = csgrange.begin();
std::advance(it, i);
if (ret == csgrange.end())
ret = it;
}
}
return ret;
} }
} }

View File

@ -10881,7 +10881,18 @@ TriangleMesh Plater::combine_mesh_fff(const ModelObject& mo, int instance_id, st
bool has_splitable_volume = csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh), bool has_splitable_volume = csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh),
csg::mpartsPositive | csg::mpartsNegative); csg::mpartsPositive | csg::mpartsNegative);
if (csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }) == csgmesh.end()) { std::string fail_msg = _u8L("Unable to perform boolean operation on model meshes. "
"Only positive parts will be kept. You may fix the meshes and try agian.");
if (auto fail_reason = csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }); fail_reason != csg::BooleanFailReason::OK) {
std::map<csg::BooleanFailReason, std::string> fail_reasons = {
{csg::BooleanFailReason::OK, "OK"},
{csg::BooleanFailReason::MeshEmpty, _u8L("Reason: mesh is empty.")},
{csg::BooleanFailReason::NotBoundAVolume, _u8L("Reason: mesh does not bound a volume.")},
{csg::BooleanFailReason::SelfIntersect, _u8L("Reason: mesh has self intersection.")},
{csg::BooleanFailReason::NoIntersection, _u8L("Reason: meshes have no intersection.")} };
fail_msg += " " + fail_reasons[fail_reason];
}
else {
try { try {
MeshBoolean::mcut::McutMeshPtr meshPtr = csg::perform_csgmesh_booleans_mcut(Range{ std::begin(csgmesh), std::end(csgmesh) }); MeshBoolean::mcut::McutMeshPtr meshPtr = csg::perform_csgmesh_booleans_mcut(Range{ std::begin(csgmesh), std::end(csgmesh) });
mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*meshPtr); mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*meshPtr);
@ -10901,8 +10912,7 @@ TriangleMesh Plater::combine_mesh_fff(const ModelObject& mo, int instance_id, st
if (mesh.empty()) { if (mesh.empty()) {
if (notify_func) if (notify_func)
notify_func(_u8L("Unable to perform boolean operation on model meshes. " notify_func(fail_msg);
"Only positive parts will be exported."));
for (const ModelVolume* v : mo.volumes) for (const ModelVolume* v : mo.volumes)
if (v->is_model_part()) { if (v->is_model_part()) {