From a274b0e0accc715b63aa013f4c1b9f089cf21d95 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Fri, 24 Apr 2026 05:28:28 -0300 Subject: [PATCH] CGAL Fix Model: New for Linux and Mac (#12155) * CGAL Fix Model Multi OS fix model with CGAL Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Clean unused variables * Early exit * Validation * Orca comments * Steps to fix * Simplify fixer * BY10 to BYCGAL Full refactor * repair_polygon_soup * Revert "repair_polygon_soup" This reverts commit cb88841e7a72a42c148e144fbf0cab146a54c3c8. * CGAL 6.1.1 Update CGAL.cmake * Update MeshBoolean.cpp * Revert "CGAL 6.1.1" This reverts commit c581887adb5f84ec4af97b320067b152f8812f49. * Funca with RF New remake, it seems to be working fine... por ahora Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Update src/libslic3r/MeshBoolean.cpp Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Include cleanup Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Update Part list Revert "Update Part list" This reverts commit 95cab337d7ea602682ee00be2986ef941d0b06c2. Reapply "Update Part list" This reverts commit e401bec579f64b97e3f6deeb4131a8e5a79a146d. * Comments * Update FixModelByCgal.cpp * Remove no 3d parts Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Remove netfabb and w10 sdk Co-Authored-By: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * Update src/slic3r/Utils/FixModelByCgal.cpp Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> * redundant check * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Revet: suggestion from @RF47 Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> --------- Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/build_orca.yml | 7 +- CMakeLists.txt | 75 ++-- src/libslic3r/MeshBoolean.cpp | 88 +++- src/libslic3r/MeshBoolean.hpp | 3 + src/slic3r/CMakeLists.txt | 4 +- src/slic3r/GUI/GUI_Factories.cpp | 32 +- src/slic3r/GUI/GUI_Factories.hpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 31 +- src/slic3r/GUI/GUI_ObjectList.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 8 +- src/slic3r/GUI/NotificationManager.hpp | 4 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 1 - src/slic3r/GUI/Plater.cpp | 27 +- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/PresetComboBoxes.cpp | 1 - src/slic3r/Utils/FixModelByCgal.cpp | 214 +++++++++ src/slic3r/Utils/FixModelByCgal.hpp | 18 + src/slic3r/Utils/FixModelByWin10.cpp | 447 ------------------- src/slic3r/Utils/FixModelByWin10.hpp | 31 -- 20 files changed, 421 insertions(+), 584 deletions(-) create mode 100644 src/slic3r/Utils/FixModelByCgal.cpp create mode 100644 src/slic3r/Utils/FixModelByCgal.hpp delete mode 100644 src/slic3r/Utils/FixModelByWin10.cpp delete mode 100644 src/slic3r/Utils/FixModelByWin10.hpp diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 9f6a61025a..63217c038b 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -286,9 +286,10 @@ jobs: - name: Build slicer Win if: runner.os == 'Windows' working-directory: ${{ github.workspace }} - env: - WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\' - WindowsSDKVersion: '10.0.26100.0\' + # Orca: Removed Netfabb STL fixing service support in favor of CGAL. + # env: + # WindowsSdkDir: 'C:\Program Files (x86)\Windows Kits\10\' + # WindowsSDKVersion: '10.0.26100.0\' run: .\build_release_vs.bat slicer - name: Create installer Win diff --git a/CMakeLists.txt b/CMakeLists.txt index bc374f4251..64d042db4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,43 +323,44 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. # We pick it from environment if it is not defined in another way -if(WIN32) - if(NOT DEFINED WIN10SDK_PATH) - if(DEFINED ENV{WIN10SDK_PATH}) - set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") - endif() - endif() - if(DEFINED WIN10SDK_PATH) - #BBS: modify win10sdk_path - if (EXISTS "${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h") - set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}") - else() - message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") - message("${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h was not found") - message("STL fixing by the Netfabb service will not be compiled") - unset(WIN10SDK_PATH) - endif() - else() - # Try to use the default Windows 10 SDK path. - if (DEFINED ENV{WindowsSdkDir} AND DEFINED ENV{WindowsSDKVersion}) - set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}") - else () - set(WIN10SDK_INCLUDE_PATH "C:/Program Files (x86)/Windows Kits/10/Include/10.0.26100.0") - endif () - if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h") - message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found") - message("STL fixing by the Netfabb service will not be compiled") - unset(WIN10SDK_INCLUDE_PATH) - endif() - endif() - if(WIN10SDK_INCLUDE_PATH) - message("Building with Win10 Netfabb STL fixing service support") - add_definitions(-DHAS_WIN10SDK) - include_directories(SYSTEM "${WIN10SDK_INCLUDE_PATH}") - else() - message("Building without Win10 Netfabb STL fixing service support") - endif() -endif() +# ORCA: Removed Netfabb STL fixing service support in favor of CGAL. +# if(WIN32) +# if(NOT DEFINED WIN10SDK_PATH) +# if(DEFINED ENV{WIN10SDK_PATH}) +# set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") +# endif() +# endif() +# if(DEFINED WIN10SDK_PATH) +# #BBS: modify win10sdk_path +# if (EXISTS "${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h") +# set(WIN10SDK_INCLUDE_PATH "${WIN10SDK_PATH}") +# else() +# message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") +# message("${WIN10SDK_PATH}/winrt/windows.graphics.printing3d.h was not found") +# message("STL fixing by the Netfabb service will not be compiled") +# unset(WIN10SDK_PATH) +# endif() +# else() +# # Try to use the default Windows 10 SDK path. +# if (DEFINED ENV{WindowsSdkDir} AND DEFINED ENV{WindowsSDKVersion}) +# set(WIN10SDK_INCLUDE_PATH "$ENV{WindowsSdkDir}/Include/$ENV{WindowsSDKVersion}") +# else () +# set(WIN10SDK_INCLUDE_PATH "C:/Program Files (x86)/Windows Kits/10/Include/10.0.26100.0") +# endif () +# if (NOT EXISTS "${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h") +# message("${WIN10SDK_INCLUDE_PATH}/winrt/windows.graphics.printing3d.h was not found") +# message("STL fixing by the Netfabb service will not be compiled") +# unset(WIN10SDK_INCLUDE_PATH) +# endif() +# endif() +# if(WIN10SDK_INCLUDE_PATH) +# message("Building with Win10 Netfabb STL fixing service support") +# add_definitions(-DHAS_WIN10SDK) +# include_directories(SYSTEM "${WIN10SDK_INCLUDE_PATH}") +# else() +# message("Building without Win10 Netfabb STL fixing service support") +# endif() +# endif() if (APPLE) message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}") diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index c195e4358f..8b50681370 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include // BBS: for segment #include @@ -475,6 +475,92 @@ bool empty(const CGALMesh &mesh) return mesh.m.is_empty(); } +bool repair(TriangleMesh& mesh, RepairedMeshErrors* repaired_errors, std::string* error) +{ + using namespace CGAL; + namespace PMP = CGAL::Polygon_mesh_processing; + + if (mesh.empty()) + return true; + + try { + // 1) Convert to polygon soup + std::vector<_EpicMesh::Point> points; + std::vector> polygons; + + points.reserve(mesh.its.vertices.size()); + polygons.reserve(mesh.its.indices.size()); + + for (const auto& v : mesh.its.vertices) + points.emplace_back(v.x(), v.y(), v.z()); + + for (const auto& f : mesh.its.indices) + polygons.push_back({size_t(f[0]), size_t(f[1]), size_t(f[2])}); + + // 2) Aggressive soup cleanup + PMP::repair_polygon_soup(points, polygons); + + // 3) Convert soup → mesh + _EpicMesh cgal_mesh; + PMP::polygon_soup_to_polygon_mesh(points, polygons, cgal_mesh); + + // 4) Remove degenerate geometry + PMP::remove_degenerate_faces(cgal_mesh); + PMP::remove_isolated_vertices(cgal_mesh); + + // 5) Fix remaining non-manifold vertices + PMP::duplicate_non_manifold_vertices(cgal_mesh); + + // 6) Boolean union (keeps only outer shell) + _EpicMesh tmp; + if (PMP::corefine_and_compute_union(cgal_mesh, cgal_mesh, tmp)) { + cgal_mesh = std::move(tmp); + } + // If it fails, continue anyway with previous mesh + + // 7) Fill holes + if (!CGAL::is_closed(cgal_mesh)) { + using halfedge_descriptor = boost::graph_traits<_EpicMesh>::halfedge_descriptor; + + std::vector borders; + PMP::extract_boundary_cycles(cgal_mesh, std::back_inserter(borders)); + + for (halfedge_descriptor h : borders) { + PMP::triangulate_and_refine_hole(cgal_mesh, h); + } + } + + // 8) Final validity check + if (!CGAL::is_closed(cgal_mesh)) { + if (error) + *error = "Repair failed: mesh still open after hole filling."; + return false; + } + + // 9) Ensure outward orientation + if (!PMP::does_bound_a_volume(cgal_mesh)) + PMP::orient_to_bound_a_volume(cgal_mesh); + + // 10) Convert back + indexed_triangle_set its = cgal_to_indexed_triangle_set(cgal_mesh); + + RepairedMeshErrors errs{}; + errs.facets_removed = 0; + errs.edges_fixed = 0; + + mesh = TriangleMesh(std::move(its), errs); + + if (repaired_errors) + *repaired_errors = errs; + + return true; + } catch (const std::exception& e) { + if (error) + *error = e.what(); + return false; + } +} + CGALMeshPtr clone(const CGALMesh &m) { return CGALMeshPtr{new CGALMesh{m}}; diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index 92592f5f61..d9a5d196fb 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -70,6 +70,9 @@ TriangleMesh merge(std::vector meshes); bool does_bound_a_volume(const CGALMesh &mesh); bool empty(const CGALMesh &mesh); + +// Repair a mesh using CGAL. Returns true on success. Optionally returns a summary of repairs and an error string. +bool repair(TriangleMesh &mesh, RepairedMeshErrors *repaired_errors = nullptr, std::string *error = nullptr); } namespace mcut { diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 59605d437a..d06afca40b 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -580,8 +580,8 @@ set(SLIC3R_GUI_SOURCES Utils/ESP3D.hpp Utils/FileHelp.cpp Utils/FileHelp.hpp - Utils/FixModelByWin10.cpp - Utils/FixModelByWin10.hpp + Utils/FixModelByCgal.cpp + Utils/FixModelByCgal.hpp Utils/FlashAir.cpp Utils/FlashAir.hpp Utils/Flashforge.cpp diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 401b2e0e7f..9b98052cdd 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -21,7 +21,6 @@ #include #include "slic3r/GUI/Tab.hpp" -#include "slic3r/Utils/FixModelByWin10.hpp" #include "ParamsPanel.hpp" #include "MsgDialog.hpp" #include "wx/utils.h" @@ -879,14 +878,11 @@ void MenuFactory::append_menu_item_rename(wxMenu* menu) menu->AppendSeparator(); } -wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu) +wxMenuItem* MenuFactory::append_menu_item_fix_through_cgal(wxMenu* menu) { - if (!is_windows10()) - return nullptr; - wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix model"), "", - [](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu, - []() {return plater()->can_fix_through_netfabb(); }, plater()); + [](wxCommandEvent&) { obj_list()->fix_through_cgal(); }, "", menu, + []() {return plater()->can_fix_through_cgal(); }, plater()); return menu_item; } @@ -1356,7 +1352,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu) // "Scale to print volume" makes a sense just for whole object append_menu_item_scale_selection_to_fit_print_volume(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_items_mirror(menu); } @@ -1395,7 +1391,7 @@ void MenuFactory::create_extra_object_menu() // Object Clone append_menu_item_clone(&m_object_menu); // Object Repair - append_menu_item_fix_through_netfabb(&m_object_menu); + append_menu_item_fix_through_cgal(&m_object_menu); // Object Simplify append_menu_item_simplify(&m_object_menu); // Object Mesh Subdivision @@ -1452,7 +1448,7 @@ void MenuFactory::create_bbl_assemble_object_menu() // Delete append_menu_item_delete(&m_assemble_object_menu); // Object Repair - append_menu_item_fix_through_netfabb(&m_assemble_object_menu); + append_menu_item_fix_through_cgal(&m_assemble_object_menu); // Object Simplify append_menu_item_simplify(&m_assemble_object_menu); // Object Mesh Subdivision @@ -1482,7 +1478,7 @@ void MenuFactory::create_part_menu() append_menu_item_reload_from_disk(menu); append_menu_item_export_stl(menu); append_menu_item_export_drc(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_items_mirror(menu); append_menu_item_merge_parts_to_single_part(menu); @@ -1507,7 +1503,7 @@ void MenuFactory::create_text_part_menu() append_menu_item_edit_text(menu); append_menu_item_delete(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_item_simplify(menu); append_menu_item_center(menu); append_menu_items_mirror(menu); @@ -1523,7 +1519,7 @@ void MenuFactory::create_svg_part_menu() append_menu_item_edit_svg(menu); append_menu_item_delete(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_item_simplify(menu); append_menu_items_mirror(menu); menu->AppendSeparator(); @@ -1538,7 +1534,7 @@ void MenuFactory::create_bbl_part_menu() append_menu_item_delete(menu); append_menu_item_edit_text(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); append_menu_item_simplify(menu); append_menu_item_smooth_mesh(menu); append_menu_item_center(menu); @@ -1859,7 +1855,7 @@ wxMenu* MenuFactory::multi_selection_menu() } append_menu_item_center(menu); append_menu_item_drop(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); //append_menu_item_simplify(menu); append_menu_item_delete(menu); menu->AppendSeparator(); @@ -1884,7 +1880,7 @@ wxMenu* MenuFactory::multi_selection_menu() else { append_menu_item_center(menu); append_menu_item_drop(menu); - append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_through_cgal(menu); //append_menu_item_simplify(menu); append_menu_item_delete(menu); append_menu_items_convert_unit(menu); @@ -1922,7 +1918,7 @@ wxMenu* MenuFactory::assemble_multi_selection_menu() wxMenu* menu = new MenuWithSeparators(); append_menu_item_set_visible(menu); - //append_menu_item_fix_through_netfabb(menu); + //append_menu_item_fix_through_cgal(menu); //append_menu_item_simplify(menu); append_menu_item_delete(menu); menu->AppendSeparator(); @@ -1972,7 +1968,7 @@ wxMenu* MenuFactory::assemble_object_menu() // Delete append_menu_item_delete(menu); //// Object Repair - //append_menu_item_fix_through_netfabb(menu); + //append_menu_item_fix_through_cgal(menu); //// Object Simplify //append_menu_item_simplify(menu); menu->AppendSeparator(); diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index b3915c0705..f80ef85d53 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -139,7 +139,7 @@ private: wxMenuItem* append_menu_item_printable(wxMenu* menu); wxMenuItem* append_menu_item_auto_drop(wxMenu* menu); void append_menu_item_rename(wxMenu* menu); - wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); + wxMenuItem* append_menu_item_fix_through_cgal(wxMenu* menu); //wxMenuItem* append_menu_item_simplify(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu, bool is_mulity_menu = false); void append_menu_item_export_drc(wxMenu* menu, bool is_mulity_menu = false); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 919b7217b1..726ea1aca3 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -36,7 +36,7 @@ #include #include -#include "slic3r/Utils/FixModelByWin10.hpp" +#include "slic3r/Utils/FixModelByCgal.hpp" #include "libslic3r/Format/bbs_3mf.hpp" #include "libslic3r/PrintConfig.hpp" @@ -578,7 +578,7 @@ MeshErrorsInfo ObjectList::get_mesh_errors_info(const int obj_idx, const int vol if (non_manifold_edges) *non_manifold_edges = stats.open_edges; - if (is_windows10() && !sidebar_info) + if (!sidebar_info) tooltip += "\n" + _L("Click the icon to repair model object"); return { tooltip, get_warning_icon_name(stats) }; @@ -1550,9 +1550,9 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me } else if (col_num == colName) { - if (is_windows10() && m_objects_model->HasWarningIcon(item) && + if (m_objects_model->HasWarningIcon(item) && mouse_pos.x > 2 * wxGetApp().em_unit() && mouse_pos.x < 4 * wxGetApp().em_unit()) - fix_through_netfabb(); + fix_through_cgal(); else if (evt_context_menu) show_context_menu(evt_context_menu); // show context menu for "Name" column too } @@ -5984,7 +5984,7 @@ void ObjectList::rename_item() update_name_in_model(item); } -void ObjectList::fix_through_netfabb() +void ObjectList::fix_through_cgal() { // Do not fix anything when a gizmo is open. There might be issues with updates // and what is worse, the snapshot time would refer to the internal stack. @@ -6004,11 +6004,11 @@ void ObjectList::fix_through_netfabb() // clear selections from the non-broken models if any exists // and than fill names of models to repairing if (vol_idxs.empty()) { -#if !FIX_THROUGH_NETFABB_ALWAYS +#if !FIX_THROUGH_CGAL_ALWAYS for (int i = int(obj_idxs.size())-1; i >= 0; --i) if (object(obj_idxs[i])->get_repaired_errors_count() == 0) obj_idxs.erase(obj_idxs.begin()+i); -#endif // FIX_THROUGH_NETFABB_ALWAYS +#endif // FIX_THROUGH_CGAL_ALWAYS for (int obj_idx : obj_idxs) if (object(obj_idx)) model_names.push_back(object(obj_idx)->name); @@ -6016,11 +6016,11 @@ void ObjectList::fix_through_netfabb() else { ModelObject* obj = object(obj_idxs.front()); if (obj) { -#if !FIX_THROUGH_NETFABB_ALWAYS +#if !FIX_THROUGH_CGAL_ALWAYS for (int i = int(vol_idxs.size()) - 1; i >= 0; --i) if (obj->get_repaired_errors_count(vol_idxs[i]) == 0) vol_idxs.erase(vol_idxs.begin() + i); -#endif // FIX_THROUGH_NETFABB_ALWAYS +#endif // FIX_THROUGH_CGAL_ALWAYS for (int vol_idx : vol_idxs) model_names.push_back(obj->volumes[vol_idx]->name); } @@ -6049,13 +6049,18 @@ void ObjectList::fix_through_netfabb() } plater->clear_before_change_mesh(obj_idx); + const size_t volumes_before = object(obj_idx)->volumes.size(); std::string res; - if (!fix_model_by_win10_sdk_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res)) + if (!fix_model_with_cgal_gui(*(object(obj_idx)), vol_idx, progress_dlg, msg, res)) return false; //wxGetApp().plater()->changed_mesh(obj_idx); object(obj_idx)->ensure_on_bed(); plater->changed_mesh(obj_idx); + const size_t volumes_after = object(obj_idx)->volumes.size(); + if (volumes_after != volumes_before) + add_volumes_to_object_in_list(obj_idx); + plater->get_partplate_list().notify_instance_update(obj_idx, 0); plater->sidebar().obj_list()->update_plate_values_for_items(); @@ -6079,10 +6084,10 @@ void ObjectList::fix_through_netfabb() if (vol_idxs.empty()) { int vol_idx{ -1 }; for (int obj_idx : obj_idxs) { -#if !FIX_THROUGH_NETFABB_ALWAYS +#if !FIX_THROUGH_CGAL_ALWAYS if (object(obj_idx)->get_repaired_errors_count(vol_idx) == 0) continue; -#endif // FIX_THROUGH_NETFABB_ALWAYS +#endif // FIX_THROUGH_CGAL_ALWAYS if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models)) break; model_idx++; @@ -6115,7 +6120,7 @@ void ObjectList::fix_through_netfabb() } if (msg.IsEmpty()) msg = _L("Repairing was canceled"); - plater->get_notification_manager()->push_notification(NotificationType::NetfabbFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, into_u8(msg)); + plater->get_notification_manager()->push_notification(NotificationType::CgalFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, into_u8(msg)); } void ObjectList::simplify() diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index ed0dfabd11..b97eff05ae 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -37,7 +37,7 @@ typedef std::pair t_layer_height_range; typedef std::map t_layer_config_ranges; // Manifold mesh may contain self-intersections, so we want to always allow fixing the mesh. -#define FIX_THROUGH_NETFABB_ALWAYS 1 +#define FIX_THROUGH_CGAL_ALWAYS 1 namespace GUI { struct ObjectVolumeID { @@ -430,7 +430,7 @@ public: void instances_to_separated_objects(const int obj_idx); void split_instances(); void rename_item(); - void fix_through_netfabb(); + void fix_through_cgal(); void simplify(); void smooth_mesh(); void update_item_error_icon(const int obj_idx, int vol_idx) const ; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index e807e477d3..bd6bf6c67e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -659,8 +659,7 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) cut_with_groove ? cut.perform_with_groove(m_groove, m_rotate_matrix) : cut.perform_with_plane(); // fix_non_manifold_edges -#ifdef HAS_WIN10SDK - if (is_windows10()) { + { bool is_showed_dialog = false; bool user_fix_model = false; for (size_t i = 0; i < new_objects.size(); i++) { @@ -687,7 +686,7 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) wxString msg = _L("Repairing model object"); msg += ": " + from_u8(model_name) + "\n"; std::string res; - if (!fix_model_by_win10_sdk_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; + if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; return true; }; ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); @@ -700,7 +699,6 @@ void GLGizmoAdvancedCut::perform_cut(const Selection& selection) } } } - #endif // set offset for new_objects // save cut_id to post update synchronization diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 6c5185b0d2..2483f7bd1b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -17,7 +17,7 @@ #include "imgui/imgui_internal.h" #include "slic3r/GUI/Field.hpp" #include "slic3r/GUI/MsgDialog.hpp" -#include "FixModelByWin10.hpp" +#include "FixModelByCgal.hpp" namespace Slic3r { namespace GUI { @@ -3340,8 +3340,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) cut.perform_with_plane(); // fix_non_manifold_edges -#ifdef HAS_WIN10SDK - if (is_windows10()) { + { bool is_showed_dialog = false; bool user_fix_model = false; for (size_t i = 0; i < new_objects.size(); i++) { @@ -3368,7 +3367,7 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) wxString msg = _L("Repairing model object"); msg += ": " + from_u8(model_name) + "\n"; std::string res; - if (!fix_model_by_win10_sdk_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; + if (!fix_model_with_cgal_gui(*model_object, vol_idx, progress_dlg, msg, res)) return false; return true; }; ProgressDialog progress_dlg(_L("Repairing model object"), "", 100, find_toplevel_parent(plater), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT, true); @@ -3381,7 +3380,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) } } } - #endif check_objects_after_cut(new_objects); // save cut_id to post update synchronization diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index c9bb132f68..4d31861390 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -126,8 +126,8 @@ enum class NotificationType SimplifySuggestion, // Change of text will change font to similar one on. UnknownFont, - // information about netfabb is finished repairing model (blocking proccess) - NetfabbFinished, + // information about cgal is finished repairing model (blocking process) + CgalFinished, // Short meesage to fill space between start and finish of export ExportOngoing, // Progressbar of download from prusaslicer://url diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 034fb653cd..6e2eee8496 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -31,7 +31,6 @@ #include "PrintHostDialogs.hpp" #include "../Utils/ASCIIFolding.hpp" #include "../Utils/PrintHost.hpp" -#include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" #include "RemovableDriveManager.hpp" #include "BitmapCache.hpp" diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 22d590a969..65e239eebd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -112,7 +112,6 @@ #include "ConfigWizard.hpp" #include "SyncAmsInfoDialog.hpp" #include "../Utils/ASCIIFolding.hpp" -#include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" #include "../Utils/PresetUpdater.hpp" #include "../Utils/Process.hpp" @@ -4731,7 +4730,7 @@ struct Plater::priv bool can_split_to_volumes() const; bool can_arrange() const; bool can_layers_editing() const; - bool can_fix_through_netfabb() const; + bool can_fix_through_cgal() const; bool can_simplify() const; bool can_smooth_mesh() const; bool can_set_instance_to_object() const; @@ -10240,10 +10239,10 @@ void Plater::priv::on_object_select(SimpleEvent& evt) selection_changed(); } -//BBS: repair model through netfabb +//BBS: repair model through cgal void Plater::priv::on_repair_model(wxCommandEvent &event) { - wxGetApp().obj_list()->fix_through_netfabb(); + wxGetApp().obj_list()->fix_through_cgal(); } void Plater::priv::on_filament_color_changed(wxCommandEvent &event) @@ -11217,15 +11216,15 @@ bool Plater::priv::can_delete_plate() const return q->get_partplate_list().get_plate_count() > 1; } -bool Plater::priv::can_fix_through_netfabb() const +bool Plater::priv::can_fix_through_cgal() const { std::vector obj_idxs, vol_idxs; sidebar->obj_list()->get_selection_indexes(obj_idxs, vol_idxs); -#if FIX_THROUGH_NETFABB_ALWAYS +#if FIX_THROUGH_CGAL_ALWAYS // Fixing always. return ! obj_idxs.empty() || ! vol_idxs.empty(); -#else // FIX_THROUGH_NETFABB_ALWAYS +#else // FIX_THROUGH_CGAL_ALWAYS // Fixing only if the model is not manifold. if (vol_idxs.empty()) { for (auto obj_idx : obj_idxs) @@ -11239,7 +11238,7 @@ bool Plater::priv::can_fix_through_netfabb() const if (model.objects[obj_idx]->get_repaired_errors_count(vol_idx) > 0) return true; return false; -#endif // FIX_THROUGH_NETFABB_ALWAYS +#endif // FIX_THROUGH_CGAL_ALWAYS } bool Plater::priv::can_simplify() const @@ -17976,15 +17975,13 @@ void Plater::show_object_info() int non_manifold_edges = 0; auto mesh_errors = p->sidebar->obj_list()->get_mesh_errors_info(&info_manifold, &non_manifold_edges); - #ifndef __WINDOWS__ - if (non_manifold_edges > 0) { - info_manifold += into_u8("\n" + _L("Tips:") + "\n" +_L("\"Fix Model\" feature is currently only on Windows. Please repair the model on Orca Slicer(windows) or CAD softwares.")); - } - #endif //APPLE & LINUX + if (non_manifold_edges > 0) { + info_manifold += into_u8("\n" + _L("Tips:") + "\n" + _L("Use \"Fix Model\" to repair the mesh.")); + } info_manifold = "" + info_manifold + ""; info_text += into_u8(info_manifold); - notify_manager->bbl_show_objectsinfo_notification(info_text, is_windows10()&&(non_manifold_edges > 0), !(p->current_panel == p->view3D)); + notify_manager->bbl_show_objectsinfo_notification(info_text, non_manifold_edges > 0, !(p->current_panel == p->view3D)); } bool Plater::show_publish_dialog(bool show) @@ -18161,7 +18158,7 @@ bool Plater::can_delete_plate() const { return p->can_delete_plate(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); } bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); } -bool Plater::can_fix_through_netfabb() const { return p->can_fix_through_netfabb(); } +bool Plater::can_fix_through_cgal() const { return p->can_fix_through_cgal(); } bool Plater::can_simplify() const { return p->can_simplify(); } bool Plater::can_smooth_mesh() const { return p->can_smooth_mesh(); } bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 83c5dd6b75..8fde64ead2 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -662,7 +662,7 @@ public: bool can_increase_instances() const; bool can_decrease_instances() const; bool can_set_instance_to_object() const; - bool can_fix_through_netfabb() const; + bool can_fix_through_cgal() const; bool can_simplify() const; bool can_smooth_mesh() const; bool can_split_to_objects() const; diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index fa427cc493..55cc4d8d96 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -35,7 +35,6 @@ #include "Tab.hpp" #include "ConfigWizard.hpp" #include "../Utils/ASCIIFolding.hpp" -#include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" #include "../Utils/ColorSpaceConvert.hpp" #include "BitmapCache.hpp" diff --git a/src/slic3r/Utils/FixModelByCgal.cpp b/src/slic3r/Utils/FixModelByCgal.cpp new file mode 100644 index 0000000000..c014ceaf04 --- /dev/null +++ b/src/slic3r/Utils/FixModelByCgal.cpp @@ -0,0 +1,214 @@ +#include "FixModelByCgal.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/MeshBoolean.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/format.hpp" +#include "../GUI/I18N.hpp" + +// Orca: This file provides utilities for repairing 3D model meshes using the CGAL library, handling mesh splitting, merging, and boolean operations. + +namespace Slic3r { + +namespace { + +// Orca: Helper functions for analyzing mesh properties and transformations. + +bool is_not_3dimensional_part(const TriangleMesh &mesh) +{ + // Orca: Determines if a mesh is degenerate or represents a non-3dimensional part by checking volume and bounding box dimensions. + if (mesh.its.indices.empty()) + return true; + + indexed_triangle_set tmp = mesh.its; + its_remove_degenerate_faces(tmp, true); + if (tmp.indices.empty()) + return true; + + const BoundingBoxf3 bbox = mesh.bounding_box(); + const Vec3d size = bbox.size(); + const double min_dim = std::min(size.x(), std::min(size.y(), size.z())); + const double max_dim = std::max(size.x(), std::max(size.y(), size.z())); + if (min_dim <= EPSILON) + return true; + + const double volume = std::abs(its_volume(mesh.its)); + const double bbox_volume = size.x() * size.y() * size.z(); + if (volume <= EPSILON) + return true; + + const double min_relative_thickness = 1e-6; + const double min_volume_ratio = 1e-6; + if (min_dim / max_dim <= min_relative_thickness) + return true; + if (bbox_volume > 0.0 && volume / bbox_volume <= min_volume_ratio) + return true; + + return false; +} + +} // namespace + +// Orca: Exception class for handling user-initiated cancellation of model repair operations. +class RepairCanceledException : public std::exception { +public: + const char* what() const noexcept override { return "Model repair has been canceled"; } +}; + +// Orca: Main function to repair model objects using CGAL, with progress dialog and cancellation support. +// Returns false if fixing was canceled. fix_result contains error message if failed. +bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dialog, const wxString &msg_header, std::string &fix_result) +{ + // Orca: Synchronization primitives for progress updates between worker thread and GUI. + std::mutex mtx; + std::condition_variable condition; + struct Progress { + std::string message; + int percent = 0; + bool updated = false; + } progress; + + std::atomic canceled = false; + std::atomic finished = false; + + bool success = false; + size_t ivolume = 0; + + // Orca: Lambda for updating progress from worker thread. + auto on_progress = [&mtx, &condition, &ivolume, &model_object, &progress](const char *msg, unsigned prcnt) { + std::unique_lock lock(mtx); + progress.message = msg; + const size_t total = std::max(1, model_object.volumes.size()); + progress.percent = int(std::floor((float(prcnt) + float(ivolume) * 100.f) / float(total))); + progress.updated = true; + condition.notify_all(); + }; + + // Orca: Worker thread that performs the actual model repair operations. + auto worker_thread = std::thread([&model_object, volume_idx, &ivolume, on_progress, &success, &canceled, &finished, &fix_result]() { + try { + size_t start_volume = volume_idx == -1 ? 0 : size_t(volume_idx); + size_t end_volume = volume_idx == -1 ? std::numeric_limits::max() : size_t(volume_idx); + + for (ivolume = start_volume; ivolume < model_object.volumes.size(); ++ivolume) { + if (volume_idx != -1 && ivolume > end_volume) + break; + if (canceled) + throw RepairCanceledException(); + + on_progress(L("Repairing model object"), 10); + + ModelVolume *volume = model_object.volumes[ivolume]; + + // Orca: Split splittable volumes into parts for individual processing. + size_t parts_count = 1; + if (volume->is_splittable()) { + parts_count = volume->split(1); + if (parts_count > 1) { + const std::string msg = Slic3r::format(L("Split into %1% parts"), parts_count); + on_progress(msg.c_str(), 10); + } + } + + size_t part_end = std::min(ivolume + parts_count - 1, model_object.volumes.size() - 1); + if (volume_idx != -1) + end_volume = part_end; + + size_t removed_parts = 0; + for (size_t idx = part_end + 1; idx > ivolume; --idx) { + const size_t part_idx = idx - 1; + const ModelVolume *part_volume = model_object.volumes[part_idx]; + if (!is_not_3dimensional_part(part_volume->mesh())) + continue; + + model_object.delete_volume(part_idx); + ++removed_parts; + if (part_end > 0) + --part_end; + else + part_end = 0; + if (volume_idx != -1) + end_volume = part_end; + } + + if (removed_parts >= parts_count) { + ivolume = part_end; + on_progress(L("Repair finished"), 100); + continue; + } + + for (size_t part_idx = ivolume; part_idx <= part_end && part_idx < model_object.volumes.size(); ++part_idx) { + ModelVolume *part_volume = model_object.volumes[part_idx]; + TriangleMesh mesh = part_volume->mesh(); + if (its_num_open_edges(mesh.its) != 0) { + std::string error; + if (!MeshBoolean::cgal::repair(mesh, nullptr, &error)) + throw Slic3r::RuntimeError(error.empty() ? L("Repair failed") : error.c_str()); + + part_volume->set_mesh(std::move(mesh)); + part_volume->calculate_convex_hull(); + part_volume->invalidate_convex_hull_2d(); + part_volume->set_new_unique_id(); + } + } + + ivolume = part_end; + + on_progress(L("Repair finished"), 100); + } + + model_object.invalidate_bounding_box(); + + if (ivolume > 0) + --ivolume; + on_progress(L("Repair finished"), 100); + success = true; + finished = true; + } catch (RepairCanceledException &) { + canceled = true; + finished = true; + on_progress(L("Repair canceled"), 100); + } catch (std::exception &ex) { + success = false; + finished = true; + fix_result = ex.what(); + on_progress(ex.what(), 100); + } + }); + + // Orca: Main GUI loop to update progress dialog and handle cancellation. + while (!finished) { + std::unique_lock lock(mtx); + condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); + + // Decrease progress percent slightly to avoid auto-closing. + if (!progress_dialog.Update(progress.percent - 1, msg_header + _(progress.message))) + canceled = true; + else + progress_dialog.Fit(); + + progress.updated = false; + } + + if (canceled) { + // Nothing to show. + } else if (success) { + fix_result.clear(); + } + + if (worker_thread.joinable()) + worker_thread.join(); + + return !canceled; +} + +} // namespace Slic3r diff --git a/src/slic3r/Utils/FixModelByCgal.hpp b/src/slic3r/Utils/FixModelByCgal.hpp new file mode 100644 index 0000000000..3caa6d7fb9 --- /dev/null +++ b/src/slic3r/Utils/FixModelByCgal.hpp @@ -0,0 +1,18 @@ +#ifndef slic3r_GUI_Utils_FixModelByCgal_hpp_ +#define slic3r_GUI_Utils_FixModelByCgal_hpp_ + +#include +#include "../GUI/Widgets/ProgressDialog.hpp" + +namespace Slic3r { + +class Model; +class ModelObject; +class Print; + +// Return false if fixing was canceled. fix_result is empty on success. +extern bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dlg, const wxString &msg_header, std::string &fix_result); + +} // namespace Slic3r + +#endif /* slic3r_GUI_Utils_FixModelByCgal_hpp_ */ diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp deleted file mode 100644 index 4967caf479..0000000000 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ /dev/null @@ -1,447 +0,0 @@ -#ifdef HAS_WIN10SDK - -#ifndef NOMINMAX -# define NOMINMAX -#endif - -// Windows Runtime -#include -// for ComPtr -#include - -// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/ -#include -#include -#include - -#include "FixModelByWin10.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "libslic3r/Model.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Format/3mf.hpp" -#include "../GUI/GUI.hpp" -#include "../GUI/I18N.hpp" -#include "../GUI/MsgDialog.hpp" - -#include -#include - -extern "C"{ - // from rapi.h - typedef HRESULT (__stdcall* FunctionRoInitialize)(int); - typedef HRESULT (__stdcall* FunctionRoUninitialize)(); - typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance); - typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory); - // from winstring.h - typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string); - typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string); -} - -namespace Slic3r { - -static std::string saving_failed_str = L("Saving objects into the 3MF failed."); - -HMODULE s_hRuntimeObjectLibrary = nullptr; -FunctionRoInitialize s_RoInitialize = nullptr; -FunctionRoUninitialize s_RoUninitialize = nullptr; -FunctionRoActivateInstance s_RoActivateInstance = nullptr; -FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr; -FunctionWindowsCreateString s_WindowsCreateString = nullptr; -FunctionWindowsDelteString s_WindowsDeleteString = nullptr; - -bool winrt_load_runtime_object_library() -{ - if (s_hRuntimeObjectLibrary == nullptr) - s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll"); - if (s_hRuntimeObjectLibrary != nullptr) { - s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize"); - s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize"); - s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance"); - s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory"); - s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString"); - s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString"); - } - return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString; -} - -static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst) -{ - HSTRING hClassName; - HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); - if (S_OK != hr) - return hr; - hr = (*s_RoActivateInstance)(hClassName, pinst); - (*s_WindowsDeleteString)(hClassName); - return hr; -} - -template -static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst) -{ - IInspectable *pinspectable = nullptr; - HRESULT hr = winrt_activate_instance(class_name, &pinspectable); - if (S_OK != hr) - return hr; - hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst); - pinspectable->Release(); - return hr; -} - -static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst) -{ - HSTRING hClassName; - HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); - if (S_OK != hr) - return hr; - hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst); - (*s_WindowsDeleteString)(hClassName); - return hr; -} - -template -static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst) -{ - return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast(pinst)); -} - -// To be called often to test whether to cancel the operation. -typedef std::function ThrowOnCancelFn; - -template -static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100) -{ - Microsoft::WRL::ComPtr asyncInfo; - asyncAction.As(&asyncInfo); - AsyncStatus status; - // Ugly blocking loop until the RepairAsync call finishes. -//FIXME replace with a callback. -// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage - for (;;) { - asyncInfo->get_Status(&status); - if (status != AsyncStatus::Started) - return status; - throw_on_cancel(); - ::Sleep(blocking_tick_ms); - } -} - -static HRESULT winrt_open_file_stream( - const std::wstring &path, - ABI::Windows::Storage::FileAccessMode mode, - ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream, - ThrowOnCancelFn throw_on_cancel) -{ - // Get the file factory. - Microsoft::WRL::ComPtr fileFactory; - HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf()); - if (FAILED(hr)) return hr; - - // Open the file asynchronously. - HSTRING hstr_path; - hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path); - if (FAILED(hr)) return hr; - Microsoft::WRL::ComPtr> fileOpenAsync; - hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf()); - if (FAILED(hr)) return hr; - (*s_WindowsDeleteString)(hstr_path); - - // Wait until the file gets open, get the actual file. - AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel); - Microsoft::WRL::ComPtr storageFile; - if (status == AsyncStatus::Completed) { - hr = fileOpenAsync->GetResults(storageFile.GetAddressOf()); - } else { - Microsoft::WRL::ComPtr asyncInfo; - hr = fileOpenAsync.As(&asyncInfo); - if (FAILED(hr)) return hr; - HRESULT err; - hr = asyncInfo->get_ErrorCode(&err); - return FAILED(hr) ? hr : err; - } - - Microsoft::WRL::ComPtr> fileStreamAsync; - hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf()); - if (FAILED(hr)) return hr; - - status = winrt_async_await(fileStreamAsync, throw_on_cancel); - if (status == AsyncStatus::Completed) { - hr = fileStreamAsync->GetResults(fileStream); - } else { - Microsoft::WRL::ComPtr asyncInfo; - hr = fileStreamAsync.As(&asyncInfo); - if (FAILED(hr)) return hr; - HRESULT err; - hr = asyncInfo->get_ErrorCode(&err); - if (!FAILED(hr)) - hr = err; - } - return hr; -} - -bool is_windows10() -{ - HKEY hKey; - LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey); - if (lRes == ERROR_SUCCESS) { - WCHAR szBuffer[512]; - DWORD dwBufferSize = sizeof(szBuffer); - lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize); - if (lRes == ERROR_SUCCESS) - return wcsncmp(szBuffer, L"Windows 10", 10) == 0; - RegCloseKey(hKey); - } - return false; -} - -// Progress function, to be called regularly to update the progress. -typedef std::function ProgressFn; - -void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel) -{ - if (! is_windows10()) - throw Slic3r::RuntimeError(L("Only Windows 10 is supported.")); - - if (! winrt_load_runtime_object_library()) - throw Slic3r::RuntimeError(L("Failed to initialize the WinRT library.")); - - HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED); - { - on_progress(L("Exporting objects"), 20); - - Microsoft::WRL::ComPtr fileStream; - hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel); - - Microsoft::WRL::ComPtr printing3d3mfpackage; - hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf()); - - Microsoft::WRL::ComPtr> modelAsync; - hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf()); - - AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel); - Microsoft::WRL::ComPtr model; - if (status == AsyncStatus::Completed) - hr = modelAsync->GetResults(model.GetAddressOf()); - else - throw Slic3r::RuntimeError(L("Failed loading objects.")); - - Microsoft::WRL::ComPtr> meshes; - hr = model->get_Meshes(meshes.GetAddressOf()); - unsigned num_meshes = 0; - hr = meshes->get_Size(&num_meshes); - on_progress(L("Repairing object by Windows service"), 40); - Microsoft::WRL::ComPtr repairAsync; - hr = model->RepairAsync(repairAsync.GetAddressOf()); - status = winrt_async_await(repairAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Repair failed.")); - repairAsync->GetResults(); - - on_progress(L("Loading repaired objects"), 60); - - // Verify the number of meshes returned after the repair action. - meshes.Reset(); - hr = model->get_Meshes(meshes.GetAddressOf()); - hr = meshes->get_Size(&num_meshes); - - // Save model to this class' Printing3D3MFPackage. - Microsoft::WRL::ComPtr saveToPackageAsync; - hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); - status = winrt_async_await(saveToPackageAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(saving_failed_str); - hr = saveToPackageAsync->GetResults(); - - Microsoft::WRL::ComPtr> generatorStreamAsync; - hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); - status = winrt_async_await(generatorStreamAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(saving_failed_str); - Microsoft::WRL::ComPtr generatorStream; - hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); - - // Go to the beginning of the stream. - generatorStream->Seek(0); - Microsoft::WRL::ComPtr inputStream; - hr = generatorStream.As(&inputStream); - - // Get the buffer factory. - Microsoft::WRL::ComPtr bufferFactory; - hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf()); - - // Open the destination file. - FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb"); - - Microsoft::WRL::ComPtr buffer; - byte *buffer_ptr; - bufferFactory->Create(65536 * 2048, buffer.GetAddressOf()); - { - Microsoft::WRL::ComPtr bufferByteAccess; - buffer.As(&bufferByteAccess); - hr = bufferByteAccess->Buffer(&buffer_ptr); - } - uint32_t length; - hr = buffer->get_Length(&length); - - Microsoft::WRL::ComPtr> asyncRead; - for (;;) { - hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); - status = winrt_async_await(asyncRead, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(saving_failed_str); - hr = buffer->get_Length(&length); - if (length == 0) - break; - fwrite(buffer_ptr, length, 1, fout); - } - fclose(fout); - // Here all the COM objects will be released through the ComPtr destructors. - } - (*s_RoUninitialize)(); -} - -class RepairCanceledException : public std::exception { -public: - const char* what() const throw() { return "Model repair has been canceled"; } -}; - -// returt FALSE, if fixing was canceled -// fix_result is empty, if fixing finished successfully -// fix_result containes a message if fixing failed -bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result) -{ - std::mutex mtx; - std::condition_variable condition; - struct Progress { - std::string message; - int percent = 0; - bool updated = false; - } progress; - std::atomic canceled = false; - std::atomic finished = false; - - std::vector volumes; - if (volume_idx == -1) - volumes = model_object.volumes; - else - volumes.emplace_back(model_object.volumes[volume_idx]); - - // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. - // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). - bool success = false; - size_t ivolume = 0; - auto on_progress = [&mtx, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { - std::unique_lock lock(mtx); - progress.message = msg; - progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size())); - progress.updated = true; - condition.notify_all(); - }; - auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() { - try { - std::vector meshes_repaired; - meshes_repaired.reserve(volumes.size()); - for (; ivolume < volumes.size(); ++ ivolume) { - on_progress(L("Exporting objects"), 0); - boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_src += ".3mf"; - Model model; - ModelObject *mo = model.add_object(); - mo->add_volume(*volumes[ivolume]); - - // We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back. - // store_3mf currently bakes the volume transformation into the mesh itself. - // If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume - // (which remembers the matrix the whole time), the transformation would be used twice. - // We will therefore set the volume transform on the dummy ModelVolume to identity. - mo->volumes.back()->set_transformation(Geometry::Transformation()); - - mo->add_instance(); - if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) { - boost::filesystem::remove(path_src); - throw Slic3r::RuntimeError(L("Exporting 3MF file failed")); - } - model.clear_objects(); - model.clear_materials(); - boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_dst += ".3mf"; - fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, - [&canceled]() { if (canceled) throw RepairCanceledException(); }); - boost::filesystem::remove(path_src); - // PresetBundle bundle; - on_progress(L("Loading repaired objects"), 80); - DynamicPrintConfig config; - ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent }; - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false); - boost::filesystem::remove(path_dst); - if (! loaded) - throw Slic3r::RuntimeError(L("Import 3MF file failed")); - if (model.objects.size() == 0) - throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object")); - if (model.objects.size() > 1) - throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object")); - if (model.objects.front()->volumes.size() == 0) - throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume")); - if (model.objects.front()->volumes.size() > 1) - throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume")); - meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); - } - for (size_t i = 0; i < volumes.size(); ++ i) { - volumes[i]->set_mesh(std::move(meshes_repaired[i])); - volumes[i]->calculate_convex_hull(); - volumes[i]->invalidate_convex_hull_2d(); - volumes[i]->set_new_unique_id(); - } - model_object.invalidate_bounding_box(); - -- ivolume; - on_progress(L("Repair finished"), 100); - success = true; - finished = true; - } catch (RepairCanceledException & /* ex */) { - canceled = true; - finished = true; - on_progress(L("Repair canceled"), 100); - } catch (std::exception &ex) { - success = false; - finished = true; - on_progress(ex.what(), 100); - } - }); - while (! finished) { - std::unique_lock lock(mtx); - condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); - // decrease progress.percent value to avoid closing of the progress dialog - if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message))) - canceled = true; - else - progress_dialog.Fit(); - progress.updated = false; - } - - if (canceled) { - // Nothing to show. - } else if (success) { - fix_result = ""; - } else { - fix_result = progress.message; - } - worker_thread.join(); - return !canceled; -} - -} // namespace Slic3r - -#endif /* HAS_WIN10SDK */ diff --git a/src/slic3r/Utils/FixModelByWin10.hpp b/src/slic3r/Utils/FixModelByWin10.hpp deleted file mode 100644 index 7beb39e028..0000000000 --- a/src/slic3r/Utils/FixModelByWin10.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef slic3r_GUI_Utils_FixModelByWin10_hpp_ -#define slic3r_GUI_Utils_FixModelByWin10_hpp_ - -#include -#include "../GUI/Widgets/ProgressDialog.hpp" - -class ProgressDialog; - -namespace Slic3r { - -class Model; -class ModelObject; -class Print; - -#ifdef HAS_WIN10SDK - -extern bool is_windows10(); -// returt false, if fixing was canceled -extern bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx,GUI::ProgressDialog &progress_dlg, const wxString &msg_header, std::string &fix_result); - -#else /* HAS_WIN10SDK */ - -inline bool is_windows10() { return false; } -// returt false, if fixing was canceled -inline bool fix_model_by_win10_sdk_gui(ModelObject &, int, GUI::ProgressDialog &, const wxString &, std::string &) { return false; } - -#endif /* HAS_WIN10SDK */ - -} // namespace Slic3r - -#endif /* slic3r_GUI_Utils_FixModelByWin10_hpp_ */