diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index b84921279..daa3154d7 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -58,29 +58,6 @@ T sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) return execution::reduce(ex_tbb, from, to, initv, mergefn, accessfn, grainsize); } -// Try to guess the number of support points needed to support a mesh -double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr) -{ - if (mesh.its.vertices.empty()) return std::nan(""); - - auto accessfn = [&mesh, &tr](size_t fi) { - auto triangle = get_transformed_triangle(mesh, tr, fi); - Vec3f U = triangle[1] - triangle[0]; - Vec3f V = triangle[2] - triangle[0]; - Vec3f C = U.cross(V); - - // We should score against the alignment with the reference planes - return scaled(std::abs(C.dot(Vec3f::UnitX())) + - std::abs(C.dot(Vec3f::UnitY()))); - }; - - size_t facecount = mesh.its.indices.size(); - size_t Nthreads = std::thread::hardware_concurrency(); - double S = unscaled(sum_score(accessfn, facecount, Nthreads)); - - return S / facecount; -} - // Get area and normal of a triangle struct Facestats { Vec3f normal; @@ -96,18 +73,45 @@ struct Facestats { } }; +// Try to guess the number of support points needed to support a mesh +double get_misalginment_score(const TriangleMesh &mesh, const Transform3f &tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + auto accessfn = [&mesh, &tr](size_t fi) { + Facestats fc{get_transformed_triangle(mesh, tr, fi)}; + + float score = fc.area + * (std::abs(fc.normal.dot(Vec3f::UnitX())) + + std::abs(fc.normal.dot(Vec3f::UnitY())) + + std::abs(fc.normal.dot(Vec3f::UnitZ()))); + + // We should score against the alignment with the reference planes + return scaled(score); + }; + + size_t facecount = mesh.its.indices.size(); + size_t Nthreads = std::thread::hardware_concurrency(); + double S = unscaled(sum_score(accessfn, facecount, Nthreads)); + + return S / facecount; +} + // The score function for a particular face inline double get_supportedness_score(const Facestats &fc) { // Simply get the angle (acos of dot product) between the face normal and // the DOWN vector. - float phi = 1. - std::acos(fc.normal.dot(DOWN)) / float(PI); + float cosphi = fc.normal.dot(DOWN); + float phi = 1.f - std::acos(cosphi) / float(PI); - // Only consider faces that have slopes below 90 deg: - phi = phi * (phi >= 0.5f); + // Phi is raised by 1.0 to not be less than zero when squared in the next + // step. If phi is greater than 0.5 (slope is > 90 deg) make phi zero + // to not skip this face in the overall score. + phi = (1.f + phi) * (phi >= 0.5f); // Make the huge slopes more significant than the smaller slopes - phi = phi * phi * phi; + phi = phi * phi; // Multiply with the area of the current face return fc.area * POINTS_PER_UNIT_AREA * phi; @@ -121,7 +125,7 @@ double get_supportedness_score(const TriangleMesh &mesh, const Transform3f &tr) auto accessfn = [&mesh, &tr](size_t fi) { Facestats fc{get_transformed_triangle(mesh, tr, fi)}; - return get_supportedness_score(fc); + return scaled(get_supportedness_score(fc)); }; size_t facecount = mesh.its.indices.size(); @@ -164,7 +168,7 @@ float get_supportedness_onfloor_score(const TriangleMesh &mesh, Facestats fc{tri}; if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl) - return -fc.area * POINTS_PER_UNIT_AREA; + return -2 * fc.area * POINTS_PER_UNIT_AREA; return get_supportedness_score(fc); }; @@ -283,6 +287,26 @@ std::array find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn) } // namespace +// Assemble the mesh with the correct transformation to be used in rotation +// optimization. +TriangleMesh get_mesh_to_rotate(const ModelObject &mo) +{ + TriangleMesh mesh = mo.raw_mesh(); + mesh.require_shared_vertices(); + + ModelInstance *mi = mo.instances[0]; + auto rotation = Vec3d::Zero(); + auto offset = Vec3d::Zero(); + Transform3d trafo_instance = Geometry::assemble_transform(offset, + rotation, + mi->get_scaling_factor(), + mi->get_mirror()); + + mesh.transform(trafo_instance); + + return mesh; +} + Vec2d find_best_misalignment_rotation(const ModelObject & mo, const RotOptimizeParams ¶ms) { @@ -293,8 +317,7 @@ Vec2d find_best_misalignment_rotation(const ModelObject & mo, // We will use only one instance of this converted mesh to examine different // rotations - TriangleMesh mesh = mo.raw_mesh(); - mesh.require_shared_vertices(); + TriangleMesh mesh = get_mesh_to_rotate(mo); // To keep track of the number of iterations int status = 0; @@ -350,8 +373,7 @@ Vec2d find_least_supports_rotation(const ModelObject & mo, // We will use only one instance of this converted mesh to examine different // rotations - TriangleMesh mesh = mo.raw_mesh(); - mesh.require_shared_vertices(); + TriangleMesh mesh = get_mesh_to_rotate(mo); // To keep track of the number of iterations unsigned status = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index a495db4f1..417a6a644 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -497,9 +497,6 @@ void GLGizmoRotate3D::on_render() m_gizmos[Z].render(); } -const char * GLGizmoRotate3D::RotoptimzeWindow::options[RotoptimizeJob::get_methods_count()]; -bool GLGizmoRotate3D::RotoptimzeWindow::options_valid = false; - GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, State & state, const Alignment &alignment) @@ -517,19 +514,24 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, ImGui::PushItemWidth(200.f); - size_t methods_cnt = RotoptimizeJob::get_methods_count(); - if (!options_valid) { - for (size_t i = 0; i < methods_cnt; ++i) - options[i] = RotoptimizeJob::get_method_names()[i].c_str(); + if (ImGui::BeginCombo(_L("Choose goal").c_str(), RotoptimizeJob::get_method_name(state.method_id).c_str())) { + for (size_t i = 0; i < RotoptimizeJob::get_methods_count(); ++i) { + if (ImGui::Selectable(RotoptimizeJob::get_method_name(i).c_str())) { + state.method_id = i; + wxGetApp().app_config->set("sla_auto_rotate", + "method_id", + std::to_string(state.method_id)); + } - options_valid = true; + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", RotoptimizeJob::get_method_description(i).c_str()); + } + + ImGui::EndCombo(); } - int citem = state.method_id; - if (ImGui::Combo(_L("Choose goal").c_str(), &citem, options, methods_cnt) ) { - state.method_id = citem; - wxGetApp().app_config->set("sla_auto_rotate", "method_id", std::to_string(state.method_id)); - } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", RotoptimizeJob::get_method_description(state.method_id).c_str()); ImGui::Separator(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index a51f900bf..3245c4dbe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -138,10 +138,6 @@ private: class RotoptimzeWindow { ImGuiWrapper *m_imgui = nullptr; - - static const char * options []; - static bool options_valid; - public: struct State { diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index 3144f3c3e..bb4310e63 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -15,14 +15,21 @@ class RotoptimizeJob : public PlaterJob using FindFn = std::function; - struct FindMethod { std::string name; FindFn findfn; }; + struct FindMethod { std::string name; FindFn findfn; std::string descr; }; - static inline const FindMethod Methods[] = { - { L("Best surface quality"), sla::find_best_misalignment_rotation }, - { L("Least supports"), sla::find_least_supports_rotation }, - // Just a min area bounding box that is done for all methods anyway. - { L("Z axis only"), nullptr } - }; + static inline const FindMethod Methods[] + = {{L("Best surface quality"), + sla::find_best_misalignment_rotation, + L("Optimize object rotation for best surface quality.")}, + {L("Least supports"), + sla::find_least_supports_rotation, + L("Optimize object rotation to have minimum amount of overhangs needing support " + "structures.\nNote that this method will try to find the best surface of the object " + "for touching the print bed if no elevation is set.")}, + // Just a min area bounding box that is done for all methods anyway. + {L("Z axis only"), + nullptr, + L("Rotate the object only in Z axis to have the smallest bounding box.")}}; size_t m_method_id = 0; float m_accuracy = 0.75; @@ -52,20 +59,15 @@ public: void finalize() override; static constexpr size_t get_methods_count() { return std::size(Methods); } - static const auto & get_method_names() + + static std::string get_method_name(size_t i) { - static bool m_method_names_valid = false; - static std::array m_method_names; + return _utf8(Methods[i].name); + } - if (!m_method_names_valid) { - - for (size_t i = 0; i < std::size(Methods); ++i) - m_method_names[i] = _utf8(Methods[i].name); - - m_method_names_valid = true; - } - - return m_method_names; + static std::string get_method_description(size_t i) + { + return _utf8(Methods[i].descr); } };