diff --git a/bbl/i18n/zh_cn/BambuStudio_zh_CN.po b/bbl/i18n/zh_cn/BambuStudio_zh_CN.po index eb7b671c8d..ba14b8d1a0 100644 --- a/bbl/i18n/zh_cn/BambuStudio_zh_CN.po +++ b/bbl/i18n/zh_cn/BambuStudio_zh_CN.po @@ -924,6 +924,9 @@ msgstr "恢复到米" msgid "Assemble" msgstr "组合" +msgid "Mesh boolean" +msgstr "网格布尔操作" + msgid "Assemble the selected objects to an object with multiple parts" msgstr "组合所选对象为一个多零件对象" diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index f61f639276..41d6923e4d 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -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 diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index eaacb556df..5451e28aaf 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -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 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); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index bafa0b5ba3..3316df3253 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -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); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0ca2bd09ec..791c4aca61 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -9557,6 +9557,55 @@ void Plater::export_core_3mf() } #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 notify_func) +{ + TriangleMesh mesh; + + std::vector 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) { 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())) 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 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) { TriangleMesh mesh; 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 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; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 71b76024f6..f6b629e287 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -322,7 +322,8 @@ public: void export_gcode(bool prefer_removable); void export_gcode_3mf(bool export_all = false); 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 notify_func = {}); void export_stl(bool extended = false, bool selection_only = false); //BBS: remove amf //void export_amf();