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"
msgstr "组合"
msgid "Mesh boolean"
msgstr "网格布尔操作"
msgid "Assemble the selected objects to an object with multiple parts"
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)
{
menu->AppendSeparator();
append_menu_item(menu, wxID_ANY, _L("Assemble"), _L("Assemble the selected parts to a single part"),
[](wxCommandEvent&) { obj_list()->merge_volumes(); }, "", menu,
[]() { return true; }, m_parent);
append_menu_item(menu, wxID_ANY, _L("Mesh boolean"), _L("Mesh boolean operations including union and subtraction"),
[](wxCommandEvent&) { obj_list()->boolean/*merge_volumes*/(); }, "", menu,
[]() { return obj_list()->can_merge_to_single_object(); }, m_parent);
}
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);
// Object Simplify
append_menu_item_simplify(&m_object_menu);
// merge to single part
append_menu_item_merge_parts_to_single_part(&m_object_menu);
// Object Center
append_menu_item_center(&m_object_menu);
// Object Split

View file

@ -2895,6 +2895,45 @@ void ObjectList::layers_editing()
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)
{
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 layers_editing();
void boolean(); // BBS: Boolean Operation of parts
wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item);
wxDataViewItem add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config);

View file

@ -9557,23 +9557,9 @@ void Plater::export_core_3mf()
}
#define USE_CGAL_BOOLEAN 0
void Plater::export_stl(bool extended, bool selection_only)
// 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)
{
if (p->model.objects.empty()) { return; }
wxString path = p->get_export_file(FT_STL);
if (path.empty()) { return; }
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
const auto &selection = p->get_selection();
const auto obj_idx = selection.get_object_idx();
if (selection_only && (obj_idx == -1 || selection.is_wipe_tower()))
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;
@ -9594,8 +9580,8 @@ void Plater::export_stl(bool extended, bool selection_only)
}
if (mesh.empty()) {
get_notification_manager()->push_plater_error_notification(
_u8L("Unable to perform boolean operation on model meshes. "
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)
@ -9618,7 +9604,22 @@ void Plater::export_stl(bool extended, bool selection_only)
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)
{
if (p->model.objects.empty()) { return; }
wxString path = p->get_export_file(FT_STL);
if (path.empty()) { return; }
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
const auto &selection = p->get_selection();
const auto obj_idx = selection.get_object_idx();
if (selection_only && (obj_idx == -1 || selection.is_wipe_tower()))
return;
auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) {
TriangleMesh mesh;
@ -9626,7 +9627,7 @@ void Plater::export_stl(bool extended, bool selection_only)
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())
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 {
const Transform3d mesh_trafo_inv = object->trafo().inverse();
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)>
mesh_to_export;
if (p->printer_technology == ptFFF )
mesh_to_export = mesh_to_export_fff;
if (p->printer_technology == ptFFF)
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
mesh_to_export = mesh_to_export_sla;

View file

@ -323,6 +323,7 @@ public:
void export_gcode_3mf(bool export_all = false);
void send_gcode_finish(wxString name);
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);
//BBS: remove amf
//void export_amf();