diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 7b3476d711..c87d16be9d 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -700,6 +700,16 @@ wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu) return menu_item; } + +wxMenuItem* MenuFactory::append_menu_item_fix_model_mesh(wxMenu* menu) +{ + wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix model mesh"), "", + [](wxCommandEvent&) { obj_list()->fix_model_mesh(); }, "", menu, + []() {return plater()->can_fix_model_mesh(); }, m_parent); + + return menu_item; +} + wxMenuItem* MenuFactory::append_menu_item_simplify(wxMenu* menu) { wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Simplify model"), "", @@ -923,6 +933,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu) append_menu_item_scale_selection_to_fit_print_volume(menu); append_menu_item_fix_through_netfabb(menu); + append_menu_item_fix_model_mesh(menu); append_menu_item_simplify(menu); append_menu_items_mirror(menu); } diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 0c478a97b2..4b36137c88 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -92,6 +92,7 @@ private: wxMenuItem* append_menu_item_printable(wxMenu* menu); void append_menu_items_osx(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); + wxMenuItem* append_menu_item_fix_model_mesh(wxMenu* menu); wxMenuItem* append_menu_item_simplify(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu); void append_menu_item_reload_from_disk(wxMenu* menu); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4370a2f644..71a74dcb82 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -28,6 +28,7 @@ #include #include "slic3r/Utils/FixModelByWin10.hpp" +#include "slic3r/Utils/FixModelMesh.hpp" #ifdef __WXMSW__ #include "wx/uiaction.h" @@ -4164,6 +4165,117 @@ void ObjectList::fix_through_netfabb() plater->get_notification_manager()->push_notification(NotificationType::NetfabbFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, boost::nowide::narrow(msg)); } + +void ObjectList::fix_model_mesh() +{ + // 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. + if (!wxGetApp().plater()->canvas3D()->get_gizmos_manager().check_gizmos_closed_except(GLGizmosManager::Undefined)) + return; + + // model_name + std::vector succes_models; + // model_name failing reason + std::vector> failed_models; + + std::vector obj_idxs, vol_idxs; + get_selection_indexes(obj_idxs, vol_idxs); + + std::vector model_names; + + // fill names of models to repairing + if (vol_idxs.empty()) { + for (int obj_idx : obj_idxs) + model_names.push_back(object(obj_idx)->name); + } + else { + ModelObject* obj = object(obj_idxs.front()); + for (int vol_idx : vol_idxs) + model_names.push_back(obj->volumes[vol_idx]->name); + } + + auto plater = wxGetApp().plater(); + + auto fix_and_update_progress = [this, plater, model_names](const int obj_idx, const int vol_idx, + int model_idx, + wxProgressDialog& progress_dlg, + std::vector& succes_models, + std::vector>& failed_models) + { + const std::string& model_name = model_names[model_idx]; + wxString msg = _L("Repairing model"); + if (model_names.size() == 1) + msg += ": " + from_u8(model_name) + "\n"; + else { + msg += ":\n"; + for (int i = 0; i < int(model_names.size()); ++i) + msg += (i == model_idx ? " > " : " ") + from_u8(model_names[i]) + "\n"; + msg += "\n"; + } + + plater->clear_before_change_mesh(obj_idx); + std::string res; + if (!fix_by_raycasting(*(object(obj_idx)), vol_idx, progress_dlg, msg, res)) + return false; + wxGetApp().plater()->changed_mesh(obj_idx); + + plater->changed_mesh(obj_idx); + + if (res.empty()) + succes_models.push_back(model_name); + else + failed_models.push_back({ model_name, res }); + + update_item_error_icon(obj_idx, vol_idx); + update_info_items(obj_idx); + + return true; + }; + + Plater::TakeSnapshot snapshot(plater, _L("Fix model mesh")); + + // Open a progress dialog. + wxProgressDialog progress_dlg(_L("Fixing model mesh"), "", 100, find_toplevel_parent(plater), + wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); + int model_idx{ 0 }; + if (vol_idxs.empty()) { + int vol_idx{ -1 }; + for (int obj_idx : obj_idxs) { + if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models)) + break; + model_idx++; + } + } + else { + int obj_idx{ obj_idxs.front() }; + for (int vol_idx : vol_idxs) { + if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models)) + break; + model_idx++; + } + } + // Close the progress dialog + progress_dlg.Update(100, ""); + + // Show info notification + wxString msg; + wxString bullet_suf = "\n - "; + if (!succes_models.empty()) { + msg = _L_PLURAL("The following model was repaired successfully", "The following models were repaired successfully", succes_models.size()) + ":"; + for (auto& model : succes_models) + msg += bullet_suf + from_u8(model); + msg += "\n\n"; + } + if (!failed_models.empty()) { + msg += _L_PLURAL("Folowing model repair failed", "Folowing models repair failed", failed_models.size()) + ":\n"; + for (auto& model : failed_models) + msg += bullet_suf + from_u8(model.first) + ": " + _(model.second); + } + if (msg.IsEmpty()) + msg = _L("Repairing was canceled"); + plater->get_notification_manager()->push_notification(NotificationType::NetfabbFinished, NotificationManager::NotificationLevel::PrintInfoShortNotificationLevel, boost::nowide::narrow(msg)); +} + void ObjectList::simplify() { auto plater = wxGetApp().plater(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index b9b816b7be..1f31ae1c26 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -368,6 +368,7 @@ public: void split_instances(); void rename_item(); void fix_through_netfabb(); + void fix_model_mesh(); void simplify(); void update_item_error_icon(const int obj_idx, int vol_idx) const ; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0456936e44..f1178ddb7b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1910,6 +1910,7 @@ struct Plater::priv bool can_arrange() const; bool can_layers_editing() const; bool can_fix_through_netfabb() const; + bool can_fix_model_mesh() const; bool can_simplify() const; bool can_set_instance_to_object() const; bool can_mirror() const; @@ -4857,6 +4858,13 @@ bool Plater::priv::can_fix_through_netfabb() const #endif // FIX_THROUGH_NETFABB_ALWAYS } +bool Plater::priv::can_fix_model_mesh() const +{ + std::vector obj_idxs, vol_idxs; + sidebar->obj_list()->get_selection_indexes(obj_idxs, vol_idxs); + return ! obj_idxs.empty() || ! vol_idxs.empty(); +} + bool Plater::priv::can_simplify() const { // is object for simplification selected @@ -7075,6 +7083,7 @@ 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_model_mesh() const { return p->can_fix_model_mesh(); } bool Plater::can_simplify() const { return p->can_simplify(); } bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 79e23a7a99..da1ef2234e 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -351,6 +351,7 @@ public: bool can_decrease_instances() const; bool can_set_instance_to_object() const; bool can_fix_through_netfabb() const; + bool can_fix_model_mesh() const; bool can_simplify() const; bool can_split_to_objects() const; bool can_split_to_volumes() const; diff --git a/src/slic3r/Utils/FixModelMesh.hpp b/src/slic3r/Utils/FixModelMesh.hpp new file mode 100644 index 0000000000..016e27176c --- /dev/null +++ b/src/slic3r/Utils/FixModelMesh.hpp @@ -0,0 +1,154 @@ +#ifndef SRC_SLIC3R_UTILS_FIXMODELMESH_HPP_ +#define SRC_SLIC3R_UTILS_FIXMODELMESH_HPP_ + +#include +#include "libslic3r/AABBTreeIndirect.hpp" +#include "tbb/parallel_for.h" +#include "tbb/blocked_range.h" +#include "libigl/igl/copyleft/marching_cubes.h" +#include "libigl/igl/voxel_grid.h" + +class wxProgressDialog; + +namespace Slic3r { + +namespace detail { + +Vec3f sample_sphere_uniform(const Vec2f &samples) { + float term1 = 2.0f * float(PI) * samples.x(); + float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); + return {cos(term1) * term2, sin(term1) * term2, + 1.0f - 2.0f * samples.y()}; +} + +indexed_triangle_set fix_model_volume_mesh(const TriangleMesh &mesh) { + float thickness = 2.0f; + float resolution = 0.2f; + + //prepare uniform samples of a sphere + size_t sqrt_sample_count = 8; + float step_size = 1.0f / sqrt_sample_count; + std::vector precomputed_sample_directions( + sqrt_sample_count * sqrt_sample_count); + for (size_t x_idx = 0; x_idx < sqrt_sample_count; ++x_idx) { + float sample_x = x_idx * step_size + step_size / 2.0; + for (size_t y_idx = 0; y_idx < sqrt_sample_count; ++y_idx) { + size_t dir_index = x_idx * sqrt_sample_count + y_idx; + float sample_y = y_idx * step_size + step_size / 2.0; + precomputed_sample_directions[dir_index] = sample_sphere_uniform( { sample_x, sample_y }); + } + } + + indexed_triangle_set its = mesh.its; + float max_size = mesh.bounding_box().size().maxCoeff(); + int samples = max_size / resolution; + // create grid + Eigen::MatrixXf grid_points; + Eigen::RowVector3i res; + + const BoundingBoxf3 &slicer_bbox = mesh.bounding_box(); + Eigen::AlignedBox eigen_box(slicer_bbox.min.cast(), slicer_bbox.max.cast()); + + std::cout << "building voxel grid " << std::endl; + igl::voxel_grid(eigen_box, samples, 1, grid_points, res); + + auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(its.vertices, its.indices); + Eigen::VectorXf grid_values; + grid_values.resize(grid_points.size()); + + std::cout << "g info: " << grid_points.size() << std::endl; + std::cout << "g info: " << grid_points.rows() << std::endl; + std::cout << "g info: " << grid_points.cols() << std::endl; + + std::cout << "raycasting " << std::endl; + + tbb::parallel_for( + tbb::blocked_range(0, grid_points.rows()), [&](tbb::blocked_range r) { + for (size_t index = r.begin(); index < r.end(); ++index) { + Vec3f origin = grid_points.row(index); + float &value = grid_values(index); + + size_t hit_idx; + Vec3f hit_point; + bool apply_bonus = false; + float distance = sqrt(AABBTreeIndirect::squared_distance_to_indexed_triangle_set(its.vertices, its.indices, tree, origin, hit_idx, hit_point)); + Vec3f face_normal = its_face_normal(its, hit_idx); + if ((hit_point - origin).dot(face_normal) > 0 && distance < thickness) { + apply_bonus = true; + } + + igl::Hit hit; + size_t inside_hits = 0; + for (const Vec3f &dir : precomputed_sample_directions) { + if (AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices, tree, + Vec3d(origin.cast()), + Vec3d(dir.cast()), hit)) { + Vec3f face_normal = its_face_normal(its, hit.id); + if (dir.dot(face_normal) > 0) { + inside_hits += 1; + } + } + } + + if (inside_hits > precomputed_sample_directions.size() / 2) { + value = -distance; + if (apply_bonus) { + value = -2.0f * distance; + } + } else { + value = distance; + } + } + } + ); + + std::cout << "marching cubes " << std::endl; + Eigen::MatrixXf vertices; + Eigen::MatrixXi faces; + igl::copyleft::marching_cubes(grid_values, grid_points, res.x(), res.y(), res.z(), vertices, faces); + + std::cout << "vertices info: " << vertices.size() << std::endl; + std::cout << "vertices info: " << vertices.rows() << std::endl; + std::cout << "vertices info: " << vertices.cols() << std::endl; + + indexed_triangle_set fixed_mesh; + fixed_mesh.vertices.resize(vertices.rows()); + fixed_mesh.indices.resize(faces.rows()); + + for (int v = 0; v < vertices.rows(); ++v) { + fixed_mesh.vertices[v] = vertices.row(v); + } + + for (int f = 0; f < faces.rows(); ++f) { + fixed_mesh.indices[f] = faces.row(f); + } + + return fixed_mesh; +} +} + +bool fix_by_raycasting(ModelObject &model_object, int volume_idx, wxProgressDialog &progress_dlg, + const wxString &msg_header, std::string &fix_result) { + + std::vector volumes; + if (volume_idx == -1) { + volumes = model_object.volumes; + } else { + volumes.emplace_back(model_object.volumes[volume_idx]); + } + + for (ModelVolume *mv : volumes) { + auto mesh = mv->mesh(); + mv->set_mesh(detail::fix_model_volume_mesh(mesh)); + std::cout << "update mv " << std::endl; + mv->calculate_convex_hull(); + mv->set_new_unique_id(); + } + model_object.invalidate_bounding_box(); + + return true; +} + +} + +#endif /* SRC_SLIC3R_UTILS_FIXMODELMESH_HPP_ */