From 9fce0ce3a614295331391ed1f6b4d05018a21600 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 1 Oct 2021 18:07:30 +0200 Subject: [PATCH 01/10] Fix compile issues and overlapping polygon fails --- src/libslic3r/Geometry.cpp | 214 +++++++++++++++++++++++++++- src/libslic3r/Geometry.hpp | 2 + tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_geometry.cpp | 222 ++++++++++++++++++++++++++++++ 4 files changed, 438 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 3214432049..10c617c602 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -20,6 +20,12 @@ #include #include +#if defined(_MSC_VER) && defined(__clang__) +#define BOOST_NO_CXX17_HDR_STRING_VIEW +#endif + +#include + #ifdef SLIC3R_DEBUG #include "SVG.hpp" #endif @@ -1543,4 +1549,210 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) return (axis.z() < 0) ? -angle : angle; } -} } +namespace rotcalip { + +using int256_t = boost::multiprecision::int256_t; +using int128_t = boost::multiprecision::int128_t; + +inline int128_t magnsq(const Point &p) +{ + return int128_t(p.x()) * p.x() + int64_t(p.y()) * p.y(); +} + +inline int128_t dot(const Point &a, const Point &b) +{ + return int128_t(a.x()) * b.x() + int64_t(a.y()) * b.y(); +} + +template +inline Scalar dotperp(const Point &a, const Point &b) +{ + return Scalar(a.x()) * b.y() - Scalar(a.y()) * b.x(); +} + +using boost::multiprecision::abs; + +// Compares the angle enclosed by vectors dir and dirA (alpha) with the angle +// enclosed by -dir and dirB (beta). Returns -1 if alpha is less than beta, 0 +// if they are equal and 1 if alpha is greater than beta. Note that dir is +// reversed for beta, because it represents the opposite side of a caliper. +int cmp_angles(const Point &dir, const Point &dirA, const Point &dirB) { + int128_t dotA = dot(dir, dirA); + int128_t dotB = dot(-dir, dirB); + int256_t dcosa = int256_t(magnsq(dirB)) * int256_t(abs(dotA)) * dotA; + int256_t dcosb = int256_t(magnsq(dirA)) * int256_t(abs(dotB)) * dotB; + int256_t diff = dcosa - dcosb; + + return diff > 0? -1 : (diff < 0 ? 1 : 0); +} + +// A helper class to navigate on a polygon. Given a vertex index, one can +// get the edge belonging to that vertex, the coordinates of the vertex, the +// next and previous edges. Stuff that is needed in the rotating calipers algo. +class Idx +{ + size_t m_idx; + const Polygon *m_poly; +public: + explicit Idx(const Polygon &p): m_idx{0}, m_poly{&p} {} + explicit Idx(size_t idx, const Polygon &p): m_idx{idx}, m_poly{&p} {} + + size_t idx() const { return m_idx; } + void set_idx(size_t i) { m_idx = i; } + size_t next() const { return (m_idx + 1) % m_poly->size(); } + size_t inc() { return m_idx = (m_idx + 1) % m_poly->size(); } + Point prev_dir() const { + return pt() - (*m_poly)[(m_idx + m_poly->size() - 1) % m_poly->size()]; + } + + const Point &pt() const { return (*m_poly)[m_idx]; } + const Point dir() const { return (*m_poly)[next()] - pt(); } + const Point next_dir() const + { + return (*m_poly)[(m_idx + 2) % m_poly->size()] - (*m_poly)[next()]; + } + const Polygon &poly() const { return *m_poly; } +}; + +enum class AntipodalVisitMode { Full, EdgesOnly }; + +// Visit all antipodal pairs starting from the initial ia, ib pair which +// has to be a valid antipodal pair (not checked). fn is called for every +// antipodal pair encountered including the initial one. +// The callback Fn has a signiture of bool(size_t i, size_t j, const Point &dir) +// where i,j are the vertex indices of the antipodal pair and dir is the +// direction of the calipers touching the i vertex. +template +void visit_antipodals (Idx& ia, Idx &ib, Fn &&fn) +{ + // Set current caliper direction to be the lower edge angle from X axis + int cmp = cmp_angles(ia.prev_dir(), ia.dir(), ib.dir()); + Idx *current = cmp <= 0 ? &ia : &ib, *other = cmp <= 0 ? &ib : &ia; + bool visitor_continue = true; + + size_t a_start = ia.idx(), b_start = ib.idx(); + bool a_finished = false, b_finished = false; + + while (visitor_continue && !(a_finished && b_finished)) { + Point current_dir_a = current == &ia ? current->dir() : -current->dir(); + visitor_continue = fn(ia.idx(), ib.idx(), current_dir_a); + + // Parallel edges encountered. An additional pair of antipodals + // can be yielded. + if constexpr (mode == AntipodalVisitMode::Full) + if (cmp == 0 && visitor_continue) { + visitor_continue = fn(current == &ia ? ia.idx() : ia.next(), + current == &ib ? ib.idx() : ib.next(), + current_dir_a); + } + + cmp = cmp_angles(current->dir(), current->next_dir(), other->dir()); + + current->inc(); + if (cmp > 0) { + std::swap(current, other); + } + + if (ia.idx() == a_start) a_finished = true; + if (ib.idx() == b_start) b_finished = true; + } +} + +} // namespace rotcalip + +bool intersects(const Polygon &A, const Polygon &B) +{ + using namespace rotcalip; + + // Establish starting antipodals as extremes in XY plane. Use the + // easily obtainable bounding boxes to check if A and B is disjoint + // and return false if the are. + + struct BB + { + size_t xmin = 0, xmax = 0, ymin = 0, ymax = 0; + const Polygon &P; + static bool cmpy(const Point &l, const Point &u) + { + return l.y() < u.y() || (l.y() == u.y() && l.x() < u.x()); + } + + BB(const Polygon &poly): P{poly} + { + for (size_t i = 0; i < P.size(); ++i) { + if (P[i] < P[xmin]) xmin = i; + if (P[xmax] < P[i]) xmax = i; + if (cmpy(P[i], P[ymin])) ymin = i; + if (cmpy(P[ymax], P[i])) ymax = i; + } + } + }; + + BB bA{A}, bB{B}; + BoundingBox bbA{{A[bA.xmin].x(), A[bA.ymin].y()}, {A[bA.xmax].x(), A[bA.ymax].y()}}; + BoundingBox bbB{{B[bB.xmin].x(), B[bB.ymin].y()}, {B[bB.xmax].x(), B[bB.ymax].y()}}; + + if (!bbA.overlap(bbB)) + return false; + + // Establish starting antipodals as extreme vertex pairs in X or Y direction + // which reside on different polygons. If no such pair is found, the two + // polygons are certainly not disjoint. + Idx imin{bA.xmin, A}, imax{bB.xmax, B}; + if (B[bB.xmin] < imin.pt()) imin = Idx{bB.xmin, B}; + if (imax.pt() < A[bA.xmax]) imax = Idx{bA.xmax, A}; + if (&imin.poly() == &imax.poly()) { + imin = Idx{bA.ymin, A}; + imax = Idx{bB.ymax, B}; + if (B[bB.ymin] < imin.pt()) imin = Idx{bB.ymin, B}; + if (imax.pt() < A[bA.ymax]) imax = Idx{bA.ymax, A}; + } + + if (&imin.poly() == &imax.poly()) + return true; + + bool found_divisor = false; + visit_antipodals( + imin, imax, + [&imin, &imax, &found_divisor](size_t ia, size_t ib, const Point &dir) { + // std::cout << "A" << ia << " B" << ib << " dir " << + // dir.x() << " " << dir.y() << std::endl; + const Polygon &A = imin.poly(), &B = imax.poly(); + + Point ref_a = A[(ia + 2) % A.size()], ref_b = B[(ib + 2) % B.size()]; + + bool is_left_a = dotperp( dir, ref_a - A[ia]) > 0; + bool is_left_b = dotperp(-dir, ref_b - B[ib]) > 0; + + // If both reference points are on the left (or right) of the + // support line and the opposite support line is to the righ (or + // left), the divisor line is found. We only test the reference + // point, as by definition, if that is on one side, all the other + // points must be on the same side of a support line. + + auto d = dotperp(dir, B[ib] - A[ia]); + if (d == 0 && ((is_left_a && is_left_b) || (!is_left_a && !is_left_b))) { + // The caliper lines are collinear, not just parallel + + // Check if the lines are overlapping and if they do ignore the divisor + Point a = A[ia], b = A[(ia + 1) % A.size()]; + if (b < a) std::swap(a, b); + Point c = B[ib], d = B[(ib + 1) % B.size()]; + if (d < c) std::swap(c, d); + + found_divisor = b < c; + + } else if (d > 0) { // B is to the left of (A, A+1) + found_divisor = !is_left_a && !is_left_b; + } else { // B is to the right of (A, A+1) + found_divisor = is_left_a && is_left_b; + } + + return !found_divisor; + }); + + // Intersects if the divisor was not found + return !found_divisor; +} + +}} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index c6af515c83..bb583c33c1 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -532,6 +532,8 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation) return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z()); } +bool intersects(const Polygon &convex_poly1, const Polygon &convex_poly2); + } } // namespace Slicer::Geometry #endif diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 575878cf2c..05898db28a 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests test_png_io.cpp test_timeutils.cpp test_indexed_triangle_set.cpp + ../libnest2d/printer_parts.cpp ) if (TARGET OpenVDB::openvdb) diff --git a/tests/libslic3r/test_geometry.cpp b/tests/libslic3r/test_geometry.cpp index 24e0908cc5..308e29fcaa 100644 --- a/tests/libslic3r/test_geometry.cpp +++ b/tests/libslic3r/test_geometry.cpp @@ -9,6 +9,14 @@ #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ShortestPath.hpp" +//#include +//#include "libnest2d/tools/benchmark.h" +#include "libslic3r/SVG.hpp" + +#include "../libnest2d/printer_parts.hpp" + +#include + using namespace Slic3r; TEST_CASE("Polygon::contains works properly", "[Geometry]"){ @@ -452,3 +460,217 @@ SCENARIO("Ported from xs/t/14_geometry.t", "[Geometry]"){ REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, PI, M_PI /180)); } } + +TEST_CASE("Convex polygon intersection on two disjoint squares", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + B.translate(20 / SCALING_FACTOR, 0); + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters != true); +} + +TEST_CASE("Convex polygon intersection on two intersecting squares", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + B.translate(5 / SCALING_FACTOR, 5 / SCALING_FACTOR); + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters == true); +} + +TEST_CASE("Convex polygon intersection on two squares touching one edge", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + B.translate(10 / SCALING_FACTOR, 0); + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters == true); +} + +TEST_CASE("Convex polygon intersection on two squares touching one vertex", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + B.translate(10 / SCALING_FACTOR, 10); + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters == true); +} + +TEST_CASE("Convex polygon intersection on two overlapping squares", "[Geometry][Rotcalip]") { + Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; + A.scale(1. / SCALING_FACTOR); + + Polygon B = A; + + bool is_inters = Geometry::intersects(A, B); + + REQUIRE(is_inters == true); +} + +// Only for benchmarking +//static Polygon gen_convex_poly(std::mt19937_64 &rg, size_t point_cnt) +//{ +// std::uniform_int_distribution dist(0, 100); + +// Polygon out; +// out.points.reserve(point_cnt); + +// coord_t tr = dist(rg) * 2 / SCALING_FACTOR; + +// for (size_t i = 0; i < point_cnt; ++i) +// out.points.emplace_back(tr + dist(rg) / SCALING_FACTOR, +// tr + dist(rg) / SCALING_FACTOR); + +// return Geometry::convex_hull(out.points); +//} +//TEST_CASE("Convex polygon intersection test on random polygons", "[Geometry]") { +// constexpr size_t TEST_CNT = 1000; +// constexpr size_t POINT_CNT = 1000; + +// std::mt19937_64 rg{std::random_device{}()}; +// Benchmark bench; + +// auto tests = reserve_vector>(TEST_CNT); +// auto results = reserve_vector(TEST_CNT); +// auto expects = reserve_vector(TEST_CNT); + +// for (size_t i = 0; i < TEST_CNT; ++i) { +// tests.emplace_back(gen_convex_poly(rg, POINT_CNT), gen_convex_poly(rg, POINT_CNT)); +// } + +// bench.start(); +// for (const auto &test : tests) +// results.emplace_back(Geometry::intersects(test.first, test.second)); +// bench.stop(); + +// std::cout << "Test time: " << bench.getElapsedSec() << std::endl; + +// bench.start(); +// for (const auto &test : tests) +// expects.emplace_back(!intersection(test.first, test.second).empty()); +// bench.stop(); + +// std::cout << "Clipper time: " << bench.getElapsedSec() << std::endl; + +// REQUIRE(results.size() == expects.size()); + +// for (size_t i = 0; i < results.size(); ++i) { +// // std::cout << expects[i] << " "; + +// if (results[i] != expects[i]) { +// SVG svg{std::string("fail") + std::to_string(i) + ".svg"}; +// svg.draw(tests[i].first, "blue"); +// svg.draw(tests[i].second, "green"); +// svg.Close(); + +// // std::cout << std::endl; +// } +// REQUIRE(results[i] == expects[i]); +// } +// std::cout << std::endl; + +//} + +struct Pair +{ + size_t first, second; + bool operator==(const Pair &b) const { return first == b.first && second == b.second; } +}; + +template<> struct std::hash { + size_t operator()(const Pair &c) const + { + return c.first * PRINTER_PART_POLYGONS.size() + c.second; + } +}; + +TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcalip]") { + + // Overlap of the same polygon should always be an intersection + for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) { + Polygon P = PRINTER_PART_POLYGONS[i]; + P = Geometry::convex_hull(P.points); + bool res = Geometry::intersects(P, P); + if (!res) { + SVG svg{std::string("fail_self") + std::to_string(i) + ".svg"}; + svg.draw(P, "green"); + svg.Close(); + } + REQUIRE(res == true); + } + + std::unordered_set combos; + for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) { + for (size_t j = 0; j < PRINTER_PART_POLYGONS.size(); ++j) { + if (i != j) { + size_t a = std::min(i, j), b = std::max(i, j); + combos.insert(Pair{a, b}); + } + } + } + + // All disjoint + for (const auto &combo : combos) { + Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second]; + A = Geometry::convex_hull(A.points); + B = Geometry::convex_hull(B.points); + + auto bba = A.bounding_box(); + auto bbb = B.bounding_box(); + + A.translate(-bba.center()); + B.translate(-bbb.center()); + + B.translate(bba.size() + bbb.size()); + + bool res = Geometry::intersects(A, B); + bool ref = !intersection(A, B).empty(); + + if (res != ref) { + SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"}; + svg.draw(A, "blue"); + svg.draw(B, "green"); + svg.Close(); + } + + REQUIRE(res == ref); + } + + // All intersecting + for (const auto &combo : combos) { + Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second]; + A = Geometry::convex_hull(A.points); + B = Geometry::convex_hull(B.points); + + auto bba = A.bounding_box(); + auto bbb = B.bounding_box(); + + A.translate(-bba.center()); + B.translate(-bbb.center()); + + bool res = Geometry::intersects(A, B); + bool ref = !intersection(A, B).empty(); + + if (res != ref) { + SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"}; + svg.draw(A, "blue"); + svg.draw(B, "green"); + svg.Close(); + } + + REQUIRE(res == ref); + } +} From 0b78e009b49fc2b957bb9a0da7cde5ee8c2b6192 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 4 Oct 2021 09:02:03 +0200 Subject: [PATCH 02/10] #7050 - GCodeProcessor: Fixed processing of gcode lines M109 --- src/libslic3r/GCode/GCodeProcessor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 3e42c91f5b..9c90535c4e 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -2861,6 +2861,8 @@ void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line) else m_extruder_temps[m_extruder_id] = new_temp; } + else if (line.has_value('S', new_temp)) + m_extruder_temps[m_extruder_id] = new_temp; } void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line) From b78d7cea28798f9668282ebd50e8d1d0ca2e3ac1 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 4 Oct 2021 09:20:35 +0200 Subject: [PATCH 03/10] #7054 - Tech ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED: enable command Save (CTRL+S) for untitled projects (automatically calls command Save As) --- src/slic3r/GUI/MainFrame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 79003f518f..6538c0a9a4 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -834,7 +834,7 @@ bool MainFrame::can_save() const #if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED return (m_plater != nullptr) && !m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false) && - !m_plater->get_project_filename().empty() && m_plater->is_project_dirty(); + m_plater->is_project_dirty(); #else return (m_plater != nullptr) && !m_plater->model().objects.empty() && !m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false) && From 91462cf9d5ef9a34752bbdb6ceff330a10bbffad Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 4 Oct 2021 09:23:06 +0200 Subject: [PATCH 04/10] Fix wireframe transformation when supports in SLA are added --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 52e65f1b69..9e4c71d2f3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -478,12 +478,14 @@ void GLGizmoSimplify::render_wireframe() const // is initialized? if (m_wireframe_VBO_id == 0 || m_wireframe_IBO_id == 0) return; if (!m_show_wireframe) return; - ModelVolume *act_volume = get_selected_volume(); - if (act_volume == nullptr) return; - const Transform3d trafo_matrix = - act_volume->get_object()->instances[m_parent.get_selection().get_instance_idx()] - ->get_transformation().get_matrix() * - act_volume->get_matrix(); + + const GLVolume *selected_volume = m_parent.get_selection().get_volume(0); + + // check selected_volume == act_volume + const auto& cid = selected_volume->composite_id; + assert(wxGetApp().plater()->model().objects[cid.object_id]->volumes[cid.volume_id] == act_volume); + + const Transform3d trafo_matrix = m_parent.get_selection().get_volume(0)->world_matrix(); glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); From 56996104cc8f1e52ef8e0057c350400a67658e60 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 4 Oct 2021 09:30:30 +0200 Subject: [PATCH 05/10] fix assert --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 9e4c71d2f3..e5f42e9a9a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -481,9 +481,9 @@ void GLGizmoSimplify::render_wireframe() const const GLVolume *selected_volume = m_parent.get_selection().get_volume(0); - // check selected_volume == act_volume + // check selected_volume == m_volume const auto& cid = selected_volume->composite_id; - assert(wxGetApp().plater()->model().objects[cid.object_id]->volumes[cid.volume_id] == act_volume); + assert(wxGetApp().plater()->model().objects[cid.object_id]->volumes[cid.volume_id] == m_volume); const Transform3d trafo_matrix = m_parent.get_selection().get_volume(0)->world_matrix(); From 9f0b31dc554b517180d3134c04fa92a925064b6a Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 4 Oct 2021 09:56:23 +0200 Subject: [PATCH 06/10] Close validate warnings on empty plater. --- src/slic3r/GUI/Plater.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 842055298e..074832d5a3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3080,10 +3080,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool //actualizate warnings if (invalidated != Print::APPLY_STATUS_UNCHANGED) { + if (background_process.empty()) + process_validation_warning(std::string()); actualize_slicing_warnings(*this->background_process.current_print()); actualize_object_warnings(*this->background_process.current_print()); show_warning_dialog = false; process_completed_with_error = false; + } if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() && From e533d237f9961e73e3f2c5c59a0fe6be1b3b7083 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 4 Oct 2021 10:39:53 +0200 Subject: [PATCH 07/10] Manifold mesh may contain self-intersections, so we want to always allow fixing the mesh. This is a fix of a regression wrt. https://github.com/prusa3d/PrusaSlicer/releases/tag/version_2.4.0-alpha2 --- src/slic3r/GUI/GUI_ObjectList.cpp | 13 ++++++++----- src/slic3r/GUI/GUI_ObjectList.hpp | 3 +++ src/slic3r/GUI/Plater.cpp | 8 ++++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b941104270..1413af5533 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -377,10 +377,7 @@ void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vectorget_mesh_errors_count(vol_idx); + return obj_idx >= 0 ? (*m_objects)[obj_idx]->get_mesh_errors_count(vol_idx) : 0; } static std::string get_warning_icon_name(const TriangleMeshStats& stats) @@ -393,7 +390,7 @@ std::pair ObjectList::get_mesh_errors(const int obj_idx, const int errors = get_mesh_errors_count(obj_idx, vol_idx); if (errors == 0) - return { "", "" }; // hide tooltip + return { {}, {} }; // hide tooltip // Create tooltip string, if there are errors wxString tooltip = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors) + ":\n"; @@ -4043,17 +4040,21 @@ 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 for (int i = int(obj_idxs.size())-1; i >= 0; --i) if (object(obj_idxs[i])->get_mesh_errors_count() == 0) obj_idxs.erase(obj_idxs.begin()+i); +#endif // FIX_THROUGH_NETFABB_ALWAYS for (int obj_idx : obj_idxs) model_names.push_back(object(obj_idx)->name); } else { ModelObject* obj = object(obj_idxs.front()); +#if !FIX_THROUGH_NETFABB_ALWAYS for (int i = int(vol_idxs.size()) - 1; i >= 0; --i) if (obj->get_mesh_errors_count(vol_idxs[i]) == 0) vol_idxs.erase(vol_idxs.begin() + i); +#endif // FIX_THROUGH_NETFABB_ALWAYS for (int vol_idx : vol_idxs) model_names.push_back(obj->volumes[vol_idx]->name); } @@ -4106,8 +4107,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 (object(obj_idx)->get_mesh_errors_count(vol_idx) == 0) continue; +#endif // FIX_THROUGH_NETFABB_ALWAYS if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models)) break; model_idx++; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 491bd26842..f168f6ff10 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -36,6 +36,9 @@ typedef double coordf_t; 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 + namespace GUI { wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 074832d5a3..1f91b3589e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4536,6 +4536,11 @@ bool Plater::priv::can_fix_through_netfabb() const std::vector obj_idxs, vol_idxs; sidebar->obj_list()->get_selection_indexes(obj_idxs, vol_idxs); +#if FIX_THROUGH_NETFABB_ALWAYS + // Fixing always. + return ! obj_idxs.empty() || ! vol_idxs.empty(); +#else // FIX_THROUGH_NETFABB_ALWAYS + // Fixing only if the model is not manifold. if (vol_idxs.empty()) { for (auto obj_idx : obj_idxs) if (model.objects[obj_idx]->get_mesh_errors_count() > 0) @@ -4547,11 +4552,10 @@ bool Plater::priv::can_fix_through_netfabb() const for (auto vol_idx : vol_idxs) if (model.objects[obj_idx]->get_mesh_errors_count(vol_idx) > 0) return true; - return false; +#endif // FIX_THROUGH_NETFABB_ALWAYS } - bool Plater::priv::can_simplify() const { // is object for simplification selected From 4103086a6e8a1b1ce84350deab4f7cb82f795095 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 4 Oct 2021 11:12:29 +0200 Subject: [PATCH 08/10] #7056 - Gizmo cut input set to use current locale --- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 2 +- src/slic3r/GUI/ImGuiWrapper.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index c8142ba347..b148196714 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -182,7 +182,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) double cut_z = m_cut_z; if (imperial_units) cut_z *= ObjectManipulation::mm_to_in; - ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f"); + ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); ImGui::SameLine(); m_imgui->text(imperial_units ? _L("in") : _L("mm")); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index b8b2337fb1..03e5bdc092 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -359,7 +359,7 @@ bool ImGuiWrapper::image_button() bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { - return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str()); + return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str(), ImGuiInputTextFlags_CharsDecimal); } bool ImGuiWrapper::input_double(const wxString &label, const double &value, const std::string &format) From 580f157d283757889c4eba7d486bedd9a7a7eee2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 4 Oct 2021 11:35:27 +0200 Subject: [PATCH 09/10] ConfigWizard: Suppress to select SLA printer if a multi-parts object is on a Plater --- src/slic3r/GUI/ConfigWizard.cpp | 50 +++++++++++++++++++++++++++++---- src/slic3r/GUI/GUI_App.cpp | 24 +++++++++++----- src/slic3r/GUI/GUI_App.hpp | 1 + src/slic3r/GUI/Tab.cpp | 17 +---------- src/slic3r/GUI/Tab.hpp | 1 - 5 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index ee6e3d5ab5..4d50e14905 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -30,6 +30,8 @@ #include "libslic3r/Platform.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Config.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Model.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_Utils.hpp" @@ -40,7 +42,6 @@ #include "slic3r/Utils/PresetUpdater.hpp" #include "format.hpp" #include "MsgDialog.hpp" -#include "libslic3r/libslic3r.h" #include "UnsavedChangesDialog.hpp" #if defined(__linux__) && defined(__WXGTK3__) @@ -2477,6 +2478,46 @@ static std::string get_first_added_preset(const std::mapmodels) { + if (const auto model_it = config->second.find(model.id); + model_it != config->second.end() && model_it->second.size() > 0) { + if (pt == ptAny) + pt = model.technology; + // if preferred printer model has SLA printer technology it's important to check the model for multypart state + if (pt == ptSLA && suppress_sla_printer) + continue; + else + return pt; + } + } + } + return pt; + }; + // Prusa printers are considered first, then 3rd party. + if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle()); + preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) { + for (const auto& bundle : bundles) { + if (bundle.second.is_prusa_bundle) { continue; } + if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny) + continue; + else if (preferred_pt == ptAny) + preferred_pt = pt; + if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer))) + break; + } + } + + if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption)) + return false; + bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); if (check_unsaved_preset_changes) header = _L("All user presets will be deleted."); @@ -2484,8 +2525,6 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (!check_unsaved_preset_changes) act_btns |= UnsavedChangesDialog::ActionButtons::SAVE; - const auto enabled_vendors = appconfig_new.vendors(); - // Install bundles from resources if needed: std::vector install_bundles; for (const auto &pair : bundles) { @@ -2564,13 +2603,14 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese std::string preferred_model; std::string preferred_variant; const auto enabled_vendors_old = app_config->vendors(); - auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { + auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { const auto config = enabled_vendors.find(bundle_name); if (config == enabled_vendors.end()) return std::string(); for (const auto& model : bundle.vendor_profile->models) { if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0) { + model_it != config->second.end() && model_it->second.size() > 0 && + preferred_pt == model.technology) { variant = *model_it->second.begin(); const auto config_old = enabled_vendors_old.find(bundle_name); if (config_old == enabled_vendors_old.end()) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9c08159fce..41f4c4b892 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2432,6 +2432,20 @@ void GUI_App::open_web_page_localized(const std::string &http_address) open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe()); } +// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). +// Because of we can't to print the multi-part objects with SLA technology. +bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) +{ + if (model_has_multi_part_objects(model())) { + show_info(nullptr, + _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + + _L("Please check your object list before preset changing."), + caption); + return false; + } + return true; +} + bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) { wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); @@ -2447,13 +2461,9 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage if (res) { load_current_presets(); - if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA - && Slic3r::model_has_multi_part_objects(wxGetApp().model())) { - GUI::show_info(nullptr, - _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + - _L("Please check and fix your object list."), - _L("Attention!")); - } + // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() + if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) + may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); } return res; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 60a143144f..a581cf8b30 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -309,6 +309,7 @@ public: PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); } void open_web_page_localized(const std::string &http_address); + bool may_switch_to_SLA_preset(const wxString& caption); bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME); void show_desktop_integration_dialog(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c91ac47843..8b3cd4cf89 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3236,7 +3236,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset); PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); - if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !may_switch_to_SLA_preset()) + if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset is selecting"))) canceled = true; else { struct PresetUpdate { @@ -3409,21 +3409,6 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr return true; } -// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). -// Because of we can't to print the multi-part objects with SLA technology. -bool Tab::may_switch_to_SLA_preset() -{ - if (model_has_multi_part_objects(wxGetApp().model())) - { - show_info( parent(), - _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + - _(L("Please check your object list before preset changing.")), - _(L("Attention!")) ); - return false; - } - return true; -} - void Tab::clear_pages() { // invalidated highlighter, if any exists diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 5b7983711e..6a7b56fe40 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -282,7 +282,6 @@ public: // Select a new preset, possibly delete the current one. void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = ""); bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = ""); - bool may_switch_to_SLA_preset(); virtual void clear_pages(); virtual void update_description_lines(); From 39a98e97b4c38850173b73e5b8a321d3c75c664b Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 4 Oct 2021 12:29:45 +0200 Subject: [PATCH 10/10] Fix. Allow simplify volume inside object, fix drawing wireframe --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 58 +++++++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp | 4 +- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index e5f42e9a9a..c09d67317a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -60,8 +60,9 @@ void GLGizmoSimplify::on_render_for_picking() {} void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit) { create_gui_cfg(); - int obj_index; - ModelVolume *act_volume = get_selected_volume(&obj_index); + const Selection &selection = m_parent.get_selection(); + int obj_index = selection.get_object_idx(); + ModelVolume *act_volume = get_volume(selection, wxGetApp().plater()->model()); if (act_volume == nullptr) { switch (m_state) { case State::settings: close(); break; @@ -427,17 +428,34 @@ bool GLGizmoSimplify::exist_volume(ModelVolume *volume) { return false; } -ModelVolume *GLGizmoSimplify::get_selected_volume(int *object_idx_ptr) const +ModelVolume * GLGizmoSimplify::get_volume(const Selection &selection, Model &model) { - const Selection &selection = m_parent.get_selection(); - int object_idx = selection.get_object_idx(); - if (object_idx_ptr != nullptr) *object_idx_ptr = object_idx; - if (object_idx < 0) return nullptr; - ModelObjectPtrs &objs = wxGetApp().plater()->model().objects; - if (static_cast(objs.size()) <= object_idx) return nullptr; - ModelObject *obj = objs[object_idx]; - if (obj->volumes.empty()) return nullptr; - return obj->volumes.front(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + if (idxs.empty()) return nullptr; + // only one selected volume + if (idxs.size() != 1) return nullptr; + const GLVolume *selected_volume = selection.get_volume(*idxs.begin()); + if (selected_volume == nullptr) return nullptr; + + const GLVolume::CompositeID &cid = selected_volume->composite_id; + const ModelObjectPtrs& objs = model.objects; + if (cid.object_id < 0 || objs.size() <= static_cast(cid.object_id)) + return nullptr; + const ModelObject* obj = objs[cid.object_id]; + if (cid.volume_id < 0 || obj->volumes.size() <= static_cast(cid.volume_id)) + return nullptr; + return obj->volumes[cid.volume_id]; +} + +const ModelVolume *GLGizmoSimplify::get_volume(const GLVolume::CompositeID &cid, const Model &model) +{ + const ModelObjectPtrs &objs = model.objects; + if (cid.object_id < 0 || objs.size() <= static_cast(cid.object_id)) + return nullptr; + const ModelObject *obj = objs[cid.object_id]; + if (cid.volume_id < 0 || obj->volumes.size() <= static_cast(cid.volume_id)) + return nullptr; + return obj->volumes[cid.volume_id]; } void GLGizmoSimplify::init_wireframe() @@ -479,14 +497,16 @@ void GLGizmoSimplify::render_wireframe() const if (m_wireframe_VBO_id == 0 || m_wireframe_IBO_id == 0) return; if (!m_show_wireframe) return; - const GLVolume *selected_volume = m_parent.get_selection().get_volume(0); - - // check selected_volume == m_volume - const auto& cid = selected_volume->composite_id; - assert(wxGetApp().plater()->model().objects[cid.object_id]->volumes[cid.volume_id] == m_volume); - - const Transform3d trafo_matrix = m_parent.get_selection().get_volume(0)->world_matrix(); + const auto& selection = m_parent.get_selection(); + const auto& volume_idxs = selection.get_volume_idxs(); + if (volume_idxs.empty() || volume_idxs.size() != 1) return; + const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin()); + + // check that selected model is wireframe initialized + if (m_volume != get_volume(selected_volume->composite_id, *m_parent.get_model())) + return; + const Transform3d trafo_matrix = selected_volume->world_matrix(); glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index d2358b2d32..d624e33518 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -43,7 +43,9 @@ private: void set_its(indexed_triangle_set &its); void create_gui_cfg(); void request_rerender(); - ModelVolume *get_selected_volume(int *object_idx = nullptr) const; + // move to global functions + static ModelVolume *get_volume(const Selection &selection, Model &model); + static const ModelVolume *get_volume(const GLVolume::CompositeID &cid, const Model &model); // return false when volume was deleted static bool exist_volume(ModelVolume *volume);