diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index e8cc7df1b..e033009aa 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -5,27 +5,23 @@ #include #include #include -#include + +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include #include "Model.hpp" #include namespace Slic3r { namespace sla { -using VertexFaceMap = std::vector>; +inline bool is_on_floor(const SLAPrintObject &mo) +{ + auto opt_elevation = mo.config().support_object_elevation.getFloat(); + auto opt_padaround = mo.config().pad_around_object.getBool(); -VertexFaceMap create_vertex_face_map(const TriangleMesh &mesh) { - std::vector> vmap(mesh.its.vertices.size()); - - size_t fi = 0; - for (const Vec3i &tri : mesh.its.indices) { - for (int vi = 0; vi < tri.size(); ++vi) { - auto from = vmap[tri(vi)].begin(), to = vmap[tri(vi)].end(); - vmap[tri(vi)].insert(std::lower_bound(from, to, fi), fi); - } - } - - return vmap; + return opt_elevation < EPSILON || opt_padaround; } // Find transformed mesh ground level without copy and with parallel reduce. @@ -41,62 +37,163 @@ double find_ground_level(const TriangleMesh &mesh, return (tr * mesh.its.vertices[vi].template cast()).z(); }; - double zmin = mesh.its.vertices.front().z(); + double zmin = std::numeric_limits::max(); size_t granularity = vsize / threads; return ccr_par::reduce(size_t(0), vsize, zmin, minfn, accessfn, granularity); } -// Try to guess the number of support points needed to support a mesh -double calculate_model_supportedness(const TriangleMesh & mesh, - const Transform3d & tr) +// Get the vertices of a triangle directly in an array of 3 points +std::array get_triangle_vertices(const TriangleMesh &mesh, + size_t faceidx) { - static constexpr double POINTS_PER_UNIT_AREA = 1.; - - if (mesh.its.vertices.empty()) return std::nan(""); - - size_t Nthr = std::thread::hardware_concurrency(); - size_t facesize = mesh.its.indices.size(); - - double zmin = find_ground_level(mesh, tr, Nthr); - - auto accessfn = [&mesh, &tr, zmin](size_t fi) { - - static const Vec3d DOWN = {0., 0., -1.}; - - const auto &face = mesh.its.indices[fi]; - Vec3d p1 = tr * mesh.its.vertices[face(0)].template cast(); - Vec3d p2 = tr * mesh.its.vertices[face(1)].template cast(); - Vec3d p3 = tr * mesh.its.vertices[face(2)].template cast(); - - Vec3d U = p2 - p1; - Vec3d V = p3 - p1; - Vec3d C = U.cross(V); - Vec3d N = C.normalized(); - double area = 0.5 * C.norm(); - - double zlvl = zmin + 0.1; - if (p1.z() <= zlvl && p2.z() <= zlvl && p3.z() <= zlvl) { - // score += area * POINTS_PER_UNIT_AREA; - return 0.; - } - - double phi = 1. - std::acos(N.dot(DOWN)) / PI; -// phi = phi * (phi > 0.5); - - // std::cout << "area: " << area << std::endl; - - return area * POINTS_PER_UNIT_AREA * phi; - }; - - double score = ccr_par::reduce(size_t(0), facesize, 0., std::plus{}, accessfn, facesize / Nthr); - - return score / mesh.its.indices.size(); + const auto &face = mesh.its.indices[faceidx]; + return {Vec3d{mesh.its.vertices[face(0)].cast()}, + Vec3d{mesh.its.vertices[face(1)].cast()}, + Vec3d{mesh.its.vertices[face(2)].cast()}}; } -std::array find_best_rotation(const ModelObject& modelobj, - float accuracy, - std::function statuscb, - std::function stopcond) +std::array get_transformed_triangle(const TriangleMesh &mesh, + const Transform3d & tr, + size_t faceidx) +{ + const auto &tri = get_triangle_vertices(mesh, faceidx); + return {tr * tri[0], tr * tri[1], tr * tri[2]}; +} + +// Get area and normal of a triangle +struct Face { Vec3d normal; double area; }; +inline Face facestats(const std::array &triangle) +{ + Vec3d U = triangle[1] - triangle[0]; + Vec3d V = triangle[2] - triangle[0]; + Vec3d C = U.cross(V); + Vec3d N = C.normalized(); + double area = 0.5 * C.norm(); + + return {N, area}; +} + +inline const Vec3d DOWN = {0., 0., -1.}; +constexpr double POINTS_PER_UNIT_AREA = 1.; + +inline double get_score(const Face &fc) +{ + double phi = 1. - std::acos(fc.normal.dot(DOWN)) / PI; + phi = phi * (phi > 0.5); + phi = phi * phi * phi; + + return fc.area * POINTS_PER_UNIT_AREA * phi; +} + +template +double sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) +{ + double initv = 0.; + auto mergefn = std::plus{}; + size_t grainsize = facecount / Nthreads; + size_t from = 0, to = facecount; + + return ccr_par::reduce(from, to, initv, mergefn, accessfn, grainsize); +} + +// Try to guess the number of support points needed to support a mesh +double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + auto accessfn = [&mesh, &tr](size_t fi) { + Face fc = facestats(get_transformed_triangle(mesh, tr, fi)); + return get_score(fc); + }; + + size_t facecount = mesh.its.indices.size(); + size_t Nthreads = std::thread::hardware_concurrency(); + return sum_score(accessfn, facecount, Nthreads) / facecount; +} + +double get_model_supportedness_onfloor(const TriangleMesh &mesh, + const Transform3d & tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + size_t Nthreads = std::thread::hardware_concurrency(); + + double zmin = find_ground_level(mesh, tr, Nthreads); + double zlvl = zmin + 0.1; // Set up a slight tolerance from z level + + auto accessfn = [&mesh, &tr, zlvl](size_t fi) { + std::array tri = get_transformed_triangle(mesh, tr, fi); + Face fc = facestats(tri); + + if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl) + return -fc.area * POINTS_PER_UNIT_AREA; + + return get_score(fc); + }; + + size_t facecount = mesh.its.indices.size(); + return sum_score(accessfn, facecount, Nthreads) / facecount; +} + +using XYRotation = std::array; + +// prepare the rotation transformation +Transform3d to_transform3d(const XYRotation &rot) +{ + Transform3d rt = Transform3d::Identity(); + rt.rotate(Eigen::AngleAxisd(rot[1], Vec3d::UnitY())); + rt.rotate(Eigen::AngleAxisd(rot[0], Vec3d::UnitX())); + return rt; +} + +XYRotation from_transform3d(const Transform3d &tr) +{ + Vec3d rot3d = Geometry::Transformation {tr}.get_rotation(); + return {rot3d.x(), rot3d.y()}; +} + +// Find the best score from a set of function inputs. Evaluate for every point. +template +std::array find_min_score(Fn &&fn, Cmp &&cmp, It from, It to) +{ + std::array ret; + + double score = std::numeric_limits::max(); + + for (auto it = from; it != to; ++it) { + double sc = fn(*it); + if (cmp(sc, score)) { + score = sc; + ret = *it; + } + } + + return ret; +} + +// collect the rotations for each face of the convex hull +std::vector get_chull_rotations(const TriangleMesh &mesh) +{ + TriangleMesh chull = mesh.convex_hull_3d(); + chull.require_shared_vertices(); + + size_t facecount = chull.its.indices.size(); + auto inputs = reserve_vector(facecount); + + for (size_t fi = 0; fi < facecount; ++fi) { + Face fc = facestats(get_triangle_vertices(chull, fi)); + + auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN); + inputs.emplace_back(from_transform3d(Transform3d::Identity() * q)); + } + + return inputs; +} + +XYRotation find_best_rotation(const SLAPrintObject & po, + float accuracy, + std::function statuscb, + std::function stopcond) { static const unsigned MAX_TRIES = 10000; @@ -105,10 +202,10 @@ std::array find_best_rotation(const ModelObject& modelobj, // We will use only one instance of this converted mesh to examine different // rotations - TriangleMesh mesh = modelobj.raw_mesh(); + TriangleMesh mesh = po.model_object()->raw_mesh(); mesh.require_shared_vertices(); - // For current iteration number + // To keep track of the number of iterations unsigned status = 0; // The maximum number of iterations @@ -117,51 +214,66 @@ std::array find_best_rotation(const ModelObject& modelobj, // call status callback with zero, because we are at the start statuscb(status); - // So this is the object function which is called by the solver many times - // It has to yield a single value representing the current score. We will - // call the status callback in each iteration but the actual value may be - // the same for subsequent iterations (status goes from 0 to 100 but - // iterations can be many more) - auto objfunc = [&mesh, &status, &statuscb, &stopcond, max_tries] - (const opt::Input<2> &in) - { - std::cout << "in: " << in[0] << " " << in[1] << std::endl; - - // prepare the rotation transformation - Transform3d rt = Transform3d::Identity(); - rt.rotate(Eigen::AngleAxisd(in[1], Vec3d::UnitY())); - rt.rotate(Eigen::AngleAxisd(in[0], Vec3d::UnitX())); - - double score = sla::calculate_model_supportedness(mesh, rt); - std::cout << score << std::endl; - + auto statusfn = [&statuscb, &status, max_tries] { // report status - if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) ); - - return score; + statuscb(unsigned(++status * 100.0/max_tries) ); }; - // Firing up the optimizer. - opt::Optimizer solver(opt::StopCriteria{} - .max_iterations(max_tries) - .rel_score_diff(1e-6) - .stop_condition(stopcond), - std::sqrt(max_tries)/*grid size*/); + // Different search methods have to be used depending on the model elevation + if (is_on_floor(po)) { - // We are searching rotations around only two axes x, y. Thus the - // problem becomes a 2 dimensional optimization task. - // We can specify the bounds for a dimension in the following way: - auto b = opt::Bound{-PI, PI}; + // If the model can be placed on the bed directly, we only need to + // check the 3D convex hull face rotations. - // Now we start the optimization process with initial angles (0, 0) - auto result = solver.to_min().optimize(objfunc, opt::initvals({0., 0.}), - opt::bounds({b, b})); + auto inputs = get_chull_rotations(mesh); - // Save the result and fck off - rot[0] = std::get<0>(result.optimum); - rot[1] = std::get<1>(result.optimum); + auto cmpfn = [](double a, double b) { return a < b; }; + auto objfn = [&mesh, &statusfn](const XYRotation &rot) { + statusfn(); + // We actually need the reverserotation to make the object lie on + // this face + Transform3d tr = to_transform3d(rot); + return get_model_supportedness_onfloor(mesh, tr); + }; + + rot = find_min_score<2>(objfn, cmpfn, inputs.begin(), inputs.end()); + } else { + + // Preparing the optimizer. + size_t grid_size = std::sqrt(max_tries); + opt::Optimizer solver(opt::StopCriteria{} + .max_iterations(max_tries) + .stop_condition(stopcond), + grid_size); + + // We are searching rotations around only two axes x, y. Thus the + // problem becomes a 2 dimensional optimization task. + // We can specify the bounds for a dimension in the following way: + auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} }); + + auto result = solver.to_min().optimize( + [&mesh, &statusfn] (const XYRotation &rot) + { + statusfn(); + return get_model_supportedness(mesh, to_transform3d(rot)); + }, opt::initvals({0., 0.}), bounds); + + // Save the result and fck off + rot = result.optimum; + + std::cout << "best score: " << result.score << std::endl; + } return rot; } +double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr) +{ + TriangleMesh mesh = po.model_object()->raw_mesh(); + mesh.require_shared_vertices(); + + return is_on_floor(po) ? get_model_supportedness_onfloor(mesh, tr) : + get_model_supportedness(mesh, tr); +} + }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index 583703203..4fa529600 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -4,9 +4,11 @@ #include #include +#include + namespace Slic3r { -class ModelObject; +class SLAPrintObject; namespace sla { @@ -26,13 +28,16 @@ namespace sla { * @return Returns the rotations around each axis (x, y, z) */ std::array find_best_rotation( - const ModelObject& modelobj, + const SLAPrintObject& modelobj, float accuracy = 1.0f, std::function statuscb = [] (unsigned) {}, std::function stopcond = [] () { return false; } ); -} -} +double get_model_supportedness(const SLAPrintObject &mesh, + const Transform3d & tr); + +} // namespace sla +} // namespace Slic3r #endif // SLAROTFINDER_HPP diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 8365875d9..77366c633 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -200,6 +200,8 @@ void GLGizmoRotate::on_render_for_picking() const glsafe(::glPopMatrix()); } + + GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, State & state, const Alignment &alignment) @@ -215,20 +217,26 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, y = std::min(y, alignment.bottom_limit - win_h); ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - ImGui::SliderFloat(_L("Accuracy").c_str(), &state.accuracy, 0.01f, 1.f, "%.1f"); + static constexpr const char * button_txt = L("Optimize orientation"); + static constexpr const char * slider_txt = L("Accuracy"); - if (imgui->button(_L("Optimize orientation"))) { + float button_width = imgui->calc_text_size(_(button_txt)).x; + ImGui::PushItemWidth(100.); + //if (imgui->button(_(button_txt))) { + if (ImGui::ArrowButton(_(button_txt).c_str(), ImGuiDir_Down)){ std::cout << "Blip" << std::endl; } + ImGui::SliderFloat(_(slider_txt).c_str(), &state.accuracy, 0.01f, 1.f, "%.1f"); + static const std::vector options = { _L("Least supports").ToStdString(), _L("Suface quality").ToStdString() }; - if (imgui->combo(_L("Choose method"), options, state.method) ) { - std::cout << "method: " << state.method << std::endl; - } +// if (imgui->combo(_L("Choose method"), options, state.method) ) { +// std::cout << "method: " << state.method << std::endl; +// } } @@ -243,18 +251,10 @@ void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limi if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) return; -// m_rotoptimizewin_state.mobj = ; - RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}}; +// TODO: -// if ((last_h != win_h) || (last_y != y)) -// { -// // ask canvas for another frame to render the window in the correct position -// m_parent.request_extra_frame(); -// if (last_h != win_h) -// last_h = win_h; -// if (last_y != y) -// last_y = y; -// } +// m_rotoptimizewin_state.mobj = ?; +// RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}}; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index c547dfbc0..c418c4b31 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -136,12 +136,14 @@ protected: } void on_render_input_window(float x, float y, float bottom_limit) override; + private: class RotoptimzeWindow { ImGuiWrapper *m_imgui = nullptr; public: + struct State { enum Metods { mMinSupportPoints, mLegacy }; @@ -152,7 +154,10 @@ private: struct Alignment { float x, y, bottom_limit; }; - RotoptimzeWindow(ImGuiWrapper *imgui, State &settings, const Alignment &bottom_limit); + RotoptimzeWindow(ImGuiWrapper * imgui, + State & state, + const Alignment &bottom_limit); + ~RotoptimzeWindow(); RotoptimzeWindow(const RotoptimzeWindow&) = delete; diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 3fd86b13f..91a67cfa2 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -4,6 +4,7 @@ #include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/MinAreaBoundingBox.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/SLAPrint.hpp" #include "slic3r/GUI/Plater.hpp" @@ -15,9 +16,26 @@ void RotoptimizeJob::process() if (obj_idx < 0) { return; } ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; + const SLAPrintObject *po = m_plater->sla_print().objects()[size_t(obj_idx)]; + + if (!o || !po) return; + + TriangleMesh mesh = o->raw_mesh(); + mesh.require_shared_vertices(); + +// for (auto inst : o->instances) { +// Transform3d tr = Transform3d::Identity(); +// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Z), Vec3d::UnitZ())); +// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Y), Vec3d::UnitY())); +// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(X), Vec3d::UnitX())); + +// double score = sla::get_model_supportedness(*po, tr); + +// std::cout << "Model supportedness before: " << score << std::endl; +// } auto r = sla::find_best_rotation( - *o, + *po, 1.f, [this](unsigned s) { if (s < 100)