FIX: gizmo boolean crashes due to self-intersection

Check if boolean operation is possible first, just like in context menu.

jira: STUDIO-6471
Change-Id: I9c201010dad90bbfa615178aa835c7e371755cd2
(cherry picked from commit db8a02e8328b5529123fb83c0373eb01cb925f0a)
This commit is contained in:
Arthur 2024-05-29 12:19:39 +08:00 committed by Lane.Wei
parent 3849bfd4d6
commit ca3a372630
5 changed files with 84 additions and 57 deletions

View File

@ -19,7 +19,7 @@ enum ModelParts {
};
template<class OutIt>
bool model_to_csgmesh(const ModelObject &mo,
bool model_to_csgmesh(const std::vector<const ModelVolume*> & volumes,
const Transform3d &trafo, // Applies to all exported parts
OutIt out, // Output iterator
// values of ModelParts OR-ed
@ -32,7 +32,7 @@ bool model_to_csgmesh(const ModelObject &mo,
bool do_splits = parts_to_include & mpartsDoSplits;
bool has_splitable_volume = false;
for (const ModelVolume *vol : mo.volumes) {
for (const ModelVolume *vol : volumes) {
if (vol && vol->mesh_ptr() &&
((do_positives && vol->is_model_part()) ||
(do_negatives && vol->is_negative_volume()))) {

View File

@ -389,6 +389,7 @@ public:
CutConnectors cut_connectors;
CutObjectBase cut_id;
std::vector<const ModelVolume*> const_volumes() const {return std::vector<const ModelVolume*>(volumes.begin(), volumes.end());}
Model* get_model() { return m_model; }
const Model* get_model() const { return m_model; }
// BBS: production extension

View File

@ -1,12 +1,16 @@
#include "GLGizmoMeshBoolean.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "libslic3r/CSGMesh/CSGMesh.hpp"
#include "libslic3r/MeshBoolean.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/CSGMesh/ModelToCSGMesh.hpp"
#include "libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "slic3r/GUI/Plater.hpp"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
@ -364,18 +368,21 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l
if (m_operation_mode == MeshBooleanOperation::Union)
{
if (operate_button(_L("Union") + "##btn", enable_button)) {
TriangleMesh temp_src_mesh = m_src.mv->mesh();
temp_src_mesh.transform(m_src.trafo);
TriangleMesh temp_tool_mesh = m_tool.mv->mesh();
temp_tool_mesh.transform(m_tool.trafo);
std::vector<TriangleMesh> temp_mesh_resuls;
Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "UNION");
if (temp_mesh_resuls.size() != 0) {
generate_new_volume(true, *temp_mesh_resuls.begin());
m_warning_texts[index] = "";
}
else {
m_warning_texts[index] = warning_text_common;
m_warning_texts[index] = check_boolean_possible({ m_src.mv, m_tool.mv });
if(m_warning_texts[index] == "") {
TriangleMesh temp_src_mesh = m_src.mv->mesh();
temp_src_mesh.transform(m_src.trafo);
TriangleMesh temp_tool_mesh = m_tool.mv->mesh();
temp_tool_mesh.transform(m_tool.trafo);
std::vector<TriangleMesh> temp_mesh_resuls;
Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "UNION");
if (temp_mesh_resuls.size() != 0) {
generate_new_volume(true, *temp_mesh_resuls.begin());
m_warning_texts[index] = "";
}
else {
m_warning_texts[index] = warning_text_common;
}
}
m_selecting_state = MeshBooleanSelectingState::SelectSource;
m_src.reset();
@ -385,18 +392,21 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l
else if (m_operation_mode == MeshBooleanOperation::Difference) {
m_imgui->bbl_checkbox(_L("Delete input"), m_diff_delete_input);
if (operate_button(_L("Difference") + "##btn", enable_button)) {
TriangleMesh temp_src_mesh = m_src.mv->mesh();
temp_src_mesh.transform(m_src.trafo);
TriangleMesh temp_tool_mesh = m_tool.mv->mesh();
temp_tool_mesh.transform(m_tool.trafo);
std::vector<TriangleMesh> temp_mesh_resuls;
Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "A_NOT_B");
if (temp_mesh_resuls.size() != 0) {
generate_new_volume(m_diff_delete_input, *temp_mesh_resuls.begin());
m_warning_texts[index] = "";
}
else {
m_warning_texts[index] = warning_text_common;
m_warning_texts[index] = check_boolean_possible({ m_src.mv, m_tool.mv });
if (m_warning_texts[index] == "") {
TriangleMesh temp_src_mesh = m_src.mv->mesh();
temp_src_mesh.transform(m_src.trafo);
TriangleMesh temp_tool_mesh = m_tool.mv->mesh();
temp_tool_mesh.transform(m_tool.trafo);
std::vector<TriangleMesh> temp_mesh_resuls;
Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "A_NOT_B");
if (temp_mesh_resuls.size() != 0) {
generate_new_volume(m_diff_delete_input, *temp_mesh_resuls.begin());
m_warning_texts[index] = "";
}
else {
m_warning_texts[index] = warning_text_common;
}
}
m_selecting_state = MeshBooleanSelectingState::SelectSource;
m_src.reset();
@ -406,18 +416,21 @@ void GLGizmoMeshBoolean::on_render_input_window(float x, float y, float bottom_l
else if (m_operation_mode == MeshBooleanOperation::Intersection){
m_imgui->bbl_checkbox(_L("Delete input"), m_inter_delete_input);
if (operate_button(_L("Intersection") + "##btn", enable_button)) {
TriangleMesh temp_src_mesh = m_src.mv->mesh();
temp_src_mesh.transform(m_src.trafo);
TriangleMesh temp_tool_mesh = m_tool.mv->mesh();
temp_tool_mesh.transform(m_tool.trafo);
std::vector<TriangleMesh> temp_mesh_resuls;
Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "INTERSECTION");
if (temp_mesh_resuls.size() != 0) {
generate_new_volume(m_inter_delete_input, *temp_mesh_resuls.begin());
m_warning_texts[index] = "";
}
else {
m_warning_texts[index] = warning_text_intersection;
m_warning_texts[index] = check_boolean_possible({ m_src.mv, m_tool.mv });
if (m_warning_texts[index] == "") {
TriangleMesh temp_src_mesh = m_src.mv->mesh();
temp_src_mesh.transform(m_src.trafo);
TriangleMesh temp_tool_mesh = m_tool.mv->mesh();
temp_tool_mesh.transform(m_tool.trafo);
std::vector<TriangleMesh> temp_mesh_resuls;
Slic3r::MeshBoolean::mcut::make_boolean(temp_src_mesh, temp_tool_mesh, temp_mesh_resuls, "INTERSECTION");
if (temp_mesh_resuls.size() != 0) {
generate_new_volume(m_inter_delete_input, *temp_mesh_resuls.begin());
m_warning_texts[index] = "";
}
else {
m_warning_texts[index] = warning_text_intersection;
}
}
m_selecting_state = MeshBooleanSelectingState::SelectSource;
m_src.reset();

View File

@ -11561,6 +11561,29 @@ void Plater::export_core_3mf()
export_3mf(path_u8, SaveStrategy::Silence);
}
// OK if fail_msg is empty
std::string check_boolean_possible(const std::vector<const ModelVolume*>& volumes) {
std::string fail_msg;
std::vector<csg::CSGPart> csgmesh;
csgmesh.reserve(2 * volumes.size());
bool has_splitable_volume = csg::model_to_csgmesh(volumes, Transform3d::Identity(), std::back_inserter(csgmesh),
csg::mpartsPositive | csg::mpartsNegative);
if (auto fail_reason_name = csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }); std::get<0>(fail_reason_name) != csg::BooleanFailReason::OK) {
fail_msg = _u8L("Unable to perform boolean operation on model meshes. "
"You may fix the meshes and try agian.");
std::string name = std::get<1>(fail_reason_name);
std::map<csg::BooleanFailReason, std::string> fail_reasons = {
{csg::BooleanFailReason::OK, "OK"},
{csg::BooleanFailReason::MeshEmpty, Slic3r::format(_u8L("Reason: part \"%1%\" is empty."), name)},
{csg::BooleanFailReason::NotBoundAVolume, Slic3r::format(_u8L("Reason: part \"%1%\" does not bound a volume."), name)},
{csg::BooleanFailReason::SelfIntersect, Slic3r::format(_u8L("Reason: part \"%1%\" has self intersection."), name)},
{csg::BooleanFailReason::NoIntersection, Slic3r::format(_u8L("Reason: \"%1%\" and another part have no intersection."), name)} };
fail_msg += " " + fail_reasons[std::get<0>(fail_reason_name)];
}
return fail_msg;
}
// Following lambda generates a combined mesh for export with normals pointing outwards.
TriangleMesh Plater::combine_mesh_fff(const ModelObject& mo, int instance_id, std::function<void(const std::string&)> notify_func)
{
@ -11568,22 +11591,11 @@ TriangleMesh Plater::combine_mesh_fff(const ModelObject& mo, int instance_id, st
std::vector<csg::CSGPart> csgmesh;
csgmesh.reserve(2 * mo.volumes.size());
bool has_splitable_volume = csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh),
bool has_splitable_volume = csg::model_to_csgmesh(mo.const_volumes(), Transform3d::Identity(), std::back_inserter(csgmesh),
csg::mpartsPositive | csg::mpartsNegative);
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_name = csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }); std::get<0>(fail_reason_name) != csg::BooleanFailReason::OK) {
std::string name = std::get<1>(fail_reason_name);
std::map<csg::BooleanFailReason, std::string> fail_reasons = {
{csg::BooleanFailReason::OK, "OK"},
{csg::BooleanFailReason::MeshEmpty, Slic3r::format( _u8L("Reason: part \"%1%\" is empty."), name)},
{csg::BooleanFailReason::NotBoundAVolume, Slic3r::format(_u8L("Reason: part \"%1%\" does not bound a volume."), name)},
{csg::BooleanFailReason::SelfIntersect, Slic3r::format(_u8L("Reason: part \"%1%\" has self intersection."), name)},
{csg::BooleanFailReason::NoIntersection, Slic3r::format(_u8L("Reason: \"%1%\" and another part have no intersection."), name)} };
fail_msg += " " + fail_reasons[std::get<0>(fail_reason_name)];
}
else {
std::string fail_msg = check_boolean_possible(mo.const_volumes());
if (fail_msg.empty()) {
try {
MeshBoolean::mcut::McutMeshPtr meshPtr = csg::perform_csgmesh_booleans_mcut(Range{ std::begin(csgmesh), std::end(csgmesh) });
mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*meshPtr);

View File

@ -792,6 +792,7 @@ private:
};
std::vector<int> get_min_flush_volumes(const DynamicPrintConfig& full_config);
std::string check_boolean_possible(const std::vector<const ModelVolume*>& volumes);
} // namespace GUI
} // namespace Slic3r