mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-08 07:27:41 -06:00
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:
parent
45167d6b13
commit
b9abdbe4f8
6 changed files with 103 additions and 55 deletions
|
@ -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 "组合所选对象为一个多零件对象"
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue