ENH: add context menu "Mesh boolean"

Apply boolean operation on parts. No need to export stl to apply.

Change-Id: I14479d0977e5ec6f5f80f55b22ed02974b8271c7
(cherry picked from commit 14fe96b2b7529380832007a3d3eb93eb8b9a5217)
This commit is contained in:
Arthur 2023-05-24 18:34:10 +08:00 committed by Lane.Wei
parent 45167d6b13
commit b9abdbe4f8
6 changed files with 103 additions and 55 deletions

View file

@ -924,6 +924,9 @@ msgstr "恢复到米"
msgid "Assemble" msgid "Assemble"
msgstr "组合" msgstr "组合"
msgid "Mesh boolean"
msgstr "网格布尔操作"
msgid "Assemble the selected objects to an object with multiple parts" msgid "Assemble the selected objects to an object with multiple parts"
msgstr "组合所选对象为一个多零件对象" msgstr "组合所选对象为一个多零件对象"

View file

@ -913,9 +913,9 @@ void MenuFactory::append_menu_item_merge_to_single_object(wxMenu* menu)
void MenuFactory::append_menu_item_merge_parts_to_single_part(wxMenu* menu) void MenuFactory::append_menu_item_merge_parts_to_single_part(wxMenu* menu)
{ {
menu->AppendSeparator(); menu->AppendSeparator();
append_menu_item(menu, wxID_ANY, _L("Assemble"), _L("Assemble the selected parts to a single part"), append_menu_item(menu, wxID_ANY, _L("Mesh boolean"), _L("Mesh boolean operations including union and subtraction"),
[](wxCommandEvent&) { obj_list()->merge_volumes(); }, "", menu, [](wxCommandEvent&) { obj_list()->boolean/*merge_volumes*/(); }, "", menu,
[]() { return true; }, m_parent); []() { return obj_list()->can_merge_to_single_object(); }, m_parent);
} }
void MenuFactory::append_menu_items_mirror(wxMenu* menu) void MenuFactory::append_menu_items_mirror(wxMenu* menu)
@ -1029,6 +1029,8 @@ void MenuFactory::create_bbl_object_menu()
append_menu_item_fix_through_netfabb(&m_object_menu); append_menu_item_fix_through_netfabb(&m_object_menu);
// Object Simplify // Object Simplify
append_menu_item_simplify(&m_object_menu); append_menu_item_simplify(&m_object_menu);
// merge to single part
append_menu_item_merge_parts_to_single_part(&m_object_menu);
// Object Center // Object Center
append_menu_item_center(&m_object_menu); append_menu_item_center(&m_object_menu);
// Object Split // Object Split

View file

@ -2895,6 +2895,45 @@ void ObjectList::layers_editing()
Expand(layers_item); Expand(layers_item);
} }
// BBS: merge parts of a single object into one volume, similar to export_stl, but no need to export and then import
void ObjectList::boolean()
{
std::vector<int> obj_idxs, vol_idxs;
get_selection_indexes(obj_idxs, vol_idxs);
if (obj_idxs.empty() && vol_idxs.empty())
return;
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()];
TriangleMesh mesh = Plater::combine_mesh_fff(*object, -1);
ModelVolume* new_volume = new_object->add_volume(mesh);
// BBS: ensure on bed but no need to ensure locate in the center around origin
new_object->ensure_on_bed();
new_object->center_around_origin();
new_object->translate_instances(-new_object->origin_translation);
new_object->origin_translation = Vec3d::Zero();
// BBS: notify it before move
notify_instance_updated(m_objects->size() - 1);
// remove selected objects
remove();
// Add new object(UNION) to the object_list
add_object_to_list(m_objects->size() - 1);
select_item(m_objects_model->GetItemById(m_objects->size() - 1));
update_selections_on_canvas();
}
wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item) wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item)
{ {
const int obj_idx = m_objects_model->GetIdByItem(obj_item); const int obj_idx = m_objects_model->GetIdByItem(obj_item);

View file

@ -300,6 +300,7 @@ public:
void merge_volumes(); // BBS: merge parts to single part void merge_volumes(); // BBS: merge parts to single part
void layers_editing(); void layers_editing();
void boolean(); // BBS: Boolean Operation of parts
wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item); wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item);
wxDataViewItem add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config); wxDataViewItem add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config);

View file

@ -9557,6 +9557,55 @@ void Plater::export_core_3mf()
} }
#define USE_CGAL_BOOLEAN 0 #define USE_CGAL_BOOLEAN 0
// 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)
{
TriangleMesh mesh;
std::vector<csg::CSGPart> csgmesh;
csgmesh.reserve(2 * mo.volumes.size());
csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh),
csg::mpartsPositive | csg::mpartsNegative | csg::mpartsDoSplits);
if (csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }) == csgmesh.end()) {
try {
#if USE_CGAL_BOOLEAN
auto meshPtr = csg::perform_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) });
mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*meshPtr);
#else
MeshBoolean::mcut::McutMeshPtr meshPtr = csg::perform_csgmesh_booleans_mcut(Range{std::begin(csgmesh), std::end(csgmesh)});
mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*meshPtr);
#endif
} catch (...) {}
}
if (mesh.empty()) {
if (notify_func)
notify_func(_u8L("Unable to perform boolean operation on model meshes. "
"Only positive parts will be exported."));
for (const ModelVolume* v : mo.volumes)
if (v->is_model_part()) {
TriangleMesh vol_mesh(v->mesh());
vol_mesh.transform(v->get_matrix(), true);
mesh.merge(vol_mesh);
}
}
if (instance_id == -1) {
TriangleMesh vols_mesh(mesh);
mesh = TriangleMesh();
for (const ModelInstance* i : mo.instances) {
TriangleMesh m = vols_mesh;
m.transform(i->get_matrix(), true);
mesh.merge(m);
}
}
else if (0 <= instance_id && instance_id < int(mo.instances.size()))
mesh.transform(mo.instances[instance_id]->get_matrix(), true);
return mesh;
}
void Plater::export_stl(bool extended, bool selection_only) void Plater::export_stl(bool extended, bool selection_only)
{ {
if (p->model.objects.empty()) { return; } if (p->model.objects.empty()) { return; }
@ -9572,61 +9621,13 @@ void Plater::export_stl(bool extended, bool selection_only)
if (selection_only && (obj_idx == -1 || selection.is_wipe_tower())) if (selection_only && (obj_idx == -1 || selection.is_wipe_tower()))
return; return;
// Following lambda generates a combined mesh for export with normals pointing outwards.
auto mesh_to_export_fff = [this](const ModelObject& mo, int instance_id) {
TriangleMesh mesh;
std::vector<csg::CSGPart> csgmesh;
csgmesh.reserve(2 * mo.volumes.size());
csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh),
csg::mpartsPositive | csg::mpartsNegative | csg::mpartsDoSplits);
if (csg::check_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) }) == csgmesh.end()) {
try {
#if USE_CGAL_BOOLEAN
auto meshPtr = csg::perform_csgmesh_booleans(Range{ std::begin(csgmesh), std::end(csgmesh) });
mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*meshPtr);
#else
MeshBoolean::mcut::McutMeshPtr meshPtr = csg::perform_csgmesh_booleans_mcut(Range{std::begin(csgmesh), std::end(csgmesh)});
mesh = MeshBoolean::mcut::mcut_to_triangle_mesh(*meshPtr);
#endif
} catch (...) {}
}
if (mesh.empty()) {
get_notification_manager()->push_plater_error_notification(
_u8L("Unable to perform boolean operation on model meshes. "
"Only positive parts will be exported."));
for (const ModelVolume* v : mo.volumes)
if (v->is_model_part()) {
TriangleMesh vol_mesh(v->mesh());
vol_mesh.transform(v->get_matrix(), true);
mesh.merge(vol_mesh);
}
}
if (instance_id == -1) {
TriangleMesh vols_mesh(mesh);
mesh = TriangleMesh();
for (const ModelInstance* i : mo.instances) {
TriangleMesh m = vols_mesh;
m.transform(i->get_matrix(), true);
mesh.merge(m);
}
}
else if (0 <= instance_id && instance_id < int(mo.instances.size()))
mesh.transform(mo.instances[instance_id]->get_matrix(), true);
return mesh;
};
auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) { auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) {
TriangleMesh mesh; TriangleMesh mesh;
const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id()); const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id());
if (auto m = object->get_mesh_to_print(); m.empty()) if (auto m = object->get_mesh_to_print(); m.empty())
mesh = mesh_to_export_fff(mo, instance_id); mesh = combine_mesh_fff(mo, instance_id, [this](const std::string& msg) {return get_notification_manager()->push_plater_error_notification(msg); });
else { else {
const Transform3d mesh_trafo_inv = object->trafo().inverse(); const Transform3d mesh_trafo_inv = object->trafo().inverse();
const bool is_left_handed = object->is_left_handed(); const bool is_left_handed = object->is_left_handed();
@ -9690,8 +9691,9 @@ void Plater::export_stl(bool extended, bool selection_only)
std::function<TriangleMesh(const ModelObject& mo, int instance_id)> std::function<TriangleMesh(const ModelObject& mo, int instance_id)>
mesh_to_export; mesh_to_export;
if (p->printer_technology == ptFFF ) if (p->printer_technology == ptFFF)
mesh_to_export = mesh_to_export_fff; mesh_to_export = [this](const ModelObject& mo, int instance_id) {return Plater::combine_mesh_fff(mo, instance_id,
[this](const std::string& msg) {return get_notification_manager()->push_plater_error_notification(msg); }); };
else else
mesh_to_export = mesh_to_export_sla; mesh_to_export = mesh_to_export_sla;

View file

@ -322,7 +322,8 @@ public:
void export_gcode(bool prefer_removable); void export_gcode(bool prefer_removable);
void export_gcode_3mf(bool export_all = false); void export_gcode_3mf(bool export_all = false);
void send_gcode_finish(wxString name); void send_gcode_finish(wxString name);
void export_core_3mf(); void export_core_3mf();
static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function<void(const std::string&)> notify_func = {});
void export_stl(bool extended = false, bool selection_only = false); void export_stl(bool extended = false, bool selection_only = false);
//BBS: remove amf //BBS: remove amf
//void export_amf(); //void export_amf();