diff --git a/.clang-format b/.clang-format index 6ec205af8..42ac9f1a0 100644 --- a/.clang-format +++ b/.clang-format @@ -13,8 +13,8 @@ AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: true -AllowShortLoopsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false diff --git a/resources/icons/collapse_btn.svg b/resources/icons/collapse_btn.svg index 4ee221a44..32d7f9959 100644 --- a/resources/icons/collapse_btn.svg +++ b/resources/icons/collapse_btn.svg @@ -3,11 +3,10 @@ - - - - - - + + + + + - \ No newline at end of file + diff --git a/resources/icons/cut_.svg b/resources/icons/cut_.svg index a7f462bb9..0919e3952 100644 --- a/resources/icons/cut_.svg +++ b/resources/icons/cut_.svg @@ -1,28 +1,26 @@ - - - - - - - - - - - - - - + + + + + + + + diff --git a/resources/icons/cut_connectors.svg b/resources/icons/cut_connectors.svg index 8cd03aa06..504df0a41 100644 --- a/resources/icons/cut_connectors.svg +++ b/resources/icons/cut_connectors.svg @@ -1,26 +1,20 @@ - - - - - - - + + + + + + diff --git a/resources/icons/expand_btn.svg b/resources/icons/expand_btn.svg index 32d7f9959..4ee221a44 100644 --- a/resources/icons/expand_btn.svg +++ b/resources/icons/expand_btn.svg @@ -3,10 +3,11 @@ - - - - - + + + + + + - + \ No newline at end of file diff --git a/resources/icons/mode.svg b/resources/icons/mode.svg new file mode 100644 index 000000000..613e99b0d --- /dev/null +++ b/resources/icons/mode.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/resources/icons/notification_open.svg b/resources/icons/notification_open.svg new file mode 100644 index 000000000..a83138a88 --- /dev/null +++ b/resources/icons/notification_open.svg @@ -0,0 +1,63 @@ + +image/svg+xml + + + + + + diff --git a/resources/icons/notification_open_hover.svg b/resources/icons/notification_open_hover.svg new file mode 100644 index 000000000..774728088 --- /dev/null +++ b/resources/icons/notification_open_hover.svg @@ -0,0 +1,64 @@ + +image/svg+xml + + + + + + diff --git a/resources/icons/notification_pause.svg b/resources/icons/notification_pause.svg new file mode 100644 index 000000000..dc0d61311 --- /dev/null +++ b/resources/icons/notification_pause.svg @@ -0,0 +1,75 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/notification_pause_hover.svg b/resources/icons/notification_pause_hover.svg new file mode 100644 index 000000000..6654f3775 --- /dev/null +++ b/resources/icons/notification_pause_hover.svg @@ -0,0 +1,75 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/notification_play.svg b/resources/icons/notification_play.svg new file mode 100644 index 000000000..5aa80cd94 --- /dev/null +++ b/resources/icons/notification_play.svg @@ -0,0 +1,75 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/notification_play_hover.svg b/resources/icons/notification_play_hover.svg new file mode 100644 index 000000000..f0d07fc12 --- /dev/null +++ b/resources/icons/notification_play_hover.svg @@ -0,0 +1,75 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/open_browser.svg b/resources/icons/open_browser.svg new file mode 100644 index 000000000..a19f62268 --- /dev/null +++ b/resources/icons/open_browser.svg @@ -0,0 +1,61 @@ + +image/svg+xml + + + + diff --git a/resources/icons/splashscreen-gcodepreview.jpg b/resources/icons/splashscreen-gcodepreview.jpg index bf30739be..473b11b2e 100644 Binary files a/resources/icons/splashscreen-gcodepreview.jpg and b/resources/icons/splashscreen-gcodepreview.jpg differ diff --git a/resources/icons/splashscreen.jpg b/resources/icons/splashscreen.jpg index bf9ad3d0f..1019cdf2b 100644 Binary files a/resources/icons/splashscreen.jpg and b/resources/icons/splashscreen.jpg differ diff --git a/resources/profiles/Creality/ENDER5S1_thumbnail.png b/resources/profiles/Creality/ENDER5S1_thumbnail.png new file mode 100644 index 000000000..410c4f3d4 Binary files /dev/null and b/resources/profiles/Creality/ENDER5S1_thumbnail.png differ diff --git a/resources/profiles/Creality/SERMOONV1PRO_thumbnail.png b/resources/profiles/Creality/SERMOONV1PRO_thumbnail.png new file mode 100644 index 000000000..adbd42162 Binary files /dev/null and b/resources/profiles/Creality/SERMOONV1PRO_thumbnail.png differ diff --git a/resources/profiles/Creality/SERMOONV1_thumbnail.png b/resources/profiles/Creality/SERMOONV1_thumbnail.png new file mode 100644 index 000000000..7ec62ef84 Binary files /dev/null and b/resources/profiles/Creality/SERMOONV1_thumbnail.png differ diff --git a/resources/profiles/Creality/sermoonv1.svg b/resources/profiles/Creality/sermoonv1.svg new file mode 100644 index 000000000..16ea62ae6 --- /dev/null +++ b/resources/profiles/Creality/sermoonv1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/profiles/Creality/sermoonv1_bed.stl b/resources/profiles/Creality/sermoonv1_bed.stl new file mode 100644 index 000000000..b9ea60a0f Binary files /dev/null and b/resources/profiles/Creality/sermoonv1_bed.stl differ diff --git a/resources/shapes/sphere.png b/resources/shapes/sphere.png index c3853d68d..305b64f41 100644 Binary files a/resources/shapes/sphere.png and b/resources/shapes/sphere.png differ diff --git a/resources/shapes/sphere.stl b/resources/shapes/sphere.stl index ca1ef24f5..80d6ac596 100644 Binary files a/resources/shapes/sphere.stl and b/resources/shapes/sphere.stl differ diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 918d2ca3c..1ee4b0d34 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -113,6 +113,9 @@ int CLI::run(int argc, char **argv) std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); + bool start_downloader = false; + bool delete_after_load = false; + std::string download_url; bool start_as_gcodeviewer = #ifdef _WIN32 false; @@ -221,6 +224,11 @@ int CLI::run(int argc, char **argv) } if (!start_as_gcodeviewer) { for (const std::string& file : m_input_files) { + if (boost::starts_with(file, "prusaslicer://")) { + start_downloader = true; + download_url = file; + continue; + } if (!boost::filesystem::exists(file)) { boost::nowide::cerr << "No such file: " << file << std::endl; exit(1); @@ -478,6 +486,9 @@ int CLI::run(int argc, char **argv) // Models are repaired by default. //for (auto &model : m_models) // model.repair(); + + } else if (opt_key == "delete-after-load") { + delete_after_load = true; } else { boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; return 1; @@ -663,9 +674,12 @@ int CLI::run(int argc, char **argv) params.extra_config = std::move(m_extra_config); params.input_files = std::move(m_input_files); params.start_as_gcodeviewer = start_as_gcodeviewer; + params.start_downloader = start_downloader; + params.download_url = download_url; + params.delete_after_load = delete_after_load; #if ENABLE_GL_CORE_PROFILE - params.opengl_version = opengl_version; #if ENABLE_OPENGL_DEBUG_OPTION + params.opengl_version = opengl_version; params.opengl_debug = opengl_debug; #endif // ENABLE_OPENGL_DEBUG_OPTION #endif // ENABLE_GL_CORE_PROFILE diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 83f24c307..90bd2d89f 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -242,13 +242,14 @@ int wmain(int argc, wchar_t **argv) #ifdef SLIC3R_GUI // Here one may push some additional parameters based on the wrapper type. bool force_mesa = false; + bool force_hw = false; #endif /* SLIC3R_GUI */ for (int i = 1; i < argc; ++ i) { #ifdef SLIC3R_GUI if (wcscmp(argv[i], L"--sw-renderer") == 0) force_mesa = true; else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) - force_mesa = false; + force_hw = true; #endif /* SLIC3R_GUI */ argv_extended.emplace_back(argv[i]); } @@ -261,7 +262,7 @@ int wmain(int argc, wchar_t **argv) force_mesa || // Running over a rempote desktop, and the RemoteFX is not enabled, therefore Windows will only provide SW OpenGL 1.1 context. // In that case, use Mesa. - ::GetSystemMetrics(SM_REMOTESESSION) || + (::GetSystemMetrics(SM_REMOTESESSION) && !force_hw) || // Try to load the default OpenGL driver and test its context version. ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); #endif /* SLIC3R_GUI */ diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index 807d9ef4c..8ead78d08 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -210,8 +210,9 @@ bool its_write_obj(const indexed_triangle_set &its, const char *file) bool its_write_obj(const indexed_triangle_set& its, const std::vector &color, const char* file) { Slic3r::CNumericLocalesSetter locales_setter; - FILE* fp = fopen(file, "w"); + FILE* fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing"; return false; } diff --git a/src/imgui/README.md b/src/imgui/README.md index 58008bded..9413811ae 100644 --- a/src/imgui/README.md +++ b/src/imgui/README.md @@ -17,6 +17,12 @@ imstb_truetype.h modification: Hot fix for open symbolic fonts on windows 62bdfe6f8d04b88e8bd511cd613be80c0baa7f55 +Add case STBTT_MS_EID_SYMBOL to swith in file imstb_truetype.h on line 1440. Hot fix for open curved fonts mainly on MAC 2148e49f75d82cb19dc6ec409fb7825296ed005c +viz. https://github.com/nothings/stb/issues/1296 +In file imstb_truetype.h line 1667 change malloc size from: +vertices = (stbtt_vertex *) STBTT_malloc((m + 1) * sizeof(vertices[0]), info->userdata); +to: +vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); \ No newline at end of file diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 6dabace5b..651df6183 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -158,6 +158,12 @@ namespace ImGui const wchar_t SliderFloatEditBtnIcon = 0x2604; const wchar_t SliderFloatEditBtnPressedIcon = 0x2605; const wchar_t ClipboardBtnIcon = 0x2606; + const wchar_t PlayButton = 0x2618; + const wchar_t PlayHoverButton = 0x2619; + const wchar_t PauseButton = 0x261A; + const wchar_t PauseHoverButton = 0x261B; + const wchar_t OpenButton = 0x261C; + const wchar_t OpenHoverButton = 0x261D; const wchar_t LegendTravel = 0x2701; const wchar_t LegendWipe = 0x2702; @@ -173,8 +179,8 @@ namespace ImGui const wchar_t LegendToolMarker = 0x2712; const wchar_t WarningMarkerSmall = 0x2713; const wchar_t ExpandBtn = 0x2714; - const wchar_t CollapseBtn = 0x2715; const wchar_t InfoMarkerSmall = 0x2716; + const wchar_t CollapseBtn = 0x2715; // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/AABBTreeLines.hpp b/src/libslic3r/AABBTreeLines.hpp index 6426b7d12..21678bfcd 100644 --- a/src/libslic3r/AABBTreeLines.hpp +++ b/src/libslic3r/AABBTreeLines.hpp @@ -1,85 +1,197 @@ #ifndef SRC_LIBSLIC3R_AABBTREELINES_HPP_ #define SRC_LIBSLIC3R_AABBTREELINES_HPP_ +#include "Point.hpp" #include "Utils.hpp" #include "libslic3r.h" #include "libslic3r/AABBTreeIndirect.hpp" #include "libslic3r/Line.hpp" +#include +#include #include #include -namespace Slic3r { - -namespace AABBTreeLines { +namespace Slic3r { namespace AABBTreeLines { namespace detail { -template -struct IndexedLinesDistancer { - using LineType = ALineType; - using TreeType = ATreeType; +template struct IndexedLinesDistancer +{ + using LineType = ALineType; + using TreeType = ATreeType; using VectorType = AVectorType; using ScalarType = typename VectorType::Scalar; const std::vector &lines; - const TreeType &tree; + const TreeType &tree; const VectorType origin; - inline VectorType closest_point_to_origin(size_t primitive_index, - ScalarType &squared_distance) const { + inline VectorType closest_point_to_origin(size_t primitive_index, ScalarType &squared_distance) const + { Vec nearest_point; - const LineType &line = lines[primitive_index]; + const LineType &line = lines[primitive_index]; squared_distance = line_alg::distance_to_squared(line, origin.template cast(), &nearest_point); return nearest_point.template cast(); } }; +// returns number of intersections of ray starting in ray_origin and following the specified coordinate line with lines in tree +// first number is hits in positive direction of ray, second number hits in negative direction. returns neagtive numbers when ray_origin is +// on some line exactly. +template +inline std::tuple coordinate_aligned_ray_hit_count(size_t node_idx, + const TreeType &tree, + const std::vector &lines, + const VectorType &ray_origin) +{ + static constexpr int other_coordinate = (coordinate + 1) % 2; + using Scalar = typename LineType::Scalar; + using Floating = typename std::conditional::value, Scalar, double>::type; + const auto &node = tree.node(node_idx); + assert(node.is_valid()); + if (node.is_leaf()) { + const LineType &line = lines[node.idx]; + if (ray_origin[other_coordinate] < std::min(line.a[other_coordinate], line.b[other_coordinate]) || + ray_origin[other_coordinate] >= std::max(line.a[other_coordinate], line.b[other_coordinate])) { + // the second inequality is nonsharp for a reason + // without it, we may count contour border twice when the lines meet exactly at the spot of intersection. this prevents is + return {0, 0}; + } + + Scalar line_max = std::max(line.a[coordinate], line.b[coordinate]); + Scalar line_min = std::min(line.a[coordinate], line.b[coordinate]); + if (ray_origin[coordinate] > line_max) { + return {1, 0}; + } else if (ray_origin[coordinate] < line_min) { + return {0, 1}; + } else { + // find intersection of ray with line + // that is when ( line.a + t * (line.b - line.a) )[other_coordinate] == ray_origin[other_coordinate] + // t = ray_origin[oc] - line.a[oc] / (line.b[oc] - line.a[oc]); + // then we want to get value of intersection[ coordinate] + // val_c = line.a[c] + t * (line.b[c] - line.a[c]); + // Note that ray and line may overlap, when (line.b[oc] - line.a[oc]) is zero + // In that case, we return negative number + Floating distance_oc = line.b[other_coordinate] - line.a[other_coordinate]; + Floating t = (ray_origin[other_coordinate] - line.a[other_coordinate]) / distance_oc; + Floating val_c = line.a[coordinate] + t * (line.b[coordinate] - line.a[coordinate]); + if (ray_origin[coordinate] > val_c) { + return {1, 0}; + } else if (ray_origin[coordinate] < val_c) { + return {0, 1}; + } else { // ray origin is on boundary + return {-1, -1}; + } + } + } else { + int intersections_above = 0; + int intersections_below = 0; + size_t left_node_idx = node_idx * 2 + 1; + size_t right_node_idx = left_node_idx + 1; + const auto &node_left = tree.node(left_node_idx); + const auto &node_right = tree.node(right_node_idx); + assert(node_left.is_valid()); + assert(node_right.is_valid()); + + if (node_left.bbox.min()[other_coordinate] <= ray_origin[other_coordinate] && + node_left.bbox.max()[other_coordinate] >= + ray_origin[other_coordinate]) { + auto [above, below] = coordinate_aligned_ray_hit_count(left_node_idx, tree, lines, + ray_origin); + if (above < 0 || below < 0) return {-1, -1}; + intersections_above += above; + intersections_below += below; + } + + if (node_right.bbox.min()[other_coordinate] <= ray_origin[other_coordinate] && + node_right.bbox.max()[other_coordinate] >= ray_origin[other_coordinate]) { + auto [above, below] = coordinate_aligned_ray_hit_count(right_node_idx, tree, lines, + ray_origin); + if (above < 0 || below < 0) return {-1, -1}; + intersections_above += above; + intersections_below += below; + } + return {intersections_above, intersections_below}; + } } +template +inline std::vector> get_intersections_with_line(size_t node_idx, + const TreeType &tree, + const std::vector &lines, + const LineType &line, + const typename TreeType::BoundingBox &line_bb) +{ + const auto &node = tree.node(node_idx); + assert(node.is_valid()); + if (node.is_leaf()) { + VectorType intersection_pt; + if (line_alg::intersection(line, lines[node.idx], &intersection_pt)) { + return {std::pair(intersection_pt, node.idx)}; + } else { + return {}; + } + } else { + size_t left_node_idx = node_idx * 2 + 1; + size_t right_node_idx = left_node_idx + 1; + const auto &node_left = tree.node(left_node_idx); + const auto &node_right = tree.node(right_node_idx); + assert(node_left.is_valid()); + assert(node_right.is_valid()); + + std::vector> result; + + if (node_left.bbox.intersects(line_bb)) { + std::vector> intersections = + get_intersections_with_line(left_node_idx, tree, lines, line, line_bb); + result.insert(result.end(), intersections.begin(), intersections.end()); + } + + if (node_right.bbox.intersects(line_bb)) { + std::vector> intersections = + get_intersections_with_line(right_node_idx, tree, lines, line, line_bb); + result.insert(result.end(), intersections.begin(), intersections.end()); + } + + return result; + } +} + +} // namespace detail + // Build a balanced AABB Tree over a vector of lines, balancing the tree // on centroids of the lines. // Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies // during tree traversal. template -inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines( - const std::vector &lines, - //FIXME do we want to apply an epsilon? - const double eps = 0) - { +inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(const std::vector &lines) +{ using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>; -// using CoordType = typename TreeType::CoordType; - using VectorType = typename TreeType::VectorType; + // using CoordType = typename TreeType::CoordType; + using VectorType = typename TreeType::VectorType; using BoundingBox = typename TreeType::BoundingBox; - struct InputType { - size_t idx() const { - return m_idx; - } - const BoundingBox& bbox() const { - return m_bbox; - } - const VectorType& centroid() const { - return m_centroid; - } + struct InputType + { + size_t idx() const { return m_idx; } + const BoundingBox &bbox() const { return m_bbox; } + const VectorType ¢roid() const { return m_centroid; } - size_t m_idx; + size_t m_idx; BoundingBox m_bbox; - VectorType m_centroid; + VectorType m_centroid; }; std::vector input; input.reserve(lines.size()); - const VectorType veps(eps, eps); for (size_t i = 0; i < lines.size(); ++i) { const LineType &line = lines[i]; - InputType n; - n.m_idx = i; + InputType n; + n.m_idx = i; n.m_centroid = (line.a + line.b) * 0.5; - n.m_bbox = BoundingBox(line.a, line.a); + n.m_bbox = BoundingBox(line.a, line.a); n.m_bbox.extend(line.b); - n.m_bbox.min() -= veps; - n.m_bbox.max() += veps; input.emplace_back(n); } @@ -103,8 +215,8 @@ inline typename VectorType::Scalar squared_distance_to_indexed_lines( using Scalar = typename VectorType::Scalar; if (tree.empty()) return Scalar(-1); auto distancer = detail::IndexedLinesDistancer{lines, tree, point}; - return AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive( - distancer, size_t(0), Scalar(0), max_sqr_dist, hit_idx_out, hit_point_out); + return AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), max_sqr_dist, + hit_idx_out, hit_point_out); } // Returns all lines within the given radius limit @@ -112,7 +224,7 @@ template inline std::vector all_lines_in_radius(const std::vector &lines, const TreeType &tree, const VectorType &point, - typename VectorType::Scalar max_distance_squared) + typename VectorType::Scalar max_distance_squared) { auto distancer = detail::IndexedLinesDistancer{lines, tree, point}; @@ -123,12 +235,71 @@ inline std::vector all_lines_in_radius(const std::vector &line return found_lines; } +// return 1 if true, -1 if false, 0 for point on contour (or if cannot be determined) +template +inline int point_outside_closed_contours(const std::vector &lines, const TreeType &tree, const VectorType &point) +{ + if (tree.empty()) { return 1; } + + auto [hits_above, hits_below] = detail::coordinate_aligned_ray_hit_count(0, tree, lines, point); + if (hits_above < 0 || hits_below < 0) { + return 0; + } else if (hits_above % 2 == 1 && hits_below % 2 == 1) { + return -1; + } else if (hits_above % 2 == 0 && hits_below % 2 == 0) { + return 1; + } else { // this should not happen with closed contours. lets check it in Y direction + auto [hits_above, hits_below] = detail::coordinate_aligned_ray_hit_count(0, tree, lines, point); + if (hits_above < 0 || hits_below < 0) { + return 0; + } else if (hits_above % 2 == 1 && hits_below % 2 == 1) { + return -1; + } else if (hits_above % 2 == 0 && hits_below % 2 == 0) { + return 1; + } else { // both results were unclear + return 0; + } + } +} + +template +inline std::vector> get_intersections_with_line(const std::vector &lines, + const TreeType &tree, + const LineType &line) +{ + if (tree.empty()) { + return {}; + } + auto line_bb = typename TreeType::BoundingBox(line.a, line.a); + line_bb.extend(line.b); + + auto intersections = detail::get_intersections_with_line(0, tree, lines, line, line_bb); + if (sorted) { + using Floating = + typename std::conditional::value, typename LineType::Scalar, double>::type; + + std::vector>> points_with_sq_distance{}; + for (const auto &p : intersections) { + points_with_sq_distance.emplace_back((p.first - line.a).template cast().squaredNorm(), p); + } + std::sort(points_with_sq_distance.begin(), points_with_sq_distance.end(), + [](const std::pair> &left, + std::pair> &right) { return left.first < right.first; }); + for (size_t i = 0; i < points_with_sq_distance.size(); i++) { + intersections[i] = points_with_sq_distance[i].second; + } + } + + return intersections; +} + template class LinesDistancer { -private: - std::vector lines; +public: using Scalar = typename LineType::Scalar; using Floating = typename std::conditional::value, Scalar, double>::type; +private: + std::vector lines; AABBTreeIndirect::Tree<2, Scalar> tree; public: @@ -137,41 +308,40 @@ public: tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(this->lines); } + explicit LinesDistancer(std::vector &&lines) : lines(lines) + { + tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(this->lines); + } + + LinesDistancer() = default; + + // 1 true, -1 false, 0 cannot determine + int outside(const Vec<2, Scalar> &point) const { return point_outside_closed_contours(lines, tree, point); } + // negative sign means inside - std::tuple> signed_distance_from_lines_extra(const Vec<2, Scalar> &point) const + template + std::tuple> distance_from_lines_extra(const Vec<2, Scalar> &point) const { size_t nearest_line_index_out = size_t(-1); Vec<2, Floating> nearest_point_out = Vec<2, Floating>::Zero(); Vec<2, Floating> p = point.template cast(); auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, nearest_line_index_out, nearest_point_out); - if (distance < 0) { return {std::numeric_limits::infinity(), nearest_line_index_out, nearest_point_out}; } - distance = sqrt(distance); - const LineType &line = lines[nearest_line_index_out]; - Vec<2, Floating> v1 = (line.b - line.a).template cast(); - Vec<2, Floating> v2 = (point - line.a).template cast(); - auto d1 = (v1.x() * v2.y()) - (v1.y() * v2.x()); - - LineType second_line = line; - if ((line.a.template cast() - nearest_point_out).squaredNorm() < SCALED_EPSILON) { - second_line = lines[prev_idx_modulo(nearest_line_index_out, lines.size())]; - } else { - second_line = lines[next_idx_modulo(nearest_line_index_out, lines.size())]; + if (distance < 0) { + return {std::numeric_limits::infinity(), nearest_line_index_out, nearest_point_out}; } - v1 = (second_line.b - second_line.a).template cast(); - v2 = (point - second_line.a).template cast(); - auto d2 = (v1.x() * v2.y()) - (v1.y() * v2.x()); - - auto d = abs(d1) > abs(d2) ? d1 : d2; - - if (d > 0.0) { distance *= -1.0; } - + distance = sqrt(distance); + + if (SIGNED_DISTANCE) { + distance *= outside(point); + } + return {distance, nearest_line_index_out, nearest_point_out}; } - Floating signed_distance_from_lines(const Vec<2, typename LineType::Scalar> &point) const + template Floating distance_from_lines(const Vec<2, typename LineType::Scalar> &point) const { - auto [dist, idx, np] = signed_distance_from_lines_extra(point); + auto [dist, idx, np] = distance_from_lines_extra(point); return dist; } @@ -180,6 +350,11 @@ public: return all_lines_in_radius(this->lines, this->tree, point, radius * radius); } + template std::vector, size_t>> intersections_with_line(const LineType &line) const + { + return get_intersections_with_line>(lines, tree, line); + } + const LineType &get_line(size_t line_idx) const { return lines[line_idx]; } const std::vector &get_lines() const { return lines; } diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 04a1042d8..cd58f34c6 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -573,6 +573,59 @@ inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalT } } +bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments) +{ + for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) { + if (!cell.incident_edge()) + continue; // Degenerated cell, there is no spoon + + if (!cell.contains_segment()) + continue; // Skip cells that don't contain segments. + + const VoronoiUtils::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); + const Vec2d source_segment_from = source_segment.from().cast(); + const Vec2d source_segment_vec = source_segment.to().cast() - source_segment_from; + + Point start_source_point, end_source_point; + VoronoiUtils::vd_t::edge_type *begin_voronoi_edge = nullptr, *end_voronoi_edge = nullptr; + SkeletalTrapezoidation::computeSegmentCellRange(cell, start_source_point, end_source_point, begin_voronoi_edge, end_voronoi_edge, segments); + // All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid. + // FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases. + // It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf). + if (begin_voronoi_edge != nullptr && end_voronoi_edge != nullptr) + for (VoronoiUtils::vd_t::edge_type *edge = begin_voronoi_edge; edge != end_voronoi_edge; edge = edge->next()) + if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) + return true; + } + + return false; +} + +enum class VoronoiDiagramStatus { + NO_ISSUE_DETECTED, + MISSING_VORONOI_VERTEX, + NON_PLANAR_VORONOI_DIAGRAM, + VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT, + OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION +}; + +// Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram +// is not planar or some Voronoi edge is intersecting input segment. +VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const Geometry::VoronoiDiagram &voronoi_diagram, + const std::vector &segments) +{ + if (const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); has_missing_voronoi_vertex) { + return VoronoiDiagramStatus::MISSING_VORONOI_VERTEX; + } else if (const bool has_voronoi_edge_intersecting_input_segment = detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments); has_voronoi_edge_intersecting_input_segment) { + // Detection if Voronoi edge is intersecting input segment detects at least one model in GH issue #8446. + return VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT; + } else if (const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments); !is_voronoi_diagram_planar) { + // Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446. + return VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM; + } + return VoronoiDiagramStatus::NO_ISSUE_DETECTED; +} + void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) { #ifdef ARACHNE_DEBUG @@ -614,36 +667,35 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) } #endif - // Try to detect cases when some Voronoi vertex is missing and when - // the Voronoi diagram is not planar. - // When any Voronoi vertex is missing, or the Voronoi diagram is not - // planar, rotate the input polygon and try again. - const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); - // Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446. - const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram); - const double fix_angle = PI / 6; + // When any Voronoi vertex is missing, the Voronoi diagram is not planar, or some voronoi edge is + // intersecting input segment, rotate the input polygon and try again. + VoronoiDiagramStatus status = detect_voronoi_diagram_known_issues(voronoi_diagram, segments); + const double fix_angle = PI / 6; std::unordered_map vertex_mapping; // polys_copy is referenced through items stored in the std::vector segments. Polygons polys_copy = polys; - if (has_missing_voronoi_vertex || !is_voronoi_diagram_planar) { - if (has_missing_voronoi_vertex) + if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) { + if (status == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX) BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth."; - else if (!is_voronoi_diagram_planar) + else if (status == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM) BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth."; + else if (status == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) + BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth."; vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle); assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments)); - assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram)); + assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments)); + assert(!detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments)); if (detect_missing_voronoi_vertex(voronoi_diagram, segments)) BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input."; - else if (!Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram)) + else if (!Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments)) BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input."; + else if (detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments)) + BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input."; } - bool degenerated_voronoi_diagram = has_missing_voronoi_vertex || !is_voronoi_diagram_planar; - process_voronoi_diagram: assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty()); for (vd_t::cell_type cell : voronoi_diagram.cells()) { @@ -652,35 +704,35 @@ process_voronoi_diagram: Point start_source_point; Point end_source_point; - vd_t::edge_type* starting_vonoroi_edge = nullptr; - vd_t::edge_type* ending_vonoroi_edge = nullptr; + vd_t::edge_type* starting_voronoi_edge = nullptr; + vd_t::edge_type* ending_voronoi_edge = nullptr; // Compute and store result in above variables - + if (cell.contains_point()) { - const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); + const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); if (!keep_going) continue; } else { assert(cell.contains_segment()); - computeSegmentCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); + computeSegmentCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); } - - if (!starting_vonoroi_edge || !ending_vonoroi_edge) { + + if (!starting_voronoi_edge || !ending_voronoi_edge) { assert(false && "Each cell should start / end in a polygon vertex"); continue; } - + // Copy start to end edge to graph edge_t* prev_edge = nullptr; - assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() >= std::numeric_limits::lowest()); - transferEdge(start_source_point, VoronoiUtils::p(starting_vonoroi_edge->vertex1()).cast(), *starting_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments); - node_t* starting_node = vd_node_to_he_node[starting_vonoroi_edge->vertex0()]; + assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() >= std::numeric_limits::lowest()); + transferEdge(start_source_point, VoronoiUtils::p(starting_voronoi_edge->vertex1()).cast(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); + node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()]; starting_node->data.distance_to_boundary = 0; constexpr bool is_next_to_start_or_end = true; graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); - for (vd_t::edge_type* vd_edge = starting_vonoroi_edge->next(); vd_edge != ending_vonoroi_edge; vd_edge = vd_edge->next()) { + for (vd_t::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { assert(vd_edge->is_finite()); assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits::lowest()); @@ -692,12 +744,12 @@ process_voronoi_diagram: Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast(); transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments); - graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_vonoroi_edge); + graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge); } - assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() >= std::numeric_limits::lowest()); - assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() >= std::numeric_limits::lowest()); - transferEdge(VoronoiUtils::p(ending_vonoroi_edge->vertex0()).cast(), end_source_point, *ending_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments); + assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() >= std::numeric_limits::lowest()); + assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() <= std::numeric_limits::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() >= std::numeric_limits::lowest()); + transferEdge(VoronoiUtils::p(ending_voronoi_edge->vertex0()).cast(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); prev_edge->to->data.distance_to_boundary = 0; } @@ -705,9 +757,9 @@ process_voronoi_diagram: // When this degenerated Voronoi diagram is processed, the resulting half-edge structure contains some edges that don't have // a twin edge. Based on this, we created a fast mechanism that detects those causes and tries to recompute the Voronoi // diagram on slightly rotated input polygons that usually make the Voronoi generator generate a non-degenerated Voronoi diagram. - if (!degenerated_voronoi_diagram && has_missing_twin_edge(this->graph)) { + if (status == VoronoiDiagramStatus::NO_ISSUE_DETECTED && has_missing_twin_edge(this->graph)) { BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth."; - degenerated_voronoi_diagram = true; + status = VoronoiDiagramStatus::OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION; vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle); assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments)); @@ -724,14 +776,14 @@ process_voronoi_diagram: goto process_voronoi_diagram; } - if (degenerated_voronoi_diagram) { + if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) { assert(!has_missing_twin_edge(this->graph)); if (has_missing_twin_edge(this->graph)) BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input."; } - if (degenerated_voronoi_diagram) + if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fix_angle, vertex_mapping); #ifdef ARACHNE_DEBUG @@ -742,7 +794,7 @@ process_voronoi_diagram: graph.collapseSmallEdges(); - // Set [incident_edge] the the first possible edge that way we can iterate over all reachable edges from node.incident_edge, + // Set [incident_edge] the first possible edge that way we can iterate over all reachable edges from node.incident_edge, // without needing to iterate backward for (edge_t& edge : graph.edges) if (!edge.prev) diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index 819b71367..b4029d586 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -9,6 +9,7 @@ #include // smart pointers #include #include // pair +#include #include "utils/HalfEdgeGraph.hpp" #include "utils/PolygonsSegmentIndex.hpp" @@ -229,7 +230,7 @@ protected: * /return Whether the cell is inside of the polygon. If it's outside of the * polygon we should skip processing it altogether. */ - bool computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); + static bool computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); /*! * Compute the range of line segments that surround a cell of the skeletal @@ -255,7 +256,7 @@ protected: * /return Whether the cell is inside of the polygon. If it's outside of the * polygon we should skip processing it altogether. */ - void computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); + static void computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector& segments); /*! * For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two @@ -597,6 +598,8 @@ protected: * Genrate small segments for local maxima where the beading would only result in a single bead */ void generateLocalMaximaSingleBeads(); + + friend bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector &segments); }; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index 7ac1a3c4c..c0c5e7db1 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -14,7 +14,7 @@ #include "../../../clipper/clipper_z.hpp" namespace Slic3r { -class ThickPolyline; +struct ThickPolyline; } namespace Slic3r::Arachne diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index a6aecd205..e200b7edb 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -425,24 +425,11 @@ template<> std::function AutoArranger::get_objfn() { auto bincenter = m_bin.center(); return [this, bincenter](const Item &item) { - + auto result = objfunc(item, bincenter); - + double score = std::get<0>(result); - - auto isBig = [this](const Item& itm) { - return itm.area() / m_bin_area > BIG_ITEM_TRESHOLD ; - }; - - if(isBig(item)) { - auto mp = m_merged_pile; - mp.push_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - double miss = Placer::overfit(chull, m_bin); - if(miss < 0) miss = 0; - score += miss*miss; - } - + return score; }; } diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index a501680a1..6c16d08b7 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -174,7 +174,6 @@ public: BoundingBox rotated(double angle, const Point ¢er) const; void rotate(double angle) { (*this) = this->rotated(angle); } void rotate(double angle, const Point ¢er) { (*this) = this->rotated(angle, center); } - bool intersects(const BoundingBox &other) const { return this->min(0) <= other.max(0) && this->max(0) >= other.min(0) && this->min(1) <= other.max(1) && this->max(1) >= other.min(1); } // Align the min corner to a grid of cell_size x cell_size cells, // to encompass the original bounding box. void align_to_grid(const coord_t cell_size); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index e9aeb68e2..707dbfeb1 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -193,7 +193,6 @@ set(SLIC3R_SOURCES Model.hpp ModelArrange.hpp ModelArrange.cpp - #ModelVolumeType.hpp MultiMaterialSegmentation.cpp MultiMaterialSegmentation.hpp MeshNormals.hpp diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index a8d0473b7..ef3dc32c8 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1677,6 +1677,7 @@ public: case coPercent: { auto opt = new ConfigOptionPercent(); archive(*opt); return opt; } case coPercents: { auto opt = new ConfigOptionPercents(); archive(*opt); return opt; } case coFloatOrPercent: { auto opt = new ConfigOptionFloatOrPercent(); archive(*opt); return opt; } + case coFloatsOrPercents:{ auto opt = new ConfigOptionFloatsOrPercents();archive(*opt); return opt; } case coPoint: { auto opt = new ConfigOptionPoint(); archive(*opt); return opt; } case coPoints: { auto opt = new ConfigOptionPoints(); archive(*opt); return opt; } case coPoint3: { auto opt = new ConfigOptionPoint3(); archive(*opt); return opt; } @@ -1708,6 +1709,7 @@ public: case coPercent: archive(*static_cast(opt)); break; case coPercents: archive(*static_cast(opt)); break; case coFloatOrPercent: archive(*static_cast(opt)); break; + case coFloatsOrPercents:archive(*static_cast(opt));break; case coPoint: archive(*static_cast(opt)); break; case coPoints: archive(*static_cast(opt)); break; case coPoint3: archive(*static_cast(opt)); break; diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp index 375b6b7a0..88e3bba75 100644 --- a/src/libslic3r/CutSurface.cpp +++ b/src/libslic3r/CutSurface.cpp @@ -78,6 +78,8 @@ using EI = CGAL::SM_Edge_index; using FI = CGAL::SM_Face_index; using P3 = CGAL::Epick::Point_3; +inline Vec3d to_vec3d(const P3 &p) { return Vec3d(p.x(),p.y(),p.z()); } + /// /// Convert triangle mesh model to CGAL Surface_mesh /// Filtrate out opposite triangles @@ -1573,8 +1575,52 @@ void priv::create_reduce_map(ReductionMap &reduction_map, const CutMesh &mesh) assert(!is_reducible_vertex(left)); VI &vi = reduction_map[erase]; // check if it is first add - if (!vi.is_valid()) - reduction_map[erase] = left; + if (vi.is_valid()) + return; + + // check that all triangles after reduction has 'erase' and 'left' vertex + // on same side of opposite line of vertex in triangle + Vec3d v_erase = to_vec3d(mesh.point(erase)); + Vec3d v_left = to_vec3d(mesh.point(left)); + for (FI fi : mesh.faces_around_target(hi)) { + if (!fi.is_valid()) + continue; + // get vertices of rest + VI vi_a, vi_b; + for (VI vi : mesh.vertices_around_face(mesh.halfedge(fi))) { + if (!vi.is_valid()) + continue; + if (vi == erase) + continue; + if (!vi_a.is_valid()) + vi_a = vi; + else { + assert(!vi_b.is_valid()); + vi_b = vi; + } + } + assert(vi_b.is_valid()); + // do not check triangle, which will be removed + if (vi_a == left || vi_b == left) + continue; + + Vec3d v_a = to_vec3d(mesh.point(vi_a)); + Vec3d v_b = to_vec3d(mesh.point(vi_b)); + // Vectors of triangle edges + Vec3d v_ab = v_b - v_a; + Vec3d v_ae = v_erase - v_a; + Vec3d v_al = v_left - v_a; + + Vec3d n1 = v_ab.cross(v_ae); + Vec3d n2 = v_ab.cross(v_al); + // check that normal has same direction + if ((n1.x() > 0 != n2.x() > 0) || + (n1.y() > 0 != n2.y() > 0) || + (n1.z() > 0 != n2.z() > 0)) + return; // this reduction will create CCW triangle + } + + reduction_map[erase] = left; // I have no better rule than take the first // for decide which reduction will be better // But it could be use only one of them @@ -2521,7 +2567,7 @@ bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper) BoundingBoxf3 priv::bounding_box(const CutAOI &cut, const CutMesh &mesh) { const P3& p_from_cut = mesh.point(mesh.target(mesh.halfedge(cut.first.front()))); - Vec3d min(p_from_cut.x(), p_from_cut.y(), p_from_cut.z()); + Vec3d min = to_vec3d(p_from_cut); Vec3d max = min; for (FI fi : cut.first) { for(VI vi: mesh.vertices_around_face(mesh.halfedge(fi))){ @@ -2537,9 +2583,8 @@ BoundingBoxf3 priv::bounding_box(const CutAOI &cut, const CutMesh &mesh) { BoundingBoxf3 priv::bounding_box(const CutMesh &mesh) { - const P3 &p_from_cut = *mesh.points().begin(); - Vec3d min(p_from_cut.x(), p_from_cut.y(), p_from_cut.z()); - Vec3d max = min; + Vec3d min = to_vec3d(*mesh.points().begin()); + Vec3d max = min; for (VI vi : mesh.vertices()) { const P3 &p = mesh.point(vi); for (size_t i = 0; i < 3; ++i) { @@ -2806,7 +2851,7 @@ bool priv::is_patch_inside_of_model(const SurfacePatch &patch, { // TODO: Solve model with hole in projection direction !!! const P3 &a = patch.mesh.point(VI(0)); - Vec3d a_(a.x(), a.y(), a.z()); + Vec3d a_ = to_vec3d(a); Vec3d b_ = projection.project(a_); P3 b(b_.x(), b_.y(), b_.z()); @@ -3396,7 +3441,7 @@ Polygons priv::unproject_loops(const SurfacePatch &patch, const Project &project pts.reserve(l.size()); for (VI vi : l) { const P3 &p3 = patch.mesh.point(vi); - Vec3d p(p3.x(), p3.y(), p3.z()); + Vec3d p = to_vec3d(p3); double depth; std::optional p2_opt = projection.unproject(p, &depth); if (depth_range[0] > depth) depth_range[0] = depth; // min @@ -3426,9 +3471,21 @@ ExPolygon priv::to_expoly(const SurfacePatch &patch, const Project &projection, // should not be used when no opposit triangle are counted so should not create overlaps ClipperLib::PolyFillType fill_type = ClipperLib::PolyFillType::pftEvenOdd; ExPolygons expolys = Slic3r::union_ex(polys, fill_type); - assert(expolys.size() == 1); + if (expolys.size() == 1) + return expolys.front(); + + // It should be one expolygon + assert(false); + if (expolys.empty()) return {}; - return expolys.front(); + // find biggest + const ExPolygon *biggest = &expolys.front(); + for (size_t index = 1; index < expolys.size(); ++index) { + const ExPolygon *current = &expolys[index]; + if (biggest->contour.size() < current->contour.size()) + biggest = current; + } + return *biggest; } SurfaceCut priv::patch2cut(SurfacePatch &patch) @@ -3597,7 +3654,7 @@ void priv::store(const CutMesh &mesh, const FaceTypeMap &face_type_map, const st default: color = CGAL::Color{0, 0, 255}; // blue } } - CGAL::IO::write_OFF(off_file, mesh); + CGAL::IO::write_OFF(off_file, mesh, CGAL::parameters::face_color_map(face_colors)); mesh_.remove_property_map(face_colors); } @@ -3624,7 +3681,7 @@ void priv::store(const CutMesh &mesh, const ReductionMap &reduction_map, const s vertex_colors[reduction_to] = CGAL::Color{0, 0, 255}; } - CGAL::IO::write_OFF(off_file, mesh); + CGAL::IO::write_OFF(off_file, mesh, CGAL::parameters::vertex_color_map(vertex_colors)); mesh_.remove_property_map(vertex_colors); } diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index d7a3ac1f6..440903757 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -283,7 +283,7 @@ ExPolygons Emboss::heal_shape(const Polygons &shape) { // Do not remove all duplicits but do it better way // Overlap all duplicit points by rectangle 3x3 - Points duplicits = collect_duplications(to_points(polygons)); + Points duplicits = collect_duplicates(to_points(polygons)); if (!duplicits.empty()) { polygons.reserve(polygons.size() + duplicits.size()); for (const Point &p : duplicits) { @@ -310,7 +310,7 @@ bool Emboss::heal_shape(ExPolygons &shape, unsigned max_iteration) priv::remove_same_neighbor(shape); Pointfs intersections = intersection_points(shape); - Points duplicits = collect_duplications(to_points(shape)); + Points duplicits = collect_duplicates(to_points(shape)); //Points close = priv::collect_close_points(shape, 1.); if (intersections.empty() && duplicits.empty() /* && close.empty() */) break; @@ -353,7 +353,7 @@ bool Emboss::heal_shape(ExPolygons &shape, unsigned max_iteration) svg.draw(shape, "green"); svg.draw(duplicits, "lightgray", 13 / Emboss::SHAPE_SCALE); - Points duplicits3 = collect_duplications(to_points(shape)); + Points duplicits3 = collect_duplicates(to_points(shape)); svg.draw(duplicits3, "black", 7 / Emboss::SHAPE_SCALE); Pointfs pts2 = intersection_points(shape); @@ -387,7 +387,7 @@ bool Emboss::heal_shape(ExPolygons &shape, unsigned max_iteration) } assert(intersection_points(shape).empty()); - assert(collect_duplications(to_points(shape)).empty()); + assert(collect_duplicates(to_points(shape)).empty()); return true; } @@ -1186,7 +1186,7 @@ indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d, const IProjection &projection) { Points points = to_points(shape2d); - Points duplicits = collect_duplications(points); + Points duplicits = collect_duplicates(points); return (duplicits.empty()) ? priv::polygons2model_unique(shape2d, projection, points) : priv::polygons2model_duplicit(shape2d, projection, points, duplicits); diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 8dc0ff547..dc991e46d 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -320,7 +320,9 @@ void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polyl { ThickPolylines tp; this->medial_axis(min_width, max_width, &tp); - polylines->insert(polylines->end(), tp.begin(), tp.end()); + polylines->reserve(polylines->size() + tp.size()); + for (auto &pl : tp) + polylines->emplace_back(pl.points); } Lines ExPolygon::lines() const diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index bc379814e..8997df192 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -148,7 +148,7 @@ static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed"; static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges"; // Store / load of TextConfiguration -static constexpr const char *TEXT_TAG = "emboss"; +static constexpr const char *TEXT_TAG = "slic3rpe:text"; static constexpr const char *TEXT_DATA_ATTR = "text"; // TextConfiguration::EmbossStyle static constexpr const char *STYLE_NAME_ATTR = "style_name"; @@ -3628,8 +3628,8 @@ std::optional TextConfigurationSerialization::read(const char float distance = get_attribute_value_float(attributes, num_attributes, DISTANCE_ATTR); if (std::fabs(distance) > std::numeric_limits::epsilon()) fp.distance = distance; - std::string use_surface = get_attribute_value_string(attributes, num_attributes, USE_SURFACE_ATTR); - if (!use_surface.empty()) fp.use_surface = true; + int use_surface = get_attribute_value_int(attributes, num_attributes, USE_SURFACE_ATTR); + if (use_surface == 1) fp.use_surface = true; float angle = get_attribute_value_float(attributes, num_attributes, ANGLE_ATTR); if (std::fabs(angle) > std::numeric_limits::epsilon()) fp.angle = angle; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index dc587f388..b9817776f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1,4 +1,5 @@ #include "libslic3r.h" +#include "GCode/ExtrusionProcessor.hpp" #include "I18N.hpp" #include "GCode.hpp" #include "Exception.hpp" @@ -2160,14 +2161,18 @@ LayerResult GCode::process_layer( Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); - if (this->config().avoid_curled_filament_during_travels) { - m_avoid_curled_filaments.clear(); + if (this->config().avoid_crossing_curled_overhangs) { + m_avoid_crossing_curled_overhangs.clear(); for (const ObjectLayerToPrint &layer_to_print : layers) { - m_avoid_curled_filaments.add_obstacles(layer_to_print.object_layer, Point(scaled(this->origin()))); - m_avoid_curled_filaments.add_obstacles(layer_to_print.support_layer, Point(scaled(this->origin()))); + m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.object_layer, Point(scaled(this->origin()))); + m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.support_layer, Point(scaled(this->origin()))); } } + for (const ObjectLayerToPrint &layer_to_print : layers) { + m_extrusion_quality_estimator.prepare_for_new_layer(layer_to_print.object_layer); + } + // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. for (unsigned int extruder_id : layer_tools.extruders) { @@ -2295,6 +2300,8 @@ void GCode::process_layer_single_object( const PrintObject &print_object = print_instance.print_object; const Print &print = *print_object.print(); + m_extrusion_quality_estimator.set_current_object(&print_object); + if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr) if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) { ExtrusionRole role = support_layer.support_fills.role(); @@ -2327,7 +2334,7 @@ void GCode::process_layer_single_object( interface_extruder = dontcare_extruder; } bool extrude_support = has_support && support_extruder == extruder_id; - bool extrude_interface = interface_extruder && interface_extruder == extruder_id; + bool extrude_interface = has_interface && interface_extruder == extruder_id; if (extrude_support || extrude_interface) { init_layer_delayed(); m_layer = layer_to_print.support_layer; @@ -2845,12 +2852,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de acceleration = m_config.first_layer_acceleration.value; } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { acceleration = m_config.first_layer_acceleration_over_raft.value; - } else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) { - acceleration = m_config.perimeter_acceleration.value; } else if (m_config.bridge_acceleration.value > 0 && is_bridge(path.role())) { acceleration = m_config.bridge_acceleration.value; } else if (m_config.infill_acceleration.value > 0 && is_infill(path.role())) { acceleration = m_config.infill_acceleration.value; + } else if (m_config.perimeter_acceleration.value > 0 && is_perimeter(path.role())) { + acceleration = m_config.perimeter_acceleration.value; } else { acceleration = m_config.default_acceleration.value; } @@ -2905,6 +2912,16 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm ); } + + bool variable_speed = false; + std::vector new_points{}; + if (this->m_config.enable_dynamic_overhang_speeds && !this->on_first_layer() && is_perimeter(path.role())) { + new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, m_config.overhang_overlap_levels, + m_config.dynamic_overhang_speeds, + m_config.get_abs_value("external_perimeter_speed"), speed); + variable_speed = std::any_of(new_points.begin(), new_points.end(), [speed](const ProcessedPoint &p) { return p.speed != speed; }); + } + double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line @@ -2966,10 +2983,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de comment += ";_EXTERNAL_PERIMETER"; } - // F is mm per minute. - gcode += m_writer.set_speed(F, "", comment); - double path_length = 0.; - { + if (!variable_speed) { + // F is mm per minute. + gcode += m_writer.set_speed(F, "", comment); + double path_length = 0.; std::string comment; if (m_config.gcode_comments) { comment = description; @@ -2985,7 +3002,29 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); prev = p; } + } else { + std::string comment; + if (m_config.gcode_comments) { + comment = description; + comment += description_bridge; + } + double last_set_speed = new_points[0].speed * 60.0; + gcode += m_writer.set_speed(last_set_speed, "", comment); + Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); + for (size_t i = 1; i < new_points.size(); i++) { + const ProcessedPoint& processed_point = new_points[i]; + Vec2d p = this->point_to_gcode_quantized(processed_point.p); + const double line_length = (p - prev).norm(); + gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment); + prev = p; + double new_speed = processed_point.speed * 60.0; + if (last_set_speed != new_speed) { + gcode += m_writer.set_speed(new_speed, "", comment); + last_set_speed = new_speed; + } + } } + if (m_enable_cooling_markers) gcode += is_bridge(path.role()) ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n"; @@ -3001,10 +3040,15 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string this->origin in order to get G-code coordinates. */ Polyline travel { this->last_pos(), point }; - if (this->config().avoid_curled_filament_during_travels) { - Point scaled_origin = Point(scaled(this->origin())); - travel = m_avoid_curled_filaments.find_path(this->last_pos() + scaled_origin, point + scaled_origin); - travel.translate(-scaled_origin); + if (this->config().avoid_crossing_curled_overhangs) { + if (m_config.avoid_crossing_perimeters) { + BOOST_LOG_TRIVIAL(warning) + << "Option >avoid crossing curled overhangs< is not compatible with avoid crossing perimeters and it will be ignored!"; + } else { + Point scaled_origin = Point(scaled(this->origin())); + travel = m_avoid_crossing_curled_overhangs.find_path(this->last_pos() + scaled_origin, point + scaled_origin); + travel.translate(-scaled_origin); + } } // check whether a straight travel move would need retraction diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index e37d11a2e..09442cf0f 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_GCode_hpp_ #define slic3r_GCode_hpp_ +#include "GCode/ExtrusionProcessor.hpp" #include "JumpPointSearch.hpp" #include "libslic3r.h" #include "ExPolygon.hpp" @@ -333,6 +334,8 @@ private: // Cache for custom seam enforcers/blockers for each layer. SeamPlacer m_seam_placer; + ExtrusionQualityEstimator m_extrusion_quality_estimator; + /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() methods. */ @@ -349,7 +352,7 @@ private: OozePrevention m_ooze_prevention; Wipe m_wipe; AvoidCrossingPerimeters m_avoid_crossing_perimeters; - JPSPathFinder m_avoid_curled_filaments; + JPSPathFinder m_avoid_crossing_curled_overhangs; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp new file mode 100644 index 000000000..5a13e5484 --- /dev/null +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -0,0 +1,325 @@ +#ifndef slic3r_ExtrusionProcessor_hpp_ +#define slic3r_ExtrusionProcessor_hpp_ + +#include "../AABBTreeLines.hpp" +#include "../SupportSpotsGenerator.hpp" +#include "../libslic3r.h" +#include "../ExtrusionEntity.hpp" +#include "../Layer.hpp" +#include "../Point.hpp" +#include "../SVG.hpp" +#include "../BoundingBox.hpp" +#include "../Polygon.hpp" +#include "../ClipperUtils.hpp" +#include "../Flow.hpp" +#include "../Config.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { + +class SlidingWindowCurvatureAccumulator +{ + float window_size; + float total_distance = 0; // accumulated distance + float total_curvature = 0; // accumulated signed ccw angles + deque distances; + deque angles; + +public: + SlidingWindowCurvatureAccumulator(float window_size) : window_size(window_size) {} + + void add_point(float distance, float angle) + { + total_distance += distance; + total_curvature += angle; + distances.push_back(distance); + angles.push_back(angle); + + while (distances.size() > 1 && total_distance > window_size) { + total_distance -= distances.front(); + total_curvature -= angles.front(); + distances.pop_front(); + angles.pop_front(); + } + } + + float get_curvature() const + { + return total_curvature / window_size; + } + + void reset() + { + total_curvature = 0; + total_distance = 0; + distances.clear(); + angles.clear(); + } +}; + +class CurvatureEstimator +{ + static const size_t sliders_count = 3; + SlidingWindowCurvatureAccumulator sliders[sliders_count] = {{1.0},{4.0}, {10.0}}; + +public: + void add_point(float distance, float angle) + { + if (distance < EPSILON) + return; + for (SlidingWindowCurvatureAccumulator &slider : sliders) { + slider.add_point(distance, angle); + } + } + float get_curvature() + { + float max_curvature = 0.0f; + for (const SlidingWindowCurvatureAccumulator &slider : sliders) { + if (abs(slider.get_curvature()) > abs(max_curvature)) { + max_curvature = slider.get_curvature(); + } + } + return max_curvature; + } + void reset() + { + for (SlidingWindowCurvatureAccumulator &slider : sliders) { + slider.reset(); + } + } +}; + +struct ExtendedPoint +{ + ExtendedPoint(Vec2d position, float distance = 0.0, size_t nearest_prev_layer_line = size_t(-1), float curvature = 0.0) + : position(position), distance(distance), nearest_prev_layer_line(nearest_prev_layer_line), curvature(curvature) + {} + + Vec2d position; + float distance; + size_t nearest_prev_layer_line; + float curvature; +}; + +template +std::vector estimate_points_properties(const std::vector

&input_points, + const AABBTreeLines::LinesDistancer &unscaled_prev_layer, + float flow_width, + float max_line_length = -1.0f) +{ + using AABBScalar = typename AABBTreeLines::LinesDistancer::Scalar; + if (input_points.empty()) + return {}; + float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f; + CurvatureEstimator cestim; + auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast(); }; + + std::vector

extrusion_points; + { + if (max_line_length <= 0) { + extrusion_points = input_points; + } else { + extrusion_points.reserve(input_points.size() * 2); + for (size_t i = 0; i + 1 < input_points.size(); i++) { + const P &curr = input_points[i]; + const P &next = input_points[i + 1]; + extrusion_points.push_back(curr); + auto len = maybe_unscale(next - curr).squaredNorm(); + double t = sqrt((max_line_length * max_line_length) / len); + size_t new_point_count = 1.0 / (t + EPSILON); + for (size_t j = 1; j < new_point_count + 1; j++) { + extrusion_points.push_back(curr * (1.0 - j * t) + next * (j * t)); + } + } + extrusion_points.push_back(input_points.back()); + } + } + + std::vector points; + points.reserve(extrusion_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1)); + + { + ExtendedPoint start_point{maybe_unscale(extrusion_points.front())}; + auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(start_point.position.cast()); + start_point.distance = distance + boundary_offset; + start_point.nearest_prev_layer_line = nearest_line; + points.push_back(start_point); + } + for (size_t i = 1; i < extrusion_points.size(); i++) { + ExtendedPoint next_point{maybe_unscale(extrusion_points[i])}; + auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(next_point.position.cast()); + next_point.distance = distance + boundary_offset; + next_point.nearest_prev_layer_line = nearest_line; + + if (ADD_INTERSECTIONS && + ((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) { + const ExtendedPoint &prev_point = points.back(); + auto intersections = unscaled_prev_layer.template intersections_with_line(L{prev_point.position.cast(), next_point.position.cast()}); + for (const auto &intersection : intersections) { + points.emplace_back(intersection.first.template cast(), boundary_offset, intersection.second); + } + } + points.push_back(next_point); + } + + if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) { + std::vector new_points; + new_points.reserve(points.size() * 2); + new_points.push_back(points.front()); + for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) { + const ExtendedPoint &curr = points[point_idx]; + const ExtendedPoint &next = points[point_idx + 1]; + + if ((curr.distance > 0 && curr.distance < boundary_offset + 2.0f) || + (next.distance > 0 && next.distance < boundary_offset + 2.0f)) { + double line_len = (next.position - curr.position).norm(); + if (line_len > 4.0f) { + double a0 = std::clamp((curr.distance + 2 * boundary_offset) / line_len, 0.0, 1.0); + double a1 = std::clamp(1.0f - (next.distance + 2 * boundary_offset) / line_len, 0.0, 1.0); + double t0 = std::min(a0, a1); + double t1 = std::max(a0, a1); + + if (t0 < 1.0) { + auto p0 = curr.position + t0 * (next.position - curr.position); + auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra(p0.cast()); + new_points.push_back(ExtendedPoint{p0, float(p0_dist + boundary_offset), p0_near_l}); + } + if (t1 > 0.0) { + auto p1 = curr.position + t1 * (next.position - curr.position); + auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra(p1.cast()); + new_points.push_back(ExtendedPoint{p1, float(p1_dist + boundary_offset), p1_near_l}); + } + } + } + new_points.push_back(next); + } + points = std::move(new_points); + } + + for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) { + ExtendedPoint &a = points[point_idx]; + ExtendedPoint &prev = points[point_idx > 0 ? point_idx - 1 : point_idx]; + + int prev_point_idx = point_idx; + while (prev_point_idx > 0) { + prev_point_idx--; + if ((a.position - points[prev_point_idx].position).squaredNorm() > EPSILON) { break; } + } + + int next_point_index = point_idx; + while (next_point_index < int(points.size()) - 1) { + next_point_index++; + if ((a.position - points[next_point_index].position).squaredNorm() > EPSILON) { break; } + } + + if (prev_point_idx != point_idx && next_point_index != point_idx) { + float distance = (prev.position - a.position).norm(); + float alfa = angle(a.position - points[prev_point_idx].position, points[next_point_index].position - a.position); + cestim.add_point(distance, alfa); + } + + a.curvature = cestim.get_curvature(); + } + + return points; +} + +struct ProcessedPoint +{ + Point p; + float speed = 1.0f; +}; + +class ExtrusionQualityEstimator +{ + std::unordered_map> prev_layer_boundaries; + std::unordered_map> next_layer_boundaries; + const PrintObject *current_object; + +public: + void set_current_object(const PrintObject *object) { current_object = object; } + + void prepare_for_new_layer(const Layer *layer) + { + if (layer == nullptr) return; + const PrintObject *object = layer->object(); + prev_layer_boundaries[object] = next_layer_boundaries[object]; + next_layer_boundaries[object] = AABBTreeLines::LinesDistancer{to_unscaled_linesf(layer->lslices)}; + } + + std::vector estimate_extrusion_quality(const ExtrusionPath &path, + const ConfigOptionPercents &overlaps, + const ConfigOptionFloatsOrPercents &speeds, + float ext_perimeter_speed, + float original_speed) + { + size_t speed_sections_count = std::min(overlaps.values.size(), speeds.values.size()); + std::vector> speed_sections; + for (size_t i = 0; i < speed_sections_count; i++) { + float distance = path.width * (1.0 - (overlaps.get_at(i) / 100.0)); + float speed = speeds.get_at(i).percent ? (ext_perimeter_speed * speeds.get_at(i).value / 100.0) : speeds.get_at(i).value; + speed_sections.push_back({distance, speed}); + } + std::sort(speed_sections.begin(), speed_sections.end(), + [](const std::pair &a, const std::pair &b) { + if (a.first == b.first) { + return a.second > b.second; + } + return a.first < b.first; }); + + std::pair last_section{INFINITY, 0}; + for (auto §ion : speed_sections) { + if (section.first == last_section.first) { + section.second = last_section.second; + } else { + last_section = section; + } + } + + std::vector extended_points = + estimate_points_properties(path.polyline.points, prev_layer_boundaries[current_object], path.width); + + std::vector processed_points; + processed_points.reserve(extended_points.size()); + for (size_t i = 0; i < extended_points.size(); i++) { + const ExtendedPoint &curr = extended_points[i]; + const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i]; + + auto calculate_speed = [&speed_sections, &original_speed](float distance) { + float final_speed; + if (distance <= speed_sections.front().first) { + final_speed = original_speed; + } else if (distance >= speed_sections.back().first) { + final_speed = speed_sections.back().second; + } else { + size_t section_idx = 0; + while (distance > speed_sections[section_idx + 1].first) { + section_idx++; + } + float t = (distance - speed_sections[section_idx].first) / + (speed_sections[section_idx + 1].first - speed_sections[section_idx].first); + t = std::clamp(t, 0.0f, 1.0f); + final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second; + } + return final_speed; + }; + + float extrusion_speed = std::min(calculate_speed(curr.distance), calculate_speed(next.distance)); + + processed_points.push_back({scaled(curr.position), extrusion_speed}); + } + return processed_points; + } +}; + +} // namespace Slic3r + +#endif // slic3r_ExtrusionProcessor_hpp_ diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index c8a9618ef..399ab4272 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -367,7 +367,16 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo case 'T': { // Activate an extruder head. - int new_extruder = parse_int(line); + int new_extruder = -1; + try { + new_extruder = parse_int(line); + } catch (Slic3r::InvalidArgument &) { + // Ignore invalid GCodes starting with T. + eatws(line); + break; + } + assert(new_extruder != -1); + if (new_extruder != int(m_current_extruder)) { m_current_extruder = new_extruder; m_retracted = true; diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index c92ff8212..685855721 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1071,7 +1071,7 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) for (SeamCandidate &perimeter_point : layers[layer_idx].points) { Vec2f point = Vec2f { perimeter_point.position.head<2>() }; if (prev_layer_distancer.get() != nullptr) { - perimeter_point.overhang = prev_layer_distancer->signed_distance_from_lines(point.cast()) + perimeter_point.overhang = prev_layer_distancer->distance_from_lines(point.cast()) + 0.6f * perimeter_point.perimeter.flow_width - tan(SeamPlacer::overhang_angle_threshold) * po->layers()[layer_idx]->height; @@ -1080,7 +1080,7 @@ void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) } if (should_compute_layer_embedding) { // search for embedded perimeter points (points hidden inside the print ,e.g. multimaterial join, best position for seam) - perimeter_point.embedded_distance = current_layer_distancer->signed_distance_from_lines(point.cast()) + perimeter_point.embedded_distance = current_layer_distancer->distance_from_lines(point.cast()) + 0.6f * perimeter_point.perimeter.flow_width; } } diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 0242a9eee..7366d2233 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -430,7 +430,26 @@ static bool contains_skew(const Transform3d& trafo) Matrix3d rotation; Matrix3d scale; trafo.computeRotationScaling(&rotation, &scale); - return !scale.isDiagonal(); + + if (scale.isDiagonal()) + return false; + + if (scale.determinant() >= 0.0) + return true; + + // the matrix contains mirror + const Matrix3d ratio = scale.cwiseQuotient(trafo.matrix().block<3,3>(0,0)); + + auto check_skew = [&ratio](int i, int j, bool& skew) { + if (!std::isnan(ratio(i, j)) && !std::isnan(ratio(j, i))) + skew |= std::abs(ratio(i, j) * ratio(j, i) - 1.0) > EPSILON; + }; + + bool has_skew = false; + check_skew(0, 1, has_skew); + check_skew(0, 2, has_skew); + check_skew(1, 2, has_skew); + return has_skew; } Vec3d Transformation::get_rotation() const diff --git a/src/libslic3r/Geometry/MedialAxis.cpp b/src/libslic3r/Geometry/MedialAxis.cpp index c92796f41..4f614169c 100644 --- a/src/libslic3r/Geometry/MedialAxis.cpp +++ b/src/libslic3r/Geometry/MedialAxis.cpp @@ -498,6 +498,7 @@ void MedialAxis::build(ThickPolylines* polylines) polyline.width.emplace_back(seed_edge_data.width_end); // Grow the polyline in a forward direction. this->process_edge_neighbors(&*seed_edge, &polyline); + assert(polyline.width.size() == polyline.points.size() * 2 - 2); // Grow the polyline in a backward direction. reverse_polyline.clear(); @@ -505,7 +506,6 @@ void MedialAxis::build(ThickPolylines* polylines) polyline.points.insert(polyline.points.begin(), reverse_polyline.points.rbegin(), reverse_polyline.points.rend()); polyline.width.insert(polyline.width.begin(), reverse_polyline.width.rbegin(), reverse_polyline.width.rend()); polyline.endpoints.first = reverse_polyline.endpoints.second; - assert(polyline.width.size() == polyline.points.size() * 2 - 2); // Prevent loop endpoints from being extended. @@ -538,7 +538,9 @@ void MedialAxis::build(Polylines* polylines) { ThickPolylines tp; this->build(&tp); - polylines->insert(polylines->end(), tp.begin(), tp.end()); + polylines->reserve(polylines->size() + tp.size()); + for (auto &pl : tp) + polylines->emplace_back(pl.points); } void MedialAxis::process_edge_neighbors(const VD::edge_type *edge, ThickPolyline* polyline) diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp index 062a3b397..c3348110b 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp @@ -8,17 +8,135 @@ #include "VoronoiUtilsCgal.hpp" using VD = Slic3r::Geometry::VoronoiDiagram; +using namespace Slic3r::Arachne; namespace Slic3r::Geometry { -using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2; -using CGAL_Segment = CGAL::Arr_segment_traits_2::Curve_2; +// The tangent vector of the parabola is computed based on the Proof of the reflective property. +// https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property +// https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663 +namespace impl { + using K = CGAL::Simple_cartesian; + using FK = CGAL::Simple_cartesian; + using EK = CGAL::Simple_cartesian; + using C2E = CGAL::Cartesian_converter; + using C2F = CGAL::Cartesian_converter; + class Epick : public CGAL::Filtered_kernel_adaptor::Type, Epick>, true> {}; -inline static CGAL_Point to_cgal_point(const VD::vertex_type &pt) { return {pt.x(), pt.y()}; } + template + inline typename K::Vector_2 calculate_parabolic_tangent_vector( + // Test point on the parabola, where the tangent will be calculated. + const typename K::Point_2 &p, + // Focus point of the parabola. + const typename K::Point_2 &f, + // Points of a directrix of the parabola. + const typename K::Point_2 &u, + const typename K::Point_2 &v, + // On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent. + const typename K::Orientation &tangent_orientation) + { + using RT = typename K::RT; + using Vector_2 = typename K::Vector_2; + + const Vector_2 directrix_vec = v - u; + const RT directrix_vec_sqr_length = CGAL::scalar_product(directrix_vec, directrix_vec); + Vector_2 focus_vec = (f - u) * directrix_vec_sqr_length - directrix_vec * CGAL::scalar_product(directrix_vec, p - u); + Vector_2 tangent_vec = focus_vec.perpendicular(tangent_orientation); + return tangent_vec; + } + + template struct ParabolicTangentToSegmentOrientationPredicate + { + using Point_2 = typename K::Point_2; + using Vector_2 = typename K::Vector_2; + using Orientation = typename K::Orientation; + using result_type = typename K::Orientation; + + result_type operator()( + // Test point on the parabola, where the tangent will be calculated. + const Point_2 &p, + // End of the linear segment (p, q), for which orientation towards the tangent to parabola will be evaluated. + const Point_2 &q, + // Focus point of the parabola. + const Point_2 &f, + // Points of a directrix of the parabola. + const Point_2 &u, + const Point_2 &v, + // On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent. + const Orientation &tangent_orientation) const + { + assert(tangent_orientation == CGAL::Orientation::LEFT_TURN || tangent_orientation == CGAL::Orientation::RIGHT_TURN); + + Vector_2 tangent_vec = calculate_parabolic_tangent_vector(p, f, u, v, tangent_orientation); + Vector_2 linear_vec = q - p; + + return CGAL::sign(tangent_vec.x() * linear_vec.y() - tangent_vec.y() * linear_vec.x()); + } + }; + + template struct ParabolicTangentToParabolicTangentOrientationPredicate + { + using Point_2 = typename K::Point_2; + using Vector_2 = typename K::Vector_2; + using Orientation = typename K::Orientation; + using result_type = typename K::Orientation; + + result_type operator()( + // Common point on both parabolas, where the tangent will be calculated. + const Point_2 &p, + // Focus point of the first parabola. + const Point_2 &f_0, + // Points of a directrix of the first parabola. + const Point_2 &u_0, + const Point_2 &v_0, + // On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent. + const Orientation &tangent_orientation_0, + // Focus point of the second parabola. + const Point_2 &f_1, + // Points of a directrix of the second parabola. + const Point_2 &u_1, + const Point_2 &v_1, + // On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent. + const Orientation &tangent_orientation_1) const + { + assert(tangent_orientation_0 == CGAL::Orientation::LEFT_TURN || tangent_orientation_0 == CGAL::Orientation::RIGHT_TURN); + assert(tangent_orientation_1 == CGAL::Orientation::LEFT_TURN || tangent_orientation_1 == CGAL::Orientation::RIGHT_TURN); + + Vector_2 tangent_vec_0 = calculate_parabolic_tangent_vector(p, f_0, u_0, v_0, tangent_orientation_0); + Vector_2 tangent_vec_1 = calculate_parabolic_tangent_vector(p, f_1, u_1, v_1, tangent_orientation_1); + + return CGAL::sign(tangent_vec_0.x() * tangent_vec_1.y() - tangent_vec_0.y() * tangent_vec_1.x()); + } + }; + + using ParabolicTangentToSegmentOrientationPredicateFiltered = CGAL::Filtered_predicate, ParabolicTangentToSegmentOrientationPredicate, C2E, C2F>; + using ParabolicTangentToParabolicTangentOrientationPredicateFiltered = CGAL::Filtered_predicate, ParabolicTangentToParabolicTangentOrientationPredicate, C2E, C2F>; +} // namespace impl + +using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrientationPredicateFiltered; +using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered; +using CGAL_Point = impl::K::Point_2; + +inline static CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; } +inline static CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; } +inline static CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; } + +inline static Linef make_linef(const VD::edge_type &edge) +{ + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())}; +} + +inline static bool is_equal(const VD::vertex_type &first, const VD::vertex_type &second) { return first.x() == second.x() && first.y() == second.y(); } // FIXME Lukas H.: Also includes parabolic segments. bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram) { + using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2; + using CGAL_Segment = CGAL::Arr_segment_traits_2::Curve_2; + auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_Point { return {pt.x(), pt.y()}; }; + assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(), [](const VD::edge_type &edge) { return edge.color() == 0; })); @@ -30,7 +148,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_ continue; if (edge.is_finite() && edge.is_linear() && edge.vertex0() != nullptr && edge.vertex1() != nullptr && - Arachne::VoronoiUtils::is_finite(*edge.vertex0()) && Arachne::VoronoiUtils::is_finite(*edge.vertex1())) { + VoronoiUtils::is_finite(*edge.vertex0()) && VoronoiUtils::is_finite(*edge.vertex1())) { segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1())); edge.color(1); assert(edge.twin() != nullptr); @@ -46,37 +164,101 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_ return intersections_pt.empty(); } -static bool check_if_three_vectors_are_ccw(const CGAL_Point &common_pt, const CGAL_Point &pt_1, const CGAL_Point &pt_2, const CGAL_Point &test_pt) { - CGAL::Orientation orientation = CGAL::orientation(common_pt, pt_1, pt_2); +struct ParabolicSegment +{ + const Point focus; + const Line directrix; + // Two points on the parabola; + const Linef segment; + // Indicate if focus point is on the left side or right side relative to parabolic segment endpoints. + const CGAL::Orientation is_focus_on_left; +}; + +inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector &segments) +{ + assert(edge.is_curved()); + + const VD::cell_type *left_cell = edge.cell(); + const VD::cell_type *right_cell = edge.twin()->cell(); + + const Point focus_pt = VoronoiUtils::getSourcePoint(*(left_cell->contains_point() ? left_cell : right_cell), segments); + const VoronoiUtils::Segment &directrix = VoronoiUtils::getSourceSegment(*(left_cell->contains_point() ? right_cell : left_cell), segments); + CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); + + assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN); + return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side}; +} + +inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector &segments) { + assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0())); + CGAL::Orientation orientation; + if (edge_a.is_linear() && edge_b.is_linear()) { + orientation = CGAL::orientation(to_cgal_point(edge_a.vertex0()), to_cgal_point(edge_a.vertex1()), to_cgal_point(edge_b.vertex1())); + } else if (edge_a.is_curved() && edge_b.is_curved()) { + const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segments); + const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segments); + orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a), + to_cgal_point(parabolic_a.focus), + to_cgal_point(parabolic_a.directrix.a), + to_cgal_point(parabolic_a.directrix.b), + parabolic_a.is_focus_on_left, + to_cgal_point(parabolic_b.focus), + to_cgal_point(parabolic_b.directrix.a), + to_cgal_point(parabolic_b.directrix.b), + parabolic_b.is_focus_on_left); + return orientation; + } else { + assert(edge_a.is_curved() != edge_b.is_curved()); + + const VD::edge_type &linear_edge = edge_a.is_curved() ? edge_b : edge_a; + const VD::edge_type ¶bolic_edge = edge_a.is_curved() ? edge_a : edge_b; + const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segments); + orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()), + to_cgal_point(parabolic.focus), + to_cgal_point(parabolic.directrix.a), + to_cgal_point(parabolic.directrix.b), + parabolic.is_focus_on_left); + + if (edge_b.is_curved()) + orientation = CGAL::opposite(orientation); + } + + return orientation; +} + +static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector &segments) +{ + assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0())); + + CGAL::Orientation orientation = orientation_of_two_edges(first, second, segments); if (orientation == CGAL::Orientation::COLLINEAR) { // The first two edges are collinear, so the third edge must be on the right side on the first of them. - return CGAL::orientation(common_pt, pt_1, test_pt) == CGAL::Orientation::RIGHT_TURN; + return orientation_of_two_edges(first, third, segments) == CGAL::Orientation::RIGHT_TURN; } else if (orientation == CGAL::Orientation::LEFT_TURN) { // CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI. // So we need to check if test_pt isn't between them. - CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt); - CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt); + CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments); + CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments); return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN); } else { assert(orientation == CGAL::Orientation::RIGHT_TURN); // CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI. // So we need to check if test_pt is between them. - CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt); - CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt); + CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments); + CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments); return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN); } } -bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram) +bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector &segments) { for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) { std::vector edges; const VD::edge_type *edge = vertex.incident_edge(); do { - // FIXME Lukas H.: Also process parabolic segments. - if (edge->is_finite() && edge->is_linear() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && - Arachne::VoronoiUtils::is_finite(*edge->vertex0()) && Arachne::VoronoiUtils::is_finite(*edge->vertex1())) + if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && + VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1())) edges.emplace_back(edge); edge = edge->rot_next(); @@ -89,8 +271,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it; const Geometry::VoronoiDiagram::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it); - if (!check_if_three_vectors_are_ccw(to_cgal_point(*prev_edge->vertex0()), to_cgal_point(*prev_edge->vertex1()), - to_cgal_point(*curr_edge->vertex1()), to_cgal_point(*next_edge->vertex1()))) + if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segments)) return false; } } @@ -99,5 +280,4 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor return true; } - } // namespace Slic3r::Geometry \ No newline at end of file diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp index 897891bd9..cad54615b 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp @@ -2,6 +2,7 @@ #define slic3r_VoronoiUtilsCgal_hpp_ #include "Voronoi.hpp" +#include "../Arachne/utils/VoronoiUtils.hpp" namespace Slic3r::Geometry { class VoronoiDiagram; @@ -13,7 +14,7 @@ public: static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram); // Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex. - static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram); + static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector &segments); }; } // namespace Slic3r::Geometry diff --git a/src/libslic3r/IntersectionPoints.cpp b/src/libslic3r/IntersectionPoints.cpp index f2c63a53b..3537e74ab 100644 --- a/src/libslic3r/IntersectionPoints.cpp +++ b/src/libslic3r/IntersectionPoints.cpp @@ -133,7 +133,7 @@ Slic3r::Pointfs compute_intersections(const Slic3r::Lines &lines) Point max_(std::max(a_.x(), b_.x()), std::max(a_.y(), b_.y())); BoundingBox bb_(min_, max_); // intersect of BB compare min max - if (bb.intersects(bb_) && + if (bb.overlap(bb_) && l.intersection(l_, &i)) pts.push_back(i.cast()); } diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 2fa0c5c3c..37a24b54c 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -317,13 +317,16 @@ void Layer::build_up_down_graph(Layer& below, Layer& above) coord_t* end = srcs + 4; std::sort(begin, end); end = std::unique(begin, end); - assert(begin + 2 == end); - if (begin + 1 == end) + if (begin + 1 == end) { + // Self intersection may happen on source contour. Just copy the Z value. pt.z() = *begin; - else if (begin + 2 <= end) { - // store a -1 based negative index into the "intersections" vector here. - m_intersections.emplace_back(srcs[0], srcs[1]); - pt.z() = -coord_t(m_intersections.size()); + } else { + assert(begin + 2 == end); + if (begin + 2 <= end) { + // store a -1 based negative index into the "intersections" vector here. + m_intersections.emplace_back(srcs[0], srcs[1]); + pt.z() = -coord_t(m_intersections.size()); + } } } const std::vector>& intersections() const { return m_intersections; } @@ -494,15 +497,18 @@ void Layer::make_perimeters() } else { SurfaceCollection new_slices; // Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence. - LayerRegion *layerm_config = m_regions[layer_region_ids.front()]; - { + uint32_t region_id_config = layer_region_ids.front(); + LayerRegion* layerm_config = m_regions[region_id_config]; + { // Merge slices (surfaces) according to number of extra perimeters. for (uint32_t region_id : layer_region_ids) { LayerRegion &layerm = *m_regions[region_id]; for (const Surface &surface : layerm.slices()) surfaces_to_merge.emplace_back(&surface); - if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) - layerm_config = &layerm; + if (layerm.region().config().fill_density > layerm_config->region().config().fill_density) { + region_id_config = region_id; + layerm_config = &layerm; + } } std::sort(surfaces_to_merge.begin(), surfaces_to_merge.end(), [](const Surface *l, const Surface *r){ return l->extra_perimeters < r->extra_perimeters; }); for (size_t i = 0; i < surfaces_to_merge.size();) { @@ -522,7 +528,7 @@ void Layer::make_perimeters() } // make perimeters layerm_config->make_perimeters(new_slices, perimeter_and_gapfill_ranges, fill_expolygons, fill_expolygons_ranges); - this->sort_perimeters_into_islands(new_slices, region_id, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); + this->sort_perimeters_into_islands(new_slices, region_id_config, perimeter_and_gapfill_ranges, std::move(fill_expolygons), fill_expolygons_ranges, layer_region_ids); } } } diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 024ed41a4..cfeace67b 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -324,7 +324,7 @@ public: coordf_t height; // layer height in unscaled coordinates coordf_t bottom_z() const { return this->print_z - this->height; } - //Lines estimated to be seriously malformed, info from the IssueSearch algorithm. These lines should probably be avoided during fast travels. + //Extrusions estimated to be seriously malformed, estimated during "Estimating curled extrusions" step. These lines should be avoided during fast travels. Lines malformed_lines; // Collection of expolygons generated by slicing the possibly multiple meshes of the source geometry diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 68a7449c7..7e75d5632 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -91,28 +91,7 @@ bool Line::perpendicular_to(const Line& line) const bool Line::intersection(const Line &l2, Point *intersection) const { - const Line &l1 = *this; - const Vec2d v1 = (l1.b - l1.a).cast(); - const Vec2d v2 = (l2.b - l2.a).cast(); - double denom = cross2(v1, v2); - if (fabs(denom) < EPSILON) -#if 0 - // Lines are collinear. Return true if they are coincident (overlappign). - return ! (fabs(nume_a) < EPSILON && fabs(nume_b) < EPSILON); -#else - return false; -#endif - const Vec2d v12 = (l1.a - l2.a).cast(); - double nume_a = cross2(v2, v12); - double nume_b = cross2(v1, v12); - double t1 = nume_a / denom; - double t2 = nume_b / denom; - if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) { - // Get the intersection point. - (*intersection) = (l1.a.cast() + t1 * v1).cast(); - return true; - } - return false; // not intersecting + return line_alg::intersection(*this, l2, intersection); } bool Line::clip_with_bbox(const BoundingBox &bbox) diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 947c7fcba..90f564898 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -120,6 +120,33 @@ double distance_to_infinite(const L &line, const Vec, Scalar> &point) return std::sqrt(distance_to_infinite_squared(line, point)); } +template bool intersection(const L &l1, const L &l2, Vec, Scalar> *intersection_pt) +{ + using Floating = typename std::conditional>::value, Scalar, double>::type; + using VecType = const Vec, Floating>; + const VecType v1 = (l1.b - l1.a).template cast(); + const VecType v2 = (l2.b - l2.a).template cast(); + Floating denom = cross2(v1, v2); + if (fabs(denom) < EPSILON) +#if 0 + // Lines are collinear. Return true if they are coincident (overlappign). + return ! (fabs(nume_a) < EPSILON && fabs(nume_b) < EPSILON); +#else + return false; +#endif + const VecType v12 = (l1.a - l2.a).template cast(); + Floating nume_a = cross2(v2, v12); + Floating nume_b = cross2(v1, v12); + Floating t1 = nume_a / denom; + Floating t2 = nume_b / denom; + if (t1 >= 0 && t1 <= 1.0f && t2 >= 0 && t2 <= 1.0f) { + // Get the intersection point. + (*intersection_pt) = (l1.a.template cast() + t1 * v1).template cast>(); + return true; + } + return false; // not intersecting +} + } // namespace line_alg class Line diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 6572f7484..40a87cb69 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1641,13 +1641,47 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, return res; } +///

+/// Compare TriangleMeshes by Bounding boxes (mainly for sort) +/// From Front(Z) Upper(Y) TopLeft(X) corner. +/// 1. Seraparate group not overlaped i Z axis +/// 2. Seraparate group not overlaped i Y axis +/// 3. Start earlier in X (More on left side) +/// +/// Compare from +/// Compare to +/// True when triangle mesh 1 is closer, upper or lefter than triangle mesh 2 other wise false +static bool is_front_up_left(const TriangleMesh &trinagle_mesh1, const TriangleMesh &triangle_mesh2) +{ + // stats form t1 + const Vec3f &min1 = trinagle_mesh1.stats().min; + const Vec3f &max1 = trinagle_mesh1.stats().max; + // stats from t2 + const Vec3f &min2 = triangle_mesh2.stats().min; + const Vec3f &max2 = triangle_mesh2.stats().max; + // priority Z, Y, X + for (int axe = 2; axe > 0; --axe) { + if (max1[axe] < min2[axe]) + return true; + if (min1[axe] > max2[axe]) + return false; + } + return min1.x() < min2.x(); +} + void ModelObject::split(ModelObjectPtrs* new_objects) { for (ModelVolume* volume : this->volumes) { if (volume->type() != ModelVolumeType::MODEL_PART) continue; + // splited volume should not be text object + if (volume->text_configuration.has_value()) + volume->text_configuration.reset(); + std::vector meshes = volume->mesh().split(); + std::sort(meshes.begin(), meshes.end(), is_front_up_left); + size_t counter = 1; for (TriangleMesh &mesh : meshes) { // FIXME: crashes if not satisfied @@ -2131,9 +2165,15 @@ size_t ModelVolume::split(unsigned int max_extruders) if (meshes.size() <= 1) return 1; + std::sort(meshes.begin(), meshes.end(), is_front_up_left); + + // splited volume should not be text object + if (text_configuration.has_value()) + text_configuration.reset(); + size_t idx = 0; size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin(); - const std::string name = this->name; + const std::string& name = this->name; unsigned int extruder_counter = 0; const Vec3d offset = this->get_offset(); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 74d95b20f..4c6eaed1c 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -14,7 +14,6 @@ #include "Arrange.hpp" #include "CustomGCode.hpp" #include "enum_bitmask.hpp" -//#include "ModelVolumeType.hpp" #include "TextConfiguration.hpp" #include diff --git a/src/libslic3r/ModelVolumeType.hpp b/src/libslic3r/ModelVolumeType.hpp deleted file mode 100644 index 5ae1c6440..000000000 --- a/src/libslic3r/ModelVolumeType.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef slic3r_ModelVolumeType_hpp_ -#define slic3r_ModelVolumeType_hpp_ - -namespace Slic3r { - -enum class ModelVolumeType : int { - INVALID = -1, - MODEL_PART = 0, - NEGATIVE_VOLUME, - PARAMETER_MODIFIER, - SUPPORT_BLOCKER, - SUPPORT_ENFORCER, -}; - -} // namespace Slic3r -#endif /* slic3r_ModelVolumeType_hpp_ */ diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index a8f23e2e9..2844fc55e 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -3,6 +3,8 @@ using namespace Slic3r; +// inspired by nanosvgrast.h function nsvgRasterize -> nsvg__flattenShape -> nsvg__flattenCubicBez +// https://github.com/memononen/nanosvg/blob/f0a3e1034dd22e2e87e5db22401e44998383124e/src/nanosvgrast.h#L335 void NSVGUtils::flatten_cubic_bez(Polygon &polygon, float tessTol, Vec2f p1, diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 490fd54bf..15210b641 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -397,22 +397,37 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con ClipperLib_Z::Clipper clipper; clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) { + // The clipping contour may be simplified by clipping it with a bounding box of "subject" path. + // The clipping function used may produce self intersections outside of the "subject" bounding box. Such self intersections are + // harmless to the result of the clipping operation, + // Both ends of each edge belong to the same source: Either they are from subject or from clipping path. + assert(e1bot.z() >= 0 && e1top.z() >= 0); + assert(e2bot.z() >= 0 && e2top.z() >= 0); + assert((e1bot.z() == 0) == (e1top.z() == 0)); + assert((e2bot.z() == 0) == (e2top.z() == 0)); + + // Start & end points of the clipped polyline (extrusion path with a non-zero width). ClipperLib_Z::IntPoint start = e1bot; ClipperLib_Z::IntPoint end = e1top; - if (start.z() <= 0 && end.z() <= 0) { start = e2bot; end = e2top; } - assert(start.z() > 0 && end.z() > 0); + if (start.z() <= 0 && end.z() <= 0) { + // Self intersection on the source contour. + assert(start.z() == 0 && end.z() == 0); + pt.z() = 0; + } else { + // Interpolate extrusion line width. + assert(start.z() > 0 && end.z() > 0); - // Interpolate extrusion line width. - double length_sqr = (end - start).cast().squaredNorm(); - double dist_sqr = (pt - start).cast().squaredNorm(); - double t = std::sqrt(dist_sqr / length_sqr); + double length_sqr = (end - start).cast().squaredNorm(); + double dist_sqr = (pt - start).cast().squaredNorm(); + double t = std::sqrt(dist_sqr / length_sqr); - pt.z() = start.z() + coord_t((end.z() - start.z()) * t); + pt.z() = start.z() + coord_t((end.z() - start.z()) * t); + } }); clipper.AddPath(subject, ClipperLib_Z::ptSubject, false); @@ -649,11 +664,11 @@ bool paths_touch(const ExtrusionPath &path_one, const ExtrusionPath &path_two, d AABBTreeLines::LinesDistancer lines_two{path_two.as_polyline().lines()}; for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) { - if (std::abs(lines_two.signed_distance_from_lines(path_one.polyline.points[pt_idx])) < limit_distance) { return true; } + if (lines_two.distance_from_lines(path_one.polyline.points[pt_idx]) < limit_distance) { return true; } } for (size_t pt_idx = 0; pt_idx < path_two.polyline.size(); pt_idx++) { - if (std::abs(lines_one.signed_distance_from_lines(path_two.polyline.points[pt_idx])) < limit_distance) { return true; } + if (lines_one.distance_from_lines(path_two.polyline.points[pt_idx]) < limit_distance) { return true; } } return false; } @@ -802,10 +817,12 @@ std::tuple, Polygons> generate_extra_perimeters_over { coord_t anchors_size = scale_(EXTERNAL_INFILL_MARGIN); - Polygons anchors = intersection(infill_area, lower_slices_polygons); - Polygons overhangs = diff(infill_area, lower_slices_polygons); - if (overhangs.empty()) { return {}; } + BoundingBox infill_area_bb = get_extents(infill_area).inflated(SCALED_EPSILON); + Polygons optimized_lower_slices = ClipperUtils::clip_clipper_polygons_with_subject_bbox(lower_slices_polygons, infill_area_bb); + Polygons overhangs = diff(infill_area, optimized_lower_slices); + if (overhangs.empty()) { return {}; } + Polygons anchors = intersection(infill_area, optimized_lower_slices); Polygons inset_anchors; // anchored area inset by the anchor length { std::vector deltas{anchors_size * 0.15 + 0.5 * overhang_flow.scaled_spacing(), @@ -814,23 +831,21 @@ std::tuple, Polygons> generate_extra_perimeters_over std::vector anchor_areas_w_delta_anchor_size{}; for (double delta : deltas) { + // for each delta, store anchors without the delta region around overhangs anchor_areas_w_delta_anchor_size.push_back(diff(anchors, expand(overhangs, delta, EXTRA_PERIMETER_OFFSET_PARAMETERS))); } for (size_t i = 0; i < anchor_areas_w_delta_anchor_size.size() - 1; i++) { - Polygons clipped = diff(anchor_areas_w_delta_anchor_size[i], expand(anchor_areas_w_delta_anchor_size[i + 1], + // Then, clip off each anchor area by the next area expanded back to original size, so that this smaller anchor region is only where larger wouldnt fit + anchor_areas_w_delta_anchor_size[i] = diff(anchor_areas_w_delta_anchor_size[i], expand(anchor_areas_w_delta_anchor_size[i + 1], deltas[i + 1], EXTRA_PERIMETER_OFFSET_PARAMETERS)); - anchor_areas_w_delta_anchor_size[i] = intersection(anchor_areas_w_delta_anchor_size[i], - expand(clipped, deltas[i+1] + 0.1*overhang_flow.scaled_spacing(), - EXTRA_PERIMETER_OFFSET_PARAMETERS)); } for (size_t i = 0; i < anchor_areas_w_delta_anchor_size.size(); i++) { inset_anchors = union_(inset_anchors, anchor_areas_w_delta_anchor_size[i]); } - inset_anchors = opening(inset_anchors, 0.8 * deltas[0], EXTRA_PERIMETER_OFFSET_PARAMETERS); - inset_anchors = closing(inset_anchors, 0.8 * deltas[0], EXTRA_PERIMETER_OFFSET_PARAMETERS); + inset_anchors = expand(inset_anchors, 0.1*overhang_flow.scaled_width()); #ifdef EXTRA_PERIM_DEBUG_FILES { @@ -884,12 +899,6 @@ std::tuple, Polygons> generate_extra_perimeters_over Polygon anchoring_convex_hull = Geometry::convex_hull(anchoring); double unbridgeable_area = area(diff(real_overhang, {anchoring_convex_hull})); - // penalize also holes - for (const Polygon &poly : perimeter_polygon) { - if (poly.is_clockwise()) { // hole, penalize bridges. - unbridgeable_area += std::abs(area(poly)); - } - } auto [dir, unsupp_dist] = detect_bridging_direction(real_overhang, anchors); @@ -903,16 +912,19 @@ std::tuple, Polygons> generate_extra_perimeters_over for (const Line &line : to_lines(anchoring_convex_hull)) svg.draw(line, "green", scale_(0.15)); for (const Line &line : to_lines(anchoring)) svg.draw(line, "yellow", scale_(0.10)); for (const Line &line : to_lines(diff_ex(perimeter_polygon, {anchoring_convex_hull}))) svg.draw(line, "black", scale_(0.10)); + for (const Line &line : to_lines(diff_pl(to_polylines(diff(real_overhang, anchors)), expand(anchors, float(SCALED_EPSILON))))) + svg.draw(line, "blue", scale_(0.30)); svg.Close(); } #endif - if (unbridgeable_area < 0.2 * area(real_overhang) && unsupp_dist < total_length(real_overhang) * 0.125) { + + if (unbridgeable_area < 0.2 * area(real_overhang) && unsupp_dist < total_length(real_overhang) * 0.2) { inset_overhang_area_left_unfilled.insert(inset_overhang_area_left_unfilled.end(),overhang_to_cover.begin(),overhang_to_cover.end()); perimeter_polygon.clear(); } else { // fill the overhang with perimeters int continuation_loops = 2; - while (continuation_loops > 0) { + while (continuation_loops >= 0) { auto prev = perimeter_polygon; // prepare next perimeter lines Polylines perimeter = intersection_pl(to_polylines(perimeter_polygon), shrinked_overhang_to_cover); @@ -936,7 +948,7 @@ std::tuple, Polygons> generate_extra_perimeters_over //gap = expolygons_simplify(gap, overhang_flow.scaled_spacing()); for (const ExPolygon &ep : gap) { - ep.medial_axis(overhang_flow.scaled_spacing() * 2.0, 0.3 * overhang_flow.scaled_width(), &fills); + ep.medial_axis(0.3 * overhang_flow.scaled_width(), overhang_flow.scaled_spacing() * 2.0, &fills); } if (!fills.empty()) { fills = intersection_pl(fills, inset_overhang_area); diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 6a8fed830..beb496b28 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -66,9 +66,9 @@ bool has_duplicate_points(std::vector &&pts) return false; } -Points collect_duplications(Points pts /* Copy */) +Points collect_duplicates(Points pts /* Copy */) { - std::stable_sort(pts.begin(), pts.end()); + std::sort(pts.begin(), pts.end()); Points duplicits; const Point *prev = &pts.front(); for (size_t i = 1; i < pts.size(); ++i) { diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index e3c61ff6b..7ddbcb20b 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -1,589 +1,597 @@ -#ifndef slic3r_Point_hpp_ -#define slic3r_Point_hpp_ - -#include "libslic3r.h" -#include -#include -#include -#include -#include -#include - -#include - -#include "LocalesUtils.hpp" - -namespace Slic3r { - -class BoundingBox; -class BoundingBoxf; -class Point; -using Vector = Point; - -// Base template for eigen derived vectors -template -using Mat = Eigen::Matrix; - -template using Vec = Mat; - -template -using DynVec = Eigen::Matrix; - -// Eigen types, to replace the Slic3r's own types in the future. -// Vector types with a fixed point coordinate base type. -using Vec2crd = Eigen::Matrix; -using Vec3crd = Eigen::Matrix; -using Vec2i = Eigen::Matrix; -using Vec3i = Eigen::Matrix; -using Vec4i = Eigen::Matrix; -using Vec2i32 = Eigen::Matrix; -using Vec2i64 = Eigen::Matrix; -using Vec3i32 = Eigen::Matrix; -using Vec3i64 = Eigen::Matrix; - -// Vector types with a double coordinate base type. -using Vec2f = Eigen::Matrix; -using Vec3f = Eigen::Matrix; -using Vec4f = Eigen::Matrix; -using Vec2d = Eigen::Matrix; -using Vec3d = Eigen::Matrix; -using Vec4d = Eigen::Matrix; - -using Points = std::vector; -using PointPtrs = std::vector; -using PointConstPtrs = std::vector; -using Points3 = std::vector; -using Pointfs = std::vector; -using Vec2ds = std::vector; -using Pointf3s = std::vector; - -using Matrix2f = Eigen::Matrix; -using Matrix2d = Eigen::Matrix; -using Matrix3f = Eigen::Matrix; -using Matrix3d = Eigen::Matrix; -using Matrix4f = Eigen::Matrix; -using Matrix4d = Eigen::Matrix; - -template -using Transform = Eigen::Transform; - -using Transform2f = Eigen::Transform; -using Transform2d = Eigen::Transform; -using Transform3f = Eigen::Transform; -using Transform3d = Eigen::Transform; - -// I don't know why Eigen::Transform::Identity() return a const object... -template Transform identity() { return Transform::Identity(); } -inline const auto &identity3f = identity<3, float>; -inline const auto &identity3d = identity<3, double>; - -inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y()); } - -// Cross product of two 2D vectors. -// None of the vectors may be of int32_t type as the result would overflow. -template -inline typename Derived::Scalar cross2(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) -{ - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); - static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); - static_assert(! std::is_same::value, "cross2(): Scalar type must not be int32_t, otherwise the cross product would overflow."); - static_assert(std::is_same::value, "cross2(): Scalar types of 1st and 2nd operand must be equal."); - return v1.x() * v2.y() - v1.y() * v2.x(); -} - -// 2D vector perpendicular to the argument. -template -inline Eigen::Matrix perp(const Eigen::MatrixBase &v) -{ - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "perp(): parameter is not a 2D vector"); - return { - v.y(), v.x() }; -} - -// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>. -template -inline double angle(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) { - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector"); - static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector"); - auto v1d = v1.template cast(); - auto v2d = v2.template cast(); - return atan2(cross2(v1d, v2d), v1d.dot(v2d)); -} - -template -Eigen::Matrix to_2d(const Eigen::MatrixBase &ptN) { - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) >= 3, "to_2d(): first parameter is not a 3D or higher dimensional vector"); - return { ptN.x(), ptN.y() }; -} - -template -inline Eigen::Matrix to_3d(const Eigen::MatrixBase &pt, const typename Derived::Scalar z) { - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "to_3d(): first parameter is not a 2D vector"); - return { pt.x(), pt.y(), z }; -} - -inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); } -inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } -inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } -inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale(x), unscale(y), unscale(z)); } -inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } -inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } - -inline std::string to_string(const Vec2crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } -inline std::string to_string(const Vec2d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } -inline std::string to_string(const Vec3crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } -inline std::string to_string(const Vec3d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } - -std::vector transform(const std::vector& points, const Transform3f& t); -Pointf3s transform(const Pointf3s& points, const Transform3d& t); - -template using Vec = Eigen::Matrix; - -class Point : public Vec2crd -{ -public: - using coord_type = coord_t; - - Point() : Vec2crd(0, 0) {} - Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {} - Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} - Point(double x, double y) : Vec2crd(coord_t(std::round(x)), coord_t(std::round(y))) {} - Point(const Point &rhs) { *this = rhs; } - explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {} - // This constructor allows you to construct Point from Eigen expressions - template - Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} - static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } - static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } - static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } - - // This method allows you to assign Eigen expressions to MyVectorType - template - Point& operator=(const Eigen::MatrixBase &other) - { - this->Vec2crd::operator=(other); - return *this; - } - - Point& operator+=(const Point& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); return *this; } - Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; } - Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; } - Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); } - - void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } - void rotate(double cos_a, double sin_a) { - double cur_x = (double)this->x(); - double cur_y = (double)this->y(); - this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y); - this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x); - } - - void rotate(double angle, const Point ¢er); - Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } - Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } - Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } -}; - -inline bool operator<(const Point &l, const Point &r) -{ - return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); -} - -inline Point operator* (const Point& l, const double &r) -{ - return {coord_t(l.x() * r), coord_t(l.y() * r)}; -} - -inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON)) -{ - Point d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon; -} - -inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON)) -{ - Vec2f d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon; -} - -inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON) -{ - Vec2d d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon; -} - -inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON)) -{ - Vec3f d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; -} - -inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON) -{ - Vec3d d = (p2 - p1).cwiseAbs(); - return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; -} - -inline Point lerp(const Point &a, const Point &b, double t) -{ - assert((t >= -EPSILON) && (t <= 1. + EPSILON)); - return ((1. - t) * a.cast() + t * b.cast()).cast(); -} - -BoundingBox get_extents(const Points &pts); -BoundingBox get_extents(const std::vector &pts); -BoundingBoxf get_extents(const std::vector &pts); - -int nearest_point_index(const Points &points, const Point &pt); - -inline std::pair nearest_point(const Points &points, const Point &pt) -{ - int idx = nearest_point_index(points, pt); - return idx == -1 ? std::make_pair(Point(), false) : std::make_pair(points[idx], true); -} - -// Test for duplicate points in a vector of points. -// The points are copied, sorted and checked for duplicates globally. -bool has_duplicate_points(std::vector &&pts); -inline bool has_duplicate_points(const std::vector &pts) -{ - std::vector cpy = pts; - return has_duplicate_points(std::move(cpy)); -} - -// Test for duplicate points in a vector of points. -// Only successive points are checked for equality. -inline bool has_duplicate_successive_points(const std::vector &pts) -{ - for (size_t i = 1; i < pts.size(); ++ i) - if (pts[i - 1] == pts[i]) - return true; - return false; -} - -// Test for duplicate points in a vector of points. -// Only successive points are checked for equality. Additionally, first and last points are compared for equality. -inline bool has_duplicate_successive_points_closed(const std::vector &pts) -{ - return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back()); -} - -// Collect adjecent(duplicit points) -Points collect_duplications(Points pts /* Copy */); - -inline bool shorter_then(const Point& p0, const coord_t len) -{ - if (p0.x() > len || p0.x() < -len) - return false; - if (p0.y() > len || p0.y() < -len) - return false; - return p0.cast().squaredNorm() <= Slic3r::sqr(int64_t(len)); -} - -namespace int128 { - // Exact orientation predicate, - // returns +1: CCW, 0: collinear, -1: CW. - int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3); - // Exact orientation predicate, - // returns +1: CCW, 0: collinear, -1: CW. - int cross(const Vec2crd &v1, const Vec2crd &v2); -} - -// To be used by std::unordered_map, std::unordered_multimap and friends. -struct PointHash { - size_t operator()(const Vec2crd &pt) const noexcept { - return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y()); - } -}; - -// A generic class to search for a closest Point in a given radius. -// It uses std::unordered_multimap to implement an efficient 2D spatial hashing. -// The PointAccessor has to return const Point*. -// If a nullptr is returned, it is ignored by the query. -template class ClosestPointInRadiusLookup -{ -public: - ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) : - m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0) - { - // Resolution of a grid, twice the search radius + some epsilon. - coord_t gridres = 2 * m_search_radius + 4; - m_grid_resolution = gridres; - assert(m_grid_resolution > 0); - assert(m_grid_resolution < (coord_t(1) << 30)); - // Compute m_grid_log2 = log2(m_grid_resolution) - if (m_grid_resolution > 32767) { - m_grid_resolution >>= 16; - m_grid_log2 += 16; - } - if (m_grid_resolution > 127) { - m_grid_resolution >>= 8; - m_grid_log2 += 8; - } - if (m_grid_resolution > 7) { - m_grid_resolution >>= 4; - m_grid_log2 += 4; - } - if (m_grid_resolution > 1) { - m_grid_resolution >>= 2; - m_grid_log2 += 2; - } - if (m_grid_resolution > 0) - ++ m_grid_log2; - m_grid_resolution = 1 << m_grid_log2; - assert(m_grid_resolution >= gridres); - assert(gridres > m_grid_resolution / 2); - } - - void insert(const ValueType &value) { - const Vec2crd *pt = m_point_accessor(value); - if (pt != nullptr) - m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), value)); - } - - void insert(ValueType &&value) { - const Vec2crd *pt = m_point_accessor(value); - if (pt != nullptr) - m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value))); - } - - // Erase a data point equal to value. (ValueType has to declare the operator==). - // Returns true if the data point equal to value was found and removed. - bool erase(const ValueType &value) { - const Point *pt = m_point_accessor(value); - if (pt != nullptr) { - // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Point((*pt).x()>>m_grid_log2, (*pt).y()>>m_grid_log2)); - // Remove the first item. - for (auto it = range.first; it != range.second; ++ it) { - if (it->second == value) { - m_map.erase(it); - return true; - } - } - } - return false; - } - - // Return a pair of - std::pair find(const Vec2crd &pt) { - // Iterate over 4 closest grid cells around pt, - // find the closest start point inside these cells to pt. - const ValueType *value_min = nullptr; - double dist_min = std::numeric_limits::max(); - // Round pt to a closest grid_cell corner. - Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); - // For four neighbors of grid_corner: - for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { - for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { - // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); - // Find the map entry closest to pt. - for (auto it = range.first; it != range.second; ++it) { - const ValueType &value = it->second; - const Vec2crd *pt2 = m_point_accessor(value); - if (pt2 != nullptr) { - const double d2 = (pt - *pt2).cast().squaredNorm(); - if (d2 < dist_min) { - dist_min = d2; - value_min = &value; - } - } - } - } - } - return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ? - std::make_pair(value_min, dist_min) : - std::make_pair(nullptr, std::numeric_limits::max()); - } - - // Returns all pairs of values and squared distances. - std::vector> find_all(const Vec2crd &pt) { - // Iterate over 4 closest grid cells around pt, - // Round pt to a closest grid_cell corner. - Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); - // For four neighbors of grid_corner: - std::vector> out; - const double r2 = double(m_search_radius) * m_search_radius; - for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { - for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { - // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); - // Find the map entry closest to pt. - for (auto it = range.first; it != range.second; ++it) { - const ValueType &value = it->second; - const Vec2crd *pt2 = m_point_accessor(value); - if (pt2 != nullptr) { - const double d2 = (pt - *pt2).cast().squaredNorm(); - if (d2 <= r2) - out.emplace_back(&value, d2); - } - } - } - } - return out; - } - -private: - using map_type = typename std::unordered_multimap; - PointAccessor m_point_accessor; - map_type m_map; - coord_t m_search_radius; - coord_t m_grid_resolution; - coord_t m_grid_log2; -}; - -std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf); - - -// ///////////////////////////////////////////////////////////////////////////// -// Type safe conversions to and from scaled and unscaled coordinates -// ///////////////////////////////////////////////////////////////////////////// - -// Semantics are the following: -// Upscaling (scaled()): only from floating point types (or Vec) to either -// floating point or integer 'scaled coord' coordinates. -// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only - -// Conversion definition from unscaled to floating point scaled -template> -inline constexpr FloatingOnly scaled(const Tin &v) noexcept -{ - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion definition from unscaled to integer 'scaled coord'. -// TODO: is the rounding necessary? Here it is commented out to show that -// it can be different for integers but it does not have to be. Using -// std::round means loosing noexcept and constexpr modifiers -template> -inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept -{ - //return static_cast(std::round(v / SCALING_FACTOR)); - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion for Eigen vectors (N dimensional points) -template, - int...EigenArgs> -inline Eigen::Matrix, N, EigenArgs...> -scaled(const Eigen::Matrix &v) -{ - return (v / SCALING_FACTOR).template cast(); -} - -// Conversion from arithmetic scaled type to floating point unscaled -template, - class = FloatingOnly> -inline constexpr Tout unscaled(const Tin &v) noexcept -{ - return Tout(v) * Tout(SCALING_FACTOR); -} - -// Unscaling for Eigen vectors. Input base type can be arithmetic, output base -// type can only be floating point. -template, - class = FloatingOnly, - int...EigenArgs> -inline constexpr Eigen::Matrix -unscaled(const Eigen::Matrix &v) noexcept -{ - return v.template cast() * Tout(SCALING_FACTOR); -} - -// Align a coordinate to a grid. The coordinate may be negative, -// the aligned value will never be bigger than the original one. -inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) { - // Current C++ standard defines the result of integer division to be rounded to zero, - // for both positive and negative numbers. Here we want to round down for negative - // numbers as well. - coord_t aligned = (coord < 0) ? - ((coord - spacing + 1) / spacing) * spacing : - (coord / spacing) * spacing; - assert(aligned <= coord); - return aligned; -} -inline Point align_to_grid(Point coord, Point spacing) - { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); } -inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) - { return base + align_to_grid(coord - base, spacing); } -inline Point align_to_grid(Point coord, Point spacing, Point base) - { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } - -} // namespace Slic3r - -// start Boost -#include -#include -namespace boost { namespace polygon { - template <> - struct geometry_concept { using type = point_concept; }; - - template <> - struct point_traits { - using coordinate_type = coord_t; - - static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { - return static_cast(point((orient == HORIZONTAL) ? 0 : 1)); - } - }; - - template <> - struct point_mutable_traits { - using coordinate_type = coord_t; - static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { - point((orient == HORIZONTAL) ? 0 : 1) = value; - } - static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { - return Slic3r::Point(x_value, y_value); - } - }; -} } -// end Boost - -#include -// Serialization through the Cereal library -namespace cereal { -// template void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); } -// template void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); } - template void serialize(Archive& archive, Slic3r::Vec2i &v) { archive(v.x(), v.y()); } - template void serialize(Archive& archive, Slic3r::Vec3i &v) { archive(v.x(), v.y(), v.z()); } -// template void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); } -// template void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); } - template void serialize(Archive& archive, Slic3r::Vec2f &v) { archive(v.x(), v.y()); } - template void serialize(Archive& archive, Slic3r::Vec3f &v) { archive(v.x(), v.y(), v.z()); } - template void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); } - template void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); } - - template void serialize(Archive& archive, Slic3r::Matrix4d &m){ archive(binary_data(m.data(), 4*4*sizeof(double))); } - template void serialize(Archive& archive, Slic3r::Matrix2f &m){ archive(binary_data(m.data(), 2*2*sizeof(float))); } - - // Eigen Transformation serialization - template inline void serialize(Archive& archive, Eigen::Transform& t){ archive(t.matrix()); } -} - -// To be able to use Vec<> and Mat<> in range based for loops: -namespace Eigen { -template -T* begin(Slic3r::Mat &mat) { return mat.data(); } - -template -T* end(Slic3r::Mat &mat) { return mat.data() + N * M; } - -template -const T* begin(const Slic3r::Mat &mat) { return mat.data(); } - -template -const T* end(const Slic3r::Mat &mat) { return mat.data() + N * M; } -} // namespace Eigen - -#endif +#ifndef slic3r_Point_hpp_ +#define slic3r_Point_hpp_ + +#include "libslic3r.h" +#include +#include +#include +#include +#include +#include + +#include + +#include "LocalesUtils.hpp" + +namespace Slic3r { + +class BoundingBox; +class BoundingBoxf; +class Point; +using Vector = Point; + +// Base template for eigen derived vectors +template +using Mat = Eigen::Matrix; + +template using Vec = Mat; + +template +using DynVec = Eigen::Matrix; + +// Eigen types, to replace the Slic3r's own types in the future. +// Vector types with a fixed point coordinate base type. +using Vec2crd = Eigen::Matrix; +using Vec3crd = Eigen::Matrix; +using Vec2i = Eigen::Matrix; +using Vec3i = Eigen::Matrix; +using Vec4i = Eigen::Matrix; +using Vec2i32 = Eigen::Matrix; +using Vec2i64 = Eigen::Matrix; +using Vec3i32 = Eigen::Matrix; +using Vec3i64 = Eigen::Matrix; + +// Vector types with a double coordinate base type. +using Vec2f = Eigen::Matrix; +using Vec3f = Eigen::Matrix; +using Vec4f = Eigen::Matrix; +using Vec2d = Eigen::Matrix; +using Vec3d = Eigen::Matrix; +using Vec4d = Eigen::Matrix; + +using Points = std::vector; +using PointPtrs = std::vector; +using PointConstPtrs = std::vector; +using Points3 = std::vector; +using Pointfs = std::vector; +using Vec2ds = std::vector; +using Pointf3s = std::vector; + +using Matrix2f = Eigen::Matrix; +using Matrix2d = Eigen::Matrix; +using Matrix3f = Eigen::Matrix; +using Matrix3d = Eigen::Matrix; +using Matrix4f = Eigen::Matrix; +using Matrix4d = Eigen::Matrix; + +template +using Transform = Eigen::Transform; + +using Transform2f = Eigen::Transform; +using Transform2d = Eigen::Transform; +using Transform3f = Eigen::Transform; +using Transform3d = Eigen::Transform; + +// I don't know why Eigen::Transform::Identity() return a const object... +template Transform identity() { return Transform::Identity(); } +inline const auto &identity3f = identity<3, float>; +inline const auto &identity3d = identity<3, double>; + +inline bool operator<(const Vec2d &lhs, const Vec2d &rhs) { return lhs.x() < rhs.x() || (lhs.x() == rhs.x() && lhs.y() < rhs.y()); } + +// Cross product of two 2D vectors. +// None of the vectors may be of int32_t type as the result would overflow. +template +inline typename Derived::Scalar cross2(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "cross2(): first parameter is not a 2D vector"); + static_assert(! std::is_same::value, "cross2(): Scalar type must not be int32_t, otherwise the cross product would overflow."); + static_assert(std::is_same::value, "cross2(): Scalar types of 1st and 2nd operand must be equal."); + return v1.x() * v2.y() - v1.y() * v2.x(); +} + +// 2D vector perpendicular to the argument. +template +inline Eigen::Matrix perp(const Eigen::MatrixBase &v) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "perp(): parameter is not a 2D vector"); + return { - v.y(), v.x() }; +} + +// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>. +template +inline double angle(const Eigen::MatrixBase &v1, const Eigen::MatrixBase &v2) { + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector"); + auto v1d = v1.template cast(); + auto v2d = v2.template cast(); + return atan2(cross2(v1d, v2d), v1d.dot(v2d)); +} + +template +Eigen::Matrix to_2d(const Eigen::MatrixBase &ptN) { + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) >= 3, "to_2d(): first parameter is not a 3D or higher dimensional vector"); + return ptN.template head<2>(); +} + +template +inline Eigen::Matrix to_3d(const Eigen::MatrixBase &pt, const typename Derived::Scalar z) { + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "to_3d(): first parameter is not a 2D vector"); + return { pt.x(), pt.y(), z }; +} + +inline Vec2d unscale(coord_t x, coord_t y) { return Vec2d(unscale(x), unscale(y)); } +inline Vec2d unscale(const Vec2crd &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } +inline Vec2d unscale(const Vec2d &pt) { return Vec2d(unscale(pt.x()), unscale(pt.y())); } +inline Vec3d unscale(coord_t x, coord_t y, coord_t z) { return Vec3d(unscale(x), unscale(y), unscale(z)); } +inline Vec3d unscale(const Vec3crd &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } +inline Vec3d unscale(const Vec3d &pt) { return Vec3d(unscale(pt.x()), unscale(pt.y()), unscale(pt.z())); } + +inline std::string to_string(const Vec2crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } +inline std::string to_string(const Vec2d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + "]"; } +inline std::string to_string(const Vec3crd &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } +inline std::string to_string(const Vec3d &pt) { return std::string("[") + float_to_string_decimal_point(pt.x()) + ", " + float_to_string_decimal_point(pt.y()) + ", " + float_to_string_decimal_point(pt.z()) + "]"; } + +std::vector transform(const std::vector& points, const Transform3f& t); +Pointf3s transform(const Pointf3s& points, const Transform3d& t); + +/// +/// Check whether transformation matrix contains odd number of mirroring. +/// NOTE: In code is sometime function named is_left_handed +/// +/// Transformation to check +/// Is positive determinant +inline bool has_reflection(const Transform3d &transform) { return transform.matrix().determinant() < 0; } + +template using Vec = Eigen::Matrix; + +class Point : public Vec2crd +{ +public: + using coord_type = coord_t; + + Point() : Vec2crd(0, 0) {} + Point(int32_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {} + Point(int64_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} + Point(double x, double y) : Vec2crd(coord_t(std::round(x)), coord_t(std::round(y))) {} + Point(const Point &rhs) { *this = rhs; } + explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {} + // This constructor allows you to construct Point from Eigen expressions + template + Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} + static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } + static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } + static Point new_scale(const Vec2f &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } + + // This method allows you to assign Eigen expressions to MyVectorType + template + Point& operator=(const Eigen::MatrixBase &other) + { + this->Vec2crd::operator=(other); + return *this; + } + + Point& operator+=(const Point& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); return *this; } + Point& operator-=(const Point& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); return *this; } + Point& operator*=(const double &rhs) { this->x() = coord_t(this->x() * rhs); this->y() = coord_t(this->y() * rhs); return *this; } + Point operator*(const double &rhs) { return Point(this->x() * rhs, this->y() * rhs); } + + void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } + void rotate(double cos_a, double sin_a) { + double cur_x = (double)this->x(); + double cur_y = (double)this->y(); + this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y); + this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x); + } + + void rotate(double angle, const Point ¢er); + Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } + Point rotated(double cos_a, double sin_a) const { Point res(*this); res.rotate(cos_a, sin_a); return res; } + Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } +}; + +inline bool operator<(const Point &l, const Point &r) +{ + return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y()); +} + +inline Point operator* (const Point& l, const double &r) +{ + return {coord_t(l.x() * r), coord_t(l.y() * r)}; +} + +inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON)) +{ + Point d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon; +} + +inline bool is_approx(const Vec2f &p1, const Vec2f &p2, float epsilon = float(EPSILON)) +{ + Vec2f d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon; +} + +inline bool is_approx(const Vec2d &p1, const Vec2d &p2, double epsilon = EPSILON) +{ + Vec2d d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon; +} + +inline bool is_approx(const Vec3f &p1, const Vec3f &p2, float epsilon = float(EPSILON)) +{ + Vec3f d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; +} + +inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON) +{ + Vec3d d = (p2 - p1).cwiseAbs(); + return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; +} + +inline Point lerp(const Point &a, const Point &b, double t) +{ + assert((t >= -EPSILON) && (t <= 1. + EPSILON)); + return ((1. - t) * a.cast() + t * b.cast()).cast(); +} + +BoundingBox get_extents(const Points &pts); +BoundingBox get_extents(const std::vector &pts); +BoundingBoxf get_extents(const std::vector &pts); + +int nearest_point_index(const Points &points, const Point &pt); + +inline std::pair nearest_point(const Points &points, const Point &pt) +{ + int idx = nearest_point_index(points, pt); + return idx == -1 ? std::make_pair(Point(), false) : std::make_pair(points[idx], true); +} + +// Test for duplicate points in a vector of points. +// The points are copied, sorted and checked for duplicates globally. +bool has_duplicate_points(std::vector &&pts); +inline bool has_duplicate_points(const std::vector &pts) +{ + std::vector cpy = pts; + return has_duplicate_points(std::move(cpy)); +} + +// Test for duplicate points in a vector of points. +// Only successive points are checked for equality. +inline bool has_duplicate_successive_points(const std::vector &pts) +{ + for (size_t i = 1; i < pts.size(); ++ i) + if (pts[i - 1] == pts[i]) + return true; + return false; +} + +// Test for duplicate points in a vector of points. +// Only successive points are checked for equality. Additionally, first and last points are compared for equality. +inline bool has_duplicate_successive_points_closed(const std::vector &pts) +{ + return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back()); +} + +// Collect adjecent(duplicit points) +Points collect_duplicates(Points pts /* Copy */); + +inline bool shorter_then(const Point& p0, const coord_t len) +{ + if (p0.x() > len || p0.x() < -len) + return false; + if (p0.y() > len || p0.y() < -len) + return false; + return p0.cast().squaredNorm() <= Slic3r::sqr(int64_t(len)); +} + +namespace int128 { + // Exact orientation predicate, + // returns +1: CCW, 0: collinear, -1: CW. + int orient(const Vec2crd &p1, const Vec2crd &p2, const Vec2crd &p3); + // Exact orientation predicate, + // returns +1: CCW, 0: collinear, -1: CW. + int cross(const Vec2crd &v1, const Vec2crd &v2); +} + +// To be used by std::unordered_map, std::unordered_multimap and friends. +struct PointHash { + size_t operator()(const Vec2crd &pt) const noexcept { + return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y()); + } +}; + +// A generic class to search for a closest Point in a given radius. +// It uses std::unordered_multimap to implement an efficient 2D spatial hashing. +// The PointAccessor has to return const Point*. +// If a nullptr is returned, it is ignored by the query. +template class ClosestPointInRadiusLookup +{ +public: + ClosestPointInRadiusLookup(coord_t search_radius, PointAccessor point_accessor = PointAccessor()) : + m_search_radius(search_radius), m_point_accessor(point_accessor), m_grid_log2(0) + { + // Resolution of a grid, twice the search radius + some epsilon. + coord_t gridres = 2 * m_search_radius + 4; + m_grid_resolution = gridres; + assert(m_grid_resolution > 0); + assert(m_grid_resolution < (coord_t(1) << 30)); + // Compute m_grid_log2 = log2(m_grid_resolution) + if (m_grid_resolution > 32767) { + m_grid_resolution >>= 16; + m_grid_log2 += 16; + } + if (m_grid_resolution > 127) { + m_grid_resolution >>= 8; + m_grid_log2 += 8; + } + if (m_grid_resolution > 7) { + m_grid_resolution >>= 4; + m_grid_log2 += 4; + } + if (m_grid_resolution > 1) { + m_grid_resolution >>= 2; + m_grid_log2 += 2; + } + if (m_grid_resolution > 0) + ++ m_grid_log2; + m_grid_resolution = 1 << m_grid_log2; + assert(m_grid_resolution >= gridres); + assert(gridres > m_grid_resolution / 2); + } + + void insert(const ValueType &value) { + const Vec2crd *pt = m_point_accessor(value); + if (pt != nullptr) + m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), value)); + } + + void insert(ValueType &&value) { + const Vec2crd *pt = m_point_accessor(value); + if (pt != nullptr) + m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value))); + } + + // Erase a data point equal to value. (ValueType has to declare the operator==). + // Returns true if the data point equal to value was found and removed. + bool erase(const ValueType &value) { + const Point *pt = m_point_accessor(value); + if (pt != nullptr) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Point((*pt).x()>>m_grid_log2, (*pt).y()>>m_grid_log2)); + // Remove the first item. + for (auto it = range.first; it != range.second; ++ it) { + if (it->second == value) { + m_map.erase(it); + return true; + } + } + } + return false; + } + + // Return a pair of + std::pair find(const Vec2crd &pt) { + // Iterate over 4 closest grid cells around pt, + // find the closest start point inside these cells to pt. + const ValueType *value_min = nullptr; + double dist_min = std::numeric_limits::max(); + // Round pt to a closest grid_cell corner. + Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); + // For four neighbors of grid_corner: + for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { + for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); + // Find the map entry closest to pt. + for (auto it = range.first; it != range.second; ++it) { + const ValueType &value = it->second; + const Vec2crd *pt2 = m_point_accessor(value); + if (pt2 != nullptr) { + const double d2 = (pt - *pt2).cast().squaredNorm(); + if (d2 < dist_min) { + dist_min = d2; + value_min = &value; + } + } + } + } + } + return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ? + std::make_pair(value_min, dist_min) : + std::make_pair(nullptr, std::numeric_limits::max()); + } + + // Returns all pairs of values and squared distances. + std::vector> find_all(const Vec2crd &pt) { + // Iterate over 4 closest grid cells around pt, + // Round pt to a closest grid_cell corner. + Vec2crd grid_corner((pt.x()+(m_grid_resolution>>1))>>m_grid_log2, (pt.y()+(m_grid_resolution>>1))>>m_grid_log2); + // For four neighbors of grid_corner: + std::vector> out; + const double r2 = double(m_search_radius) * m_search_radius; + for (coord_t neighbor_y = -1; neighbor_y < 1; ++ neighbor_y) { + for (coord_t neighbor_x = -1; neighbor_x < 1; ++ neighbor_x) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Vec2crd(grid_corner.x() + neighbor_x, grid_corner.y() + neighbor_y)); + // Find the map entry closest to pt. + for (auto it = range.first; it != range.second; ++it) { + const ValueType &value = it->second; + const Vec2crd *pt2 = m_point_accessor(value); + if (pt2 != nullptr) { + const double d2 = (pt - *pt2).cast().squaredNorm(); + if (d2 <= r2) + out.emplace_back(&value, d2); + } + } + } + } + return out; + } + +private: + using map_type = typename std::unordered_multimap; + PointAccessor m_point_accessor; + map_type m_map; + coord_t m_search_radius; + coord_t m_grid_resolution; + coord_t m_grid_log2; +}; + +std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf); + + +// ///////////////////////////////////////////////////////////////////////////// +// Type safe conversions to and from scaled and unscaled coordinates +// ///////////////////////////////////////////////////////////////////////////// + +// Semantics are the following: +// Upscaling (scaled()): only from floating point types (or Vec) to either +// floating point or integer 'scaled coord' coordinates. +// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only + +// Conversion definition from unscaled to floating point scaled +template> +inline constexpr FloatingOnly scaled(const Tin &v) noexcept +{ + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion definition from unscaled to integer 'scaled coord'. +// TODO: is the rounding necessary? Here it is commented out to show that +// it can be different for integers but it does not have to be. Using +// std::round means loosing noexcept and constexpr modifiers +template> +inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept +{ + //return static_cast(std::round(v / SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion for Eigen vectors (N dimensional points) +template, + int...EigenArgs> +inline Eigen::Matrix, N, EigenArgs...> +scaled(const Eigen::Matrix &v) +{ + return (v / SCALING_FACTOR).template cast(); +} + +// Conversion from arithmetic scaled type to floating point unscaled +template, + class = FloatingOnly> +inline constexpr Tout unscaled(const Tin &v) noexcept +{ + return Tout(v) * Tout(SCALING_FACTOR); +} + +// Unscaling for Eigen vectors. Input base type can be arithmetic, output base +// type can only be floating point. +template, + class = FloatingOnly, + int...EigenArgs> +inline constexpr Eigen::Matrix +unscaled(const Eigen::Matrix &v) noexcept +{ + return v.template cast() * Tout(SCALING_FACTOR); +} + +// Align a coordinate to a grid. The coordinate may be negative, +// the aligned value will never be bigger than the original one. +inline coord_t align_to_grid(const coord_t coord, const coord_t spacing) { + // Current C++ standard defines the result of integer division to be rounded to zero, + // for both positive and negative numbers. Here we want to round down for negative + // numbers as well. + coord_t aligned = (coord < 0) ? + ((coord - spacing + 1) / spacing) * spacing : + (coord / spacing) * spacing; + assert(aligned <= coord); + return aligned; +} +inline Point align_to_grid(Point coord, Point spacing) + { return Point(align_to_grid(coord.x(), spacing.x()), align_to_grid(coord.y(), spacing.y())); } +inline coord_t align_to_grid(coord_t coord, coord_t spacing, coord_t base) + { return base + align_to_grid(coord - base, spacing); } +inline Point align_to_grid(Point coord, Point spacing, Point base) + { return Point(align_to_grid(coord.x(), spacing.x(), base.x()), align_to_grid(coord.y(), spacing.y(), base.y())); } + +} // namespace Slic3r + +// start Boost +#include +#include +namespace boost { namespace polygon { + template <> + struct geometry_concept { using type = point_concept; }; + + template <> + struct point_traits { + using coordinate_type = coord_t; + + static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { + return static_cast(point((orient == HORIZONTAL) ? 0 : 1)); + } + }; + + template <> + struct point_mutable_traits { + using coordinate_type = coord_t; + static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { + point((orient == HORIZONTAL) ? 0 : 1) = value; + } + static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { + return Slic3r::Point(x_value, y_value); + } + }; +} } +// end Boost + +#include +// Serialization through the Cereal library +namespace cereal { +// template void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); } +// template void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2i &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3i &v) { archive(v.x(), v.y(), v.z()); } +// template void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); } +// template void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2f &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3f &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); } + + template void serialize(Archive& archive, Slic3r::Matrix4d &m){ archive(binary_data(m.data(), 4*4*sizeof(double))); } + template void serialize(Archive& archive, Slic3r::Matrix2f &m){ archive(binary_data(m.data(), 2*2*sizeof(float))); } + + // Eigen Transformation serialization + template inline void serialize(Archive& archive, Eigen::Transform& t){ archive(t.matrix()); } +} + +// To be able to use Vec<> and Mat<> in range based for loops: +namespace Eigen { +template +T* begin(Slic3r::Mat &mat) { return mat.data(); } + +template +T* end(Slic3r::Mat &mat) { return mat.data() + N * M; } + +template +const T* begin(const Slic3r::Mat &mat) { return mat.data(); } + +template +const T* end(const Slic3r::Mat &mat) { return mat.data() + N * M; } +} // namespace Eigen + +#endif diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index bee5e51ba..8ca8e4f16 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -10,7 +10,7 @@ namespace Slic3r { class Polyline; -class ThickPolyline; +struct ThickPolyline; typedef std::vector Polylines; typedef std::vector ThickPolylines; @@ -160,20 +160,28 @@ bool remove_degenerate(Polylines &polylines); // Returns index of a segment of a polyline and foot point of pt on polyline. std::pair foot_pt(const Points &polyline, const Point &pt); -class ThickPolyline : public Polyline { -public: - ThickPolyline() : endpoints(std::make_pair(false, false)) {} +struct ThickPolyline { + ThickPolyline() = default; ThickLines thicklines() const; + + const Point& first_point() const { return this->points.front(); } + const Point& last_point() const { return this->points.back(); } + bool is_valid() const { return this->points.size() >= 2; } + double length() const { return Slic3r::length(this->points); } + + void clear() { this->points.clear(); this->width.clear(); } + void reverse() { - Polyline::reverse(); + std::reverse(this->points.begin(), this->points.end()); std::reverse(this->width.begin(), this->width.end()); std::swap(this->endpoints.first, this->endpoints.second); } void clip_end(double distance); - std::vector width; - std::pair endpoints; + Points points; + std::vector width; + std::pair endpoints { false, false }; }; inline ThickPolylines to_thick_polylines(Polylines &&polylines, const coordf_t width) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 49b8c99a2..9438a823a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -420,7 +420,7 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) static std::vector s_Preset_print_options { "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "slicing_mode", "top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness", - "extra_perimeters", "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "avoid_curled_filament_during_travels", "avoid_crossing_perimeters", "thin_walls", "overhangs", + "extra_perimeters", "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "avoid_crossing_curled_overhangs", "avoid_crossing_perimeters", "thin_walls", "overhangs", "seam_position","staggered_inner_seams", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", @@ -429,6 +429,7 @@ static std::vector s_Preset_print_options { "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist", "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", + "enable_dynamic_overhang_speeds", "dynamic_overhang_speeds", "overhang_overlap_levels", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", "bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration", "bridge_acceleration", "first_layer_acceleration", "first_layer_acceleration_over_raft", "default_acceleration", "skirts", "skirt_distance", "skirt_height", "draft_shield", @@ -1215,18 +1216,19 @@ void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& } // list of options with vector variable, which is independent from number of extruders -static const std::vector independent_from_extruder_number_options = { +static const std::set independent_from_extruder_number_options = { "bed_shape", - "thumbnails", + "compatible_printers", + "compatible_prints", "filament_ramming_parameters", "gcode_substitutions", - "compatible_prints", - "compatible_printers" + "post_process", + "thumbnails", }; bool PresetCollection::is_independent_from_extruder_number_option(const std::string& opt_key) { - return std::find(independent_from_extruder_number_options.begin(), independent_from_extruder_number_options.end(), opt_key) != independent_from_extruder_number_options.end(); + return independent_from_extruder_number_options.find(opt_key) != independent_from_extruder_number_options.end(); } // Use deep_diff to correct return of changed options, considering individual options for each extruder. @@ -1253,6 +1255,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi case coStrings: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; case coPercents:add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; case coPoints: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coFloatsOrPercents: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; default: diff.emplace_back(opt_key); break; } } diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 549a13286..7d5a3a4f6 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace Slic3r { @@ -155,6 +156,13 @@ public: const std::string& new_name, const std::vector& options); static const char *PRUSA_BUNDLE; + + static std::array types_list(PrinterTechnology pt) { + if (pt == ptFFF) + return { Preset::TYPE_PRINTER, Preset::TYPE_PRINT, Preset::TYPE_FILAMENT }; + return { Preset::TYPE_PRINTER, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL }; + } + private: std::pair load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule); // Merge one vendor's presets with the other vendor's presets, report duplicates. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4e230dc6d..79fee4672 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -222,7 +222,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n osteps.emplace_back(posInfill); osteps.emplace_back(posSupportMaterial); steps.emplace_back(psSkirtBrim); - } else if (opt_key == "avoid_curled_filament_during_travels") { + } else if (opt_key == "avoid_crossing_curled_overhangs") { osteps.emplace_back(posEstimateCurledExtrusions); } else { // for legacy, if we can't handle this option let's invalidate all steps diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index ec069996e..31e78d714 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -8,6 +8,7 @@ #include "Flow.hpp" #include "Point.hpp" #include "Slicing.hpp" +#include "SupportSpotsGenerator.hpp" #include "TriangleMeshSlicer.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" @@ -20,6 +21,7 @@ #include #include +#include #include #include @@ -200,6 +202,11 @@ public: } }; + struct GeneratedSupportPoints{ + Transform3d object_transform; // for frontend object mapping + SupportSpotsGenerator::SupportPoints support_points; + }; + std::vector> all_regions; std::vector layer_ranges; // Transformation of this ModelObject into one of the associated PrintObjects (all PrintObjects derived from a single modelObject differ by a Z rotation only). @@ -207,6 +214,8 @@ public: Transform3d trafo_bboxes; std::vector cached_volume_ids; + std::optional generated_support_points; + void ref_cnt_inc() { ++ m_ref_cnt; } void ref_cnt_dec() { if (-- m_ref_cnt == 0) delete this; } void clear() { @@ -507,6 +516,7 @@ public: void set_task(const TaskParams ¶ms) override { PrintBaseWithState::set_task_impl(params, m_objects); } void process() override; void finalize() override { PrintBaseWithState::finalize_impl(m_objects); } + void cleanup() override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). @@ -599,6 +609,12 @@ private: // Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim. std::vector first_layer_wipe_tower_corners() const; + // Returns true if any of the print_objects has print_object_step valid. + // That means data shared by all print objects of the print_objects span may still use the shared data. + // Otherwise the shared data shall be released. + // Unguarded variant, thus it shall only be called from main thread with background processing stopped. + static bool is_shared_print_object_step_valid_unguarded(SpanOfConstPtrs print_objects, PrintObjectStep print_object_step); + PrintConfig m_config; PrintObjectConfig m_default_object_config; PrintRegionConfig m_default_region_config; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 330ad533c..5f95edefa 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1451,6 +1451,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ for (PrintObject *object : m_objects) object->update_slicing_parameters(); + if (apply_status == APPLY_STATUS_CHANGED || apply_status == APPLY_STATUS_INVALIDATED) + this->cleanup(); + #ifdef _DEBUG check_model_ids_equal(m_model, model); #endif /* _DEBUG */ @@ -1458,4 +1461,25 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ return static_cast(apply_status); } +void Print::cleanup() +{ + // Invalidate data of a single ModelObject shared by multiple PrintObjects. + // Find spans of PrintObjects sharing the same PrintObjectRegions. + std::vector all_objects(m_objects); + std::sort(all_objects.begin(), all_objects.end(), [](const PrintObject *l, const PrintObject *r){ return l->shared_regions() < r->shared_regions(); } ); + for (auto it = all_objects.begin(); it != all_objects.end();) { + PrintObjectRegions *shared_regions = (*it)->m_shared_regions; + auto it_begin = it; + for (++ it; it != all_objects.end() && shared_regions == (*it)->shared_regions(); ++ it); + auto this_objects = SpanOfConstPtrs(const_cast(&(*it_begin)), it - it_begin); + if (Print::is_shared_print_object_step_valid_unguarded(this_objects, posSupportSpotsSearch)) + shared_regions->generated_support_points.reset(); + } +} + +bool Print::is_shared_print_object_step_valid_unguarded(SpanOfConstPtrs print_objects, PrintObjectStep print_object_step) +{ + return std::any_of(print_objects.begin(), print_objects.end(), [print_object_step](auto po){ return po->is_step_done_unguarded(print_object_step); }); +} + } // namespace Slic3r diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index b0aa7bd1e..d9b3e9cda 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -13,7 +13,7 @@ namespace Slic3r { -void PrintTryCancel::operator()() +void PrintTryCancel::operator()() const { m_print->throw_if_canceled(); } diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 08268c04b..d7ff6500e 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -340,7 +340,7 @@ class PrintTryCancel { public: // calls print.throw_if_canceled(). - void operator()(); + void operator()() const; private: friend PrintBase; PrintTryCancel() = delete; @@ -408,6 +408,10 @@ public: // Clean up after process() finished, either with success, error or if canceled. // The adjustments on the Print / PrintObject data due to set_task() are to be reverted here. virtual void finalize() = 0; + // Clean up print step / print object step data after + // 1) some print step / print object step was invalidated inside PrintBase::apply() while holding the milestone mutex locked. + // 2) background thread finished being canceled. + virtual void cleanup() = 0; struct SlicingStatus { SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {} diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index b1a98d5ad..4639cb413 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -70,6 +70,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage) static const t_config_enum_values s_keys_map_PrintHostType { { "prusalink", htPrusaLink }, + { "prusaconnect", htPrusaConnect }, { "octoprint", htOctoPrint }, { "duet", htDuet }, { "flashair", htFlashAir }, @@ -399,10 +400,10 @@ void PrintConfigDef::init_fff_params() // Maximum extruder temperature, bumped to 1500 to support printing of glass. const int max_temp = 1500; - def = this->add("avoid_curled_filament_during_travels", coBool); - def->label = L("Avoid curled filament during travels"); - def->tooltip = L("Plan travel moves such that the extruder avoids areas where filament may be curled up. " - "This is mostly happening on steeper rounded overhangs and may cause crash or borken print. " + def = this->add("avoid_crossing_curled_overhangs", coBool); + def->label = L("Avoid crossing curled overhangs (Experimental)"); + def->tooltip = L("Plan travel moves such that the extruder avoids areas where the filament may be curled up. " + "This is mostly happening on steeper rounded overhangs and may cause a crash with the nozzle. " "This feature slows down both the print and the G-code generation."); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); @@ -528,6 +529,40 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(60)); + def = this->add("enable_dynamic_overhang_speeds", coBool); + def->label = L("Enable dynamic overhang speeds (Experimental)"); + def->category = L("Speed"); + def->tooltip = L("This setting enables dynamic speed control on overhangs."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("overhang_overlap_levels", coPercents); + def->full_label = L("Overhang overlap levels"); + def->category = L("Speed"); + def->tooltip = L("Controls overhang levels, expressed as a percentage of overlap of the extrusion with the previous layer - " + "100% represents full overlap - no overhang is present, while 0% represents full overhang (floating extrusion). " + "Each overhang level then corresponds with the overhang speed below. Speeds for overhang levels in between are " + "calculated via linear interpolation." + "If you set multiple different speeds for the same overhang level, only the largest speed is used. " + ); + def->sidetext = L("%"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercents({60, 40, 20, 0})); + + def = this->add("dynamic_overhang_speeds", coFloatsOrPercents); + def->full_label = L("Dynamic speed on overhangs"); + def->category = L("Speed"); + def->tooltip = L("This setting controls the speed on the overhang with the overlap value set above. " + "The speed of the extrusion is calculated as a linear interpolation of the speeds for higher and lower overlap. " + "If set as percentage, the speed is calculated over the external perimeter speed." + ); + def->sidetext = L("mm/s or %"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatsOrPercents({{25, false}, {20, false}, {15, false}, {15, false}})); + def = this->add("brim_width", coFloat); def->label = L("Brim width"); def->category = L("Skirt and brim"); @@ -804,9 +839,9 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(true)); def = this->add("extra_perimeters_on_overhangs", coBool); - def->label = L("Extra perimeters on overhangs"); + def->label = L("Extra perimeters on overhangs (Experimental)"); def->category = L("Layers and Perimeters"); - def->tooltip = L("Create additional perimeter paths over steep overhangs and areas where bridges cannot be anchored. "); + def->tooltip = L("Create additional perimeter paths over steep overhangs and areas where bridges cannot be anchored."); def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); @@ -1909,6 +1944,7 @@ void PrintConfigDef::init_fff_params() "the kind of the host."); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("prusalink"); + def->enum_values.push_back("prusaconnect"); def->enum_values.push_back("octoprint"); def->enum_values.push_back("duet"); def->enum_values.push_back("flashair"); @@ -1916,6 +1952,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("repetier"); def->enum_values.push_back("mks"); def->enum_labels.push_back("PrusaLink"); + def->enum_labels.push_back("PrusaConnect"); def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); def->enum_labels.push_back("FlashAir"); @@ -1924,7 +1961,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back("MKS"); def->mode = comAdvanced; def->cli = ConfigOptionDef::nocli; - def->set_default_value(new ConfigOptionEnum(htOctoPrint)); + def->set_default_value(new ConfigOptionEnum(htPrusaLink)); def = this->add("only_retract_when_crossing_perimeters", coBool); def->label = L("Only retract when crossing perimeters"); @@ -4607,6 +4644,10 @@ CLITransformConfigDef::CLITransformConfigDef() def->label = L("Scale to Fit"); def->tooltip = L("Scale to fit the given volume."); def->set_default_value(new ConfigOptionPoint3(Vec3d(0,0,0))); + + def = this->add("delete-after-load", coString); + def->label = L("Delete files after loading"); + def->tooltip = L("Delete files after loading."); } CLIMiscConfigDef::CLIMiscConfigDef() diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index d67296646..045200906 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -43,7 +43,7 @@ enum class MachineLimitsUsage { }; enum PrintHostType { - htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS + htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS }; enum AuthorizationType { @@ -516,7 +516,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, min_feature_size)) ((ConfigOptionFloatOrPercent, min_bead_width)) ((ConfigOptionBool, support_material)) - // Automatic supports (generated based on support_material_threshold). + // Automatic supports (generated based fdm support point generator). ((ConfigOptionBool, support_material_auto)) // Direction of the support pattern (in XY plane).` ((ConfigOptionFloat, support_material_angle)) @@ -564,6 +564,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionEnum, bottom_fill_pattern)) ((ConfigOptionFloatOrPercent, external_perimeter_extrusion_width)) ((ConfigOptionFloatOrPercent, external_perimeter_speed)) + ((ConfigOptionBool, enable_dynamic_overhang_speeds)) + ((ConfigOptionPercents, overhang_overlap_levels)) + ((ConfigOptionFloatsOrPercents, dynamic_overhang_speeds)) ((ConfigOptionBool, external_perimeters_first)) ((ConfigOptionBool, extra_perimeters)) ((ConfigOptionBool, extra_perimeters_on_overhangs)) @@ -729,7 +732,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( PrintConfig, (MachineEnvelopeConfig, GCodeConfig), - ((ConfigOptionBool, avoid_curled_filament_during_travels)) + ((ConfigOptionBool, avoid_crossing_curled_overhangs)) ((ConfigOptionBool, avoid_crossing_perimeters)) ((ConfigOptionFloatOrPercent, avoid_crossing_perimeters_max_detour)) ((ConfigOptionPoints, bed_shape)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ab50c105d..5423a3ad1 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -7,6 +7,7 @@ #include "I18N.hpp" #include "Layer.hpp" #include "MutablePolygon.hpp" +#include "PrintBase.hpp" #include "SupportMaterial.hpp" #include "TreeSupport.hpp" #include "Surface.hpp" @@ -417,44 +418,16 @@ std::vector problematic_layers = SupportSpotsGenerator::quick_search(thi void PrintObject::generate_support_spots() { if (this->set_started(posSupportSpotsSearch)) { - BOOST_LOG_TRIVIAL(debug) - << "Searching support spots - start"; + BOOST_LOG_TRIVIAL(debug) << "Searching support spots - start"; m_print->set_status(75, L("Searching support spots")); - if (m_config.support_material && !m_config.support_material_auto && - std::all_of(this->model_object()->volumes.begin(), this->model_object()->volumes.end(), - [](const ModelVolume* mv){return mv->supported_facets.empty();}) - ) { - SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; - SupportSpotsGenerator::Issues issues = SupportSpotsGenerator::full_search(this, params); - - auto obj_transform = this->trafo_centered(); - for (ModelVolume *model_volume : this->model_object()->volumes) { - if (model_volume->is_model_part()) { - Transform3d mesh_transformation = obj_transform * model_volume->get_matrix(); - Transform3d inv_transform = mesh_transformation.inverse(); - TriangleSelectorWrapper selector { model_volume->mesh(), mesh_transformation}; - - for (const SupportSpotsGenerator::SupportPoint &support_point : issues.support_points) { - Vec3f point = Vec3f(inv_transform.cast() * support_point.position); - Vec3f origin = Vec3f( - inv_transform.cast() * Vec3f(support_point.position.x(), support_point.position.y(), 0.0f)); - selector.enforce_spot(point, origin, support_point.spot_radius); - } - - model_volume->supported_facets.set(selector.selector); -#if 0 //DEBUG export - indexed_triangle_set copy = model_volume->mesh().its; - its_transform(copy, obj_transform * model_transformation); - its_write_obj(copy, - debug_out_path(("model"+std::to_string(model_volume->id().id)+".obj").c_str()).c_str()); -#endif - } - } + if (!this->shared_regions()->generated_support_points.has_value()) { + PrintTryCancel cancel_func = m_print->make_try_cancel(); + SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; + SupportSpotsGenerator::SupportPoints supp_points = SupportSpotsGenerator::full_search(this, cancel_func, params); + this->m_shared_regions->generated_support_points = {this->trafo_centered(), supp_points}; + m_print->throw_if_canceled(); } - - m_print->throw_if_canceled(); - BOOST_LOG_TRIVIAL(debug) - << "Searching support spots - end"; + BOOST_LOG_TRIVIAL(debug) << "Searching support spots - end"; this->set_done(posSupportSpotsSearch); } } @@ -483,7 +456,7 @@ void PrintObject::generate_support_material() void PrintObject::estimate_curled_extrusions() { if (this->set_started(posEstimateCurledExtrusions)) { - if (this->print()->config().avoid_curled_filament_during_travels) { + if (this->print()->config().avoid_crossing_curled_overhangs) { BOOST_LOG_TRIVIAL(debug) << "Estimating areas with curled extrusions - start"; m_print->set_status(88, L("Estimating curled extrusions")); @@ -492,7 +465,6 @@ void PrintObject::estimate_curled_extrusions() SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; SupportSpotsGenerator::estimate_supports_malformations(this->support_layers(), support_flow_width, params); SupportSpotsGenerator::estimate_malformations(this->layers(), params); - m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Estimating areas with curled extrusions - end"; } @@ -777,6 +749,9 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_material_speed" || opt_key == "support_material_interface_speed" || opt_key == "bridge_speed" + || opt_key == "enable_dynamic_overhang_speeds" + || opt_key == "overhang_overlap_levels" + || opt_key == "dynamic_overhang_speeds" || opt_key == "external_perimeter_speed" || opt_key == "infill_speed" || opt_key == "perimeter_speed" @@ -813,10 +788,10 @@ bool PrintObject::invalidate_step(PrintObjectStep step) } else if (step == posPrepareInfill) { invalidated |= this->invalidate_steps({ posInfill, posIroning }); } else if (step == posInfill) { - invalidated |= this->invalidate_steps({ posIroning }); + invalidated |= this->invalidate_steps({ posIroning, posSupportSpotsSearch }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posSlice) { - invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial, posEstimateCurledExtrusions }); + invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); m_slicing_params.valid = false; } else if (step == posSupportMaterial) { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 6d35f013d..bd8424ca7 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -459,6 +459,7 @@ public: void set_task(const TaskParams ¶ms) override { PrintBaseWithState::set_task_impl(params, m_objects); } void process() override; void finalize() override { PrintBaseWithState::finalize_impl(m_objects); } + void cleanup() override {} // Returns true if an object step is done on all objects and there's at least one object. bool is_step_done(SLAPrintObjectStep step) const; // Returns true if the last step was finished with success. diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index d4088089b..4770334de 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -179,8 +179,8 @@ void SVG::draw(const ThickLines &thicklines, const std::string &fill, const std: void SVG::draw(const ThickPolylines &polylines, const std::string &stroke, coordf_t stroke_width) { - for (ThickPolylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) - this->draw((Polyline)*it, stroke, stroke_width); + for (const ThickPolyline &pl : polylines) + this->draw(Polyline(pl.points), stroke, stroke_width); } void SVG::draw(const ThickPolylines &thickpolylines, const std::string &fill, const std::string &stroke, coordf_t stroke_width) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index d46094f7f..9965c499f 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -1322,7 +1322,7 @@ namespace SupportMaterialInternal { // remove the entire bridges and only support the unsupported edges //FIXME the brided regions are already collected as layerm.bridged. Use it? for (const Surface &surface : layerm.fill_surfaces()) - if (surface.surface_type == stBottomBridge && surface.bridge_angle < 0.0) + if (surface.surface_type == stBottomBridge && surface.bridge_angle >= 0.0) polygons_append(bridges, surface.expolygon); //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? // Remove the unsupported ends of the bridges from the bridged areas. diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 66c6de7a9..68a8a4011 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -3,23 +3,28 @@ #include "ExPolygon.hpp" #include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" +#include "GCode/ExtrusionProcessor.hpp" #include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "Print.hpp" +#include "PrintBase.hpp" #include "Tesselate.hpp" #include "libslic3r.h" #include "tbb/parallel_for.h" #include "tbb/blocked_range.h" #include "tbb/blocked_range2d.h" #include "tbb/parallel_reduce.h" +#include #include #include +#include #include #include #include #include #include +#include #include #include "AABBTreeLines.hpp" @@ -41,12 +46,14 @@ namespace Slic3r { class ExtrusionLine { public: - ExtrusionLine() : a(Vec2f::Zero()), b(Vec2f::Zero()), len(0.0f), origin_entity(nullptr) {} - ExtrusionLine(const Vec2f &a, const Vec2f &b, const ExtrusionEntity *origin_entity) - : a(a), b(b), len((a - b).norm()), origin_entity(origin_entity) + ExtrusionLine() : a(Vec2f::Zero()), b(Vec2f::Zero()), len(0.0), origin_entity(nullptr) {} + ExtrusionLine(const Vec2f &a, const Vec2f &b, float len, const ExtrusionEntity *origin_entity) + : a(a), b(b), len(len), origin_entity(origin_entity) {} - float length() { return (a - b).norm(); } + ExtrusionLine(const Vec2f &a, const Vec2f &b) + : a(a), b(b), len((a-b).norm()), origin_entity(nullptr) + {} bool is_external_perimeter() const { @@ -60,7 +67,8 @@ public: const ExtrusionEntity *origin_entity; bool support_point_generated = false; - float malformation = 0.0f; + float form_quality = 1.0f; + float curled_up_height = 0.0f; static const constexpr int Dim = 2; using Scalar = Vec2f::Scalar; @@ -121,7 +129,7 @@ public: Vec3f get_cell_center(const Vec3i &cell_coords) const { - return origin + cell_coords.cast().cwiseProduct(this->cell_size) + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0)); + return origin + cell_coords.cast().cwiseProduct(this->cell_size) + this->cell_size.cwiseQuotient(Vec3f(2.0f, 2.0f, 2.0f)); } void take_position(const Vec3f &position) { taken_cells.insert(to_cell_index(to_cell_coords(position))); } @@ -174,48 +182,12 @@ float get_flow_width(const LayerRegion *region, ExtrusionRole role) } } -// Accumulator of current extrusion path properties -// It remembers unsuported distance and maximum accumulated curvature over that distance. -// Used to determine local stability issues (too long bridges, extrusion curves into air) -struct ExtrusionPropertiesAccumulator -{ - float distance = 0; // accumulated distance - float curvature = 0; // accumulated signed ccw angles - float max_curvature = 0; // max absolute accumulated value - - void add_distance(float dist) { distance += dist; } - - void add_angle(float ccw_angle) - { - curvature += ccw_angle; - max_curvature = std::max(max_curvature, std::abs(curvature)); - } - - void reset() - { - distance = 0; - curvature = 0; - max_curvature = 0; - } -}; - -// base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) -// checkout e.g. here: https://www.geogebra.org/calculator -float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) -{ - float shifted = value - mean_x_coord; - float denominator = falloff_speed * shifted * shifted + 1.0f; - float exponent = 1.0f / denominator; - return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); -} - std::vector to_short_lines(const ExtrusionEntity *e, float length_limit) { assert(!e->is_collection()); Polyline pl = e->as_polyline(); std::vector lines; lines.reserve(pl.points.size() * 1.5f); - lines.emplace_back(unscaled(pl.points[0]).cast(), unscaled(pl.points[0]).cast(), e); for (int point_idx = 0; point_idx < int(pl.points.size()) - 1; ++point_idx) { Vec2f start = unscaled(pl.points[point_idx]).cast(); Vec2f next = unscaled(pl.points[point_idx + 1]).cast(); @@ -227,87 +199,102 @@ std::vector to_short_lines(const ExtrusionEntity *e, float length for (int i = 0; i < lines_count; ++i) { Vec2f a(start + v * (i * step_size)); Vec2f b(start + v * ((i + 1) * step_size)); - lines.emplace_back(a, b, e); + lines.emplace_back(a, b, (a-b).norm(), e); } } return lines; } -std::vector check_extrusion_entity_stability(const ExtrusionEntity *entity, - const LayerRegion *layer_region, - const LD &prev_layer_lines, - const Params ¶ms) +float estimate_curled_up_height( + const ExtendedPoint &point, float layer_height, float flow_width, float prev_line_curled_height, Params params) +{ + float curled_up_height = 0.0f; + if (fabs(point.distance) < 1.5 * flow_width) { + curled_up_height = 0.85 * prev_line_curled_height; + } + if (point.distance > params.malformation_distance_factors.first * flow_width && + point.distance < params.malformation_distance_factors.second * flow_width && point.curvature > -0.1f) { + float dist_factor = std::max(point.distance - params.malformation_distance_factors.first * flow_width, 0.01f) / + ((params.malformation_distance_factors.second - params.malformation_distance_factors.first) * flow_width); + + curled_up_height = layer_height * 2.0f * sqrt(sqrt(dist_factor)) * std::clamp(6.0f * point.curvature, 1.0f, 6.0f); + curled_up_height = std::min(curled_up_height, params.max_curled_height_factor * layer_height); + } + + return curled_up_height; +} + +std::vector check_extrusion_entity_stability(const ExtrusionEntity *entity, + const LayerRegion *layer_region, + const LD &prev_layer_lines, + const AABBTreeLines::LinesDistancer &prev_layer_boundary, + const Params ¶ms) { if (entity->is_collection()) { std::vector checked_lines_out; checked_lines_out.reserve(prev_layer_lines.get_lines().size() / 3); for (const auto *e : static_cast(entity)->entities) { - auto tmp = check_extrusion_entity_stability(e, layer_region, prev_layer_lines, params); + auto tmp = check_extrusion_entity_stability(e, layer_region, prev_layer_lines, prev_layer_boundary, params); checked_lines_out.insert(checked_lines_out.end(), tmp.begin(), tmp.end()); } return checked_lines_out; } else { // single extrusion path, with possible varying parameters - if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { return {}; } - - std::vector lines = to_short_lines(entity, params.bridge_distance); - - ExtrusionPropertiesAccumulator bridging_acc{}; - ExtrusionPropertiesAccumulator malformation_acc{}; - bridging_acc.add_distance(params.bridge_distance + 1.0f); - const float flow_width = get_flow_width(layer_region, entity->role()); - float min_malformation_dist = flow_width - params.malformation_overlap_factor.first * flow_width; - float max_malformation_dist = flow_width - params.malformation_overlap_factor.second * flow_width; - - for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { - ExtrusionLine ¤t_line = lines[line_idx]; - if (line_idx + 1 == lines.size() && current_line.b != lines.begin()->a) { - bridging_acc.add_distance(params.bridge_distance + 1.0f); - } - float curr_angle = 0; - if (line_idx + 1 < lines.size()) { - const Vec2f v1 = current_line.b - current_line.a; - const Vec2f v2 = lines[line_idx + 1].b - lines[line_idx + 1].a; - curr_angle = angle(v1, v2); - } - bridging_acc.add_angle(curr_angle); - // malformation in concave angles does not happen - malformation_acc.add_angle(std::max(0.0f, curr_angle)); - if (curr_angle < -20.0 * PI / 180.0) { malformation_acc.reset(); } - - auto [dist_from_prev_layer, nearest_line_idx, nearest_point] = prev_layer_lines.signed_distance_from_lines_extra(current_line.b); - if (dist_from_prev_layer < flow_width) { - bridging_acc.reset(); - } else { - bridging_acc.add_distance(current_line.len); - // if unsupported distance is larger than bridge distance linearly decreased by curvature, enforce supports. - bool in_layer_dist_condition = bridging_acc.distance > - params.bridge_distance / (1.0f + (bridging_acc.max_curvature * - params.bridge_distance_decrease_by_curvature_factor / PI)); - bool between_layers_condition = dist_from_prev_layer > max_malformation_dist; - if (in_layer_dist_condition && between_layers_condition) { - current_line.support_point_generated = true; - bridging_acc.reset(); - } - } - - // malformation propagation from below - if (fabs(dist_from_prev_layer) < 2.0f * flow_width) { - const ExtrusionLine &nearest_line = prev_layer_lines.get_line(nearest_line_idx); - current_line.malformation += 0.85 * nearest_line.malformation; - } - // current line maformation - if (dist_from_prev_layer > min_malformation_dist && dist_from_prev_layer < max_malformation_dist) { - float factor = std::abs(dist_from_prev_layer - (max_malformation_dist + min_malformation_dist) * 0.5) / - (max_malformation_dist - min_malformation_dist); - malformation_acc.add_distance(current_line.len); - current_line.malformation += layer_region->layer()->height * factor * (2.0f + 3.0f * (malformation_acc.max_curvature / PI)); - current_line.malformation = std::min(current_line.malformation, - float(layer_region->layer()->height * params.max_malformation_factor)); - } else { - malformation_acc.reset(); - } + if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { + return {}; } - return lines; + + const float flow_width = get_flow_width(layer_region, entity->role()); + + std::vector annotated_points = estimate_points_properties(entity->as_polyline().points, + prev_layer_lines, flow_width, + params.bridge_distance); + + std::vector lines_out; + lines_out.reserve(annotated_points.size()); + float bridged_distance = annotated_points.front().position != annotated_points.back().position ? (params.bridge_distance + 1.0f) : + 0.0f; + for (size_t i = 0; i < annotated_points.size(); ++i) { + ExtendedPoint &curr_point = annotated_points[i]; + float line_len = i > 0 ? ((annotated_points[i - 1].position - curr_point.position).norm()) : 0.0f; + ExtrusionLine line_out{i > 0 ? annotated_points[i - 1].position.cast() : curr_point.position.cast(), + curr_point.position.cast(), line_len, entity}; + + const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ? + prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : + ExtrusionLine{}; + + float sign = (prev_layer_boundary.distance_from_lines(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : 1.0f; + curr_point.distance *= sign; + + float max_bridge_len = params.bridge_distance / + ((1.0f + std::abs(curr_point.curvature)) * (1.0f + std::abs(curr_point.curvature))); + + if (curr_point.distance > 2.0f * flow_width) { + line_out.form_quality = 0.8f; + bridged_distance += line_len; + if (bridged_distance > max_bridge_len) { + line_out.support_point_generated = true; + bridged_distance = 0.0f; + } + } else if (curr_point.distance > flow_width * (1.0 + std::clamp(curr_point.curvature, -0.30f, 0.20f))) { + bridged_distance += line_len; + line_out.form_quality = nearest_prev_layer_line.form_quality - 0.3f; + if (line_out.form_quality < 0 && bridged_distance > max_bridge_len) { + line_out.support_point_generated = true; + line_out.form_quality = 0.5f; + bridged_distance = 0.0f; + } + } else { + bridged_distance = 0.0f; + } + + line_out.curled_up_height = estimate_curled_up_height(curr_point, layer_region->layer()->height, flow_width, + nearest_prev_layer_line.curled_up_height, params); + + lines_out.push_back(line_out); + } + + return lines_out; } } @@ -484,7 +471,7 @@ public: float movement_force = params.max_acceleration * mass; float extruder_conflict_force = params.standard_extruder_conflict_force + - std::min(extruded_line.malformation, 1.0f) * params.malformations_additive_conflict_extruder_force; + std::min(extruded_line.curled_up_height, 1.0f) * params.malformations_additive_conflict_extruder_force; // section for bed calculations { @@ -522,7 +509,8 @@ public: BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_arm: " << bed_movement_arm; BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_torque: " << bed_movement_torque; BOOST_LOG_TRIVIAL(debug) << "SSG: bed_conflict_torque_arm: " << bed_conflict_torque_arm; - BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.malformation: " << extruded_line.malformation; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.curled_up_height: " << extruded_line.curled_up_height; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.form_quality: " << extruded_line.form_quality; BOOST_LOG_TRIVIAL(debug) << "SSG: extruder_conflict_force: " << extruder_conflict_force; BOOST_LOG_TRIVIAL(debug) << "SSG: bed_extruder_conflict_torque: " << bed_extruder_conflict_torque; BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; @@ -556,7 +544,7 @@ public: float conn_total_torque = conn_movement_torque + conn_extruder_conflict_torque + conn_weight_torque - conn_yield_torque; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "conn_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); BOOST_LOG_TRIVIAL(debug) << "SSG: conn_yield_torque: " << conn_yield_torque; BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_arm: " << conn_weight_arm; BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_torque: " << conn_weight_torque; @@ -590,7 +578,7 @@ std::tuple build_object_part_from_slice(const LayerSlice &sli new_object_part.volume += volume; new_object_part.volume_centroid_accumulator += to_3d(Vec2f((line.a + line.b) / 2.0f), slice_z) * volume; - if (l->id() == 0) { // first layer + if (l->bottom_z() < EPSILON) { // layer attached on bed float sticking_area = line.len * flow_width; new_object_part.sticking_area += sticking_area; Vec2f middle = Vec2f((line.a + line.b) / 2.0f); @@ -627,9 +615,8 @@ std::tuple build_object_part_from_slice(const LayerSlice &sli } } } - const LayerRegion *thin_fill_region = layer->get_region(island.fill_region_id); for (const auto &thin_fill_idx : island.thin_fills) { - add_extrusions_to_object(thin_fill_region->thin_fills().entities[thin_fill_idx], perimeter_region); + add_extrusions_to_object(perimeter_region->thin_fills().entities[thin_fill_idx], perimeter_region); } } @@ -675,12 +662,12 @@ public: } }; -Issues check_stability(const PrintObject *po, const Params ¶ms) +SupportPoints check_stability(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms) { - Issues issues{}; + SupportPoints supp_points{}; SupportGridFilter supports_presence_grid(po, params.min_distance_between_support_points); ActiveObjectParts active_object_parts{}; - LD prev_layer_ext_perim_lines({}); + LD prev_layer_ext_perim_lines; std::unordered_map prev_slice_idx_to_object_part_mapping; std::unordered_map next_slice_idx_to_object_part_mapping; @@ -688,6 +675,7 @@ Issues check_stability(const PrintObject *po, const Params ¶ms) std::unordered_map next_slice_idx_to_weakest_connection; for (size_t layer_idx = 0; layer_idx < po->layer_count(); ++layer_idx) { + cancel_func(); const Layer *layer = po->get_layer(layer_idx); float bottom_z = layer->bottom_z(); auto create_support_point_position = [bottom_z](const Vec2f &layer_pos) { return Vec3f{layer_pos.x(), layer_pos.y(), bottom_z}; }; @@ -767,6 +755,15 @@ Issues check_stability(const PrintObject *po, const Params ¶ms) const LayerSlice &slice = layer->lslices_ex.at(slice_idx); ObjectPart &part = active_object_parts.access(prev_slice_idx_to_object_part_mapping[slice_idx]); SliceConnection &weakest_conn = prev_slice_idx_to_weakest_connection[slice_idx]; + + std::vector boundary_lines; + for (const auto &link : slice.overlaps_below) { + auto ls = to_unscaled_linesf({layer->lower_layer->lslices[link.slice_idx]}); + boundary_lines.insert(boundary_lines.end(), ls.begin(), ls.end()); + } + AABBTreeLines::LinesDistancer prev_layer_boundary{std::move(boundary_lines)}; + + std::vector current_slice_ext_perims_lines{}; current_slice_ext_perims_lines.reserve(prev_layer_ext_perim_lines.get_lines().size() / layer->lslices_ex.size()); #ifdef DETAILED_DEBUG_LOGS @@ -774,16 +771,19 @@ Issues check_stability(const PrintObject *po, const Params ¶ms) #endif // Function that is used when new support point is generated. It will update the ObjectPart stability, weakest conneciton info, // and the support presence grid and add the point to the issues. - auto reckon_new_support_point = [&part, &weakest_conn, &issues, &supports_presence_grid, ¶ms, + auto reckon_new_support_point = [&part, &weakest_conn, &supp_points, &supports_presence_grid, ¶ms, &layer_idx](const Vec3f &support_point, float force, const Vec2f &dir) { - if (supports_presence_grid.position_taken(support_point) || layer_idx <= 1) { return; } + if ((supports_presence_grid.position_taken(support_point) && force > 0) || layer_idx <= 1) { + return; + } float area = params.support_points_interface_radius * params.support_points_interface_radius * float(PI); part.add_support_point(support_point, area); float radius = params.support_points_interface_radius; - issues.support_points.emplace_back(support_point, force, radius, dir); - supports_presence_grid.take_position(support_point); - + supp_points.emplace_back(support_point, force, radius, dir); + if (force > 0) { + supports_presence_grid.take_position(support_point); + } if (weakest_conn.area > EPSILON) { // Do not add it to the weakest connection if it is not valid - does not exist weakest_conn.area += area; weakest_conn.centroid_accumulator += support_point * area; @@ -802,7 +802,7 @@ Issues check_stability(const PrintObject *po, const Params ¶ms) const ExtrusionEntity *entity = fill_region->fills().entities[fill_idx]; if (entity->role() == erBridgeInfill) { for (const ExtrusionLine &bridge : - check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines, params)) { + check_extrusion_entity_stability(entity, fill_region, prev_layer_ext_perim_lines,prev_layer_boundary, params)) { if (bridge.support_point_generated) { reckon_new_support_point(create_support_point_position(bridge.b), -EPSILON, Vec2f::Zero()); } @@ -815,7 +815,7 @@ Issues check_stability(const PrintObject *po, const Params ¶ms) for (const auto &perimeter_idx : island.perimeters) { const ExtrusionEntity *entity = perimeter_region->perimeters().entities[perimeter_idx]; std::vector perims = check_extrusion_entity_stability(entity, perimeter_region, - prev_layer_ext_perim_lines, params); + prev_layer_ext_perim_lines,prev_layer_boundary, params); for (const ExtrusionLine &perim : perims) { if (perim.support_point_generated) { reckon_new_support_point(create_support_point_position(perim.b), -EPSILON, Vec2f::Zero()); @@ -829,13 +829,13 @@ Issues check_stability(const PrintObject *po, const Params ¶ms) float unchecked_dist = params.min_distance_between_support_points + 1.0f; for (const ExtrusionLine &line : current_slice_ext_perims_lines) { - if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.malformation < 0.3f) || line.len == 0) { + if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.curled_up_height < 0.3f) || line.len < EPSILON) { unchecked_dist += line.len; } else { unchecked_dist = line.len; Vec2f pivot_site_search_point = Vec2f(line.b + (line.b - line.a).normalized() * 300.0f); auto [dist, nidx, - nearest_point] = current_slice_lines_distancer.signed_distance_from_lines_extra(pivot_site_search_point); + nearest_point] = current_slice_lines_distancer.distance_from_lines_extra(pivot_site_search_point); Vec3f support_point = create_support_point_position(nearest_point); auto force = part.is_stable_while_extruding(weakest_conn, line, support_point, bottom_z, params); if (force > 0) { reckon_new_support_point(support_point, force, (line.b - line.a).normalized()); } @@ -846,11 +846,11 @@ Issues check_stability(const PrintObject *po, const Params ¶ms) } // slice iterations prev_layer_ext_perim_lines = LD(current_layer_ext_perims_lines); } // layer iterations - return issues; + return supp_points; } #ifdef DEBUG_FILES -void debug_export(Issues issues, std::string file_name) +void debug_export(SupportPoints support_points, std::string file_name) { Slic3r::CNumericLocalesSetter locales_setter; { @@ -860,13 +860,13 @@ void debug_export(Issues issues, std::string file_name) return; } - for (size_t i = 0; i < issues.support_points.size(); ++i) { - if (issues.support_points[i].force <= 0) { - fprintf(fp, "v %f %f %f %f %f %f\n", issues.support_points[i].position(0), issues.support_points[i].position(1), - issues.support_points[i].position(2), 0.0, 1.0, 0.0); + for (size_t i = 0; i < support_points.size(); ++i) { + if (support_points[i].force <= 0) { + fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1), + support_points[i].position(2), 0.0, 1.0, 0.0); } else { - fprintf(fp, "v %f %f %f %f %f %f\n", issues.support_points[i].position(0), issues.support_points[i].position(1), - issues.support_points[i].position(2), 1.0, 0.0, 0.0); + fprintf(fp, "v %f %f %f %f %f %f\n", support_points[i].position(0), support_points[i].position(1), + support_points[i].position(2), 1.0, 0.0, 0.0); } } @@ -878,113 +878,84 @@ void debug_export(Issues issues, std::string file_name) // std::vector quick_search(const PrintObject *po, const Params ¶ms) { // return {}; // } -Issues full_search(const PrintObject *po, const Params ¶ms) +SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms) { - Issues issues = check_stability(po, params); + SupportPoints supp_points = check_stability(po, cancel_func, params); #ifdef DEBUG_FILES - debug_export(issues, "issues"); + debug_export(supp_points, "issues"); #endif - return issues; + return supp_points; } - -struct LayerCurlingEstimator -{ - LD prev_layer_lines = LD({}); - Params params; - std::function flow_width_getter; - - LayerCurlingEstimator(std::function flow_width_getter, const Params ¶ms) - : flow_width_getter(flow_width_getter), params(params) - {} - - void estimate_curling(std::vector &extrusion_lines, Layer *l) - { - ExtrusionPropertiesAccumulator malformation_acc{}; - for (size_t line_idx = 0; line_idx < extrusion_lines.size(); ++line_idx) { - ExtrusionLine ¤t_line = extrusion_lines[line_idx]; - - float flow_width = flow_width_getter(current_line); - - float min_malformation_dist = flow_width - params.malformation_overlap_factor.first * flow_width; - float max_malformation_dist = flow_width - params.malformation_overlap_factor.second * flow_width; - - float curr_angle = 0; - if (line_idx + 1 < extrusion_lines.size()) { - const Vec2f v1 = current_line.b - current_line.a; - const Vec2f v2 = extrusion_lines[line_idx + 1].b - extrusion_lines[line_idx + 1].a; - curr_angle = angle(v1, v2); - } - // malformation in concave angles does not happen - malformation_acc.add_angle(std::max(0.0f, curr_angle)); - if (curr_angle < -20.0 * PI / 180.0) { malformation_acc.reset(); } - - auto [dist_from_prev_layer, nearest_line_idx, nearest_point] = prev_layer_lines.signed_distance_from_lines_extra(current_line.b); - - if (fabs(dist_from_prev_layer) < 2.0f * flow_width) { - const ExtrusionLine &nearest_line = prev_layer_lines.get_line(nearest_line_idx); - current_line.malformation += 0.9 * nearest_line.malformation; - } - if (dist_from_prev_layer > min_malformation_dist && dist_from_prev_layer < max_malformation_dist) { - float factor = 0.5f + 0.5f * std::abs(dist_from_prev_layer - (max_malformation_dist + min_malformation_dist) * 0.5) / - (max_malformation_dist - min_malformation_dist); - malformation_acc.add_distance(current_line.len); - current_line.malformation += l->height * factor * (1.5f + 3.0f * (malformation_acc.max_curvature / PI)); - current_line.malformation = std::min(current_line.malformation, float(l->height * params.max_malformation_factor)); - } else { - malformation_acc.reset(); - } - } - - for (const ExtrusionLine &line : extrusion_lines) { - if (line.malformation > 0.3f) { l->malformed_lines.push_back(Line{Point::new_scale(line.a), Point::new_scale(line.b)}); } - } - prev_layer_lines = LD(extrusion_lines); - } -}; - - -void estimate_supports_malformations(SupportLayerPtrs &layers, float supports_flow_width, const Params ¶ms) +void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width, const Params ¶ms) { #ifdef DEBUG_FILES FILE *debug_file = boost::nowide::fopen(debug_out_path("supports_malformations.obj").c_str(), "w"); + FILE *full_file = boost::nowide::fopen(debug_out_path("supports_full.obj").c_str(), "w"); #endif - auto flow_width_getter = [=](const ExtrusionLine& l) { - return supports_flow_width; - }; - LayerCurlingEstimator lce{flow_width_getter, params}; + AABBTreeLines::LinesDistancer prev_layer_lines{}; for (SupportLayer *l : layers) { - std::vector extrusion_lines; + std::vector current_layer_lines; + for (const ExtrusionEntity *extrusion : l->support_fills.flatten().entities) { Polyline pl = extrusion->as_polyline(); - Polygon pol(pl.points); + Polygon pol(pl.points); pol.make_counter_clockwise(); - pl = pol.split_at_first_point(); - for (int point_idx = 0; point_idx < int(pl.points.size() - 1); ++point_idx) { - Vec2f start = unscaled(pl.points[point_idx]).cast(); - Vec2f next = unscaled(pl.points[point_idx + 1]).cast(); - ExtrusionLine line{start, next, extrusion}; - extrusion_lines.push_back(line); + + auto annotated_points = estimate_points_properties(pol.points, prev_layer_lines, flow_width); + + for (size_t i = 0; i < annotated_points.size(); ++i) { + ExtendedPoint &curr_point = annotated_points[i]; + float line_len = i > 0 ? ((annotated_points[i - 1].position - curr_point.position).norm()) : 0.0f; + ExtrusionLine line_out{i > 0 ? annotated_points[i - 1].position.cast() : curr_point.position.cast(), + curr_point.position.cast(), line_len, extrusion}; + + const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ? + prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : + ExtrusionLine{}; + + Vec2f v1 = (nearest_prev_layer_line.b - nearest_prev_layer_line.a); + Vec2f v2 = (curr_point.position.cast() - nearest_prev_layer_line.a); + auto d = (v1.x() * v2.y()) - (v1.y() * v2.x()); + if (d > 0) { + curr_point.distance *= -1.0f; + } + + line_out.curled_up_height = estimate_curled_up_height(curr_point, l->height, flow_width, + nearest_prev_layer_line.curled_up_height, params); + + current_layer_lines.push_back(line_out); } } - lce.estimate_curling(extrusion_lines, l); + for (const ExtrusionLine &line : current_layer_lines) { + if (line.curled_up_height > 0.3f) { + l->malformed_lines.push_back(Line{Point::new_scale(line.a), Point::new_scale(line.b)}); + } + } #ifdef DEBUG_FILES - for (const ExtrusionLine &line : extrusion_lines) { - if (line.malformation > 0.3f) { - Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_malformation_factor, line.malformation); + for (const ExtrusionLine &line : current_layer_lines) { + if (line.curled_up_height > 0.3f) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_curled_height_factor, line.curled_up_height); fprintf(debug_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); } } + for (const ExtrusionLine &line : current_layer_lines) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_curled_height_factor, line.curled_up_height); + fprintf(full_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); + } #endif + + prev_layer_lines = LD{current_layer_lines}; } #ifdef DEBUG_FILES fclose(debug_file); + fclose(full_file); #endif } @@ -992,52 +963,71 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms) { #ifdef DEBUG_FILES FILE *debug_file = boost::nowide::fopen(debug_out_path("object_malformations.obj").c_str(), "w"); + FILE *full_file = boost::nowide::fopen(debug_out_path("object_full.obj").c_str(), "w"); #endif - auto flow_width_getter = [](const ExtrusionLine &l) { return 0.0; }; - LayerCurlingEstimator lce{flow_width_getter, params}; + + LD prev_layer_lines{}; for (Layer *l : layers) { - if (l->regions().empty()) { - continue; - } - struct Visitor { - Visitor(const Params ¶ms) : params(params) {} - void recursive_do(const ExtrusionEntityCollection &collection, const LayerRegion *region) { - for (const ExtrusionEntity* entity : collection.entities) - if (entity->is_collection()) - this->recursive_do(*static_cast(entity), region); - else { - append(extrusion_lines, to_short_lines(entity, params.bridge_distance)); - extrusions_widths.emplace(entity, get_flow_width(region, entity->role())); - } + std::vector boundary_lines = l->lower_layer != nullptr ? to_unscaled_linesf(l->lower_layer->lslices) : std::vector(); + AABBTreeLines::LinesDistancer prev_layer_boundary{std::move(boundary_lines)}; + std::vector current_layer_lines; + for (const LayerRegion *layer_region : l->regions()) { + for (const ExtrusionEntity *extrusion : layer_region->perimeters().flatten().entities) { + Points extrusion_pts; + extrusion->collect_points(extrusion_pts); + float flow_width = get_flow_width(layer_region, extrusion->role()); + auto annotated_points = estimate_points_properties(extrusion_pts, prev_layer_lines, flow_width, + params.bridge_distance); + for (size_t i = 0; i < annotated_points.size(); ++i) { + ExtendedPoint &curr_point = annotated_points[i]; + float line_len = i > 0 ? ((annotated_points[i - 1].position - curr_point.position).norm()) : 0.0f; + ExtrusionLine line_out{i > 0 ? annotated_points[i - 1].position.cast() : curr_point.position.cast(), + curr_point.position.cast(), line_len, extrusion}; + + const ExtrusionLine nearest_prev_layer_line = prev_layer_lines.get_lines().size() > 0 ? + prev_layer_lines.get_line(curr_point.nearest_prev_layer_line) : + ExtrusionLine{}; + + float sign = (prev_layer_boundary.distance_from_lines(curr_point.position) + 0.5f * flow_width) < 0.0f ? -1.0f : + 1.0f; + curr_point.distance *= sign; + + line_out.curled_up_height = estimate_curled_up_height(curr_point, layer_region->layer()->height, flow_width, + nearest_prev_layer_line.curled_up_height, params); + + current_layer_lines.push_back(line_out); + } } - const Params ¶ms; - std::unordered_map extrusions_widths; - std::vector extrusion_lines; - } visitor(params); + } - for (const LayerRegion *region : l->regions()) - visitor.recursive_do(region->perimeters(), region); - - lce.flow_width_getter = [&](const ExtrusionLine &l) { return visitor.extrusions_widths[l.origin_entity]; }; - - lce.estimate_curling(visitor.extrusion_lines, l); + for (const ExtrusionLine &line : current_layer_lines) { + if (line.curled_up_height > 0.3f) { + l->malformed_lines.push_back(Line{Point::new_scale(line.a), Point::new_scale(line.b)}); + } + } #ifdef DEBUG_FILES - for (const ExtrusionLine &line : extrusion_lines) { - if (line.malformation > 0.3f) { - Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_malformation_factor, line.malformation); + for (const ExtrusionLine &line : current_layer_lines) { + if (line.curled_up_height > 0.3f) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_curled_height_factor, line.curled_up_height); fprintf(debug_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); } } + for (const ExtrusionLine &line : current_layer_lines) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_curled_height_factor, line.curled_up_height); + fprintf(full_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); + } #endif + + prev_layer_lines = LD{current_layer_lines}; } #ifdef DEBUG_FILES fclose(debug_file); + fclose(full_file); #endif } -} //SupportableIssues End -} - +} // namespace SupportSpotsGenerator +} // namespace Slic3r diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index b0cbab57d..1c07c1381 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -1,8 +1,11 @@ #ifndef SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ #define SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ -#include "libslic3r/Print.hpp" +#include "Layer.hpp" +#include "Line.hpp" +#include "PrintBase.hpp" #include +#include namespace Slic3r { @@ -20,19 +23,20 @@ struct Params { filament_type = std::string("PLA"); } else { filament_type = filament_types[0]; + BOOST_LOG_TRIVIAL(debug) + << "SupportSpotsGenerator: applying filament type: " << filament_type; } } // the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2] const float bridge_distance = 12.0f; //mm - const float bridge_distance_decrease_by_curvature_factor = 5.0f; // allowed bridge distance = bridge_distance / (1 + this factor * (curvature / PI) ) - const std::pair malformation_overlap_factor = std::pair { 0.50, -0.1 }; - const float max_malformation_factor = 10.0f; + const std::pair malformation_distance_factors = std::pair { 0.4, 1.2 }; + const float max_curled_height_factor = 10.0f; const float min_distance_between_support_points = 3.0f; //mm const float support_points_interface_radius = 1.5f; // mm const float connections_min_considerable_area = 1.5f; //mm^2 - const float min_distance_to_allow_local_supports = 2.0f; //mm + const float min_distance_to_allow_local_supports = 1.0f; //mm std::string filament_type; const float gravity_constant = 9806.65f; // mm/s^2; gravity acceleration on Earth's surface, algorithm assumes that printer is in upwards position. @@ -40,7 +44,7 @@ struct Params { const double filament_density = 1.25e-3f; // g/mm^3 ; Common filaments are very lightweight, so precise number is not that important const double material_yield_strength = 33.0f * 1e6f; // (g*mm/s^2)/mm^2; 33 MPa is yield strength of ABS, which has the lowest yield strength from common materials. const float standard_extruder_conflict_force = 20.0f * gravity_constant; // force that can occasionally push the model due to various factors (filament leaks, small curling, ... ); - const float malformations_additive_conflict_extruder_force = 300.0f * gravity_constant; // for areas with possible high layered curled filaments + const float malformations_additive_conflict_extruder_force = 100.0f * gravity_constant; // for areas with possible high layered curled filaments // MPa * 1e^6 = (g*mm/s^2)/mm^2 = g/(mm*s^2); yield strength of the bed surface double get_bed_adhesion_yield_strength() const { @@ -67,19 +71,17 @@ struct SupportPoint { Vec2f direction; }; -struct Issues { - std::vector support_points; -}; +using SupportPoints = std::vector; struct Malformations { std::vector layers; //for each layer }; // std::vector quick_search(const PrintObject *po, const Params ¶ms); -Issues full_search(const PrintObject *po, const Params ¶ms); +SupportPoints full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms); -void estimate_supports_malformations(SupportLayerPtrs &layers, float supports_flow_width, const Params ¶ms); -void estimate_malformations(LayerPtrs &layers, const Params ¶ms); +void estimate_supports_malformations(std::vector &layers, float supports_flow_width, const Params ¶ms); +void estimate_malformations(std::vector &layers, const Params ¶ms); } // namespace SupportSpotsGenerator } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 87406d724..920a101fa 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -55,6 +55,8 @@ #define ENABLE_RELOAD_FROM_DISK_REWORK (1 && ENABLE_2_6_0_ALPHA1) // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_6_0_ALPHA1) +// Shows an imgui dialog containing the matrices of the selected volumes +#define ENABLE_WORLD_COORDINATE_DEBUG (0 && ENABLE_WORLD_COORDINATE) // Enable alternative version of file_wildcards() #define ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR (1 && ENABLE_2_6_0_ALPHA1) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 6e17daa56..cf0d29eb8 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -622,7 +622,7 @@ std::vector its_face_edge_ids(const indexed_triangle_set &its, std::funct return its_face_edge_ids_impl(its, [](const uint32_t){ return true; }, throw_on_cancel_callback); } -std::vector its_face_edge_ids(const indexed_triangle_set &its, const std::vector &face_mask) +std::vector its_face_edge_ids(const indexed_triangle_set &its, const std::vector &face_mask) { return its_face_edge_ids_impl(its, [&face_mask](const uint32_t idx){ return face_mask[idx]; }, [](){}); } @@ -977,8 +977,10 @@ indexed_triangle_set its_make_cone(double r, double h, double fa) vertices.emplace_back(Vec3f(0., 0., h)); size_t i = 0; + const auto vec = Eigen::Vector2f(0, float(r)); for (double angle=0; angle<2*PI; angle+=fa) { - vertices.emplace_back(r*std::cos(angle), r*std::sin(angle), 0.); + Vec2f p = Eigen::Rotation2Df(angle) * vec; + vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); if (angle > 0.) { facets.emplace_back(0, i+2, i+1); facets.emplace_back(1, i+1, i+2); @@ -1013,58 +1015,121 @@ indexed_triangle_set its_make_pyramid(float base, float height) // Generates mesh for a sphere centered about the origin, using the generated angle // to determine the granularity. // Default angle is 1 degree. -//FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html indexed_triangle_set its_make_sphere(double radius, double fa) { - int sectorCount = int(ceil(2. * M_PI / fa)); - int stackCount = int(ceil(M_PI / fa)); - float sectorStep = float(2. * M_PI / sectorCount); - float stackStep = float(M_PI / stackCount); - + // First build an icosahedron (taken from http://www.songho.ca/opengl/gl_sphere.html) indexed_triangle_set mesh; + + const float PI = 3.1415926f; + const float H_ANGLE = PI / 180 * 72; // 72 degree = 360 / 5 + const float V_ANGLE = atanf(1.0f / 2); // elevation = 26.565 degree + auto& vertices = mesh.vertices; - vertices.reserve((stackCount - 1) * sectorCount + 2); - for (int i = 0; i <= stackCount; ++ i) { - // from pi/2 to -pi/2 - double stackAngle = 0.5 * M_PI - stackStep * i; - double xy = radius * cos(stackAngle); - double z = radius * sin(stackAngle); - if (i == 0 || i == stackCount) - vertices.emplace_back(Vec3f(float(xy), 0.f, float(z))); - else - for (int j = 0; j < sectorCount; ++ j) { - // from 0 to 2pi - double sectorAngle = sectorStep * j; - vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast()); - } - } + auto& indices = mesh.indices; + vertices.resize(12); + indices.reserve(20); - auto& facets = mesh.indices; - facets.reserve(2 * (stackCount - 1) * sectorCount); - for (int i = 0; i < stackCount; ++ i) { - // Beginning of current stack. - int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); - int k1_first = k1; - // Beginning of next stack. - int k2 = (i == 0) ? 1 : (k1 + sectorCount); - int k2_first = k2; - for (int j = 0; j < sectorCount; ++ j) { - // 2 triangles per sector excluding first and last stacks - int k1_next = k1; - int k2_next = k2; - if (i != 0) { - k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); - facets.emplace_back(k1, k2, k1_next); + float z, xy; + float hAngle1 = -PI / 2 - H_ANGLE / 2; + + vertices[0] = stl_vertex(0, 0, radius); // the first top vertex at (0, 0, r) + + for (int i = 1; i <= 5; ++i) { + z = radius * sinf(V_ANGLE); + xy = radius * cosf(V_ANGLE); + vertices[i] = stl_vertex(xy * cosf(hAngle1), xy * sinf(hAngle1), z); + vertices[i+5] = stl_vertex(xy * cosf(hAngle1 + H_ANGLE / 2), xy * sinf(hAngle1 + H_ANGLE / 2), -z); + hAngle1 += H_ANGLE; + + indices.emplace_back(stl_triangle_vertex_indices(i, i < 5 ? i+1 : 1, 0)); + indices.emplace_back(stl_triangle_vertex_indices(i, i+5, i < 5 ? i+1 : 1)); + indices.emplace_back(stl_triangle_vertex_indices(i+5, i+6 < 11 ? i+6 : 6, i+6 < 11 ? i+1 : 1)); + indices.emplace_back(stl_triangle_vertex_indices(i+5, 11, i+6 < 11 ? i+6 : 6)); + } + vertices[11] = stl_vertex(0, 0, -radius); // the last bottom vertex at (0, 0, -r) + + + // We have a beautiful icosahedron. Now subdivide the triangles. + std::vector neighbors = its_face_neighbors(mesh); // This is cheap, the mesh is small. + + const double side_len_limit = radius * fa; + const double side_len = (vertices[1] - vertices[0]).norm(); + const int iterations = std::ceil(std::log2(side_len / side_len_limit)); + + indices.reserve(indices.size() * std::pow(4, iterations)); + vertices.reserve(vertices.size() * std::pow(2, iterations)); + + struct DividedEdge { + int neighbor = -1; + int middle_vertex_idx; + std::pair children_idxs; + }; + + for (int iter=0; iter> divided_triangles(indices.size()); + std::vector new_neighbors(4*indices.size()); + + size_t orig_indices_size = indices.size(); + for (int i=0; i, 3> edge_children = { std::make_pair(i,last_triangle_idx + 2), + std::make_pair(last_triangle_idx + 2,last_triangle_idx + 3), + std::make_pair(last_triangle_idx + 3,i) }; + + std::array middle_vertices_idxs; + std::array, 3> new_neighbors_per_edge; + + for (int n=0; n<3; ++n) { // for all three edges + const int edge_neighbor = neighbors[i][n]; + + if (divided_triangles[edge_neighbor][0].neighbor == -1) { + // This n-th edge is not yet divided. Divide it now. + vertices.emplace_back(0.5 * (vertices[indices[i][n]] + vertices[indices[i][n == 2 ? 0 : n+1]])); + vertices.back() *= radius / vertices.back().norm(); + middle_vertices_idxs[n] = vertices.size()-1; + + // Save information about what we did. + int j = -1; + while (divided_triangles[i][++j].neighbor != -1); + + divided_triangles[i][j] = { edge_neighbor, int(vertices.size()-1), edge_children[n] }; + new_neighbors_per_edge[n] = std::make_pair(-1,-1); + } else { + // This edge is already divided. Get the index of the middle point. + int j = -1; + while (divided_triangles[edge_neighbor][++j].neighbor != i); + middle_vertices_idxs[n] = divided_triangles[edge_neighbor][j].middle_vertex_idx; + new_neighbors_per_edge[n] = divided_triangles[edge_neighbor][j].children_idxs; + std::swap(new_neighbors_per_edge[n].first, new_neighbors_per_edge[n].second); + + // We have saved the middle-point. We are looking for edges leading to/from it. + int idx = -1; while (indices[new_neighbors_per_edge[n].first][++idx] != middle_vertices_idxs[n]); + new_neighbors[new_neighbors_per_edge[n].first][idx] = edge_children[n].first; + new_neighbors[new_neighbors_per_edge[n].second][idx] = edge_children[n].second; + } } - if (i + 1 != stackCount) { - k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); - facets.emplace_back(k1_next, k2, k2_next); - } - k1 = k1_next; - k2 = k2_next; + + // Add three new triangles, reindex the old one. + const int last_index = indices.size() - 1; + indices.emplace_back(stl_triangle_vertex_indices(middle_vertices_idxs[0], middle_vertices_idxs[1], middle_vertices_idxs[2])); + new_neighbors[indices.size()-1] = Vec3i(last_index+2, last_index+3, i); + + indices.emplace_back(stl_triangle_vertex_indices(middle_vertices_idxs[0], indices[i][1], middle_vertices_idxs[1])); + new_neighbors[indices.size()-1] = Vec3i(new_neighbors_per_edge[0].second, new_neighbors_per_edge[1].first, last_index+1); + + indices.emplace_back(stl_triangle_vertex_indices(middle_vertices_idxs[2], middle_vertices_idxs[1], indices[i][2])); + new_neighbors[indices.size()-1] = Vec3i(last_index+1, new_neighbors_per_edge[1].second, new_neighbors_per_edge[2].first); + + indices[i][1] = middle_vertices_idxs[0]; + indices[i][2] = middle_vertices_idxs[2]; + new_neighbors[i] = Vec3i(new_neighbors_per_edge[0].first, last_index+1, new_neighbors_per_edge[2].second); + } + neighbors = std::move(new_neighbors); } - return mesh; } @@ -1089,7 +1154,7 @@ indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorC else for (int j = 0; j < sectorCount; ++j) { // from 0 to 2pi - double sectorAngle = sectorStep * j; + double sectorAngle = sectorStep * j + 0.25 * M_PI; vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast()); } } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index c9807fff0..6473f7783 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -188,7 +188,7 @@ private: // Used for chaining slice lines into polygons. std::vector its_face_edge_ids(const indexed_triangle_set &its); std::vector its_face_edge_ids(const indexed_triangle_set &its, std::function throw_on_cancel_callback); -std::vector its_face_edge_ids(const indexed_triangle_set &its, const std::vector &face_mask); +std::vector its_face_edge_ids(const indexed_triangle_set &its, const std::vector &face_mask); // Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices. std::vector its_face_edge_ids(const indexed_triangle_set &its, std::vector &face_neighbors, bool assign_unbound_edges = false, int *num_edges = nullptr); diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 63802961b..85e170bd0 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -137,18 +137,41 @@ enum class FacetSliceType { Cutting = 2 }; -// Return true, if the facet has been sliced and line_out has been filled. -static FacetSliceType slice_facet( - // Z height of the slice in XY plane. Scaled or unscaled (same as vertices[].z()). - float slice_z, - // 3 vertices of the triangle, XY scaled. Z scaled or unscaled (same as slice_z). - const stl_vertex *vertices, - const stl_triangle_vertex_indices &indices, - const Vec3i &edge_ids, - const int idx_vertex_lowest, - const bool horizontal, - IntersectionLine &line_out) +// Convert an int32_t scaled coordinate into an unscaled 3D floating point coordinate (mesh vertex). +template +inline Vec3f contour_point_to_v3f(const Point &pt, const T z) { + return to_3d( + // unscale using doubles for higher accuracy + unscaled(pt). + // then convert to floats + cast(), + float(z)); +} + +// Convert 2D projection of an int32_t scaled coordinate into an unscaled 3D floating point coordinate (mesh vertex). +template +inline Point v3f_scaled_to_contour_point(const Eigen::MatrixBase &v) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) >= 2, "v3f_scaled_to_contour_point(): Not a 2D or 3D vector."); + using T = typename Derived::Scalar; + return { coord_t(std::floor(v.x() + T(0.5))), coord_t(std::floor(v.y() + T(0.5))) }; +} + +// Return true, if the facet has been sliced and line_out has been filled. +template +inline FacetSliceType slice_facet( + // Z height of the slice in XY plane. Scaled or unscaled (same as vertices[].z()). + T slice_z, + // 3 vertices of the triangle, XY scaled. Z scaled or unscaled (same as slice_z). + const Eigen::Matrix *vertices, + const stl_triangle_vertex_indices &indices, + const Vec3i &edge_ids, + const int idx_vertex_lowest, + const bool horizontal, + IntersectionLine &line_out) +{ + using Vector = Eigen::Matrix; IntersectionPoint points[3]; size_t num_points = 0; auto point_on_layer = size_t(-1); @@ -158,7 +181,7 @@ static FacetSliceType slice_facet( // (external on the right of the line) for (int j = 0; j < 3; ++ j) { // loop through facet edges int edge_id; - const stl_vertex *a, *b; + const Vector *a, *b; int a_id, b_id; { int k = (idx_vertex_lowest + j) % 3; @@ -174,16 +197,16 @@ static FacetSliceType slice_facet( if (a->z() == slice_z && b->z() == slice_z) { // Edge is horizontal and belongs to the current layer. // The following rotation of the three vertices may not be efficient, but this branch happens rarely. - const stl_vertex &v0 = vertices[0]; - const stl_vertex &v1 = vertices[1]; - const stl_vertex &v2 = vertices[2]; + const Vector &v0 = vertices[0]; + const Vector &v1 = vertices[1]; + const Vector &v2 = vertices[2]; // We may ignore this edge for slicing purposes, but we may still use it for object cutting. FacetSliceType result = FacetSliceType::Slicing; if (horizontal) { // All three vertices are aligned with slice_z. line_out.edge_type = IntersectionLine::FacetEdgeType::Horizontal; result = FacetSliceType::Cutting; - double normal = (v1.x() - v0.x()) * (v2.y() - v1.y()) - (v1.y() - v0.y()) * (v2.x() - v1.x()); + double normal = cross2((to_2d(v1) - to_2d(v0)).template cast(), (to_2d(v2) - to_2d(v1)).template cast()); if (normal < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. std::swap(a, b); @@ -205,10 +228,8 @@ static FacetSliceType slice_facet( } else line_out.edge_type = IntersectionLine::FacetEdgeType::Bottom; } - line_out.a.x() = a->x(); - line_out.a.y() = a->y(); - line_out.b.x() = b->x(); - line_out.b.y() = b->y(); + line_out.a = v3f_scaled_to_contour_point(*a); + line_out.b = v3f_scaled_to_contour_point(*b); line_out.a_id = a_id; line_out.b_id = b_id; assert(line_out.a != line_out.b); @@ -220,8 +241,7 @@ static FacetSliceType slice_facet( if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { point_on_layer = num_points; IntersectionPoint &point = points[num_points ++]; - point.x() = a->x(); - point.y() = a->y(); + static_cast(point) = v3f_scaled_to_contour_point(*a); point.point_id = a_id; } } else if (b->z() == slice_z) { @@ -229,8 +249,7 @@ static FacetSliceType slice_facet( if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { point_on_layer = num_points; IntersectionPoint &point = points[num_points ++]; - point.x() = b->x(); - point.y() = b->y(); + static_cast(point) = v3f_scaled_to_contour_point(*b); point.point_id = b_id; } } else if ((a->z() < slice_z && b->z() > slice_z) || (b->z() < slice_z && a->z() > slice_z)) { @@ -270,16 +289,10 @@ static FacetSliceType slice_facet( } #else // Just clamp the intersection point to source triangle edge. - if (t <= 0.) { - point.x() = a->x(); - point.y() = a->y(); - } else if (t >= 1.) { - point.x() = b->x(); - point.y() = b->y(); - } else { - point.x() = coord_t(floor(double(a->x()) + (double(b->x()) - double(a->x())) * t + 0.5)); - point.y() = coord_t(floor(double(a->y()) + (double(b->y()) - double(a->y())) * t + 0.5)); - } + static_cast(point) = + t <= 0. ? v3f_scaled_to_contour_point(*a) : + t >= 1. ? v3f_scaled_to_contour_point(*b) : + v3f_scaled_to_contour_point(a->template head<2>().template cast() * (1. - t) + b->template head<2>().template cast() * t + Vec2d(0.5, 0.5)); point.edge_id = edge_id; ++ num_points; #endif @@ -1805,7 +1818,7 @@ Polygons slice_mesh( { bool trafo_identity = is_identity(params.trafo); Transform3f tf; - std::vector face_mask(mesh.indices.size(), false); + std::vector face_mask(mesh.indices.size(), 0); { // 1) Mark vertices as below or above the slicing plane. @@ -2182,28 +2195,44 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); FacetSliceType slice_type = FacetSliceType::NoSlice; if (z > min_z - EPSILON && z < max_z + EPSILON) { - Vec3f vertices_scaled[3]; + Vec3d vertices_scaled[3]; for (int i = 0; i < 3; ++ i) { const Vec3f &src = vertices[i]; - Vec3f &dst = vertices_scaled[i]; - dst.x() = scale_(src.x()); - dst.y() = scale_(src.y()); + Vec3d &dst = vertices_scaled[i]; + dst.x() = scaled(src.x()); + dst.y() = scaled(src.y()); dst.z() = src.z(); } - slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edge_ids[facet_idx], idx_vertex_lowest, min_z == max_z, line); + slice_type = slice_facet(double(z), vertices_scaled, mesh.indices[facet_idx], facets_edge_ids[facet_idx], idx_vertex_lowest, min_z == max_z, line); } if (slice_type != FacetSliceType::NoSlice) { // Save intersection lines for generating correct triangulations. if (line.edge_type == IntersectionLine::FacetEdgeType::Top) { - lower_lines.emplace_back(line); lower_slice_vertices.emplace_back(line.a_id); lower_slice_vertices.emplace_back(line.b_id); + if (lower) { + lower_lines.emplace_back(line); + if (triangulate_caps) { + // Snap these vertices to coord_t grid, so that they will be matched with the vertices produced + // by triangulating opening on the cut. + lower->vertices[line.a_id] = contour_point_to_v3f(line.a, z); + lower->vertices[line.b_id] = contour_point_to_v3f(line.b, z); + } + } } else if (line.edge_type == IntersectionLine::FacetEdgeType::Bottom) { - upper_lines.emplace_back(line); upper_slice_vertices.emplace_back(line.a_id); upper_slice_vertices.emplace_back(line.b_id); - } else if (line.edge_type == IntersectionLine::FacetEdgeType::General) { + if (upper) { + upper_lines.emplace_back(line); + if (triangulate_caps) { + // Snap these vertices to coord_t grid, so that they will be matched with the vertices produced + // by triangulating opening on the cut. + upper->vertices[line.a_id] = contour_point_to_v3f(line.a, z); + upper->vertices[line.b_id] = contour_point_to_v3f(line.b, z); + } + } + } else if (line.edge_type == IntersectionLine::FacetEdgeType::General && triangulate_caps) { lower_lines.emplace_back(line); upper_lines.emplace_back(line); } @@ -2235,11 +2264,11 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u assert(facets_edge_ids[facet_idx](iv) == line.edge_a_id || facets_edge_ids[facet_idx](iv) == line.edge_b_id); if (facets_edge_ids[facet_idx](iv) == line.edge_a_id) { // Unscale to doubles first, then to floats to reach the same accuracy as triangulate_expolygons_2d(). - v0v1 = to_3d(unscaled(line.a).cast().eval(), z); - v2v0 = to_3d(unscaled(line.b).cast().eval(), z); + v0v1 = contour_point_to_v3f(line.a, z); + v2v0 = contour_point_to_v3f(line.b, z); } else { - v0v1 = to_3d(unscaled(line.b).cast().eval(), z); - v2v0 = to_3d(unscaled(line.a).cast().eval(), z); + v0v1 = contour_point_to_v3f(line.b, z); + v2v0 = contour_point_to_v3f(line.a, z); } const stl_vertex &v0 = vertices[iv]; const int iv0 = facet[iv]; diff --git a/src/libslic3r/TriangleSelectorWrapper.cpp b/src/libslic3r/TriangleSelectorWrapper.cpp index 3a5f44d84..daf42300c 100644 --- a/src/libslic3r/TriangleSelectorWrapper.cpp +++ b/src/libslic3r/TriangleSelectorWrapper.cpp @@ -6,7 +6,6 @@ namespace Slic3r { TriangleSelectorWrapper::TriangleSelectorWrapper(const TriangleMesh &mesh, const Transform3d& mesh_transform) : mesh(mesh), mesh_transform(mesh_transform), selector(mesh), triangles_tree( AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(mesh.its.vertices, mesh.its.indices)) { - } void TriangleSelectorWrapper::enforce_spot(const Vec3f &point, const Vec3f &origin, float radius) { diff --git a/src/libslic3r/Triangulation.cpp b/src/libslic3r/Triangulation.cpp index a355d725d..f5978d3e9 100644 --- a/src/libslic3r/Triangulation.cpp +++ b/src/libslic3r/Triangulation.cpp @@ -241,7 +241,7 @@ Triangulation::Indices Triangulation::triangulate(const ExPolygon &expolygon){ Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons){ Points pts = to_points(expolygons); - Points d_pts = collect_duplications(pts); + Points d_pts = collect_duplicates(pts); if (d_pts.empty()) return triangulate(expolygons, pts); Changes changes = create_changes(pts, d_pts); @@ -262,7 +262,7 @@ Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, { assert(count_points(expolygons) == points.size()); // when contain duplicit coordinate in points will not work properly - assert(collect_duplications(points).empty()); + assert(collect_duplicates(points).empty()); HalfEdges edges; edges.reserve(points.size()); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 495bcbc9b..00de10fc4 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -267,8 +267,6 @@ class ScopeGuard { public: typedef std::function Closure; -private: -// bool committed; Closure closure; public: diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 9b8a82348..9e101f32a 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -113,7 +113,6 @@ inline void append(std::vector& dest, const std::vector& src) dest = src; // copy else dest.insert(dest.end(), src.begin(), src.end()); - // NOTE: insert reserve space when needed } template diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 2cca334d9..2615937eb 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -956,7 +956,8 @@ std::string xml_escape(std::string text, bool is_marked/* = false*/) } // Definition of escape symbols https://www.w3.org/TR/REC-xml/#AVNormalize - +// During the read of xml attribute normalization of white spaces is applied +// Soo for not lose white space character it is escaped before store std::string xml_escape_double_quotes_attribute_value(std::string text) { std::string::size_type pos = 0; diff --git a/src/miniz/miniz.c b/src/miniz/miniz.c index 0d6fc7d9b..09794bea0 100644 --- a/src/miniz/miniz.c +++ b/src/miniz/miniz.c @@ -7922,6 +7922,40 @@ mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, cha return n + 1; } +mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive* pZip, mz_uint file_index, char* buffer, mz_uint extra_buf_size) +{ + if (extra_buf_size == 0) + return 0; + mz_uint nf; + mz_uint ne; + const mz_uint8* p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + if (extra_buf_size) + buffer[0] = '\0'; + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return 0; + } + nf = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ne = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + int copy = 0; + char const* p_nf = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + nf; + char const* e = p_nf + ne + 1; + while (p_nf + 4 < e) { + mz_uint16 len = ((mz_uint16)p_nf[2]) | ((mz_uint16)p_nf[3] << 8); + if (p_nf[0] == '\x75' && p_nf[1] == '\x70' && len >= 5 && p_nf + 4 + len < e && p_nf[4] == '\x01') { + mz_uint length = MZ_MIN(len - 5, extra_buf_size - 1); + memcpy(buffer, p_nf + 9, length); + return length; + } + else { + p_nf += 4 + len; + } + } + return 0; +} + mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) { return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); diff --git a/src/miniz/miniz.h b/src/miniz/miniz.h index 8fe0461ad..62d579f9e 100644 --- a/src/miniz/miniz.h +++ b/src/miniz/miniz.h @@ -1166,6 +1166,9 @@ mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, cha int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); +/* Retrieves the filename of an archive file entry from EXTRA ID. */ +mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive * pZip, mz_uint file_index, char* buffer, mz_uint extra_buf_size); + /* Returns detailed information about an archive file entry. */ mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); diff --git a/src/platform/osx/Info.plist.in b/src/platform/osx/Info.plist.in index d3c913604..60f092016 100644 --- a/src/platform/osx/Info.plist.in +++ b/src/platform/osx/Info.plist.in @@ -110,6 +110,17 @@ Alternate + CFBundleURLTypes + + + CFBundleURLName + PrusaSlicer Downloads + CFBundleURLSchemes + + prusaslicer + + + LSMinimumSystemVersion 10.12 NSPrincipalClass diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index ac103615a..453d7eeb5 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -234,6 +234,12 @@ set(SLIC3R_GUI_SOURCES GUI/DesktopIntegrationDialog.hpp GUI/HintNotification.cpp GUI/HintNotification.hpp + GUI/FileArchiveDialog.cpp + GUI/FileArchiveDialog.hpp + GUI/Downloader.cpp + GUI/Downloader.hpp + GUI/DownloaderFileGet.cpp + GUI/DownloaderFileGet.hpp Utils/AppUpdater.cpp Utils/AppUpdater.hpp Utils/Http.cpp diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index e7c2cbe54..a4de0a7a6 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -39,8 +39,8 @@ void Bed_2D::repaint(const std::vector& shape) #else auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour #endif - dc.SetPen(*new wxPen(color, 1, wxPENSTYLE_SOLID)); - dc.SetBrush(*new wxBrush(color, wxBRUSHSTYLE_SOLID)); + dc.SetPen(wxPen(color, 1, wxPENSTYLE_SOLID)); + dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID)); auto rect = GetUpdateRegion().GetBox(); dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight()); } @@ -75,11 +75,15 @@ void Bed_2D::repaint(const std::vector& shape) // draw bed fill dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID)); + wxPointList pt_list; - for (auto pt : shape) - { - Point pt_pix = to_pixels(pt, ch); - pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); + const size_t pt_cnt = shape.size(); + std::vector points; + points.reserve(pt_cnt); + for (const auto& shape_pt : shape) { + Point pt_pix = to_pixels(shape_pt, ch); + points.emplace_back(wxPoint(pt_pix(0), pt_pix(1))); + pt_list.Append(&points.back()); } dc.DrawPolygon(&pt_list, 0, 0); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 737f3d498..185ebb743 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -127,7 +127,9 @@ void GLVolume::SinkingContours::update() init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } } - m_model.init_from(std::move(init_data)); + + if (init_data.vertices_count() > 0) + m_model.init_from(std::move(init_data)); } void GLVolume::NonManifoldEdges::render() @@ -437,9 +439,9 @@ std::vector GLVolumeCollection::load_object( int GLVolumeCollection::load_object_volume( const ModelObject* model_object, - int obj_idx, - int volume_idx, - int instance_idx) + int obj_idx, + int volume_idx, + int instance_idx) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; const int extruder_id = model_volume->extruder_id(); @@ -448,12 +450,16 @@ int GLVolumeCollection::load_object_volume( this->volumes.emplace_back(new GLVolume()); GLVolume& v = *this->volumes.back(); v.set_color(color_from_model_volume(*model_volume)); + // apply printable value from the instance + v.printable = instance->printable; #if ENABLE_SMOOTH_NORMALS v.model.init_from(*mesh, true); - v.mesh_raycaster = std::make_unique(mesh); + if (m_use_raycasters) + v.mesh_raycaster = std::make_unique(mesh); #else v.model.init_from(*mesh); - v.mesh_raycaster = std::make_unique(mesh); + if (m_use_raycasters) + v.mesh_raycaster = std::make_unique(mesh); #endif // ENABLE_SMOOTH_NORMALS v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index fb97182a6..4c19927ef 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -397,6 +397,7 @@ private: Slope m_slope; bool m_show_sinking_contours{ false }; bool m_show_non_manifold_edges{ true }; + bool m_use_raycasters{ true }; public: GLVolumePtrs volumes; @@ -435,6 +436,7 @@ public: bool empty() const { return volumes.empty(); } void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); } + void set_use_raycasters(bool value) { m_use_raycasters = value; } void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 275fcdc98..678761924 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -251,6 +251,9 @@ void BackgroundSlicingProcess::thread_proc() (m_state == STATE_CANCELED) ? SlicingProcessCompletedEvent::Cancelled : exception ? SlicingProcessCompletedEvent::Error : SlicingProcessCompletedEvent::Finished, exception); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); + // Cancelled by the user, not internally, thus cleanup() was not called yet. + // Otherwise cleanup() is called from Print::apply() + m_print->cleanup(); } m_print->restart(); lck.unlock(); diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 27caedfc3..aadf605a1 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -123,8 +123,8 @@ void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup) break; default: // rectangle, convex, concave... - optgroup->set_value("rect_size" , new ConfigOptionPoints{ to_2d(m_build_volume.bounding_volume().size()) }); - optgroup->set_value("rect_origin" , new ConfigOptionPoints{ - to_2d(m_build_volume.bounding_volume().min) }); + optgroup->set_value("rect_size" , to_2d(m_build_volume.bounding_volume().size())); + optgroup->set_value("rect_origin" , to_2d(-1 * m_build_volume.bounding_volume().min)); } } @@ -425,8 +425,6 @@ void BedShapePanel::set_shape(const ConfigOptionPoints& points) m_loaded_shape = points.values; update_shape(); - - return; } void BedShapePanel::update_preview() diff --git a/src/slic3r/GUI/ButtonsDescription.cpp b/src/slic3r/GUI/ButtonsDescription.cpp index 37daffd9d..8460a9411 100644 --- a/src/slic3r/GUI/ButtonsDescription.cpp +++ b/src/slic3r/GUI/ButtonsDescription.cpp @@ -1,5 +1,6 @@ #include "ButtonsDescription.hpp" #include +#include #include #include #include @@ -7,11 +8,82 @@ #include "GUI.hpp" #include "GUI_App.hpp" #include "I18N.hpp" +#include "OptionsGroup.hpp" #include "wxExtensions.hpp" +#include "BitmapCache.hpp" namespace Slic3r { namespace GUI { +//static ModePaletteComboBox::PalettesMap MODE_PALETTES = +static std::vector>> MODE_PALETTES = +{ + { L("Palette 1 (default)"), { "#00B000", "#FFDC00", "#E70000" } }, + { L("Palette 2"), { "#FC766A", "#B0B8B4", "#184A45" } }, + { L("Palette 3"), { "#567572", "#964F4C", "#696667" } }, + { L("Palette 4"), { "#DA291C", "#56A8CB", "#53A567" } }, + { L("Palette 5"), { "#F65058", "#FBDE44", "#28334A" } }, + { L("Palette 6"), { "#FF3EA5", "#EDFF00", "#00A4CC" } }, + { L("Palette 7"), { "#E95C20", "#006747", "#4F2C1D" } }, + { L("Palette 8"), { "#D9514E", "#2A2B2D", "#2DA8D8" } } +}; + +ModePaletteComboBox::ModePaletteComboBox(wxWindow* parent) : + BitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY) +{ + for (const auto& palette : MODE_PALETTES) + Append(_(palette.first), *get_bmp(palette.second)); +} + +void ModePaletteComboBox::UpdateSelection(const std::vector &palette_in) +{ + for (size_t idx = 0; idx < MODE_PALETTES.size(); ++idx ) { + const auto& palette = MODE_PALETTES[idx].second; + + bool is_selected = true; + for (size_t mode = 0; mode < palette_in.size(); mode++) + if (wxColour(palette[mode]) != palette_in[mode]) { + is_selected = false; + break; + } + if (is_selected) { + Select(int(idx)); + return; + } + } + + Select(-1); +} + +BitmapCache& ModePaletteComboBox::bitmap_cache() +{ + static BitmapCache bmps; + return bmps; +} + +wxBitmapBundle * ModePaletteComboBox::get_bmp(const std::vector &palette) +{ + std::string bitmap_key; + for (const auto& color : palette) + bitmap_key += color + "+"; + + const int icon_height = wxOSX ? 10 : 12; + + wxBitmapBundle* bmp_bndl = bitmap_cache().find_bndl(bitmap_key); + if (bmp_bndl == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + for (const auto& color : palette) { + bmps.emplace_back(get_bmp_bundle("mode", icon_height, color)); + bmps.emplace_back(get_empty_bmp_bundle(wxOSX ? 5 : 6, icon_height)); + } + bmp_bndl = bitmap_cache().insert_bndl(bitmap_key, bmps); + } + + return bmp_bndl; +} + + void ButtonsDescription::FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWindow* parent, wxColourPickerCtrl** sys_colour, wxColourPickerCtrl** mod_colour) { wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(3, 5, 5); @@ -48,13 +120,81 @@ void ButtonsDescription::FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWi grid_sizer->Add(*color_picker, 0, wxALIGN_CENTRE_VERTICAL); grid_sizer->Add(btn, 0, wxALIGN_CENTRE_VERTICAL); - grid_sizer->Add(sys_label, 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND); + grid_sizer->Add(sys_label, 0, wxALIGN_CENTRE_VERTICAL); }; add_color(sys_colour, wxGetApp().get_label_clr_sys(), wxGetApp().get_label_default_clr_system(), _L("Value is the same as the system value")); add_color(mod_colour, wxGetApp().get_label_clr_modified(),wxGetApp().get_label_default_clr_modified(), _L("Value was changed and is not equal to the system value or the last saved preset")); } +void ButtonsDescription::FillSizerWithModeColorDescriptions( + wxSizer* sizer, wxWindow* parent, + std::vector clr_pickers, + std::vector& mode_palette) +{ + const int margin = em_unit(parent); + + auto palette_cb = new ModePaletteComboBox(parent); + palette_cb->UpdateSelection(mode_palette); + + palette_cb->Bind(wxEVT_COMBOBOX, [clr_pickers, &mode_palette](wxCommandEvent& evt) { + const int selection = evt.GetSelection(); + if (selection < 0) + return; + const auto& palette = MODE_PALETTES[selection]; + for (int mode = 0; mode < 3; mode++) + if (*clr_pickers[mode]) { + wxColour clr = wxColour(palette.second[mode]); + (*clr_pickers[mode])->SetColour(clr); + mode_palette[mode] = clr; + } + }); + + wxBoxSizer* h_sizer = new wxBoxSizer(wxHORIZONTAL); + h_sizer->Add(new wxStaticText(parent, wxID_ANY, _L("Default palette for mode markers") + ": "), 0, wxALIGN_CENTER_VERTICAL); + h_sizer->Add(palette_cb, 1, wxEXPAND); + + sizer->Add(h_sizer, 0, wxEXPAND | wxBOTTOM, margin); + + wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(9, 5, 5); + sizer->Add(grid_sizer, 0, wxEXPAND); + + const std::vector names = { _L("Simple"), _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Expert") }; + + for (size_t mode = 0; mode < names.size(); ++mode) { + wxColour& color = mode_palette[mode]; + + wxColourPickerCtrl** color_picker = clr_pickers[mode]; + *color_picker = new wxColourPickerCtrl(parent, wxID_ANY, color); + wxGetApp().UpdateDarkUI((*color_picker)->GetPickerCtrl(), true); + + (*color_picker)->Bind(wxEVT_COLOURPICKER_CHANGED, [color_picker, &color, palette_cb, &mode_palette](wxCommandEvent&) { + const wxColour new_color = (*color_picker)->GetColour(); + if (new_color != color) { + color = new_color; + palette_cb->UpdateSelection(mode_palette); + } + }); + + wxColour def_color = color; + auto btn = new ScalableButton(parent, wxID_ANY, "undo"); + btn->SetToolTip(_L("Revert color")); + + btn->Bind(wxEVT_BUTTON, [color_picker, &color, def_color, palette_cb, &mode_palette](wxEvent& event) { + color = def_color; + (*color_picker)->SetColour(def_color); + palette_cb->UpdateSelection(mode_palette); + }); + parent->Bind(wxEVT_UPDATE_UI, [color_picker, def_color](wxUpdateUIEvent& evt) { + evt.Enable((*color_picker)->GetColour() != def_color); + }, btn->GetId()); + + grid_sizer->Add(*color_picker, 0, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(btn, 0, wxALIGN_CENTRE_VERTICAL); + grid_sizer->Add(new wxStaticText(parent, wxID_ANY, names[mode]), 0, wxALIGN_CENTRE_VERTICAL | wxRIGHT, 2*margin); + } +} + ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vector &entries) : wxDialog(parent, wxID_ANY, _(L("Buttons And Text Colors Description")), wxDefaultPosition, wxDefaultSize), m_entries(entries) @@ -74,13 +214,20 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vectorAdd(description, -1, wxALIGN_CENTRE_VERTICAL); description = new wxStaticText(this, wxID_ANY, _(entry.explanation)); - grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND); + grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL); } // Text color description wxSizer* sizer = new wxBoxSizer(wxVERTICAL); FillSizerWithTextColorDescriptions(sizer, this, &sys_colour, &mod_colour); - main_sizer->Add(sizer, 0, wxEXPAND | wxALL, 20); + main_sizer->Add(sizer, 0, wxEXPAND | wxALL, 20); + + // Mode color markers description + mode_palette = wxGetApp().get_mode_palette(); + + wxSizer* mode_sizer = new wxBoxSizer(wxVERTICAL); + FillSizerWithModeColorDescriptions(mode_sizer, this, { &simple, &advanced, &expert }, mode_palette); + main_sizer->Add(mode_sizer, 0, wxEXPAND | wxALL, 20); auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL); main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); @@ -89,8 +236,10 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vectorBind(wxEVT_BUTTON, [this](wxCommandEvent&) { wxGetApp().set_label_clr_sys(sys_colour->GetColour()); wxGetApp().set_label_clr_modified(mod_colour->GetColour()); + wxGetApp().set_mode_palette(mode_palette); + EndModal(wxID_OK); - }); + }); wxGetApp().UpdateDarkUI(btn); wxGetApp().UpdateDarkUI(static_cast(FindWindowById(wxID_CANCEL, this))); diff --git a/src/slic3r/GUI/ButtonsDescription.hpp b/src/slic3r/GUI/ButtonsDescription.hpp index fbed36c2a..d1315d3d0 100644 --- a/src/slic3r/GUI/ButtonsDescription.hpp +++ b/src/slic3r/GUI/ButtonsDescription.hpp @@ -4,16 +4,48 @@ #include #include +#include + +#include "BitmapComboBox.hpp" + class ScalableBitmap; class wxColourPickerCtrl; namespace Slic3r { namespace GUI { +class BitmapCache; + +// --------------------------------- +// *** PaletteComboBox *** +// --------------------------------- + +// BitmapComboBox used to palets list in GUI Preferences +class ModePaletteComboBox : public BitmapComboBox +{ +public: + ModePaletteComboBox(wxWindow* parent); + ~ModePaletteComboBox() = default; + + void UpdateSelection(const std::vector& palette_in); + +protected: + // Caching bitmaps for the all bitmaps, used in preset comboboxes + static BitmapCache& bitmap_cache(); + wxBitmapBundle* get_bmp( const std::vector& palette); +}; + + class ButtonsDescription : public wxDialog { wxColourPickerCtrl* sys_colour{ nullptr }; wxColourPickerCtrl* mod_colour{ nullptr }; + + wxColourPickerCtrl* simple { nullptr }; + wxColourPickerCtrl* advanced { nullptr }; + wxColourPickerCtrl* expert { nullptr }; + + std::vector mode_palette; public: struct Entry { Entry(ScalableBitmap *bitmap, const std::string &symbol, const std::string &explanation) : bitmap(bitmap), symbol(symbol), explanation(explanation) {} @@ -27,6 +59,9 @@ public: ~ButtonsDescription() {} static void FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWindow* parent, wxColourPickerCtrl** sys_colour, wxColourPickerCtrl** mod_colour); + static void FillSizerWithModeColorDescriptions(wxSizer* sizer, wxWindow* parent, + std::vector clr_pickers, + std::vector& mode_palette); private: std::vector m_entries; diff --git a/src/slic3r/GUI/CameraUtils.cpp b/src/slic3r/GUI/CameraUtils.cpp index c99616b34..99d022e4b 100644 --- a/src/slic3r/GUI/CameraUtils.cpp +++ b/src/slic3r/GUI/CameraUtils.cpp @@ -1,5 +1,6 @@ #include "CameraUtils.hpp" #include // projecting points +#include #include "slic3r/GUI/3DScene.hpp" // GLVolume #include "libslic3r/Geometry/ConvexHull.hpp" @@ -79,37 +80,44 @@ Slic3r::Polygon CameraUtils::create_hull2d(const Camera & camera, return Geometry::convex_hull(vertices_2d); } -#include -Vec3d CameraUtils::create_ray(const Camera &camera, const Vec2d &coor) { - if (camera.get_type() == Camera::EType::Ortho) - return camera.get_dir_forward(); - // check that it is known camera no other tha ORTHO or Persepective - assert(camera.get_type() == Camera::EType::Perspective); +void CameraUtils::ray_from_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction) { + switch (camera.get_type()) { + case Camera::EType::Ortho: return ray_from_ortho_screen_pos(camera, position, point, direction); + case Camera::EType::Perspective: return ray_from_persp_screen_pos(camera, position, point, direction); + default: break; + } +} +Vec3d CameraUtils::screen_point(const Camera &camera, const Vec2d &position) +{ + double height = camera.get_viewport().data()[3]; + // Y coordinate has opposit direction + return Vec3d(position.x(), height - position.y(), 0.); +} + +void CameraUtils::ray_from_ortho_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction) +{ + assert(camera.get_type() == Camera::EType::Ortho); Matrix4d modelview = camera.get_view_matrix().matrix(); Matrix4d projection = camera.get_projection_matrix().matrix(); Vec4i viewport(camera.get_viewport().data()); - - Vec3d scene_point(coor.x(), viewport[3] - coor.y(), 0.); - Vec3d unprojected_point; - igl::unproject(scene_point, modelview, projection, viewport, unprojected_point); - - Vec3d p0 = camera.get_position(); - Vec3d dir = unprojected_point - p0; - dir.normalize(); - return dir; + igl::unproject(screen_point(camera,position), modelview, projection, viewport, point); + direction = camera.get_dir_forward(); +} +void CameraUtils::ray_from_persp_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction) +{ + assert(camera.get_type() == Camera::EType::Perspective); + Matrix4d modelview = camera.get_view_matrix().matrix(); + Matrix4d projection = camera.get_projection_matrix().matrix(); + Vec4i viewport(camera.get_viewport().data()); + igl::unproject(screen_point(camera, position), modelview, projection, viewport, point); + direction = point - camera.get_position(); } Vec2d CameraUtils::get_z0_position(const Camera &camera, const Vec2d & coor) { - Vec3d dir = CameraUtils::create_ray(camera, coor); - Vec3d p0 = camera.get_position(); - if (camera.get_type() == Camera::EType::Ortho) { - Matrix4d modelview = camera.get_view_matrix().matrix(); - Matrix4d projection = camera.get_projection_matrix().matrix(); - Vec4i viewport(camera.get_viewport().data()); - igl::unproject(Vec3d(coor.x(), viewport[3] - coor.y(), 0.), modelview, projection, viewport, p0); - } + Vec3d p0, dir; + ray_from_screen_pos(camera, coor, p0, dir); // is approx zero if ((fabs(dir.z()) - 1e-4) < 0) @@ -117,7 +125,7 @@ Vec2d CameraUtils::get_z0_position(const Camera &camera, const Vec2d & coor) std::numeric_limits::max()); // find position of ray cross plane(z = 0) - double t = p0.z() / dir.z(); + double t = p0.z() / dir.z(); Vec3d p = p0 - t * dir; return Vec2d(p.x(), p.y()); } diff --git a/src/slic3r/GUI/CameraUtils.hpp b/src/slic3r/GUI/CameraUtils.hpp index 7d1b43d64..c3e938ec4 100644 --- a/src/slic3r/GUI/CameraUtils.hpp +++ b/src/slic3r/GUI/CameraUtils.hpp @@ -36,12 +36,15 @@ public: static Polygon create_hull2d(const Camera &camera, const GLVolume &volume); /// - /// Unproject screen coordinate to scene direction start from camera position + /// Create ray(point and direction) for screen coordinate /// - /// Projection params - /// Coordinate on screen - /// Scene direction - static Vec3d create_ray(const Camera &camera, const Vec2d &coor); + /// Definition of camera + /// Position on screen(aka mouse position) + /// OUT start of ray + /// OUT direction of ray + static void ray_from_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction); + static void ray_from_ortho_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction); + static void ray_from_persp_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction); /// /// Unproject mouse coordinate to get position in space where z coor is zero @@ -52,6 +55,14 @@ public: /// Position on platter under mouse static Vec2d get_z0_position(const Camera &camera, const Vec2d &coor); + /// + /// Create 3d screen point from 2d position + /// + /// Define camera viewport + /// Position on screen(aka mouse position) + /// Point represented screen coor in 3d + static Vec3d screen_point(const Camera &camera, const Vec2d &position); + }; } // Slic3r::GUI diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 876228bd9..82b49e1ff 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -7,6 +7,7 @@ #include "libslic3r/PresetBundle.hpp" #include "MsgDialog.hpp" +#include #include namespace Slic3r { @@ -220,9 +221,14 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_perimeters = config->opt_int("perimeters") > 0; for (auto el : { "extra_perimeters","extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", "seam_position","staggered_inner_seams", "external_perimeters_first", "external_perimeter_extrusion_width", - "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" }) + "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "enable_dynamic_overhang_speeds", "overhang_overlap_levels", "dynamic_overhang_speeds" }) toggle_field(el, have_perimeters); + for (size_t i = 0; i < 4; i++) { + toggle_field("overhang_overlap_levels#" + std::to_string(i), config->opt_bool("enable_dynamic_overhang_speeds")); + toggle_field("dynamic_overhang_speeds#" + std::to_string(i), config->opt_bool("enable_dynamic_overhang_speeds")); + } + bool have_infill = config->option("fill_density")->value > 0; // infill_extruder uses the same logic as in Print::extruders() for (auto el : { "fill_pattern", "infill_every_layers", "infill_only_where_needed", @@ -315,6 +321,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); + toggle_field("avoid_crossing_curled_overhangs", !config->opt_bool("avoid_crossing_perimeters")); + toggle_field("avoid_crossing_perimeters", !config->opt_bool("avoid_crossing_curled_overhangs")); + bool have_avoid_crossing_perimeters = config->opt_bool("avoid_crossing_perimeters"); toggle_field("avoid_crossing_perimeters_max_detour", have_avoid_crossing_perimeters); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 490e8e54a..ef6f53bf0 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,12 @@ #include #include +#ifdef WIN32 +#include +#include +#include +#endif // WIN32 + #ifdef _MSW_DARK_MODE #include #endif // _MSW_DARK_MODE @@ -48,6 +55,7 @@ #include "format.hpp" #include "MsgDialog.hpp" #include "UnsavedChangesDialog.hpp" +#include "slic3r/Utils/AppUpdater.hpp" #if defined(__linux__) && defined(__WXGTK3__) #define wxLinux_gtk3 true @@ -1176,31 +1184,20 @@ PageCustom::PageCustom(ConfigWizard *parent) : ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer")) { cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile")); - tc_profile_name = new wxTextCtrl(this, wxID_ANY, default_profile_name); auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:")); - wxGetApp().UpdateDarkUI(tc_profile_name); + wxBoxSizer* profile_name_sizer = new wxBoxSizer(wxVERTICAL); + profile_name_editor = new SavePresetDialog::Item{ this, profile_name_sizer, default_profile_name }; + profile_name_editor->Enable(false); - tc_profile_name->Enable(false); - tc_profile_name->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &evt) { - if (tc_profile_name->GetValue().IsEmpty()) { - if (profile_name_prev.IsEmpty()) { tc_profile_name->SetValue(default_profile_name); } - else { tc_profile_name->SetValue(profile_name_prev); } - } else { - profile_name_prev = tc_profile_name->GetValue(); - } - evt.Skip(); - }); - - cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { - tc_profile_name->Enable(custom_wanted()); + cb_custom->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { + profile_name_editor->Enable(custom_wanted()); wizard_p()->on_custom_setup(custom_wanted()); - }); append(cb_custom); append(label); - append(tc_profile_name); + append(profile_name_sizer); } PageUpdate::PageUpdate(ConfigWizard *parent) @@ -1240,6 +1237,227 @@ PageUpdate::PageUpdate(ConfigWizard *parent) box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); } +namespace DownloaderUtils +{ +#ifdef _WIN32 + + wxString get_downloads_path() + { + wxString ret; + PWSTR path = NULL; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &path); + if (SUCCEEDED(hr)) { + ret = wxString(path); + } + CoTaskMemFree(path); + return ret; + } + +#elif __APPLE__ + wxString get_downloads_path() + { + // call objective-c implementation + return wxString::FromUTF8(get_downloads_path_mac()); + } +#else + wxString get_downloads_path() + { + wxString command = "xdg-user-dir DOWNLOAD"; + wxArrayString output; + GUI::desktop_execute_get_result(command, output); + if (output.GetCount() > 0) { + return output[0]; + } + return wxString(); + } + +#endif + +Worker::Worker(wxWindow* parent) +: wxBoxSizer(wxHORIZONTAL) +, m_parent(parent) +{ + m_input_path = new wxTextCtrl(m_parent, wxID_ANY); + set_path_name(get_app_config()->get("url_downloader_dest")); + + auto* path_label = new wxStaticText(m_parent, wxID_ANY, _L("Download path") + ":"); + + this->Add(path_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + this->Add(m_input_path, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); + + auto* button_path = new wxButton(m_parent, wxID_ANY, _L("Browse")); + this->Add(button_path, 0, wxEXPAND | wxTOP | wxLEFT, 5); + button_path->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { + boost::filesystem::path chosen_dest(boost::nowide::narrow(m_input_path->GetValue())); + + wxDirDialog dialog(m_parent, L("Choose folder:"), chosen_dest.string() ); + if (dialog.ShowModal() == wxID_OK) + this->m_input_path->SetValue(dialog.GetPath()); + }); + + for (wxSizerItem* item : this->GetChildren()) + if (item->IsWindow()) { + wxWindow* win = item->GetWindow(); + wxGetApp().UpdateDarkUI(win); + } +} + +void Worker::set_path_name(wxString path) +{ + if (path.empty()) + path = boost::nowide::widen(get_app_config()->get("url_downloader_dest")); + + if (path.empty()) { + // What should be default path? Each system has Downloads folder, that could be good one. + // Other would be program location folder - not so good: access rights, apple bin is inside bundle... + // default_path = boost::dll::program_location().parent_path().string(); + path = get_downloads_path(); + } + + m_input_path->SetValue(path); +} + +void Worker::set_path_name(const std::string& name) +{ + if (!m_input_path) + return; + + set_path_name(boost::nowide::widen(name)); +} + +} // DownLoader + +PageDownloader::PageDownloader(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Downloads from URL"), _L("Downloads")) +{ + const AppConfig* app_config = wxGetApp().app_config; + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + append_spacer(VERTICAL_SPACING); + + auto* box_allow_downloads = new wxCheckBox(this, wxID_ANY, _L("Allow build-in downloader")); + // TODO: Do we want it like this? The downloader is allowed for very first time the wizard is run. + bool box_allow_value = (app_config->has("downloader_url_registered") ? app_config->get("downloader_url_registered") == "1" : true); + box_allow_downloads->SetValue(box_allow_value); + append(box_allow_downloads); + + append_text(wxString::Format(_L( + "If enabled, %s registers to start on custom URL on www.printables.com." + " You will be able to use button with %s logo to open models in this %s." + " The model will be downloaded into folder you choose bellow." + ), SLIC3R_APP_NAME, SLIC3R_APP_NAME, SLIC3R_APP_NAME)); + +#ifdef __linux__ + append_text(wxString::Format(_L( + "On Linux systems the process of registration also creates desktop integration files for this version of application." + ))); +#endif + + box_allow_downloads->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->downloader->allow(event.IsChecked()); }); + + downloader = new DownloaderUtils::Worker(this); + append(downloader); + downloader->allow(box_allow_value); +} + +bool PageDownloader::on_finish_downloader() const +{ + return downloader->on_finish(); +} + +bool DownloaderUtils::Worker::perform_register() +{ + //boost::filesystem::path chosen_dest/*(path_text_ctrl->GetValue());*/(boost::nowide::narrow(path_text_ctrl->GetValue())); + boost::filesystem::path chosen_dest (GUI::format(path_name())); + boost::system::error_code ec; + if (chosen_dest.empty() || !boost::filesystem::is_directory(chosen_dest, ec) || ec) { + std::string err_msg = GUI::format("%1%\n\n%2%",_L("Chosen directory for downloads does not Exists.") ,chosen_dest.string()); + BOOST_LOG_TRIVIAL(error) << err_msg; + show_error(m_parent, err_msg); + return false; + } + BOOST_LOG_TRIVIAL(info) << "Downloader registration: Directory for downloads: " << chosen_dest.string(); + wxGetApp().app_config->set("url_downloader_dest", chosen_dest.string()); +#ifdef _WIN32 + // Registry key creation for "prusaslicer://" URL + + boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location())); + // the path to binary needs to be correctly saved in string with respect to localized characters + wxString wbinary = wxString::FromUTF8(binary_path.string()); + std::string binary_string = (boost::format("%1%") % wbinary).str(); + BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << binary_string; + + //std::string key_string = "\"" + binary_string + "\" \"-u\" \"%1\""; + //std::string key_string = "\"" + binary_string + "\" \"%1\""; + std::string key_string = "\"" + binary_string + "\" \"--single-instance\" \"%1\""; + + wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\prusaslicer"); + wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); + if (!key_first.Exists()) { + key_first.Create(false); + } + key_first.SetValue("URL Protocol", ""); + + if (!key_full.Exists()) { + key_full.Create(false); + } + //key_full = "\"C:\\Program Files\\Prusa3D\\PrusaSlicer\\prusa-slicer-console.exe\" \"%1\""; + key_full = key_string; +#elif __APPLE__ + // Apple registers for custom url in info.plist thus it has to be already registered since build. + // The url will always trigger opening of prusaslicer and we have to check that user has allowed it. (GUI_App::MacOpenURL is the triggered method) +#else + // the performation should be called later during desktop integration + perform_registration_linux = true; +#endif + return true; +} + +void DownloaderUtils::Worker::deregister() +{ +#ifdef _WIN32 + std::string key_string = ""; + wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\prusaslicer\\shell\\open\\command"); + if (!key_full.Exists()) { + return; + } + key_full = key_string; +#elif __APPLE__ + // TODO +#else + BOOST_LOG_TRIVIAL(debug) << "DesktopIntegrationDialog::undo_downloader_registration"; + DesktopIntegrationDialog::undo_downloader_registration(); + perform_registration_linux = false; +#endif +} + +bool DownloaderUtils::Worker::on_finish() { + AppConfig* app_config = wxGetApp().app_config; + bool ac_value = app_config->get("downloader_url_registered") == "1"; + BOOST_LOG_TRIVIAL(debug) << "PageDownloader::on_finish_downloader ac_value " << ac_value << " downloader_checked " << downloader_checked; + if (ac_value && downloader_checked) { + // already registered but we need to do it again + if (!perform_register()) + return false; + app_config->set("downloader_url_registered", "1"); + } else if (!ac_value && downloader_checked) { + // register + if (!perform_register()) + return false; + app_config->set("downloader_url_registered", "1"); + } else if (ac_value && !downloader_checked) { + // deregister, downloads are banned now + deregister(); + app_config->set("downloader_url_registered", "0"); + } /*else if (!ac_value && !downloader_checked) { + // not registered and we dont want to do it + // do not deregister as other instance might be registered + } */ + return true; +} + + PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk")) , full_pathnames(false) @@ -1386,6 +1604,45 @@ void PageFirmware::apply_custom_config(DynamicPrintConfig &config) } } +static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) +{ + e.Skip(); + wxString str = ctrl->GetValue(); + + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; + + double val = 0.0; + if (!str.ToDouble(&val)) { + if (val == 0.0) + val = def_value; + ctrl->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + // On Windows, this SetFocus creates an invisible marker. + //ctrl->SetFocus(); + } + else if (was_replaced) + ctrl->SetValue(double_to_string(val)); +} + +class DiamTextCtrl : public wxTextCtrl +{ +public: + DiamTextCtrl(wxWindow* parent) + { +#ifdef _WIN32 + long style = wxBORDER_SIMPLE; +#else + long style = 0; +#endif + Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style); + wxGetApp().UpdateDarkUI(this); + } + ~DiamTextCtrl() {} +}; + PageBedShape::PageBedShape(ConfigWizard *parent) : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1) , shape_panel(new BedShapePanel(this)) @@ -1409,43 +1666,63 @@ void PageBedShape::apply_custom_config(DynamicPrintConfig &config) config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); } -static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) +PageBuildVolume::PageBuildVolume(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Build Volume"), _L("Build Volume"), 1) + , build_volume(new DiamTextCtrl(this)) { - e.Skip(); - wxString str = ctrl->GetValue(); + append_text(_L("Set verctical size of your printer.")); - const char dec_sep = is_decimal_separator_point() ? '.' : ','; - const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; - // Replace the first incorrect separator in decimal number. - bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; + wxString value = "200"; + build_volume->SetValue(value); - double val = 0.0; - if (!str.ToDouble(&val)) { - if (val == 0.0) + build_volume->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { + double def_value = 200.0; + double max_value = 1200.0; + e.Skip(); + wxString str = build_volume->GetValue(); + + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + bool was_replaced = str.Replace(dec_sep_alt, dec_sep, false) != 0; + + double val = 0.0; + if (!str.ToDouble(&val)) { val = def_value; - ctrl->SetValue(double_to_string(val)); - show_error(nullptr, _L("Invalid numeric input.")); - ctrl->SetFocus(); - } - else if (was_replaced) - ctrl->SetValue(double_to_string(val)); + build_volume->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + //build_volume->SetFocus(); + } else if (val < 0.0) { + val = def_value; + build_volume->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + //build_volume->SetFocus(); + } else if (val > max_value) { + val = max_value; + build_volume->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + //build_volume->SetFocus(); + } else if (was_replaced) + build_volume->SetValue(double_to_string(val)); + }, build_volume->GetId()); + + auto* sizer_volume = new wxFlexGridSizer(3, 5, 5); + auto* text_volume = new wxStaticText(this, wxID_ANY, _L("Max print height:")); + auto* unit_volume = new wxStaticText(this, wxID_ANY, _L("mm")); + sizer_volume->AddGrowableCol(0, 1); + sizer_volume->Add(text_volume, 0, wxALIGN_CENTRE_VERTICAL); + sizer_volume->Add(build_volume); + sizer_volume->Add(unit_volume, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_volume); } -class DiamTextCtrl : public wxTextCtrl +void PageBuildVolume::apply_custom_config(DynamicPrintConfig& config) { -public: - DiamTextCtrl(wxWindow* parent) - { -#ifdef _WIN32 - long style = wxBORDER_SIMPLE; -#else - long style = 0; -#endif - Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord), style); - wxGetApp().UpdateDarkUI(this); - } - ~DiamTextCtrl() {} -}; + double val = 0.0; + build_volume->GetValue().ToDouble(&val); + auto* opt_volume = new ConfigOptionFloat(val); + config.set_key_value("max_print_height", opt_volume); +} PageDiameters::PageDiameters(ConfigWizard *parent) : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) @@ -1915,6 +2192,7 @@ void ConfigWizard::priv::load_pages() if (page_custom->custom_wanted()) { index->add_page(page_firmware); index->add_page(page_bed); + index->add_page(page_bvolume); index->add_page(page_diams); index->add_page(page_temps); } @@ -1928,6 +2206,7 @@ void ConfigWizard::priv::load_pages() btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected); index->add_page(page_update); + index->add_page(page_downloader); index->add_page(page_reload_from_disk); #ifdef _WIN32 index->add_page(page_files_association); @@ -2357,6 +2636,11 @@ void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool i bool ConfigWizard::priv::on_bnt_finish() { wxBusyCursor wait; + + if (!page_downloader->on_finish_downloader()) { + index->go_to(page_downloader); + return false; + } /* When Filaments or Sla Materials pages are activated, * materials for this pages are automaticaly updated and presets are reloaded. * @@ -2597,8 +2881,9 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese #ifdef __linux__ // Desktop integration on Linux - if (page_welcome->integrate_desktop()) - DesktopIntegrationDialog::perform_desktop_integration(); + BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->downloader->get_perform_registration_linux(); + if (page_welcome->integrate_desktop() || page_downloader->downloader->get_perform_registration_linux()) + DesktopIntegrationDialog::perform_desktop_integration(page_downloader->downloader->get_perform_registration_linux()); #endif // Decide whether to create snapshot based on run_reason and the reset profile checkbox @@ -2765,7 +3050,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, {preferred_model, preferred_variant, first_added_filament, first_added_sla_material}); - if (!only_sla_mode && page_custom->custom_wanted()) { + if (!only_sla_mode && page_custom->custom_wanted() && page_custom->is_valid_profile_name()) { // if unsaved changes was not cheched till this moment if (!check_unsaved_preset_changes && !wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes)) @@ -2773,6 +3058,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese page_firmware->apply_custom_config(*custom_config); page_bed->apply_custom_config(*custom_config); + page_bvolume->apply_custom_config(*custom_config); page_diams->apply_custom_config(*custom_config); page_temps->apply_custom_config(*custom_config); @@ -2916,6 +3202,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->add_page(p->page_update = new PageUpdate(this)); + p->add_page(p->page_downloader = new PageDownloader(this)); p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); #ifdef _WIN32 p->add_page(p->page_files_association = new PageFilesAssociation(this)); @@ -2923,9 +3210,10 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->add_page(p->page_mode = new PageMode(this)); p->add_page(p->page_firmware = new PageFirmware(this)); p->add_page(p->page_bed = new PageBedShape(this)); + p->add_page(p->page_bvolume = new PageBuildVolume(this)); p->add_page(p->page_diams = new PageDiameters(this)); p->add_page(p->page_temps = new PageTemperatures(this)); - + p->load_pages(); p->index->go_to(size_t{0}); diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index d757eed63..a8ac09d9b 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -26,6 +26,7 @@ #include "slic3r/Utils/PresetUpdater.hpp" #include "BedShapeDialog.hpp" #include "GUI.hpp" +#include "SavePresetDialog.hpp" #include "wxExtensions.hpp" @@ -370,16 +371,20 @@ struct PageMaterials: ConfigWizardPage struct PageCustom: ConfigWizardPage { PageCustom(ConfigWizard *parent); + ~PageCustom() { + if (profile_name_editor) + delete profile_name_editor; + } - bool custom_wanted() const { return cb_custom->GetValue(); } - std::string profile_name() const { return into_u8(tc_profile_name->GetValue()); } + bool custom_wanted() const { return cb_custom->GetValue(); } + bool is_valid_profile_name() const { return profile_name_editor->is_valid();} + std::string profile_name() const { return profile_name_editor->preset_name(); } private: static const char* default_profile_name; - wxCheckBox *cb_custom; - wxTextCtrl *tc_profile_name; - wxString profile_name_prev; + wxCheckBox *cb_custom {nullptr}; + SavePresetDialog::Item *profile_name_editor {nullptr}; }; @@ -387,10 +392,56 @@ struct PageUpdate: ConfigWizardPage { bool version_check; bool preset_update; + wxTextCtrl* path_text_ctrl; PageUpdate(ConfigWizard *parent); }; +namespace DownloaderUtils { + wxString get_downloads_path(); + +class Worker : public wxBoxSizer +{ + wxWindow* m_parent {nullptr}; + wxTextCtrl* m_input_path {nullptr}; + bool downloader_checked {false}; +#ifdef __linux__ + bool perform_registration_linux { false }; +#endif // __linux__ + + bool perform_register(); + void deregister(); + +public: + Worker(wxWindow* parent); + ~Worker(){} + + void allow(bool allow_) { downloader_checked = allow_; } + bool is_checked() const { return downloader_checked; } + wxString path_name() const { return m_input_path ? m_input_path->GetValue() : wxString(); } + + void set_path_name(wxString name); + void set_path_name(const std::string& name); + + bool on_finish(); + +#ifdef __linux__ + bool get_perform_registration_linux() { return perform_registration_linux; } +#endif // __linux__ +}; + +} + + +struct PageDownloader : ConfigWizardPage +{ + DownloaderUtils::Worker* downloader{ nullptr }; + + PageDownloader(ConfigWizard* parent); + + bool on_finish_downloader() const ; +}; + struct PageReloadFromDisk : ConfigWizardPage { bool full_pathnames; @@ -450,6 +501,14 @@ struct PageBedShape: ConfigWizardPage virtual void apply_custom_config(DynamicPrintConfig &config); }; +struct PageBuildVolume : ConfigWizardPage +{ + wxTextCtrl* build_volume; + + PageBuildVolume(ConfigWizard* parent); + virtual void apply_custom_config(DynamicPrintConfig& config); +}; + struct PageDiameters: ConfigWizardPage { wxTextCtrl *diam_nozzle; @@ -570,7 +629,8 @@ struct ConfigWizard::priv PageMaterials *page_filaments = nullptr; PageMaterials *page_sla_materials = nullptr; PageCustom *page_custom = nullptr; - PageUpdate *page_update = nullptr; + PageUpdate* page_update = nullptr; + PageDownloader* page_downloader = nullptr; PageReloadFromDisk *page_reload_from_disk = nullptr; #ifdef _WIN32 PageFilesAssociation* page_files_association = nullptr; @@ -584,6 +644,7 @@ struct ConfigWizard::priv PageBedShape *page_bed = nullptr; PageDiameters *page_diams = nullptr; PageTemperatures *page_temps = nullptr; + PageBuildVolume* page_bvolume = nullptr; // Pointers to all pages (regardless or whether currently part of the ConfigWizardIndex) std::vector all_pages; @@ -617,9 +678,9 @@ struct ConfigWizard::priv bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes); // #ys_FIXME_alise void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add); -#ifdef __linux__ - void perform_desktop_integration() const; -#endif +//#ifdef __linux__ +// void perform_desktop_integration() const; +//#endif bool check_fff_selected(); // Used to decide whether to display Filaments page bool check_sla_selected(); // Used to decide whether to display SLA Materials page diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.cpp b/src/slic3r/GUI/DesktopIntegrationDialog.cpp index 7f99a505c..26a8f60e5 100644 --- a/src/slic3r/GUI/DesktopIntegrationDialog.cpp +++ b/src/slic3r/GUI/DesktopIntegrationDialog.cpp @@ -218,10 +218,9 @@ bool DesktopIntegrationDialog::integration_possible() { return true; } -void DesktopIntegrationDialog::perform_desktop_integration() +void DesktopIntegrationDialog::perform_desktop_integration(bool perform_downloader) { - BOOST_LOG_TRIVIAL(debug) << "performing desktop integration"; - + BOOST_LOG_TRIVIAL(debug) << "performing desktop integration. With downloader integration: " << perform_downloader; // Path to appimage const char *appimage_env = std::getenv("APPIMAGE"); std::string excutable_path; @@ -287,7 +286,7 @@ void DesktopIntegrationDialog::perform_desktop_integration() std::string target_dir_icons; std::string target_dir_desktop; - + // slicer icon // iterate thru target_candidates to find icons folder for (size_t i = 0; i < target_candidates.size(); ++i) { @@ -300,20 +299,20 @@ void DesktopIntegrationDialog::perform_desktop_integration() break; // success else target_dir_icons.clear(); // copying failed - // if all failed - try creating default home folder - if (i == target_candidates.size() - 1) { - // create $HOME/.local/share - create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs); - // copy icon - target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir()); - std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir()); - std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix); - if (!contains_path_dir(target_dir_icons, "icons") - || !copy_icon(icon_path, dest_path)) { - // every attempt failed - icon wont be present - target_dir_icons.clear(); - } - } + } + // if all failed - try creating default home folder + if (i == target_candidates.size() - 1) { + // create $HOME/.local/share + create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs); + // copy icon + target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir()); + std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir()); + std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix); + if (!contains_path_dir(target_dir_icons, "icons") + || !copy_icon(icon_path, dest_path)) { + // every attempt failed - icon wont be present + target_dir_icons.clear(); + } } } if(target_dir_icons.empty()) { @@ -324,25 +323,25 @@ void DesktopIntegrationDialog::perform_desktop_integration() // desktop file // iterate thru target_candidates to find applications folder - for (size_t i = 0; i < target_candidates.size(); ++i) - { + + std::string desktop_file = GUI::format( + "[Desktop Entry]\n" + "Name=PrusaSlicer%1%\n" + "GenericName=3D Printing Software\n" + "Icon=PrusaSlicer%2%\n" + "Exec=\"%3%\" %%F\n" + "Terminal=false\n" + "Type=Application\n" + "MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n" + "Categories=Graphics;3DGraphics;Engineering;\n" + "Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n" + "StartupNotify=false\n" + "StartupWMClass=prusa-slicer\n", name_suffix, version_suffix, excutable_path); + + for (size_t i = 0; i < target_candidates.size(); ++i) { if (contains_path_dir(target_candidates[i], "applications")) { target_dir_desktop = target_candidates[i]; // Write slicer desktop file - std::string desktop_file = GUI::format( - "[Desktop Entry]\n" - "Name=PrusaSlicer%1%\n" - "GenericName=3D Printing Software\n" - "Icon=PrusaSlicer%2%\n" - "Exec=\"%3%\" %%F\n" - "Terminal=false\n" - "Type=Application\n" - "MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n" - "Categories=Graphics;3DGraphics;Engineering;\n" - "Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n" - "StartupNotify=false\n" - "StartupWMClass=prusa-slicer\n", name_suffix, version_suffix, excutable_path); - std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix); if (create_desktop_file(path, desktop_file)){ BOOST_LOG_TRIVIAL(debug) << "PrusaSlicer.desktop file installation success."; @@ -352,24 +351,24 @@ void DesktopIntegrationDialog::perform_desktop_integration() BOOST_LOG_TRIVIAL(debug) << "Attempt to PrusaSlicer.desktop file installation failed. failed path: " << target_candidates[i]; target_dir_desktop.clear(); } - // if all failed - try creating default home folder - if (i == target_candidates.size() - 1) { - // create $HOME/.local/share - create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications"); - // create desktop file - target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir()); - std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix); - if (contains_path_dir(target_dir_desktop, "applications")) { - if (!create_desktop_file(path, desktop_file)) { - // Desktop file not written - end desktop integration - BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file"; - return; - } - } else { - // Desktop file not written - end desktop integration - BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found."; + } + // if all failed - try creating default home folder + if (i == target_candidates.size() - 1) { + // create $HOME/.local/share + create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications"); + // create desktop file + target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir()); + std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix); + if (contains_path_dir(target_dir_desktop, "applications")) { + if (!create_desktop_file(path, desktop_file)) { + // Desktop file not written - end desktop integration + BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file"; return; } + } else { + // Desktop file not written - end desktop integration + BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found."; + return; } } } @@ -398,7 +397,7 @@ void DesktopIntegrationDialog::perform_desktop_integration() } // Desktop file - std::string desktop_file = GUI::format( + std::string desktop_file_viewer = GUI::format( "[Desktop Entry]\n" "Name=Prusa Gcode Viewer%1%\n" "GenericName=3D Printing Software\n" @@ -410,9 +409,8 @@ void DesktopIntegrationDialog::perform_desktop_integration() "Categories=Graphics;3DGraphics;\n" "Keywords=3D;Printing;Slicer;\n" "StartupNotify=false\n", name_suffix, version_suffix, excutable_path); - std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix); - if (create_desktop_file(desktop_path, desktop_file)) + if (create_desktop_file(desktop_path, desktop_file_viewer)) // save path to desktop file app_config->set("desktop_integration_app_viewer_path", desktop_path); else { @@ -421,6 +419,37 @@ void DesktopIntegrationDialog::perform_desktop_integration() } } + if (perform_downloader) + { + std::string desktop_file_downloader = GUI::format( + "[Desktop Entry]\n" + "Name=PrusaSlicer URL Protocol%1%\n" + "Exec=\"%3%\" --single-instance %%u\n" + "Icon=PrusaSlicer%4%\n" + "Terminal=false\n" + "Type=Application\n" + "MimeType=x-scheme-handler/prusaslicer;\n" + "StartupNotify=false\n" + , name_suffix, version_suffix, excutable_path, version_suffix); + + // desktop file for downloader as part of main app + std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix); + if (create_desktop_file(desktop_path, desktop_file_downloader)) { + // save path to desktop file + app_config->set("desktop_integration_URL_path", desktop_path); + // finish registration on mime type + std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix); + BOOST_LOG_TRIVIAL(debug) << "system command: " << command; + int r = system(command.c_str()); + BOOST_LOG_TRIVIAL(debug) << "system result: " << r; + + } else { + BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create URL Protocol desktop file"; + show_error(nullptr, _L("Performing desktop integration failed - could not create URL Protocol desktop file.")); + return; + } + } + wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess); } void DesktopIntegrationDialog::undo_desktop_intgration() @@ -453,9 +482,26 @@ void DesktopIntegrationDialog::undo_desktop_intgration() std::remove(path.c_str()); } } + // URL Protocol + path = std::string(app_config->get("desktop_integration_URL_path")); + if (!path.empty()) { + BOOST_LOG_TRIVIAL(debug) << "removing " << path; + std::remove(path.c_str()); + } wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess); } +void DesktopIntegrationDialog::undo_downloader_registration() +{ + const AppConfig *app_config = wxGetApp().app_config; + std::string path = std::string(app_config->get("desktop_integration_URL_path")); + if (!path.empty()) { + BOOST_LOG_TRIVIAL(debug) << "removing " << path; + std::remove(path.c_str()); + } + // There is no need to undo xdg-mime default command. It is done automatically when desktop file is deleted. +} + DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent) : wxDialog(parent, wxID_ANY, _(L("Desktop Integration")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) { @@ -481,7 +527,7 @@ DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent) wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform")); btn_szr->Add(btn_perform, 0, wxALL, 10); - btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(); EndModal(wxID_ANY); }); + btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(false); EndModal(wxID_ANY); }); if (can_undo){ wxButton *btn_undo = new wxButton(this, wxID_ANY, _L("Undo")); diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.hpp b/src/slic3r/GUI/DesktopIntegrationDialog.hpp index 74a0a68f9..08c984083 100644 --- a/src/slic3r/GUI/DesktopIntegrationDialog.hpp +++ b/src/slic3r/GUI/DesktopIntegrationDialog.hpp @@ -26,9 +26,14 @@ public: // Creates Desktop files and icons for both PrusaSlicer and GcodeViewer. // Stores paths into App Config. // Rewrites if files already existed. - static void perform_desktop_integration(); + // if perform_downloader: + // Creates Destktop files for PrusaSlicer downloader feature + // Regiters PrusaSlicer to start on prusaslicer:// URL + static void perform_desktop_integration(bool perform_downloader); // Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config. static void undo_desktop_intgration(); + + static void undo_downloader_registration(); private: }; diff --git a/src/slic3r/GUI/Downloader.cpp b/src/slic3r/GUI/Downloader.cpp new file mode 100644 index 000000000..157233076 --- /dev/null +++ b/src/slic3r/GUI/Downloader.cpp @@ -0,0 +1,245 @@ +#include "Downloader.hpp" +#include "GUI_App.hpp" +#include "NotificationManager.hpp" + +#include +#include + +namespace Slic3r { +namespace GUI { + +namespace { +void open_folder(const std::string& path) +{ + // Code taken from NotificationManager.cpp + + // Execute command to open a file explorer, platform dependent. + // FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade. + +#ifdef _WIN32 + const wxString widepath = from_u8(path); + const wchar_t* argv[] = { L"explorer", widepath.GetData(), nullptr }; + ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr); +#elif __APPLE__ + const char* argv[] = { "open", path.data(), nullptr }; + ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr); +#else + const char* argv[] = { "xdg-open", path.data(), nullptr }; + + // Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars, + // because they may mess up the environment expected by the file manager. + // Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure. + if (wxGetEnv("APPIMAGE", nullptr)) { + // We're running from AppImage + wxEnvVariableHashMap env_vars; + wxGetEnvMap(&env_vars); + + env_vars.erase("APPIMAGE"); + env_vars.erase("APPDIR"); + env_vars.erase("LD_LIBRARY_PATH"); + env_vars.erase("LD_PRELOAD"); + env_vars.erase("UNION_PRELOAD"); + + wxExecuteEnv exec_env; + exec_env.env = std::move(env_vars); + + wxString owd; + if (wxGetEnv("OWD", &owd)) { + // This is the original work directory from which the AppImage image was run, + // set it as CWD for the child process: + exec_env.cwd = std::move(owd); + } + + ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr, &exec_env); + } + else { + // Looks like we're NOT running from AppImage, we'll make no changes to the environment. + ::wxExecute(const_cast(argv), wxEXEC_ASYNC, nullptr, nullptr); + } +#endif +} + +std::string filename_from_url(const std::string& url) +{ + // TODO: can it be done with curl? + size_t slash = url.find_last_of("/"); + if (slash == std::string::npos && slash != url.size() - 1) + return std::string(); + return url.substr(slash + 1, url.size() - slash + 1); +} +} + +Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder) + : m_id(ID) + , m_filename(filename_from_url(url)) + , m_dest_folder(dest_folder) +{ + assert(boost::filesystem::is_directory(dest_folder)); + m_final_path = dest_folder / m_filename; + m_file_get = std::make_shared(ID, std::move(url), m_filename, evt_handler, dest_folder); +} + +void Download::start() +{ + m_state = DownloadState::DownloadOngoing; + m_file_get->get(); +} +void Download::cancel() +{ + m_state = DownloadState::DownloadStopped; + m_file_get->cancel(); +} +void Download::pause() +{ + //assert(m_state == DownloadState::DownloadOngoing); + // if instead of assert - it can happen that user clicks on pause several times before the pause happens + if (m_state != DownloadState::DownloadOngoing) + return; + m_state = DownloadState::DownloadPaused; + m_file_get->pause(); +} +void Download::resume() +{ + //assert(m_state == DownloadState::DownloadPaused); + if (m_state != DownloadState::DownloadPaused) + return; + m_state = DownloadState::DownloadOngoing; + m_file_get->resume(); +} + + +Downloader::Downloader() + : wxEvtHandler() +{ + //Bind(EVT_DWNLDR_FILE_COMPLETE, [](const wxCommandEvent& evt) {}); + //Bind(EVT_DWNLDR_FILE_PROGRESS, [](const wxCommandEvent& evt) {}); + //Bind(EVT_DWNLDR_FILE_ERROR, [](const wxCommandEvent& evt) {}); + //Bind(EVT_DWNLDR_FILE_NAME_CHANGE, [](const wxCommandEvent& evt) {}); + + Bind(EVT_DWNLDR_FILE_COMPLETE, &Downloader::on_complete, this); + Bind(EVT_DWNLDR_FILE_PROGRESS, &Downloader::on_progress, this); + Bind(EVT_DWNLDR_FILE_ERROR, &Downloader::on_error, this); + Bind(EVT_DWNLDR_FILE_NAME_CHANGE, &Downloader::on_name_change, this); + Bind(EVT_DWNLDR_FILE_PAUSED, &Downloader::on_paused, this); + Bind(EVT_DWNLDR_FILE_CANCELED, &Downloader::on_canceled, this); +} + +void Downloader::start_download(const std::string& full_url) +{ + assert(m_initialized); + + // TODO: There is a misterious slash appearing in recieved msg on windows +#ifdef _WIN32 + if (!boost::starts_with(full_url, "prusaslicer://open/?file=")) { +#else + if (!boost::starts_with(full_url, "prusaslicer://open?file=")) { +#endif + BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url; + // TODO: show error? + return; + } + size_t id = get_next_id(); + // TODO: still same mistery +#ifdef _WIN32 + std::string escaped_url = FileGet::escape_url(full_url.substr(25)); +#else + std::string escaped_url = FileGet::escape_url(full_url.substr(24)); +#endif + // TODO: enable after testing + /* + if (!boost::starts_with(escaped_url, "https://media.printables.com/")) { + BOOST_LOG_TRIVIAL(error) << "Download won't start. Download URL doesn't point to https://media.printables.com : " << escaped_url; + // TODO: show error? + return; + } + */ + std::string text(escaped_url); + m_downloads.emplace_back(std::make_unique(id, std::move(escaped_url), this, m_dest_folder)); + NotificationManager* ntf_mngr = wxGetApp().notification_manager(); + ntf_mngr->push_download_URL_progress_notification(id, m_downloads.back()->get_filename(), std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2)); + m_downloads.back()->start(); + BOOST_LOG_TRIVIAL(debug) << "started download"; +} + +void Downloader::on_progress(wxCommandEvent& event) +{ + size_t id = event.GetInt(); + float percent = (float)std::stoi(boost::nowide::narrow(event.GetString())) / 100.f; + //BOOST_LOG_TRIVIAL(error) << "progress " << id << ": " << percent; + NotificationManager* ntf_mngr = wxGetApp().notification_manager(); + BOOST_LOG_TRIVIAL(trace) << "Download "<< id << ": " << percent; + ntf_mngr->set_download_URL_progress(id, percent); +} +void Downloader::on_error(wxCommandEvent& event) +{ + size_t id = event.GetInt(); + set_download_state(event.GetInt(), DownloadState::DownloadError); + BOOST_LOG_TRIVIAL(error) << "Download error: " << event.GetString(); + NotificationManager* ntf_mngr = wxGetApp().notification_manager(); + ntf_mngr->set_download_URL_error(id, boost::nowide::narrow(event.GetString())); +} +void Downloader::on_complete(wxCommandEvent& event) +{ + // TODO: is this always true? : + // here we open the file itself, notification should get 1.f progress from on progress. + set_download_state(event.GetInt(), DownloadState::DownloadDone); + wxArrayString paths; + paths.Add(event.GetString()); + wxGetApp().plater()->load_files(paths); +} +bool Downloader::user_action_callback(DownloaderUserAction action, int id) +{ + for (size_t i = 0; i < m_downloads.size(); ++i) { + if (m_downloads[i]->get_id() == id) { + switch (action) { + case DownloadUserCanceled: + m_downloads[i]->cancel(); + return true; + case DownloadUserPaused: + m_downloads[i]->pause(); + return true; + case DownloadUserContinued: + m_downloads[i]->resume(); + return true; + case DownloadUserOpenedFolder: + open_folder(m_downloads[i]->get_dest_folder()); + return true; + default: + return false; + } + } + } + return false; +} + +void Downloader::on_name_change(wxCommandEvent& event) +{ + +} + +void Downloader::on_paused(wxCommandEvent& event) +{ + size_t id = event.GetInt(); + NotificationManager* ntf_mngr = wxGetApp().notification_manager(); + ntf_mngr->set_download_URL_paused(id); +} + +void Downloader::on_canceled(wxCommandEvent& event) +{ + size_t id = event.GetInt(); + NotificationManager* ntf_mngr = wxGetApp().notification_manager(); + ntf_mngr->set_download_URL_canceled(id); +} + +void Downloader::set_download_state(int id, DownloadState state) +{ + for (size_t i = 0; i < m_downloads.size(); ++i) { + if (m_downloads[i]->get_id() == id) { + m_downloads[i]->set_state(state); + return; + } + } +} + +} +} \ No newline at end of file diff --git a/src/slic3r/GUI/Downloader.hpp b/src/slic3r/GUI/Downloader.hpp new file mode 100644 index 000000000..84a9a9569 --- /dev/null +++ b/src/slic3r/GUI/Downloader.hpp @@ -0,0 +1,99 @@ +#ifndef slic3r_Downloader_hpp_ +#define slic3r_Downloader_hpp_ + +#include "DownloaderFileGet.hpp" +#include +#include + +namespace Slic3r { +namespace GUI { + +class NotificationManager; + +enum DownloadState +{ + DownloadPending = 0, + DownloadOngoing, + DownloadStopped, + DownloadDone, + DownloadError, + DownloadPaused, + DownloadStateUnknown +}; + +enum DownloaderUserAction +{ + DownloadUserCanceled, + DownloadUserPaused, + DownloadUserContinued, + DownloadUserOpenedFolder +}; + +class Download { +public: + Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder); + void start(); + void cancel(); + void pause(); + void resume(); + + int get_id() const { return m_id; } + boost::filesystem::path get_final_path() const { return m_final_path; } + std::string get_filename() const { return m_filename; } + DownloadState get_state() const { return m_state; } + void set_state(DownloadState state) { m_state = state; } + std::string get_dest_folder() { return m_dest_folder.string(); } +private: + const int m_id; + std::string m_filename; + boost::filesystem::path m_final_path; + boost::filesystem::path m_dest_folder; + std::shared_ptr m_file_get; + DownloadState m_state { DownloadState::DownloadPending }; +}; + +class Downloader : public wxEvtHandler { +public: + Downloader(); + + bool get_initialized() { return m_initialized; } + void init(const boost::filesystem::path& dest_folder) + { + m_dest_folder = dest_folder; + m_initialized = true; + } + void start_download(const std::string& full_url); + // cancel = false -> just pause + bool user_action_callback(DownloaderUserAction action, int id); +private: + bool m_initialized { false }; + + std::vector> m_downloads; + boost::filesystem::path m_dest_folder; + + size_t m_next_id { 0 }; + size_t get_next_id() { return ++m_next_id; } + + void on_progress(wxCommandEvent& event); + void on_error(wxCommandEvent& event); + void on_complete(wxCommandEvent& event); + void on_name_change(wxCommandEvent& event); + void on_paused(wxCommandEvent& event); + void on_canceled(wxCommandEvent& event); + + void set_download_state(int id, DownloadState state); + /* + bool is_in_state(int id, DownloadState state) const; + DownloadState get_download_state(int id) const; + bool cancel_download(int id); + bool pause_download(int id); + bool resume_download(int id); + bool delete_download(int id); + wxString get_path_of(int id) const; + wxString get_folder_path_of(int id) const; + */ +}; + +} +} +#endif \ No newline at end of file diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp new file mode 100644 index 000000000..10142c7c1 --- /dev/null +++ b/src/slic3r/GUI/DownloaderFileGet.cpp @@ -0,0 +1,322 @@ +#include "DownloaderFileGet.hpp" + +#include +#include +#include +#include +#include +#include + +#include "format.hpp" + +namespace Slic3r { +namespace GUI { + +const size_t DOWNLOAD_MAX_CHUNK_SIZE = 10 * 1024 * 1024; +const size_t DOWNLOAD_SIZE_LIMIT = 1024 * 1024 * 1024; + +std::string FileGet::escape_url(const std::string& unescaped) +{ + std::string ret_val; + CURL* curl = curl_easy_init(); + if (curl) { + int decodelen; + char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen); + if (decoded) { + ret_val = std::string(decoded); + curl_free(decoded); + } + curl_easy_cleanup(curl); + } + return ret_val; +} +namespace { +unsigned get_current_pid() +{ +#ifdef WIN32 + return GetCurrentProcessId(); +#else + return ::getpid(); +#endif +} +} + +// int = DOWNLOAD ID; string = file path +wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent); +// int = DOWNLOAD ID; string = error msg +wxDEFINE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent); +// int = DOWNLOAD ID; string = progress percent +wxDEFINE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent); +// int = DOWNLOAD ID; string = name +wxDEFINE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent); +// int = DOWNLOAD ID; +wxDEFINE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent); +// int = DOWNLOAD ID; +wxDEFINE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent); + +struct FileGet::priv +{ + const int m_id; + std::string m_url; + std::string m_filename; + std::thread m_io_thread; + wxEvtHandler* m_evt_handler; + boost::filesystem::path m_dest_folder; + boost::filesystem::path m_tmp_path; // path when ongoing download + std::atomic_bool m_cancel { false }; + std::atomic_bool m_pause { false }; + std::atomic_bool m_stopped { false }; // either canceled or paused - download is not running + size_t m_written { 0 }; + size_t m_absolute_size { 0 }; + priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder); + + void get_perform(); +}; + +FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder) + : m_id(ID) + , m_url(std::move(url)) + , m_filename(filename) + , m_evt_handler(evt_handler) + , m_dest_folder(dest_folder) +{ +} + +void FileGet::priv::get_perform() +{ + assert(m_evt_handler); + assert(!m_url.empty()); + assert(!m_filename.empty()); + assert(boost::filesystem::is_directory(m_dest_folder)); + + m_stopped = false; + + // open dest file + if (m_written == 0) + { + boost::filesystem::path dest_path = m_dest_folder / m_filename; + std::string extension = boost::filesystem::extension(dest_path); + std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size()); + std::string final_filename = just_filename; + + size_t version = 0; + while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download"))) + { + ++version; + final_filename = just_filename + "(" + std::to_string(version) + ")"; + } + m_filename = final_filename + extension; + + m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download"); + + wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE); + evt->SetString(boost::nowide::widen(m_filename)); + evt->SetInt(m_id); + m_evt_handler->QueueEvent(evt); + } + + boost::filesystem::path dest_path = m_dest_folder / m_filename; + + wxString temp_path_wstring(m_tmp_path.wstring()); + + std::cout << "dest_path: " << dest_path.string() << std::endl; + std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl; + + BOOST_LOG_TRIVIAL(info) << GUI::format("Starting download from %1% to %2%. Temp path is %3%",m_url, dest_path, m_tmp_path); + + FILE* file; + // open file for writting + if (m_written == 0) + file = fopen(temp_path_wstring.c_str(), "wb"); + else + file = fopen(temp_path_wstring.c_str(), "a"); + + assert(file != NULL); + + std:: string range_string = std::to_string(m_written) + "-"; + + size_t written_previously = m_written; + size_t written_this_session = 0; + Http::get(m_url) + .size_limit(DOWNLOAD_SIZE_LIMIT) //more? + .set_range(range_string) + .on_progress([&](Http::Progress progress, bool& cancel) { + // to prevent multiple calls into following ifs (m_cancel / m_pause) + if (m_stopped){ + cancel = true; + return; + } + if (m_cancel) { + m_stopped = true; + fclose(file); + // remove canceled file + std::remove(m_tmp_path.string().c_str()); + m_written = 0; + cancel = true; + wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED); + evt->SetInt(m_id); + m_evt_handler->QueueEvent(evt); + return; + // TODO: send canceled event? + } + if (m_pause) { + m_stopped = true; + fclose(file); + cancel = true; + wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED); + evt->SetInt(m_id); + m_evt_handler->QueueEvent(evt); + return; + } + + if (m_absolute_size < progress.dltotal) { + m_absolute_size = progress.dltotal; + } + + if (progress.dlnow != 0) { + if (progress.dlnow - written_this_session > DOWNLOAD_MAX_CHUNK_SIZE || progress.dlnow == progress.dltotal) { + try + { + std::string part_for_write = progress.buffer.substr(written_this_session, progress.dlnow); + fwrite(part_for_write.c_str(), 1, part_for_write.size(), file); + } + catch (const std::exception& e) + { + // fclose(file); do it? + wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR); + evt->SetString(e.what()); + evt->SetInt(m_id); + m_evt_handler->QueueEvent(evt); + cancel = true; + return; + } + written_this_session = progress.dlnow; + m_written = written_previously + written_this_session; + } + wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PROGRESS); + int percent_total = (written_previously + progress.dlnow) * 100 / m_absolute_size; + evt->SetString(std::to_string(percent_total)); + evt->SetInt(m_id); + m_evt_handler->QueueEvent(evt); + } + + }) + .on_error([&](std::string body, std::string error, unsigned http_status) { + if (file != NULL) + fclose(file); + wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR); + evt->SetString(error); + evt->SetInt(m_id); + m_evt_handler->QueueEvent(evt); + }) + .on_complete([&](std::string body, unsigned /* http_status */) { + + // TODO: perform a body size check + // + //size_t body_size = body.size(); + //if (body_size != expected_size) { + // return; + //} + try + { + /* + if (m_written < body.size()) + { + // this code should never be entered. As there should be on_progress call after last bit downloaded. + std::string part_for_write = body.substr(m_written); + fwrite(part_for_write.c_str(), 1, part_for_write.size(), file); + } + */ + fclose(file); + boost::filesystem::rename(m_tmp_path, dest_path); + } + catch (const std::exception& /*e*/) + { + //TODO: report? + //error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path); + wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR); + evt->SetString("Failed to write and move."); + evt->SetInt(m_id); + m_evt_handler->QueueEvent(evt); + return; + } + + wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_COMPLETE); + evt->SetString(dest_path.wstring()); + evt->SetInt(m_id); + m_evt_handler->QueueEvent(evt); + }) + .perform_sync(); + +} + +FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder) + : p(new priv(ID, std::move(url), filename, evt_handler, dest_folder)) +{} + +FileGet::FileGet(FileGet&& other) : p(std::move(other.p)) {} + +FileGet::~FileGet() +{ + if (p && p->m_io_thread.joinable()) { + p->m_cancel = true; + p->m_io_thread.join(); + } +} + +void FileGet::get() +{ + assert(p); + if (p->m_io_thread.joinable()) { + // This will stop transfers being done by the thread, if any. + // Cancelling takes some time, but should complete soon enough. + p->m_cancel = true; + p->m_io_thread.join(); + } + p->m_cancel = false; + p->m_pause = false; + p->m_io_thread = std::thread([this]() { + p->get_perform(); + }); +} + +void FileGet::cancel() +{ + if(p && p->m_stopped) { + if (p->m_io_thread.joinable()) { + p->m_cancel = true; + p->m_io_thread.join(); + wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED); + evt->SetInt(p->m_id); + p->m_evt_handler->QueueEvent(evt); + } + } + + if (p) + p->m_cancel = true; + +} + +void FileGet::pause() +{ + if (p) { + p->m_pause = true; + } +} +void FileGet::resume() +{ + assert(p); + if (p->m_io_thread.joinable()) { + // This will stop transfers being done by the thread, if any. + // Cancelling takes some time, but should complete soon enough. + p->m_cancel = true; + p->m_io_thread.join(); + } + p->m_cancel = false; + p->m_pause = false; + p->m_io_thread = std::thread([this]() { + p->get_perform(); + }); +} +} +} diff --git a/src/slic3r/GUI/DownloaderFileGet.hpp b/src/slic3r/GUI/DownloaderFileGet.hpp new file mode 100644 index 000000000..38ddd9af0 --- /dev/null +++ b/src/slic3r/GUI/DownloaderFileGet.hpp @@ -0,0 +1,44 @@ +#ifndef slic3r_DownloaderFileGet_hpp_ +#define slic3r_DownloaderFileGet_hpp_ + +#include "../Utils/Http.hpp" + +#include +#include +#include +#include +#include + +namespace Slic3r { +namespace GUI { +class FileGet : public std::enable_shared_from_this { +private: + struct priv; +public: + FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder); + FileGet(FileGet&& other); + ~FileGet(); + + void get(); + void cancel(); + void pause(); + void resume(); + static std::string escape_url(const std::string& url); +private: + std::unique_ptr p; +}; +// int = DOWNLOAD ID; string = file path +wxDECLARE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent); +// int = DOWNLOAD ID; string = error msg +wxDECLARE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent); +// int = DOWNLOAD ID; string = progress percent +wxDECLARE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent); +// int = DOWNLOAD ID; string = name +wxDECLARE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent); +// int = DOWNLOAD ID; +wxDECLARE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent); +// int = DOWNLOAD ID; +wxDECLARE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent); +} +} +#endif diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index d3fef44d3..419d48d5b 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -290,6 +290,7 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true break; } case coString: case coStrings: + case coFloatsOrPercents: case coFloatOrPercent: { if (m_opt.type == coFloatOrPercent && m_opt.opt_key == "first_layer_height" && !str.IsEmpty() && str.Last() == '%') { // Workaroud to avoid of using of the % for first layer height @@ -301,7 +302,7 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true m_value = into_u8(stVal);; break; } - if (m_opt.type == coFloatOrPercent && !str.IsEmpty() && str.Last() != '%') + if ((m_opt.type == coFloatOrPercent || m_opt.type == coFloatsOrPercents) && !str.IsEmpty() && str.Last() != '%') { double val = 0.; const char dec_sep = is_decimal_separator_point() ? '.' : ','; @@ -443,6 +444,13 @@ void TextCtrl::BUILD() { text_value += "%"; break; } + case coFloatsOrPercents: { + const auto val = m_opt.get_default_value()->get_at(m_opt_idx); + text_value = double_to_string(val.value); + if (val.percent) + text_value += "%"; + break; + } case coPercent: { text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getFloat())); @@ -574,6 +582,7 @@ bool TextCtrl::value_was_changed() case coString: case coStrings: case coFloatOrPercent: + case coFloatsOrPercents: return boost::any_cast(m_value) != boost::any_cast(val); default: return true; @@ -1149,10 +1158,6 @@ void Choice::set_value(const boost::any& value, bool change_event) } case coEnum: { int val = boost::any_cast(value); - if (m_opt_id.compare("host_type") == 0 && val != 0 && - m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType - val--; - if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") { std::string key; @@ -1231,10 +1236,7 @@ boost::any& Choice::get_value() if (m_opt.type == coEnum) { - if (m_opt_id.compare("host_type") == 0 && m_opt.enum_values.size() > field->GetCount()) { - // for case, when PrusaLink isn't used as a HostType - m_value = field->GetSelection()+1; - } else if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") { + if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") { const std::string& key = m_opt.enum_values[field->GetSelection()]; m_value = int(ConfigOptionEnum::get_enum_values().at(key)); } @@ -1416,6 +1418,14 @@ void ColourPicker::sys_color_changed() #endif } +PointCtrl::~PointCtrl() +{ + if (sizer && sizer->IsEmpty()) { + delete sizer; + sizer = nullptr; + } +} + void PointCtrl::BUILD() { auto temp = new wxBoxSizer(wxHORIZONTAL); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 60a06427d..eaa4fe481 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -425,7 +425,7 @@ class PointCtrl : public Field { public: PointCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} PointCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} - ~PointCtrl() {} + ~PointCtrl(); wxSizer* sizer{ nullptr }; wxTextCtrl* x_textctrl{ nullptr }; diff --git a/src/slic3r/GUI/FileArchiveDialog.cpp b/src/slic3r/GUI/FileArchiveDialog.cpp new file mode 100644 index 000000000..2b861692a --- /dev/null +++ b/src/slic3r/GUI/FileArchiveDialog.cpp @@ -0,0 +1,370 @@ +#include "FileArchiveDialog.hpp" + +#include "I18N.hpp" +#include "GUI_App.hpp" +#include "GUI.hpp" +#include "MainFrame.hpp" +#include "ExtraRenderers.hpp" +#include "format.hpp" +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +ArchiveViewModel::ArchiveViewModel(wxWindow* parent) + :m_parent(parent) +{} +ArchiveViewModel::~ArchiveViewModel() +{} + +std::shared_ptr ArchiveViewModel::AddFile(std::shared_ptr parent, const wxString& name, bool container) +{ + std::shared_ptr node = std::make_shared(ArchiveViewNode(name)); + node->set_container(container); + + if (parent.get() != nullptr) { + parent->get_children().push_back(node); + node->set_parent(parent); + parent->set_is_folder(true); + } else { + m_top_children.emplace_back(node); + } + + wxDataViewItem child = wxDataViewItem((void*)node.get()); + wxDataViewItem parent_item= wxDataViewItem((void*)parent.get()); + ItemAdded(parent_item, child); + + if (parent) + m_ctrl->Expand(parent_item); + return node; +} + +wxString ArchiveViewModel::GetColumnType(unsigned int col) const +{ + if (col == 0) + return "bool"; + return "string";//"DataViewBitmapText"; +} + +void ArchiveViewModel::Rescale() +{ + // There should be no pictures rendered +} + +void ArchiveViewModel::Delete(const wxDataViewItem& item) +{ + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + assert(node->get_parent() != nullptr); + for (std::shared_ptr child : node->get_children()) + { + Delete(wxDataViewItem((void*)child.get())); + } + delete [] node; +} +void ArchiveViewModel::Clear() +{ +} + +wxDataViewItem ArchiveViewModel::GetParent(const wxDataViewItem& item) const +{ + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + return wxDataViewItem((void*)node->get_parent().get()); +} +unsigned int ArchiveViewModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const +{ + if (!parent.IsOk()) { + for (std::shared_ptrchild : m_top_children) { + array.push_back(wxDataViewItem((void*)child.get())); + } + return m_top_children.size(); + } + + ArchiveViewNode* node = static_cast(parent.GetID()); + for (std::shared_ptr child : node->get_children()) { + array.push_back(wxDataViewItem((void*)child.get())); + } + return node->get_children().size(); +} + +void ArchiveViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const +{ + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + if (col == 0) { + variant = node->get_toggle(); + } else { + variant = node->get_name(); + } +} + +void ArchiveViewModel::untoggle_folders(const wxDataViewItem& item) +{ + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + node->set_toggle(false); + if (node->get_parent().get() != nullptr) + untoggle_folders(wxDataViewItem((void*)node->get_parent().get())); +} + +bool ArchiveViewModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) +{ + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + if (col == 0) { + node->set_toggle(variant.GetBool()); + // if folder recursivelly check all children + for (std::shared_ptr child : node->get_children()) { + SetValue(variant, wxDataViewItem((void*)child.get()), col); + } + if(!variant.GetBool() && node->get_parent()) + untoggle_folders(wxDataViewItem((void*)node->get_parent().get())); + } else { + node->set_name(variant.GetString()); + } + m_parent->Refresh(); + return true; +} +bool ArchiveViewModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const +{ + // As of now, all items are always enabled. + // Returning false for col 1 would gray out text. + return true; +} + +bool ArchiveViewModel::IsContainer(const wxDataViewItem& item) const +{ + if(!item.IsOk()) + return true; + ArchiveViewNode* node = static_cast(item.GetID()); + return node->is_container(); +} + +ArchiveViewCtrl::ArchiveViewCtrl(wxWindow* parent, wxSize size) + : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, size, wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES +#ifdef _WIN32 + | wxBORDER_SIMPLE +#endif + ) + //, m_em_unit(em_unit(parent)) +{ + wxGetApp().UpdateDVCDarkUI(this); + + m_model = new ArchiveViewModel(parent); + this->AssociateModel(m_model); + m_model->SetAssociatedControl(this); +} + +ArchiveViewCtrl::~ArchiveViewCtrl() +{ + if (m_model) { + m_model->Clear(); + m_model->DecRef(); + } +} + +FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* archive, std::vector& selected_paths) + : DPIDialog(parent_window, wxID_ANY, _(L("Archive preview")), wxDefaultPosition, + wxSize(45 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit()), + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) + , m_selected_paths (selected_paths) +{ + int em = em_unit(); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + + m_avc = new ArchiveViewCtrl(this, wxSize(60 * em, 30 * em)); + m_avc->AppendToggleColumn(L"\u2714", 0, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em); + m_avc->AppendTextColumn("filename", 1); + + + std::vector> stack; + + std::function >&, size_t)> reduce_stack = [] (std::vector>& stack, size_t size) { + if (size == 0) { + stack.clear(); + return; + } + while (stack.size() > size) + stack.pop_back(); + }; + // recursively stores whole structure of file onto function stack and synchoronize with stack object. + std::function>&)> adjust_stack = [&adjust_stack, &reduce_stack, &avc = m_avc](const boost::filesystem::path& const_file, std::vector>& stack)->size_t { + boost::filesystem::path file(const_file); + size_t struct_size = file.has_parent_path() ? adjust_stack(file.parent_path(), stack) : 0; + + if (stack.size() > struct_size && (file.has_extension() || file.filename().string() != stack[struct_size]->get_name())) + { + reduce_stack(stack, struct_size); + } + if (!file.has_extension() && stack.size() == struct_size) + stack.push_back(avc->get_model()->AddFile((stack.empty() ? std::shared_ptr(nullptr) : stack.back()), GUI::format_wxstr(file.filename().string()), true)); // filename string to wstring? + return struct_size + 1; + }; + + const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa|step|stp)", std::regex::icase); + mz_uint num_entries = mz_zip_reader_get_num_files(archive); + mz_zip_archive_file_stat stat; + std::vector filtered_entries; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(archive, i, &stat)) { + std::string extra(1024, 0); + boost::filesystem::path path; + size_t extra_size = mz_zip_reader_get_filename_from_extra(archive, i, extra.data(), extra.size()); + if (extra_size > 0) { + path = boost::filesystem::path(extra.substr(0, extra_size)); + } else { + wxString wname = boost::nowide::widen(stat.m_filename); + std::string name = GUI::format(wname); + path = boost::filesystem::path(name); + } + assert(!path.empty()); + if (!path.has_extension()) + continue; + // filter out MACOS specific hidden files + if (boost::algorithm::starts_with(path.string(), "__MACOSX")) + continue; + filtered_entries.emplace_back(std::move(path)); + } + } + // sorting files will help adjust_stack function to not create multiple same folders + std::sort(filtered_entries.begin(), filtered_entries.end(), [](const boost::filesystem::path& p1, const boost::filesystem::path& p2){ return p1.string() > p2.string(); }); + for (const boost::filesystem::path& path : filtered_entries) + { + std::shared_ptr parent(nullptr); + + adjust_stack(path, stack); + if (!stack.empty()) + parent = stack.back(); + if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files + m_avc->get_model()->AddFile(parent, GUI::format_wxstr(path.filename().string()), false)->set_fullpath(/*std::move(path)*/path); // filename string to wstring? + } + } + wxBoxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxButton* btn_all = new wxButton(this, wxID_ANY, "All"); + btn_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_all_button(); }); + btn_sizer->Add(btn_all, 0, wxLeft); + + wxButton* btn_none = new wxButton(this, wxID_ANY, "None"); + btn_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_none_button(); }); + btn_sizer->Add(btn_none, 0, wxLeft); + + btn_sizer->AddStretchSpacer(); + wxButton* btn_run = new wxButton(this, wxID_OK, "Open"); + btn_run->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_open_button(); }); + btn_sizer->Add(btn_run, 0, wxRIGHT); + + wxButton* cancel_btn = new wxButton(this, wxID_CANCEL, "Cancel"); + cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); }); + btn_sizer->Add(cancel_btn, 0, wxRIGHT); + + topSizer->Add(m_avc, 1, wxEXPAND | wxALL, 10); + topSizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 10); + this->SetMinSize(wxSize(80 * em, 30 * em)); + this->SetSizer(topSizer); +} + +void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + int em = em_unit(); + BOOST_LOG_TRIVIAL(error) << "on_dpi_changed"; + //msw_buttons_rescale(this, em, { wxID_CANCEL, m_save_btn_id, m_move_btn_id, m_continue_btn_id }); + //for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn }) + // if (btn) btn->msw_rescale(); + + const wxSize& size = wxSize(70 * em, 30 * em); + SetMinSize(size); + + //m_tree->Rescale(em); + + Fit(); + Refresh(); +} + +void FileArchiveDialog::on_open_button() +{ + wxDataViewItemArray top_items; + m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items); + + std::function deep_fill = [&paths = m_selected_paths, &deep_fill](ArchiveViewNode* node){ + if (node == nullptr) + return; + if (node->get_children().empty()) { + if (node->get_toggle()) + paths.emplace_back(node->get_fullpath()); + } else { + for (std::shared_ptr child : node->get_children()) + deep_fill(child.get()); + } + }; + + for (const auto& item : top_items) + { + ArchiveViewNode* node = static_cast(item.GetID()); + deep_fill(node); + } + this->EndModal(wxID_OK); +} + +void FileArchiveDialog::on_all_button() +{ + + wxDataViewItemArray top_items; + m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items); + + std::function deep_fill = [&deep_fill](ArchiveViewNode* node) { + if (node == nullptr) + return; + node->set_toggle(true); + if (!node->get_children().empty()) { + for (std::shared_ptr child : node->get_children()) + deep_fill(child.get()); + } + }; + + for (const auto& item : top_items) + { + ArchiveViewNode* node = static_cast(item.GetID()); + deep_fill(node); + // Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes. + // It should be enough to call ValueChanged for top items. + m_avc->get_model()->ValueChanged(item, 0); + } + + Refresh(); +} + +void FileArchiveDialog::on_none_button() +{ + wxDataViewItemArray top_items; + m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items); + + std::function deep_fill = [&deep_fill](ArchiveViewNode* node) { + if (node == nullptr) + return; + node->set_toggle(false); + if (!node->get_children().empty()) { + for (std::shared_ptr child : node->get_children()) + deep_fill(child.get()); + } + }; + + for (const auto& item : top_items) + { + ArchiveViewNode* node = static_cast(item.GetID()); + deep_fill(node); + // Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes. + // It should be enough to call ValueChanged for top items. + m_avc->get_model()->ValueChanged(item, 0); + } + + this->Refresh(); +} + +} // namespace GUI +} // namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/FileArchiveDialog.hpp b/src/slic3r/GUI/FileArchiveDialog.hpp new file mode 100644 index 000000000..22a9ecedf --- /dev/null +++ b/src/slic3r/GUI/FileArchiveDialog.hpp @@ -0,0 +1,118 @@ +#ifndef slic3r_GUI_FileArchiveDialog_hpp_ +#define slic3r_GUI_FileArchiveDialog_hpp_ + +#include "GUI_Utils.hpp" +#include "libslic3r/miniz_extension.hpp" + +#include +#include +#include +#include "wxExtensions.hpp" + +namespace Slic3r { +namespace GUI { + +class ArchiveViewCtrl; + +class ArchiveViewNode +{ +public: + ArchiveViewNode(const wxString& name) : m_name(name) {} + + std::vector>& get_children() { return m_children; } + void set_parent(std::shared_ptr parent) { m_parent = parent; } + // On Linux, get_parent cannot just return size of m_children. ItemAdded would than crash. + std::shared_ptr get_parent() const { return m_parent; } + bool is_container() const { return m_container; } + void set_container(bool is_container) { m_container = is_container; } + wxString get_name() const { return m_name; } + void set_name(const wxString& name) { m_name = name; } + bool get_toggle() const { return m_toggle; } + void set_toggle(bool toggle) { m_toggle = toggle; } + bool get_is_folder() const { return m_folder; } + void set_is_folder(bool is_folder) { m_folder = is_folder; } + void set_fullpath(boost::filesystem::path path) { m_fullpath = path; } + boost::filesystem::path get_fullpath() const { return m_fullpath; } + +private: + wxString m_name; + std::shared_ptr m_parent { nullptr }; + std::vector> m_children; + + bool m_toggle { false }; + bool m_folder { false }; + boost::filesystem::path m_fullpath; + bool m_container { false }; +}; + +class ArchiveViewModel : public wxDataViewModel +{ +public: + ArchiveViewModel(wxWindow* parent); + ~ArchiveViewModel(); + + /* wxDataViewItem AddFolder(wxDataViewItem& parent, wxString name); + wxDataViewItem AddFile(wxDataViewItem& parent, wxString name);*/ + + std::shared_ptr AddFile(std::shared_ptr parent,const wxString& name, bool container); + + wxString GetColumnType(unsigned int col) const override; + unsigned int GetColumnCount() const override { return 2; } + + void Rescale(); + void Delete(const wxDataViewItem& item); + void Clear(); + + wxDataViewItem GetParent(const wxDataViewItem& item) const override; + unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; + + void SetAssociatedControl(ArchiveViewCtrl* ctrl) { m_ctrl = ctrl; } + + void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override; + bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override; + + void untoggle_folders(const wxDataViewItem& item); + + bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override; + bool IsContainer(const wxDataViewItem& item) const override; + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } + +protected: + wxWindow* m_parent { nullptr }; + ArchiveViewCtrl* m_ctrl { nullptr }; + std::vector> m_top_children; +}; + +class ArchiveViewCtrl : public wxDataViewCtrl +{ + public: + ArchiveViewCtrl(wxWindow* parent, wxSize size); + ~ArchiveViewCtrl(); + + ArchiveViewModel* get_model() const {return m_model; } +protected: + ArchiveViewModel* m_model; +}; + + +class FileArchiveDialog : public DPIDialog +{ +public: + FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* archive, std::vector& selected_paths); + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + + void on_open_button(); + void on_all_button(); + void on_none_button(); + + std::vector& m_selected_paths; + ArchiveViewCtrl* m_avc; +}; + +} // namespace GU +} // namespace Slic3r +#endif // slic3r_GUI_FileArchiveDialog_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index b0e11422d..097757ff9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -643,7 +643,7 @@ const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY(); GCodeViewer::GCodeViewer() { m_extrusions.reset_role_visibility_flags(); - + m_shells.volumes.set_use_raycasters(false); // m_sequential_view.skip_invisible_moves = true; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c9eb10060..6b8499dde 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -81,6 +81,11 @@ static const Slic3r::ColorRGBA ERROR_BG_LIGHT_COLOR = { 0.753f, 0.192f, 0.039f // Number of floats static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB +#define SHOW_IMGUI_DEMO_WINDOW +#ifdef SHOW_IMGUI_DEMO_WINDOW +static bool show_imgui_demo_window = false; +#endif // SHOW_IMGUI_DEMO_WINDOW + namespace Slic3r { namespace GUI { @@ -1498,6 +1503,10 @@ void GLCanvas3D::render() } #endif // ENABLE_RAYCAST_PICKING_DEBUG } + +#ifdef SHOW_IMGUI_DEMO_WINDOW + if (show_imgui_demo_window) ImGui::ShowDemoWindow(); +#endif // SHOW_IMGUI_DEMO_WINDOW const bool is_looking_downward = camera.is_looking_downward(); @@ -1746,7 +1755,20 @@ std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) void GLCanvas3D::mirror_selection(Axis axis) { +#if ENABLE_WORLD_COORDINATE + TransformationType transformation_type; + if (wxGetApp().obj_manipul()->is_local_coordinates()) + transformation_type.set_local(); + else if (wxGetApp().obj_manipul()->is_instance_coordinates()) + transformation_type.set_instance(); + + transformation_type.set_relative(); + + m_selection.setup_cache(); + m_selection.mirror(axis, transformation_type); +#else m_selection.mirror(axis); +#endif // ENABLE_WORLD_COORDINATE do_mirror(L("Mirror Object")); wxGetApp().obj_manipul()->set_dirty(); } @@ -2231,10 +2253,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) if (!m_initialized) return; - // see include/wx/defs.h enum wxKeyCode - int keyCode = evt.GetKeyCode(); - int ctrlMask = wxMOD_CONTROL; - int shiftMask = wxMOD_SHIFT; +#ifdef SHOW_IMGUI_DEMO_WINDOW + static int cur = 0; + if (wxString("demo")[cur] == evt.GetUnicodeKey()) ++cur; else cur = 0; + if (cur == 4) { show_imgui_demo_window = !show_imgui_demo_window; cur = 0;} +#endif // SHOW_IMGUI_DEMO_WINDOW auto imgui = wxGetApp().imgui(); if (imgui->update_key_data(evt)) { @@ -2242,6 +2265,10 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) return; } + // see include/wx/defs.h enum wxKeyCode + int keyCode = evt.GetKeyCode(); + int ctrlMask = wxMOD_CONTROL; + int shiftMask = wxMOD_SHIFT; if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())) return; @@ -3299,20 +3326,27 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) evt.Skip(); // Detection of doubleclick on text to open emboss edit window - if (evt.LeftDClick() && m_gizmos.get_current() == nullptr && !m_hover_volume_idxs.empty()) { + auto type = m_gizmos.get_current_type(); + if (evt.LeftDClick() && !m_hover_volume_idxs.empty() && + (type == GLGizmosManager::EType::Undefined || + type == GLGizmosManager::EType::Move || + type == GLGizmosManager::EType::Rotate || + type == GLGizmosManager::EType::Scale || + type == GLGizmosManager::EType::Emboss) ) { for (int hover_volume_id : m_hover_volume_idxs) { const GLVolume &hover_gl_volume = *m_volumes.volumes[hover_volume_id]; - const ModelObject* hover_object = m_model->objects[hover_gl_volume.object_idx()]; + int object_idx = hover_gl_volume.object_idx(); + if (object_idx < 0 || static_cast(object_idx) >= m_model->objects.size()) continue; + const ModelObject* hover_object = m_model->objects[object_idx]; int hover_volume_idx = hover_gl_volume.volume_idx(); + if (hover_volume_idx < 0 || static_cast(hover_volume_idx) >= hover_object->volumes.size()) continue; const ModelVolume* hover_volume = hover_object->volumes[hover_volume_idx]; - if (hover_volume->text_configuration.has_value()) { - //m_selection.set_mode(Selection::EMode::Volume); - //m_selection.add(hover_volume_id); // add whole instance - m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); + if (!hover_volume->text_configuration.has_value()) continue; + m_selection.add_volumes(Selection::EMode::Volume, {(unsigned) hover_volume_id}); + if (type != GLGizmosManager::EType::Emboss) m_gizmos.open_gizmo(GLGizmosManager::EType::Emboss); - wxGetApp().obj_list()->update_selections(); - return; - } + wxGetApp().obj_list()->update_selections(); + return; } } @@ -3490,7 +3524,13 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) for (const GLVolume* v : m_volumes.volumes) { if (v->is_wipe_tower) { const Vec3d offset = v->get_volume_offset(); +#if ENABLE_WORLD_COORDINATE + Vec3d rot_unit_x = v->get_volume_transformation().get_matrix().linear() * Vec3d::UnitX(); + double z_rot = std::atan2(rot_unit_x.y(), rot_unit_x.x()); + post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset.x(), offset.y(), z_rot))); +#else post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset.x(), offset.y(), v->get_volume_rotation().z()))); +#endif // ENABLE_WORLD_COORDINATE } const int object_idx = v->object_idx(); if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) @@ -4940,9 +4980,17 @@ void GLCanvas3D::_refresh_if_shown_on_screen() const Size& cnv_size = get_canvas_size(); _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + // When the application starts the following call to render() triggers the opengl initialization. + // We need to ask for an extra call to reload_scene() to force the generation of the model for wipe tower + // for printers using it, which is skipped by all the previous calls to reload_scene() because m_initialized == false + const bool requires_reload_scene = !m_initialized; + // Because of performance problems on macOS, where PaintEvents are not delivered // frequently enough, we call render() here directly when we can. render(); + assert(m_initialized); + if (requires_reload_scene) + reload_scene(true); } } @@ -5470,6 +5518,10 @@ void GLCanvas3D::_render_selection() if (!m_gizmos.is_running()) m_selection.render(scale_factor); + +#if ENABLE_WORLD_COORDINATE_DEBUG + m_selection.render_debug_window(); +#endif // ENABLE_WORLD_COORDINATE_DEBUG } void GLCanvas3D::_render_sequential_clearance() @@ -5504,10 +5556,10 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() if (wxGetApp().plater()->is_preview_shown()) return; - float scale = wxGetApp().toolbar_icon_scale(); - Size cnv_size = get_canvas_size(); + const float scale = wxGetApp().toolbar_icon_scale(); + const Size cnv_size = get_canvas_size(); - float size = GLToolbar::Default_Icons_Size * scale; + int size = int(GLToolbar::Default_Icons_Size * scale); // Set current size for all top toolbars. It will be used for next calculations GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); @@ -5516,28 +5568,28 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() m_main_toolbar.set_scale(sc); m_undoredo_toolbar.set_scale(sc); collapse_toolbar.set_scale(sc); - size *= m_retina_helper->get_scale_factor(); + size *= int(m_retina_helper->get_scale_factor()); #else m_main_toolbar.set_icons_size(size); m_undoredo_toolbar.set_icons_size(size); collapse_toolbar.set_icons_size(size); #endif // ENABLE_RETINA_GL - float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); + const float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); - float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars + const float noitems_width = top_tb_width - float(size) * items_cnt; // width of separators and borders in top toolbars // calculate scale needed for items in all top toolbars // the std::max() is there because on some Linux dialects/virtual machines this code is called when the canvas has not been properly initialized yet, // leading to negative values for the scale. // See: https://github.com/prusa3d/PrusaSlicer/issues/8563 // https://github.com/supermerill/SuperSlicer/issues/854 - float new_h_scale = std::max((cnv_size.get_width() - noitems_width), 1.0f) / (items_cnt * GLToolbar::Default_Icons_Size); + const float new_h_scale = std::max((cnv_size.get_width() - noitems_width), 1.0f) / (items_cnt * GLToolbar::Default_Icons_Size); items_cnt = m_gizmos.get_selectable_icons_cnt() + 3; // +3 means a place for top and view toolbars and separators in gizmos toolbar // calculate scale needed for items in the gizmos toolbar - float new_v_scale = cnv_size.get_height() / (items_cnt * GLGizmosManager::Default_Icons_Size); + const float new_v_scale = cnv_size.get_height() / (items_cnt * GLGizmosManager::Default_Icons_Size); // set minimum scale as a auto scale for the toolbars float new_scale = std::min(new_h_scale, new_v_scale); @@ -5552,24 +5604,12 @@ void GLCanvas3D::_render_overlays() { glsafe(::glDisable(GL_DEPTH_TEST)); + // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed + // to correctly place them _check_and_update_toolbar_icon_scale(); _render_gizmos_overlay(); - // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed - // to correctly place them -#if ENABLE_RETINA_GL - const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); - m_main_toolbar.set_scale(scale); - m_undoredo_toolbar.set_scale(scale); - wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); -#else - const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); - m_main_toolbar.set_icons_size(size); - m_undoredo_toolbar.set_icons_size(size); - wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); -#endif // ENABLE_RETINA_GL - _render_main_toolbar(); _render_undoredo_toolbar(); _render_collapse_toolbar(); diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 07a2edbe2..ef9a8ef20 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -377,7 +377,8 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy) { - bool compression_enabled = (compression_type != None) && OpenGLManager::are_compressed_textures_supported(); + const bool compression_enabled = (compression_type != None) && OpenGLManager::are_compressed_textures_supported(); + const bool use_compressor = compression_enabled && OpenGLManager::use_manually_generated_mipmaps(); // Load a PNG with an alpha channel. wxImage image; @@ -447,7 +448,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo } if (compression_enabled) { - if (compression_type == SingleThreaded) + if (compression_type == SingleThreaded || !use_compressor) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); else { // initializes the texture on GPU @@ -459,7 +460,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - if (use_mipmaps) { + if (use_mipmaps && OpenGLManager::use_manually_generated_mipmaps()) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards int lod_w = m_width; int lod_h = m_height; @@ -506,6 +507,10 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); } } + else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) { + glsafe(::glGenerateMipmap(GL_TEXTURE_2D)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + } else { glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -517,7 +522,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo m_source = filename; - if (compression_enabled && compression_type == MultiThreaded) + if (use_compressor && compression_type == MultiThreaded) // start asynchronous compression m_compressor.start_compressing(); @@ -526,7 +531,8 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECo bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) { - bool compression_enabled = compress && OpenGLManager::are_compressed_textures_supported(); + const bool compression_enabled = compress && OpenGLManager::are_compressed_textures_supported(); + const bool use_compressor = compression_enabled && OpenGLManager::use_manually_generated_mipmaps(); NSVGimage* image = BitmapCache::nsvgParseFromFileWithReplace(filename.c_str(), "px", 96.0f, {}); if (image == nullptr) { @@ -551,7 +557,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo m_height += (4 - height_rem); } - int n_pixels = m_width * m_height; + const int n_pixels = m_width * m_height; if (n_pixels <= 0) { reset(); @@ -582,10 +588,14 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo } if (compression_enabled) { - // initializes the texture on GPU - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - // and send the uncompressed data to the compressor - m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); + if (use_compressor) { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); + } + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); } else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); @@ -619,9 +629,12 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); } - } else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) { - glGenerateMipmap(GL_TEXTURE_2D); - } else { + } + else if (use_mipmaps && !OpenGLManager::use_manually_generated_mipmaps()) { + glsafe(::glGenerateMipmap(GL_TEXTURE_2D)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + } + else { glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); } @@ -632,7 +645,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo m_source = filename; - if (compression_enabled) + if (use_compressor) // start asynchronous compression m_compressor.start_compressing(); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 5886be028..d4df48d50 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -130,6 +130,18 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt val = boost::any_cast(value); break; } + case coFloatsOrPercents:{ + std::string str = boost::any_cast(value); + bool percent = false; + if (str.back() == '%') { + str.pop_back(); + percent = true; + } + double val = std::stod(str); // locale-dependent (on purpose - the input is the actual content of the field) + ConfigOptionFloatsOrPercents* vec_new = new ConfigOptionFloatsOrPercents({ {val, percent} }); + config.option(opt_key)->set_at(vec_new, opt_index, opt_index); + break; + } case coPercents:{ ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast(value) }; config.option(opt_key)->set_at(vec_new, opt_index, opt_index); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 5f5d0384a..35edf6650 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -77,6 +78,7 @@ #include "PrintHostDialogs.hpp" #include "DesktopIntegrationDialog.hpp" #include "SendSystemInfoDialog.hpp" +#include "Downloader.hpp" #include "BitmapCache.hpp" #include "Notebook.hpp" @@ -749,7 +751,9 @@ void GUI_App::post_init() if (! this->init_params->input_files.empty()) this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); } - else { + else if (this->init_params->start_downloader) { + start_download(this->init_params->download_url); + } else { if (! this->init_params->preset_substitutions.empty()) show_substitutions_info(this->init_params->preset_substitutions); @@ -777,6 +781,15 @@ void GUI_App::post_init() boost::algorithm::iends_with(filename, ".3mf")) this->plater()->set_project_filename(from_u8(filename)); } + if (this->init_params->delete_after_load) { + for (const std::string& p : this->init_params->input_files) { + boost::system::error_code ec; + boost::filesystem::remove(boost::filesystem::path(p), ec); + if (ec) { + BOOST_LOG_TRIVIAL(error) << ec.message(); + } + } + } } if (! this->init_params->extra_config.empty()) this->mainframe->load_config(this->init_params->extra_config); @@ -826,6 +839,7 @@ GUI_App::GUI_App(EAppMode mode) , m_imgui(new ImGuiWrapper()) , m_removable_drive_manager(std::make_unique()) , m_other_instance_message_handler(std::make_unique()) + , m_downloader(std::make_unique()) { //app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp this->init_app_config(); @@ -1127,7 +1141,7 @@ bool GUI_App::on_init_inner() NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled); #endif // initialize label colors and fonts - init_label_colours(); + init_ui_colours(); init_fonts(); std::string older_data_dir_path; @@ -1145,8 +1159,8 @@ bool GUI_App::on_init_inner() if (bool new_dark_color_mode = app_config->get("dark_color_mode") == "1"; init_dark_color_mode != new_dark_color_mode) { NppDarkMode::SetDarkMode(new_dark_color_mode); - init_label_colours(); - update_label_colours_from_appconfig(); + init_ui_colours(); + update_ui_colours_from_appconfig(); } if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; init_sys_menu_enabled != new_sys_menu_enabled) @@ -1258,7 +1272,8 @@ bool GUI_App::on_init_inner() Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) { show_error(nullptr, evt.GetString()); - }); + }); + } else { #ifdef __WXMSW__ @@ -1431,10 +1446,16 @@ const wxColour GUI_App::get_label_default_clr_modified() return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1); } -void GUI_App::init_label_colours() +const std::vector GUI_App::get_mode_default_palette() +{ + return { "#7DF028", "#FFDC00", "#E70000" }; +} + +void GUI_App::init_ui_colours() { m_color_label_modified = get_label_default_clr_modified(); m_color_label_sys = get_label_default_clr_system(); + m_mode_palette = get_mode_default_palette(); bool is_dark_mode = dark_mode(); #ifdef _WIN32 @@ -1450,19 +1471,30 @@ void GUI_App::init_label_colours() m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } -void GUI_App::update_label_colours_from_appconfig() +void GUI_App::update_ui_colours_from_appconfig() { + // load label colors if (app_config->has("label_clr_sys")) { auto str = app_config->get("label_clr_sys"); - if (str != "") + if (!str.empty()) m_color_label_sys = wxColour(str); } if (app_config->has("label_clr_modified")) { auto str = app_config->get("label_clr_modified"); - if (str != "") + if (!str.empty()) m_color_label_modified = wxColour(str); } + + // load mode markers colors + if (app_config->has("mode_palette")) { + const auto colors = app_config->get("mode_palette"); + if (!colors.empty()) { + m_mode_palette.clear(); + if (!unescape_strings_cstyle(colors, m_mode_palette)) + m_mode_palette = get_mode_default_palette(); + } + } } void GUI_App::update_label_colours() @@ -1649,6 +1681,39 @@ void GUI_App::set_label_clr_sys(const wxColour& clr) app_config->save(); } +const std::string& GUI_App::get_mode_btn_color(int mode_id) +{ + assert(0 <= mode_id && size_t(mode_id) < m_mode_palette.size()); + return m_mode_palette[mode_id]; +} + +std::vector GUI_App::get_mode_palette() +{ + return { wxColor(m_mode_palette[0]), + wxColor(m_mode_palette[1]), + wxColor(m_mode_palette[2]) }; +} + +void GUI_App::set_mode_palette(const std::vector& palette) +{ + bool save = false; + + for (size_t mode = 0; mode < palette.size(); ++mode) { + const wxColour& clr = palette[mode]; + std::string color_str = clr == wxTransparentColour ? std::string("") : encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); + if (m_mode_palette[mode] != color_str) { + m_mode_palette[mode] = color_str; + save = true; + } + } + + if (save) { + mainframe->update_mode_markers(); + app_config->set("mode_palette", escape_strings_cstyle(m_mode_palette)); + app_config->save(); + } +} + bool GUI_App::tabs_as_menu() const { return app_config->get("tabs_as_menu") == "1"; // || dark_mode(); @@ -2216,6 +2281,17 @@ bool GUI_App::load_language(wxString language, bool initial) // Override language at the active wxTranslations class (which is stored in the active m_wxLocale) // to load possibly different dictionary, for example, load Czech dictionary for Slovak language. wxTranslations::Get()->SetLanguage(language_dict); + { + // UKR Localization specific workaround till the wxWidgets doesn't fixed: + // From wxWidgets 3.1.6 calls setlocation(0, wxInfoLanguage->LocaleTag), see (https://github.com/prusa3d/wxWidgets/commit/deef116a09748796711d1e3509965ee208dcdf0b#diff-7de25e9a71c4dce61bbf76492c589623d5b93fd1bb105ceaf0662075d15f4472), + // where LocaleTag is a Tag of locale in BCP 47 - like notation. + // For Ukrainian Language LocaleTag == "uk". + // But setlocale(0, "uk") returns "English_United Kingdom.1252" instead of "uk", + // and, as a result, locales are set to English_United Kingdom + + if (language_info->CanonicalName == "uk") + setlocale(0, language_info->GetCanonicalWithRegion().data()); + } m_wxLocale->AddCatalog(SLIC3R_APP_KEY); m_imgui->set_language(into_u8(language_info->CanonicalName)); //FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only. @@ -2776,6 +2852,17 @@ void GUI_App::MacOpenFiles(const wxArrayString &fileNames) start_new_gcodeviewer(&filename); } } + +void GUI_App::MacOpenURL(const wxString& url) +{ + if (app_config && app_config->get("downloader_url_registered") != "1") + { + BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url; + return; + } + start_download(boost::nowide::narrow(url)); +} + #endif /* __APPLE */ Sidebar& GUI_App::sidebar() @@ -2823,7 +2910,7 @@ wxBookCtrlBase* GUI_App::tab_panel() const return mainframe->m_tabpanel; } -NotificationManager * GUI_App::notification_manager() +NotificationManager* GUI_App::notification_manager() { return plater_->get_notification_manager(); } @@ -2833,6 +2920,11 @@ GalleryDialog* GUI_App::gallery_dialog() return mainframe->gallery_dialog(); } +Downloader* GUI_App::downloader() +{ + return m_downloader.get(); +} + // extruders count from selected printer preset int GUI_App::extruders_cnt() const { @@ -3280,5 +3372,23 @@ void GUI_App::app_version_check(bool from_user) m_app_updater->sync_version(version_check_url, from_user); } +void GUI_App::start_download(std::string url) +{ + if (!plater_) { + BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr."; + return; + } + //lets always init so if the download dest folder was changed, new dest is used + boost::filesystem::path dest_folder(app_config->get("url_downloader_dest")); + if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) { + std::string msg = _utf8("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard."); + BOOST_LOG_TRIVIAL(error) << msg; + show_error(nullptr, msg); + return; + } + m_downloader->init(dest_folder); + m_downloader->start_download(url); +} + } // GUI } //Slic3r diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 682aa1d00..81382d092 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -46,6 +46,7 @@ class ObjectList; class ObjectLayers; class Plater; class NotificationManager; +class Downloader; struct GUI_InitParams; class GalleryDialog; @@ -139,6 +140,7 @@ private: wxColour m_color_selected_btn_bg; bool m_force_colors_update { false }; #endif + std::vector m_mode_palette; wxFont m_small_font; wxFont m_bold_font; @@ -164,6 +166,7 @@ private: std::unique_ptr m_other_instance_message_handler; std::unique_ptr m_app_updater; std::unique_ptr m_single_instance_checker; + std::unique_ptr m_downloader; std::string m_instance_hash_string; size_t m_instance_hash_int; @@ -194,8 +197,9 @@ public: static bool dark_mode(); const wxColour get_label_default_clr_system(); const wxColour get_label_default_clr_modified(); - void init_label_colours(); - void update_label_colours_from_appconfig(); + const std::vector get_mode_default_palette(); + void init_ui_colours(); + void update_ui_colours_from_appconfig(); void update_label_colours(); // update color mode for window void UpdateDarkUI(wxWindow *window, bool highlited = false, bool just_font = false); @@ -215,6 +219,9 @@ public: const wxColour& get_label_clr_default() { return m_color_label_default; } const wxColour& get_window_default_clr(){ return m_color_window_default; } + const std::string& get_mode_btn_color(int mode_id); + std::vector get_mode_palette(); + void set_mode_palette(const std::vector &palette); #ifdef _WIN32 const wxColour& get_label_highlight_clr() { return m_color_highlight_label_default; } @@ -287,6 +294,7 @@ public: void OSXStoreOpenFiles(const wxArrayString &files) override; // wxWidgets override to get an event on open files. void MacOpenFiles(const wxArrayString &fileNames) override; + void MacOpenURL(const wxString& url) override; #endif /* __APPLE */ Sidebar& sidebar(); @@ -299,6 +307,7 @@ public: Model& model(); NotificationManager * notification_manager(); GalleryDialog * gallery_dialog(); + Downloader* downloader(); // Parameters extracted from the command line to be passed to GUI after initialization. GUI_InitParams* init_params { nullptr }; @@ -353,6 +362,10 @@ public: void associate_gcode_files(); #endif // __WXMSW__ + + // URL download - PrusaSlicer gets system call to open prusaslicer:// URL which should contain address of download + void start_download(std::string url); + private: bool on_init_inner(); void init_app_config(); @@ -375,6 +388,7 @@ private: void app_version_check(bool from_user); bool m_datadir_redefined { false }; + }; DECLARE_APP(GUI_App) diff --git a/src/slic3r/GUI/GUI_Init.hpp b/src/slic3r/GUI/GUI_Init.hpp index 878edc1d9..14d0c4e28 100644 --- a/src/slic3r/GUI/GUI_Init.hpp +++ b/src/slic3r/GUI/GUI_Init.hpp @@ -30,6 +30,9 @@ struct GUI_InitParams std::vector input_files; bool start_as_gcodeviewer; + bool start_downloader; + bool delete_after_load; + std::string download_url; #if ENABLE_GL_CORE_PROFILE std::pair opengl_version; #if ENABLE_OPENGL_DEBUG_OPTION diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9d0311e63..8a5d6f139 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2657,47 +2657,59 @@ void ObjectList::part_selection_changed() disable_ununiform_scale = true; } else if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { - og_name = _L("Group manipulation"); - const Selection& selection = scene_selection(); - // don't show manipulation panel for case of all Object's parts selection - update_and_show_manipulations = !selection.is_single_full_instance(); - if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) { - if (selection.is_any_volume() || selection.is_any_modifier()) - enable_manipulation = !(*m_objects)[obj_idx]->is_cut(); - else// if (item && m_objects_model->GetItemType(item) == itInstanceRoot) - disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + if (selection.is_single_full_object()) { + og_name = _L("Object manipulation"); + update_and_show_manipulations = true; + + obj_idx = selection.get_object_idx(); + ModelObject* object = (*m_objects)[obj_idx]; + m_config = &object->config; + disable_ss_manipulation = object->is_cut(); } else { - wxDataViewItemArray sels; - GetSelections(sels); - if (selection.is_single_full_object() || selection.is_multiple_full_instance() ) { - int obj_idx = m_objects_model->GetObjectIdByItem(sels.front()); - disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); + og_name = _L("Group manipulation"); + + // don't show manipulation panel for case of all Object's parts selection + update_and_show_manipulations = !selection.is_single_full_instance(); + + if (int obj_idx = selection.get_object_idx(); obj_idx >= 0) { + if (selection.is_any_volume() || selection.is_any_modifier()) + enable_manipulation = !(*m_objects)[obj_idx]->is_cut(); + else// if (item && m_objects_model->GetItemType(item) == itInstanceRoot) + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); } - else if (selection.is_mixed() || selection.is_multiple_full_object()) { - std::map> cut_objects; - - // find cut objects - for (auto item : sels) { - int obj_idx = m_objects_model->GetObjectIdByItem(item); - const ModelObject* obj = object(obj_idx); - if (obj->is_cut()) { - if (cut_objects.find(obj->cut_id) == cut_objects.end()) - cut_objects[obj->cut_id] = std::set{ obj_idx }; - else - cut_objects.at(obj->cut_id).insert(obj_idx); - } + else { + wxDataViewItemArray sels; + GetSelections(sels); + if (selection.is_single_full_object() || selection.is_multiple_full_instance() ) { + int obj_idx = m_objects_model->GetObjectIdByItem(sels.front()); + disable_ss_manipulation = (*m_objects)[obj_idx]->is_cut(); } + else if (selection.is_mixed() || selection.is_multiple_full_object()) { + std::map> cut_objects; - // check if selected cut objects are "full selected" - for (auto cut_object : cut_objects) - if (cut_object.first.check_sum() != cut_object.second.size()) { - disable_ss_manipulation = true; - break; + // find cut objects + for (auto item : sels) { + int obj_idx = m_objects_model->GetObjectIdByItem(item); + const ModelObject* obj = object(obj_idx); + if (obj->is_cut()) { + if (cut_objects.find(obj->cut_id) == cut_objects.end()) + cut_objects[obj->cut_id] = std::set{ obj_idx }; + else + cut_objects.at(obj->cut_id).insert(obj_idx); + } } - disable_ununiform_scale = !cut_objects.empty(); + + // check if selected cut objects are "full selected" + for (auto cut_object : cut_objects) + if (cut_object.first.check_sum() != cut_object.second.size()) { + disable_ss_manipulation = true; + break; + } + disable_ununiform_scale = !cut_objects.empty(); + } } } } @@ -2822,8 +2834,12 @@ void ObjectList::part_selection_changed() panel.Freeze(); #if ENABLE_WORLD_COORDINATE - const ManipulationEditor* const editor = wxGetApp().obj_manipul()->get_focused_editor(); - const std::string opt_key = (editor != nullptr) ? editor->get_full_opt_name() : ""; + std::string opt_key; + if (m_selected_object_id >= 0) { + const ManipulationEditor* const editor = wxGetApp().obj_manipul()->get_focused_editor(); + if (editor != nullptr) + opt_key = editor->get_full_opt_name(); + } wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, !opt_key.empty()); #else wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index bbcad2919..41f4cf060 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -125,8 +125,10 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Load bitmaps to be used for the mirroring buttons: m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on"); +#if !ENABLE_WORLD_COORDINATE m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off"); m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent"); +#endif // !ENABLE_WORLD_COORDINATE const int border = wxOSX ? 0 : 4; const int em = wxGetApp().em_unit(); @@ -265,28 +267,44 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // We will add a button to toggle mirroring to each axis: auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); +#if ENABLE_WORLD_COORDINATE + btn->SetToolTip(_L("Mirror along") + wxString::Format(_L(" %c "), (int)label) + _L("axis")); + + m_mirror_buttons[axis_idx] = btn; +#else btn->SetToolTip(wxString::Format(_L("Toggle %c axis mirroring"), (int)label)); btn->SetBitmapDisabled_(m_mirror_bitmap_hidden); m_mirror_buttons[axis_idx].first = btn; m_mirror_buttons[axis_idx].second = mbShown; +#endif // ENABLE_WORLD_COORDINATE sizer->AddStretchSpacer(2); sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL); btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent&) { +#if !ENABLE_WORLD_COORDINATE Axis axis = (Axis)(axis_idx + X); if (m_mirror_buttons[axis_idx].second == mbHidden) - return; + return; +#endif // !ENABLE_WORLD_COORDINATE GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); #if ENABLE_WORLD_COORDINATE - if (selection.is_single_volume_or_modifier()) { + TransformationType transformation_type; + if (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + + transformation_type.set_relative(); + + selection.setup_cache(); + selection.mirror((Axis)axis_idx, transformation_type); #else if (selection.is_single_volume() || selection.is_single_modifier()) { -#endif // ENABLE_WORLD_COORDINATE GLVolume* volume = const_cast(selection.get_first_volume()); volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); } @@ -302,6 +320,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Update mirroring at the GLVolumes. selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL); selection.synchronize_unselected_volumes(); +#endif // ENABLE_WORLD_COORDINATE // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. canvas->do_mirror(L("Set Mirror")); UpdateAndShow(true); @@ -310,7 +329,12 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : editors_grid_sizer->Add(sizer, 0, wxALIGN_CENTER_HORIZONTAL); } +#if ENABLE_WORLD_COORDINATE + m_mirror_warning_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); + editors_grid_sizer->Add(m_mirror_warning_bitmap, 0, wxALIGN_CENTER_VERTICAL); +#else editors_grid_sizer->AddStretchSpacer(1); +#endif // ENABLE_WORLD_COORDINATE editors_grid_sizer->AddStretchSpacer(1); // add EditBoxes @@ -541,15 +565,15 @@ void ObjectManipulation::Show(const bool show) bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); if (selection.is_single_volume_or_modifier() && m_word_local_combo->GetCount() < 3) { #ifdef __linux__ - m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), 1); + m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Local), 2); #else - m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), wxNullBitmap, 1); + m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Local), wxNullBitmap, 2); #endif // __linux__ m_word_local_combo->Select((int)ECoordinatesType::World); this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection())); } else if (selection.is_single_full_instance() && m_word_local_combo->GetCount() > 2) { - m_word_local_combo->Delete(1); + m_word_local_combo->Delete(2); m_word_local_combo->Select((int)ECoordinatesType::World); this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection())); } @@ -660,7 +684,7 @@ void ObjectManipulation::update_ui_from_settings() void ObjectManipulation::update_settings_value(const Selection& selection) { - m_new_move_label_string = L("Position"); + m_new_move_label_string = L("Position"); m_new_rotate_label_string = L("Rotation"); m_new_scale_label_string = L("Scale factors"); @@ -688,13 +712,16 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { m_new_position = volume->get_instance_offset(); + m_new_scale_label_string = L("Scale"); + m_new_scale = Vec3d(100.0, 100.0, 100.0); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); #else if (m_world_coordinates) { + m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0; + m_new_size = selection.get_scaled_instance_bounding_box().size(); #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); m_new_rotation = Vec3d::Zero(); - m_new_size = selection.get_scaled_instance_bounding_box().size(); - m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0; } else { #if ENABLE_WORLD_COORDINATE @@ -702,12 +729,14 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_rotate_label_string = L("Rotate"); m_new_position = Vec3d::Zero(); m_new_rotation = Vec3d::Zero(); + m_new_scale = Vec3d(100.0, 100.0, 100.0); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); #else m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); + m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); #endif // ENABLE_WORLD_COORDINATE - m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); - m_new_scale = volume->get_instance_scaling_factor() * 100.0; - } + m_new_scale = volume->get_instance_scaling_factor() * 100.0; + } m_new_enabled = true; } @@ -716,9 +745,13 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = box.center(); m_new_rotation = Vec3d::Zero(); m_new_scale = Vec3d(100.0, 100.0, 100.0); - m_new_size = box.size(); +#if ENABLE_WORLD_COORDINATE + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); +#else + m_new_size = box.size(); +#endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); - m_new_scale_label_string = L("Scale"); + m_new_scale_label_string = L("Scale"); m_new_enabled = true; } #if ENABLE_WORLD_COORDINATE @@ -736,9 +769,10 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = offset; m_new_rotate_label_string = L("Rotate"); + m_new_scale_label_string = L("Scale"); + m_new_scale = Vec3d(100.0, 100.0, 100.0); m_new_rotation = Vec3d::Zero(); - m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); - m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0; + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); @@ -746,16 +780,17 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = Vec3d::Zero(); m_new_rotation = Vec3d::Zero(); m_new_scale = volume->get_volume_scaling_factor() * 100.0; - m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); } else { #endif // ENABLE_WORLD_COORDINATE - m_new_position = volume->get_volume_offset(); - m_new_rotate_label_string = L("Rotate"); - m_new_rotation = Vec3d::Zero(); + m_new_position = volume->get_volume_offset(); + m_new_rotate_label_string = L("Rotate"); + m_new_rotation = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE - m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); - m_new_scale = m_new_size.cwiseQuotient(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix_no_scaling_factor()).size()) * 100.0; + m_new_scale_label_string = L("Scale"); + m_new_scale = Vec3d(100.0, 100.0, 100.0); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; @@ -765,17 +800,21 @@ void ObjectManipulation::update_settings_value(const Selection& selection) } else if (obj_list->is_connectors_item_selected() || obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { reset_settings_value(); - m_new_move_label_string = L("Translate"); - m_new_rotate_label_string = L("Rotate"); - m_new_scale_label_string = L("Scale"); + m_new_move_label_string = L("Translate"); + m_new_rotate_label_string = L("Rotate"); + m_new_scale_label_string = L("Scale"); +#if ENABLE_WORLD_COORDINATE + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); +#else m_new_size = selection.get_bounding_box().size(); +#endif // ENABLE_WORLD_COORDINATE m_new_enabled = true; } - else { + else { // No selection, reset the cache. // assert(selection.is_empty()); - reset_settings_value(); - } + reset_settings_value(); + } } void ObjectManipulation::update_if_dirty() @@ -845,7 +884,10 @@ void ObjectManipulation::update_if_dirty() m_lock_bnt->SetLock(m_uniform_scale); m_lock_bnt->SetToolTip(wxEmptyString); m_lock_bnt->enable(); -#if !ENABLE_WORLD_COORDINATE +#if ENABLE_WORLD_COORDINATE + if (m_word_local_combo->GetSelection() != (int)m_coordinates_type) + m_word_local_combo->SetSelection((int)m_coordinates_type); +#else } { @@ -853,7 +895,7 @@ void ObjectManipulation::update_if_dirty() if (m_word_local_combo->GetSelection() != new_selection) m_word_local_combo->SetSelection(new_selection); } -#endif // !ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE if (m_new_enabled) m_og->enable(); @@ -882,6 +924,7 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE bool show_skew = false; + bool show_mirror_warning = false; if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : @@ -891,7 +934,6 @@ void ObjectManipulation::update_reset_buttons_visibility() const GLVolume* volume = selection.get_first_volume(); Transform3d rotation = Transform3d::Identity(); Transform3d scale = Transform3d::Identity(); - Geometry::Transformation skew; #else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* volume = selection.get_first_volume(); @@ -902,16 +944,11 @@ void ObjectManipulation::update_reset_buttons_visibility() if (selection.is_single_full_instance()) { #if ENABLE_WORLD_COORDINATE - const Geometry::Transformation& trafo = volume->get_instance_transformation(); - rotation = trafo.get_rotation_matrix(); - scale = trafo.get_scaling_factor_matrix(); const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int id : idxs) { - const Geometry::Transformation world_trafo(selection.get_volume(id)->world_matrix()); - if (world_trafo.has_skew()) { - skew = world_trafo; - break; - } + const Geometry::Transformation world_trafo(selection.get_volume(id)->world_matrix()); + show_skew |= world_trafo.has_skew(); + show_mirror_warning |= world_trafo.get_matrix().matrix().determinant() < 0.0; } #else rotation = volume->get_instance_rotation(); @@ -925,8 +962,8 @@ void ObjectManipulation::update_reset_buttons_visibility() rotation = trafo.get_rotation_matrix(); scale = trafo.get_scaling_factor_matrix(); const Geometry::Transformation world_trafo(volume->world_matrix()); - if (world_trafo.has_skew()) - skew = world_trafo; + show_skew |= world_trafo.has_skew(); + show_mirror_warning |= world_trafo.get_matrix().matrix().determinant() < 0.0; #else rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); @@ -936,7 +973,6 @@ void ObjectManipulation::update_reset_buttons_visibility() #if ENABLE_WORLD_COORDINATE show_rotation = !rotation.isApprox(Transform3d::Identity()); show_scale = !scale.isApprox(Transform3d::Identity()); - show_skew = skew.has_skew(); #else show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); @@ -945,7 +981,7 @@ void ObjectManipulation::update_reset_buttons_visibility() } #if ENABLE_WORLD_COORDINATE - wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed, show_skew] { + wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed, show_skew, show_mirror_warning] { #else wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { #endif // ENABLE_WORLD_COORDINATE @@ -959,6 +995,9 @@ void ObjectManipulation::update_reset_buttons_visibility() #if ENABLE_WORLD_COORDINATE m_reset_skew_button->Show(show_skew); m_skew_label->Show(show_skew); + m_mirror_warning_bitmap->SetBitmap(show_mirror_warning ? m_manifold_warning_bmp.bmp() : wxNullBitmap); + m_mirror_warning_bitmap->SetMinSize(show_mirror_warning ? m_manifold_warning_bmp.GetSize() : wxSize(0, 0)); + m_mirror_warning_bitmap->SetToolTip(show_mirror_warning ? _L("Left handed") : ""); #endif // ENABLE_WORLD_COORDINATE // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time @@ -977,16 +1016,14 @@ void ObjectManipulation::update_mirror_buttons_visibility() { GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - std::array new_states = {mbHidden, mbHidden, mbHidden}; #if ENABLE_WORLD_COORDINATE if (is_local_coordinates()) { -#else - if (!m_world_coordinates) { -#endif // ENABLE_WORLD_COORDINATE -#if ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { #else + std::array new_states = { mbHidden, mbHidden, mbHidden }; + + if (!m_world_coordinates) { if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE const GLVolume* volume = selection.get_first_volume(); @@ -997,10 +1034,19 @@ void ObjectManipulation::update_mirror_buttons_visibility() else mirror = volume->get_volume_mirror(); +#if !ENABLE_WORLD_COORDINATE for (unsigned char i=0; i<3; ++i) new_states[i] = (mirror[i] < 0. ? mbActive : mbShown); +#endif // !ENABLE_WORLD_COORDINATE } } + +#if ENABLE_WORLD_COORDINATE + const bool can_mirror = wxGetApp().plater()->can_mirror(); + for (ScalableButton* button : m_mirror_buttons) { + button->Enable(can_mirror); + } +#else else { // the mirroring buttons should be hidden in world coordinates, // unless we make it actually mirror in world coords. @@ -1022,6 +1068,7 @@ void ObjectManipulation::update_mirror_buttons_visibility() } } }); +#endif // ENABLE_WORLD_COORDINATE } @@ -1059,8 +1106,8 @@ wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) switch (type) { case ECoordinatesType::World: { return _L("World coordinates"); } - case ECoordinatesType::Instance: { return _L("Instance coordinates"); } - case ECoordinatesType::Local: { return _L("Local coordinates"); } + case ECoordinatesType::Instance: { return _L("Object coordinates"); } + case ECoordinatesType::Local: { return _L("Part coordinates"); } default: { assert(false); return _L("Unknown"); } } } @@ -1162,13 +1209,28 @@ void ObjectManipulation::change_scale_value(int axis, double value) return; Vec3d scale = m_cache.scale; - scale(axis) = value; + scale(axis) = value; +#if ENABLE_WORLD_COORDINATE + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + Vec3d ref_scale = m_cache.scale; + if (selection.is_single_volume_or_modifier()) { + if (is_local_coordinates()) + ref_scale = 100.0 * Vec3d::Ones(); + } + else if (selection.is_single_full_instance()) { + scale = scale.cwiseQuotient(ref_scale); + ref_scale = Vec3d::Ones(); + } + + this->do_scale(axis, scale.cwiseQuotient(ref_scale)); +#else this->do_scale(axis, 0.01 * scale); +#endif // ENABLE_WORLD_COORDINATE m_cache.scale = scale; - m_cache.scale_rounded(axis) = DBL_MAX; - this->UpdateAndShow(true); + m_cache.scale_rounded(axis) = DBL_MAX; + this->UpdateAndShow(true); } @@ -1195,9 +1257,12 @@ void ObjectManipulation::change_size_value(int axis, double value) Vec3d ref_size = m_cache.size; #if ENABLE_WORLD_COORDINATE if (selection.is_single_volume_or_modifier()) { + if (is_local_coordinates()) + ref_size = selection.get_first_volume()->bounding_box().size(); + size = size.cwiseQuotient(ref_size); + ref_size = Vec3d::Ones(); #else if (selection.is_single_volume() || selection.is_single_modifier()) { -#endif // ENABLE_WORLD_COORDINATE const GLVolume* v = selection.get_first_volume(); const Vec3d local_size = size.cwiseQuotient(v->get_instance_scaling_factor()); const Vec3d local_ref_size = v->bounding_box().size().cwiseProduct(v->get_volume_scaling_factor()); @@ -1205,15 +1270,20 @@ void ObjectManipulation::change_size_value(int axis, double value) size = local_change.cwiseProduct(v->get_volume_scaling_factor()); ref_size = Vec3d::Ones(); +#endif // ENABLE_WORLD_COORDINATE } - else if (selection.is_single_full_instance()) + else if (selection.is_single_full_instance()) { #if ENABLE_WORLD_COORDINATE - ref_size = is_world_coordinates() ? + if (is_world_coordinates()) + ref_size = selection.get_unscaled_instance_bounding_box().size(); + size = size.cwiseQuotient(ref_size); + ref_size = Vec3d::Ones(); #else ref_size = m_world_coordinates ? -#endif // ENABLE_WORLD_COORDINATE selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_first_volume()->object_idx()]->raw_mesh_bounding_box().size(); +#endif // ENABLE_WORLD_COORDINATE + } #if ENABLE_WORLD_COORDINATE this->do_size(axis, size.cwiseQuotient(ref_size)); @@ -1222,8 +1292,8 @@ void ObjectManipulation::change_size_value(int axis, double value) #endif // ENABLE_WORLD_COORDINATE m_cache.size = size; - m_cache.size_rounded(axis) = DBL_MAX; - this->UpdateAndShow(true); + m_cache.size_rounded(axis) = DBL_MAX; + this->UpdateAndShow(true); } void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const @@ -1240,7 +1310,8 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); - if (!selection.is_single_full_instance() && !selection.is_single_volume_or_modifier()) + if (!(selection.is_single_volume_or_modifier() && is_local_coordinates()) && + !(selection.is_single_full_instance() && is_instance_coordinates())) transformation_type.set_relative(); const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; @@ -1425,15 +1496,23 @@ void ObjectManipulation::sys_color_changed() editor->sys_color_changed(this); m_mirror_bitmap_on.sys_color_changed(); +#if !ENABLE_WORLD_COORDINATE m_mirror_bitmap_off.sys_color_changed(); m_mirror_bitmap_hidden.sys_color_changed(); +#endif // !ENABLE_WORLD_COORDINATE m_reset_scale_button->sys_color_changed(); m_reset_rotation_button->sys_color_changed(); m_drop_to_bed_button->sys_color_changed(); m_lock_bnt->sys_color_changed(); +#if ENABLE_WORLD_COORDINATE + for (int id = 0; id < 3; ++id) { + m_mirror_buttons[id]->sys_color_changed(); + } +#else for (int id = 0; id < 3; ++id) m_mirror_buttons[id].first->sys_color_changed(); +#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 9995b3e6f..69022dde7 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -132,6 +132,9 @@ private: wxCheckBox* m_check_inch {nullptr}; +#if ENABLE_WORLD_COORDINATE + std::array m_mirror_buttons; +#else // Mirroring buttons and their current state enum MirrorButtonState { mbHidden, @@ -139,18 +142,21 @@ private: mbActive }; std::array, 3> m_mirror_buttons; +#endif // ENABLE_WORLD_COORDINATE // Bitmaps for the mirroring buttons. ScalableBitmap m_mirror_bitmap_on; +#if !ENABLE_WORLD_COORDINATE ScalableBitmap m_mirror_bitmap_off; ScalableBitmap m_mirror_bitmap_hidden; +#endif // !ENABLE_WORLD_COORDINATE // Needs to be updated from OnIdle? bool m_dirty = false; // Cached labels for the delayed update, not localized! std::string m_new_move_label_string; - std::string m_new_rotate_label_string; - std::string m_new_scale_label_string; + std::string m_new_rotate_label_string; + std::string m_new_scale_label_string; Vec3d m_new_position; Vec3d m_new_rotation; Vec3d m_new_scale; @@ -167,7 +173,10 @@ private: choice_ctrl* m_word_local_combo { nullptr }; ScalableBitmap m_manifold_warning_bmp; - wxStaticBitmap* m_fix_throught_netfab_bitmap; + wxStaticBitmap* m_fix_throught_netfab_bitmap{ nullptr }; +#if ENABLE_WORLD_COORDINATE + wxStaticBitmap* m_mirror_warning_bitmap{ nullptr }; +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE // Currently focused editor (nullptr if none) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 599a54b3f..4235bf941 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -15,6 +15,7 @@ #include "libslic3r/TriangleMeshSlicer.hpp" #include "imgui/imgui_internal.h" +#include "slic3r/GUI/MsgDialog.hpp" namespace Slic3r { namespace GUI { @@ -30,6 +31,7 @@ static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY(); static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::DARK_GRAY(); static const ColorRGBA CONNECTOR_DEF_COLOR = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); static const ColorRGBA CONNECTOR_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); +static const ColorRGBA HOVERED_ERR_COLOR = ColorRGBA(1.0f, 0.3f, 0.3f, 1.0f); const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; @@ -359,10 +361,19 @@ void GLGizmoCut3D::put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp } } +// returns true if the camera (forward) is pointing in the negative direction of the cut normal +bool GLGizmoCut3D::is_looking_forward() const +{ + const Camera& camera = wxGetApp().plater()->get_camera(); + const double dot = camera.get_dir_forward().dot(m_cut_normal); + return dot < 0.05; +} + void GLGizmoCut3D::update_clipper() { BoundingBoxf3 box = bounding_box(); + // update cut_normal Vec3d beg, end = beg = m_plane_center; beg[Z] = box.center().z() - m_radius; end[Z] = box.center().z() + m_radius; @@ -370,14 +381,29 @@ void GLGizmoCut3D::update_clipper() rotate_vec3d_around_plane_center(beg); rotate_vec3d_around_plane_center(end); - double dist = (m_plane_center - beg).norm(); + // calculate normal for cut plane + Vec3d normal = m_cut_normal = end - beg; + m_cut_normal.normalize(); + + if (!is_looking_forward()) { + end = beg = m_plane_center; + beg[Z] = box.center().z() + m_radius; + end[Z] = box.center().z() - m_radius; + + rotate_vec3d_around_plane_center(beg); + rotate_vec3d_around_plane_center(end); + + // recalculate normal for clipping plane, if camera is looking downward to cut plane + normal = end - beg; + if (normal == Vec3d::Zero()) + return; + } // calculate normal and offset for clipping plane - Vec3d normal = end - beg; - if (normal == Vec3d::Zero()) - return; + double dist = (m_plane_center - beg).norm(); dist = std::clamp(dist, 0.0001, normal.norm()); normal.normalize(); + m_clp_normal = normal; const double offset = normal.dot(beg) + dist; m_c->object_clipper()->set_range_and_pos(normal, offset, dist); @@ -888,7 +914,7 @@ void GLGizmoCut3D::on_register_raycasters_for_picking() m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i + m_connectors_group_id, *(m_shapes[connectors[i].attribs]).mesh_raycaster, Transform3d::Identity())); } } - else { + else if (!cut_line_processing()) { m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, X, *m_cone.mesh_raycaster, Transform3d::Identity())); m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, X, *m_cone.mesh_raycaster, Transform3d::Identity())); @@ -949,9 +975,6 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() const Vec3d& instance_offset = mo->instances[inst_id]->get_offset(); const double sla_shift = double(m_c->selection_info()->get_sla_shift()); - const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); - const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; - for (size_t i = 0; i < connectors.size(); ++i) { const CutConnector& connector = connectors[i]; @@ -960,7 +983,7 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() Vec3d pos = connector.pos + instance_offset; if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prizm) { - pos -= height * normal; + pos -= height * m_clp_normal; height *= 2; } pos[Z] += sla_shift; @@ -969,7 +992,7 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform() m_raycasters[i]->set_transform(translation_transform(pos) * m_rotation_m * scale_trafo); } } - else { + else if (!cut_line_processing()){ const Transform3d trafo = translation_transform(m_plane_center) * m_rotation_m; const BoundingBoxf3 box = bounding_box(); @@ -1144,11 +1167,11 @@ void GLGizmoCut3D::dragging_grabber_xy(const GLGizmoBase::UpdateData &data) void GLGizmoCut3D::dragging_connector(const GLGizmoBase::UpdateData &data) { CutConnectors& connectors = m_c->selection_info()->model_object()->cut_connectors; - std::pair pos_and_normal; + Vec3d pos; Vec3d pos_world; - if (unproject_on_cut_plane(data.mouse_pos.cast(), pos_and_normal, pos_world)) { - connectors[m_hover_id - m_connectors_group_id].pos = pos_and_normal.first; + if (unproject_on_cut_plane(data.mouse_pos.cast(), pos, pos_world)) { + connectors[m_hover_id - m_connectors_group_id].pos = pos; update_raycasters_for_picking_transform(); } } @@ -1354,10 +1377,14 @@ void GLGizmoCut3D::init_rendering_items() init_from_angle_arc(m_angle_arc, m_angle, m_grabber_connection_len); if (!m_plane.is_initialized() && !m_hide_cut_plane && !m_connectors_editing) { +#if 1 + m_plane.init_from(its_make_frustum_dowel((double)m_cut_plane_radius_koef * m_radius, 0.3, m_cut_plane_as_circle ? 180 : 4)); +#else if (m_cut_plane_as_circle) m_plane.init_from(its_make_frustum_dowel(2. * m_radius, 0.3, 180)); else m_plane.init_from(its_make_square_plane(float(m_radius))); +#endif } } @@ -1376,6 +1403,8 @@ void GLGizmoCut3D::on_render() update_clipper_on_render(); m_c->object_clipper()->set_behavior(m_connectors_editing, m_connectors_editing, 0.4); } + else + update_clipper(); init_picking_models(); @@ -1395,9 +1424,14 @@ void GLGizmoCut3D::on_render() m_selection_rectangle.render(m_parent); } -void GLGizmoCut3D::render_debug_input_window() +void GLGizmoCut3D::render_debug_input_window(float x) { + return; m_imgui->begin(wxString("DEBUG")); + + ImVec2 pos = ImGui::GetWindowPos(); + pos.x = x; + ImGui::SetWindowPos(pos, ImGuiCond_Always); /* static bool hide_clipped = false; static bool fill_cut = false; @@ -1410,12 +1444,20 @@ void GLGizmoCut3D::render_debug_input_window() m_imgui->slider_float("contour_width", &contour_width, 0.f, 3.f); if (auto oc = m_c->object_clipper()) oc->set_behavior(hide_clipped || m_connectors_editing, fill_cut || m_connectors_editing, double(contour_width)); +*/ + ImGui::PushItemWidth(0.5f * m_label_width); + if (auto oc = m_c->object_clipper(); oc && m_imgui->slider_float("contour_width", &m_contour_width, 0.f, 3.f)) + oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); ImGui::Separator(); -*/ + if (m_imgui->checkbox(_L("Render cut plane as circle"), m_cut_plane_as_circle)) m_plane.reset(); + ImGui::PushItemWidth(0.5f * m_label_width); + if (m_imgui->slider_float("cut_plane_radius_koef", &m_cut_plane_radius_koef, 1.f, 2.f)) + m_plane.reset(); + m_imgui->end(); } @@ -1457,10 +1499,19 @@ void GLGizmoCut3D::render_shortcuts() if (m_imgui->button("? " + (m_show_shortcuts ? wxString(ImGui::CollapseBtn) : wxString(ImGui::ExpandBtn)))) m_show_shortcuts = !m_show_shortcuts; + if (m_shortcut_label_width < 0.f) { + for (const auto& shortcut : m_shortcuts) { + const float width = m_imgui->calc_text_size(shortcut.first).x; + if (m_shortcut_label_width < width) + m_shortcut_label_width = width; + } + m_shortcut_label_width += +m_imgui->scaled(1.f); + } + if (m_show_shortcuts) for (const auto&shortcut : m_shortcuts ){ m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, shortcut.first); - ImGui::SameLine(m_label_width); + ImGui::SameLine(m_shortcut_label_width); m_imgui->text(shortcut.second); } } @@ -1534,7 +1585,6 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) ImGui::Separator(); if (m_imgui->button(_L("Confirm connectors"))) { - m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); unselect_all_connectors(); set_connectors_editing(false); } @@ -1542,7 +1592,6 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) ImGui::SameLine(2.75f * m_label_width); if (m_imgui->button(_L("Cancel"))) { - m_clp_normal = m_c->object_clipper()->get_clipping_plane()->get_normal(); reset_connectors(); set_connectors_editing(false); } @@ -1602,8 +1651,8 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::AlignTextToFramePadding(); m_imgui->text(wxString(ImGui::InfoMarkerSmall)); ImGui::SameLine(); - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Hold SHIFT key and connect some two points of an object to cut by line")); - + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, + get_wraped_wxString(_L("Hold SHIFT key and connect some two points of an object to cut by line"), 40)); ImGui::Separator(); render_build_size(); @@ -1620,13 +1669,6 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) reset_cut_plane(); m_imgui->disabled_end(); - ImGui::SameLine(2.25f * m_label_width); - ImGui::PushItemWidth(0.75f * m_label_width); - m_is_contour_changed = m_imgui->slider_float("contour width", &m_contour_width, 0.f, 3.f); - - if (auto oc = m_c->object_clipper(); oc && m_is_contour_changed) - oc->set_behavior(m_connectors_editing, m_connectors_editing, double(m_contour_width)); - m_imgui->disabled_begin(!m_keep_upper || !m_keep_lower); if (m_imgui->button(_L("Add/Edit connectors"))) set_connectors_editing(true); @@ -1634,18 +1676,25 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) ImGui::Separator(); - auto render_part_action_line = [this, connectors](const wxString& label, const wxString& suffix, bool& keep_part, bool& place_on_cut_part, bool& rotate_part) { + float label_width = 0; + for (const wxString& label : {_L("Upper part"), _L("Lower part")}) { + const float width = m_imgui->calc_text_size(label).x + m_imgui->scaled(1.5f); + if (label_width < width) + label_width = width; + } + + auto render_part_action_line = [this, label_width, connectors](const wxString& label, const wxString& suffix, bool& keep_part, bool& place_on_cut_part, bool& rotate_part) { bool keep = true; ImGui::AlignTextToFramePadding(); m_imgui->text(label); - ImGui::SameLine(m_label_width); + ImGui::SameLine(label_width); m_imgui->disabled_begin(!connectors.empty()); m_imgui->checkbox(_L("Keep") + suffix, connectors.empty() ? keep_part : keep); m_imgui->disabled_end(); - ImGui::SameLine(2 * m_label_width); + ImGui::SameLine(); m_imgui->disabled_begin(!keep_part); if (m_imgui->checkbox(_L("Place on cut") + suffix, place_on_cut_part)) @@ -1789,7 +1838,8 @@ void GLGizmoCut3D::on_render_input_window(float x, float y, float bottom_limit) m_imgui->end(); - render_debug_input_window(); + if (!m_connectors_editing) // connectors mode + render_debug_input_window(x); } // get volume transformation regarding to the "border". Border is related from the size of connectors @@ -1824,7 +1874,7 @@ Transform3d GLGizmoCut3D::get_volume_transformation(const ModelVolume* volume) c return translation_transform(offset) * scale_transform(Vec3d::Ones() - border_scale) * vol_matrix; } -bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) +bool GLGizmoCut3D::is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) { // check if connector pos is out of clipping plane if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(cur_pos)) { @@ -1832,16 +1882,54 @@ bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& co return true; } + // check if connector bottom contour is out of clipping plane const CutConnector& cur_connector = connectors[idx]; + const CutConnectorShape shape = CutConnectorShape(cur_connector.attribs.shape); + const int sectorCount = shape == CutConnectorShape::Triangle ? 3 : + shape == CutConnectorShape::Square ? 4 : + shape == CutConnectorShape::Circle ? 60: // supposably, 60 points are enough for conflict detection + shape == CutConnectorShape::Hexagon ? 6 : 1 ; + + indexed_triangle_set mesh; + auto& vertices = mesh.vertices; + vertices.reserve(sectorCount + 1); + + float fa = 2 * PI / sectorCount; + auto vec = Eigen::Vector2f(0, cur_connector.radius); + for (float angle = 0; angle < 2.f * PI; angle += fa) { + Vec2f p = Eigen::Rotation2Df(angle) * vec; + vertices.emplace_back(Vec3f(p(0), p(1), 0.f)); + } + its_transform(mesh, translation_transform(cur_pos) * m_rotation_m); + + for (auto vertex : vertices) { + if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(vertex.cast())) { + m_info_stats.outside_cut_contour++; + return true; + } + } + + return false; +} + +bool GLGizmoCut3D::is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos) +{ + if (is_outside_of_cut_contour(idx, connectors, cur_pos)) + return true; + + const CutConnector& cur_connector = connectors[idx]; + const Transform3d matrix = translation_transform(cur_pos) * m_rotation_m * scale_transform(Vec3f(cur_connector.radius, cur_connector.radius, cur_connector.height).cast()); const BoundingBoxf3 cur_tbb = m_shapes[cur_connector.attribs].model.get_bounding_box().transformed(matrix); + // check if connector's bounding box is inside the object's bounding box if (!bounding_box().contains(cur_tbb)) { m_info_stats.outside_bb++; return true; } + // check if connectors are overlapping for (size_t i = 0; i < connectors.size(); ++i) { if (i == idx) continue; @@ -1881,9 +1969,6 @@ void GLGizmoCut3D::render_connectors() const Vec3d& instance_offset = mi->get_offset(); const double sla_shift = double(m_c->selection_info()->get_sla_shift()); - const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(); - const Vec3d& normal = cp && cp->is_active() ? cp->get_normal() : m_clp_normal; - m_has_invalid_connector = false; m_info_stats.invalidate(); @@ -1895,7 +1980,8 @@ void GLGizmoCut3D::render_connectors() Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); // First decide about the color of the point. - if (is_conflict_for_connector(i, connectors, pos)) { + const bool conflict_connector = is_conflict_for_connector(i, connectors, pos); + if (conflict_connector) { m_has_invalid_connector = true; render_color = CONNECTOR_ERR_COLOR; } @@ -1905,16 +1991,23 @@ void GLGizmoCut3D::render_connectors() if (!m_connectors_editing) render_color = CONNECTOR_ERR_COLOR; else if (size_t(m_hover_id - m_connectors_group_id) == i) - render_color = connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; + render_color = conflict_connector ? HOVERED_ERR_COLOR : + connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; else if (m_selected[i]) render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR; const Camera& camera = wxGetApp().plater()->get_camera(); if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prizm) { - pos -= height * normal; + if (is_looking_forward()) + pos -= height * m_clp_normal; + else + pos += height * m_clp_normal; height *= 2; } + else if (!is_looking_forward()) + pos += 0.05 * m_clp_normal; + const Transform3d view_model_matrix = camera.get_view_matrix() * translation_transform(pos) * m_rotation_m * scale_transform(Vec3f(connector.radius, connector.radius, height).cast()); @@ -2004,44 +2097,41 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) // Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal // Return false if no intersection was found, true otherwise. -bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, std::pair& pos_and_normal, Vec3d& pos_world) +bool GLGizmoCut3D::unproject_on_cut_plane(const Vec2d& mouse_position, Vec3d& pos, Vec3d& pos_world) { const float sla_shift = m_c->selection_info()->get_sla_shift(); const ModelObject* mo = m_c->selection_info()->model_object(); const ModelInstance* mi = mo->instances[m_c->selection_info()->get_active_instance()]; - const Transform3d instance_trafo = sla_shift > 0.f ? - translation_transform(sla_shift * Vec3d::UnitZ()) * mi->get_transformation().get_matrix() : mi->get_transformation().get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - ++mesh_id; - if (!mv->is_model_part()) - continue; - Vec3f normal; - Vec3f hit; - bool clipping_plane_was_hit = false; + // Calculate intersection with the clipping plane. + const ClippingPlane* cp = m_c->object_clipper()->get_clipping_plane(true); + Vec3d point; + Vec3d direction; + Vec3d hit; + MeshRaycaster::line_from_mouse_pos(mouse_position, Transform3d::Identity(), camera, point, direction); + Vec3d normal = -cp->get_normal().cast(); + double den = normal.dot(direction); + if (den != 0.) { + double t = (-cp->get_offset() - normal.dot(point))/den; + hit = (point + t * direction); + } else + return false; + + if (! m_c->object_clipper()->is_projection_inside_cut(hit)) + return false; -// const Transform3d volume_trafo = get_volume_transformation(mv); - const Transform3d volume_trafo = mv->get_transformation().get_matrix(); + // recalculate hit to object's local position + Vec3d hit_d = hit; + hit_d -= mi->get_offset(); + hit_d[Z] -= sla_shift; - m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(mouse_position, instance_trafo * volume_trafo, - camera, hit, normal, m_c->object_clipper()->get_clipping_plane(true), - nullptr, &clipping_plane_was_hit); - if (clipping_plane_was_hit) { - // recalculate hit to object's local position - Vec3d hit_d = hit.cast(); - hit_d -= mi->get_offset(); - hit_d[Z] -= sla_shift; + // Return both the point and the facet normal. + pos = hit_d; + pos_world = hit; - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit_d, normal.cast()); - pos_world = hit.cast(); - return true; - } - } - return false; + return true; } void GLGizmoCut3D::clear_selection() @@ -2137,17 +2227,13 @@ bool GLGizmoCut3D::add_connector(CutConnectors& connectors, const Vec2d& mouse_p if (!m_connectors_editing) return false; - std::pair pos_and_normal; + Vec3d pos; Vec3d pos_world; - if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal, pos_world)) { - // check if pos is out of enabled clipping plane - if (m_c->object_clipper() && !m_c->object_clipper()->is_projection_inside_cut(pos_world)) - return true; - + if (unproject_on_cut_plane(mouse_position.cast(), pos, pos_world)) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add connector"), UndoRedo::SnapshotType::GizmoAction); unselect_all_connectors(); - connectors.emplace_back(pos_and_normal.first, m_rotation_m, + connectors.emplace_back(pos, m_rotation_m, m_connector_size * 0.5f, m_connector_depth_ratio, m_connector_size_tolerance, m_connector_depth_ratio_tolerance, CutConnectorAttributes( CutConnectorType(m_connector_type), @@ -2235,19 +2321,22 @@ void GLGizmoCut3D::process_selection_rectangle(CutConnectors &connectors) bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) { - if (is_dragging() || m_connector_mode == CutConnectorMode::Auto || (!m_keep_upper || !m_keep_lower)) + if (is_dragging() || m_connector_mode == CutConnectorMode::Auto) return false; if ( m_hover_id < 0 && shift_down && ! m_connectors_editing && (action == SLAGizmoEventType::LeftDown || action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::Moving) ) return process_cut_line(action, mouse_position); + if (!m_keep_upper || !m_keep_lower) + return false; + if (!m_connectors_editing) { if (0 && action == SLAGizmoEventType::LeftDown) { // disable / enable current contour - std::pair pos_and_normal; + Vec3d pos; Vec3d pos_world; - if (unproject_on_cut_plane(mouse_position.cast(), pos_and_normal, pos_world)) { + if (unproject_on_cut_plane(mouse_position.cast(), pos, pos_world)) { // Following would inform the clipper about the mouse click, so it can // toggle the respective contour as disabled. m_c->object_clipper()->pass_mouse_click(pos_world); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 8a3ee6d07..6158c0405 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -73,6 +73,7 @@ class GLGizmoCut3D : public GLGizmoBase GLModel m_angle_arc; Vec3d m_old_center; + Vec3d m_cut_normal; struct InvalidConnectorsStatistics { @@ -96,7 +97,7 @@ class GLGizmoCut3D : public GLGizmoBase bool m_hide_cut_plane{ false }; bool m_connectors_editing{ false }; - bool m_cut_plane_as_circle{ true }; + bool m_cut_plane_as_circle{ false }; float m_connector_depth_ratio{ 3.f }; float m_connector_size{ 2.5f }; @@ -110,7 +111,9 @@ class GLGizmoCut3D : public GLGizmoBase bool force_update_clipper_on_render{false}; float m_contour_width{ 0.4f }; + float m_cut_plane_radius_koef{ 1.5f }; bool m_is_contour_changed{ false }; + float m_shortcut_label_width{ -1.f }; mutable std::vector m_selected; // which pins are currently selected int m_selected_count{ 0 }; @@ -155,11 +158,12 @@ public: GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); std::string get_tooltip() const override; - bool unproject_on_cut_plane(const Vec2d& mouse_pos, std::pair& pos_and_normal, Vec3d& pos_world); + bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); bool is_in_editing_mode() const override { return m_connectors_editing; } bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); } + bool is_looking_forward() const; /// /// Drag of plane @@ -197,7 +201,7 @@ protected: void on_stop_dragging() override; void on_render() override; - void render_debug_input_window(); + void render_debug_input_window(float x); void adjust_window_position(float x, float y, float bottom_limit); void unselect_all_connectors(); void select_all_connectors(); @@ -239,6 +243,7 @@ private: bool render_reset_button(const std::string& label_id, const std::string& tooltip) const; bool render_connect_type_radio_button(CutConnectorType type); Transform3d get_volume_transformation(const ModelVolume* volume) const; + bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos); void render_connectors(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp index 13b1c52ba..891e1e29e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp @@ -54,7 +54,8 @@ #define SHOW_ICONS_TEXTURE #define SHOW_FINE_POSITION // draw convex hull around volume #define SHOW_WX_WEIGHT_INPUT -#define DRAW_PLACE_TO_ADD_TEXT +#define DRAW_PLACE_TO_ADD_TEXT // Interactive draw of window position +#define ALLOW_OPEN_NEAR_VOLUME #endif // ALLOW_DEBUG_MODE using namespace Slic3r; @@ -62,30 +63,26 @@ using namespace Slic3r::Emboss; using namespace Slic3r::GUI; using namespace Slic3r::GUI::Emboss; -// anonymous namespace for unique names -namespace { -template -struct MinMax -{ - T min; - T max; -}; -template -struct Limit -{ +namespace priv { +template struct MinMax { T min; T max;}; +template struct Limit { + // Limitation for view slider range in GUI MinMax gui; + // Real limits for setting exacts values MinMax values; }; -struct Limits + +// Variable keep limits for variables +static const struct Limits { - MinMax emboss{0.01f, 1e4f}; - MinMax size_in_mm{0.1f, 1000.f}; - Limit boldness{{-200.f, 200.f}, {-2e4f, 2e4f}}; - Limit skew{{-1.f, 1.f}, {-100.f, 100.f}}; - MinMax char_gap{-20000, 20000}; - MinMax line_gap{-20000, 20000}; + MinMax emboss{0.01f, 1e4f}; // in mm + MinMax size_in_mm{0.1f, 1000.f}; // in mm + Limit boldness{{-200.f, 200.f}, {-2e4f, 2e4f}}; // in font points + Limit skew{{-1.f, 1.f}, {-100.f, 100.f}}; // ration without unit + MinMax char_gap{-20000, 20000}; // in font points + MinMax line_gap{-20000, 20000}; // in font points // distance text object from surface - MinMax angle{-180.f, 180.f}; // in mm + MinMax angle{-180.f, 180.f}; // in degrees template static bool apply(std::optional &val, const MinMax &limit) { @@ -106,8 +103,7 @@ struct Limits } return false; } -}; -static const Limits limits; +} limits; static bool is_text_empty(const std::string &text){ return text.empty() || @@ -122,8 +118,7 @@ template void to_range_pi_pi(T& angle) angle -= static_cast(count * 2 * PI); } } - -} // namespace +} // namespace priv GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) : GLGizmoBase(parent, M_ICON_FILENAME, -2) @@ -134,7 +129,7 @@ GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D &parent) , m_update_job_cancel(nullptr) { m_rotate_gizmo.set_group_id(0); - m_rotate_gizmo.set_using_local_coordinate(true); + m_rotate_gizmo.set_force_local_coordinate(true); // TODO: add suggestion to use https://fontawesome.com/ // (copy & paste) unicode symbols from web // paste HEX unicode into notepad move cursor after unicode press [alt] + [x] @@ -204,23 +199,6 @@ static void find_closest_volume(const Selection &selection, /// Define params of text /// Screen coordinat, where to create new object laying on bed static void start_create_object_job(DataBase &emboss_data, const Vec2d &coor); - -static void message_disable_cut_surface(){ - wxMessageBox(_L("Can NOT cut surface from nothing. Function 'use surface' was disabled for this text."), - _L("Disable 'use surface' from style"), wxOK | wxICON_WARNING);} - -/// -/// Create transformation for new created emboss object by mouse position -/// -/// Define where to add object -/// Actual camera view -/// Define shape of bed for its center and check that coor is on bed center -/// Emboss size / 2 -/// Transformation for create text on bed -static Transform3d create_transformation_on_bed(const Vec2d &screen_coor, - const Camera &camera, - const std::vector &bed_shape, - double z); } // namespace priv bool priv::is_valid(ModelVolumeType volume_type){ @@ -251,6 +229,7 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type, const Vec2d& mous priv::start_create_object_job(emboss_data, mouse_pos); } +// Designed for create volume without information of mouse in scene void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) { if (!priv::is_valid(volume_type)) return; @@ -264,29 +243,42 @@ void GLGizmoEmboss::create_volume(ModelVolumeType volume_type) Size s = m_parent.get_canvas_size(); Vec2d screen_center(s.get_width() / 2., s.get_height() / 2.); - DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager); - if (!selection.is_empty() && object_idx >= 0) { - // create volume inside of object - const Plater &plater = *wxGetApp().plater(); - const Camera &camera = plater.get_camera(); - const ModelObjectPtrs &objects = wxGetApp().model().objects; - - Vec2d coor; - const GLVolume *vol = nullptr; - priv::find_closest_volume(selection, screen_center, camera, objects, &coor, &vol); - if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, coor, vol, m_raycast_manager)) { - assert(vol != nullptr); - // in centroid of convex hull is not hit with object - // soo create transfomation on border of object - const ModelObject *obj = objects[vol->object_idx()]; - const BoundingBoxf3& bb = obj->bounding_box(); - Transform3d volume_trmat(Eigen::Translation3d(bb.max.x(), 0., 0.)); - priv::start_create_volume_job(obj, volume_trmat, emboss_data, volume_type); - } - } else { + DataBase emboss_data = priv::create_emboss_data_base(m_text, m_style_manager); + const ModelObjectPtrs &objects = selection.get_model()->objects; + // No selected object so create new object + if (selection.is_empty() || object_idx < 0 || static_cast(object_idx) >= objects.size()) { // create Object on center of screen - // when ray throw center of screen not hit bed it create object on center of bed + // when ray throw center of screen not hit bed it create object on center of bed priv::start_create_object_job(emboss_data, screen_center); + return; + } + + // create volume inside of selected object + Vec2d coor; + const GLVolume *vol = nullptr; + const Camera &camera = wxGetApp().plater()->get_camera(); + priv::find_closest_volume(selection, screen_center, camera, objects, &coor, &vol); + if (!priv::start_create_volume_on_surface_job(emboss_data, volume_type, coor, vol, m_raycast_manager)) { + assert(vol != nullptr); + // in centroid of convex hull is not hit with object + // soo create transfomation on border of object + + // there is no point on surface so no use of surface will be applied + FontProp &prop = emboss_data.text_configuration.style.prop; + if (prop.use_surface) + prop.use_surface = false; + + // Transformation is inspired add generic volumes in ObjectList::load_generic_subobject + const ModelObject *obj = objects[vol->object_idx()]; + BoundingBoxf3 instance_bb = obj->instance_bounding_box(vol->instance_idx()); + // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. + Transform3d tr = vol->get_instance_transformation().get_matrix_no_offset().inverse(); + Vec3d offset_tr(0, // center of instance - Can't suggest width of text before it will be created + - instance_bb.size().y() / 2 - prop.size_in_mm / 2, // under + prop.emboss / 2 - instance_bb.size().z() / 2 // lay on bed + ); + Transform3d volume_trmat = tr * Eigen::Translation3d(offset_tr); + priv::start_create_volume_job(obj, volume_trmat, emboss_data, volume_type); } } @@ -305,12 +297,13 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) angle -= PI / 2; // Grabber is upward // temporary rotation - TransformationType transformation_type = TransformationType::Local_Relative_Joint; + const TransformationType transformation_type = m_parent.get_selection().is_single_text() ? + TransformationType::Local_Relative_Joint : TransformationType::World_Relative_Joint; m_parent.get_selection().rotate(Vec3d(0., 0., angle), transformation_type); angle += *m_rotate_start_angle; // move to range <-M_PI, M_PI> - to_range_pi_pi(angle); + priv::to_range_pi_pi(angle); // propagate angle into property angle_opt = static_cast(angle); @@ -319,8 +312,8 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) angle_opt.reset(); // set into activ style - assert(m_style_manager.is_activ_font()); - if (m_style_manager.is_activ_font()) + assert(m_style_manager.is_active_font()); + if (m_style_manager.is_active_font()) m_style_manager.get_font_prop().angle = angle_opt; } @@ -329,6 +322,15 @@ bool GLGizmoEmboss::on_mouse_for_rotation(const wxMouseEvent &mouse_event) namespace priv { +/// +/// Access to model from gl_volume +/// TODO: it is more general function --> move to utils +/// +/// Volume to model belongs to +/// Object containing gl_volume +/// Model for volume +static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObject *object); + /// /// Access to model from gl_volume /// TODO: it is more general function --> move to utils @@ -343,9 +345,8 @@ static ModelVolume *get_model_volume(const GLVolume *gl_volume, const ModelObjec /// TODO: it is more general function --> move to select utils /// /// Actual selection -/// All objects /// Model from selection -static ModelVolume *get_selected_volume(const Selection &selection, const ModelObjectPtrs &objects); +static ModelVolume *get_selected_volume(const Selection &selection); /// /// Calculate offset from mouse position to center of text @@ -355,8 +356,62 @@ static ModelVolume *get_selected_volume(const Selection &selection, const ModelO /// Offset in screan coordinate static Vec2d calc_mouse_to_center_text_offset(const Vec2d &mouse, const ModelVolume &mv); +/// +/// Access to one selected volume +/// +/// Containe what is selected +/// Slected when only one volume otherwise nullptr +static const GLVolume *get_gl_volume(const Selection &selection); + +/// +/// Get transformation to world +/// - use fix after store to 3mf when exists +/// +/// +/// To identify MovelVolume with fix transformation +/// +static Transform3d world_matrix(const GLVolume *gl_volume, const Model *model); +static Transform3d world_matrix(const Selection &selection); + } // namespace priv +const GLVolume *priv::get_gl_volume(const Selection &selection) { + const auto &list = selection.get_volume_idxs(); + if (list.size() != 1) + return nullptr; + unsigned int volume_idx = *list.begin(); + return selection.get_volume(volume_idx); +} + +Transform3d priv::world_matrix(const GLVolume *gl_volume, const Model *model) +{ + if (!gl_volume) + return Transform3d::Identity(); + Transform3d res = gl_volume->world_matrix(); + + if (!model) + return res; + ModelVolume* mv = get_model_volume(gl_volume, model->objects); + if (!mv) + return res; + + const std::optional &tc = mv->text_configuration; + if (!tc.has_value()) + return res; + + const std::optional &fix = tc->fix_3mf_tr; + if (!fix.has_value()) + return res; + + return res * (*fix); +} + +Transform3d priv::world_matrix(const Selection &selection) +{ + const GLVolume *gl_volume = get_gl_volume(selection); + return world_matrix(gl_volume, selection.get_model()); +} + Vec2d priv::calc_mouse_to_center_text_offset(const Vec2d& mouse, const ModelVolume& mv) { const Transform3d &volume_tr = mv.get_matrix(); const Camera &camera = wxGetApp().plater()->get_camera(); @@ -437,27 +492,24 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) m_raycast_manager.actualize(act_model_object, &condition); m_dragging_mouse_offset = priv::calc_mouse_to_center_text_offset(mouse_pos, *m_volume); // Cancel job to prevent interuption of dragging (duplicit result) - if (m_update_job_cancel != nullptr) m_update_job_cancel->store(true); + if (m_update_job_cancel != nullptr) + m_update_job_cancel->store(true); return false; } // Dragging starts out of window - if (!m_dragging_mouse_offset.has_value()) return false; + if (!m_dragging_mouse_offset.has_value()) + return false; - const Camera &camera = wxGetApp().plater()->get_camera(); - Vec2d offseted_mouse = mouse_pos + *m_dragging_mouse_offset; - auto hit = m_raycast_manager.unproject(offseted_mouse, camera, &condition); - if (!hit.has_value()) { - // there is no hit - // show common translation of object - m_parent.toggle_model_objects_visibility(true); - m_temp_transformation = {}; - return false; - } - if (mouse_event.Dragging()) { + const Camera &camera = wxGetApp().plater()->get_camera(); + Vec2d offseted_mouse = mouse_pos + *m_dragging_mouse_offset; + auto hit = m_raycast_manager.unproject(offseted_mouse, camera, &condition); + if (!hit.has_value()) + return false; TextConfiguration &tc = *m_volume->text_configuration; - // hide common dragging of object + // INFO: GLVolume is transformed by common movement but we need move over surface + // so hide common dragging of object m_parent.toggle_model_objects_visibility(false, m_volume->get_object(), gl_volume->instance_idx(), m_volume); // Calculate temporary position @@ -470,22 +522,21 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) if (tc.fix_3mf_tr.has_value()) trmat = trmat * (*tc.fix_3mf_tr); - // temp is in wolrld coors + // temp is in world coors m_temp_transformation = object_trmat * trmat; + + // calculate scale + calculate_scale(); } else if (mouse_event.LeftUp()) { // Added because of weird case after double click into scene // with Mesa driver OR on Linux if (!m_temp_transformation.has_value()) return false; - // TODO: Disable applying of common transformation after draggig - // Call after is used for apply transformation after common dragging to rewrite it + // Override of common transformation after draggig by set transformation into gl_volume Transform3d volume_trmat = gl_volume->get_instance_transformation().get_matrix().inverse() * *m_temp_transformation; - wxGetApp().plater()->CallAfter([volume_trmat, mv = m_volume]() { - mv->set_transformation(volume_trmat); - }); - + gl_volume->set_volume_transformation(Geometry::Transformation(volume_trmat)); m_parent.toggle_model_objects_visibility(true); // Apply temporary position m_temp_transformation = {}; @@ -497,6 +548,9 @@ bool GLGizmoEmboss::on_mouse_for_translate(const wxMouseEvent &mouse_event) m_volume->set_transformation(volume_trmat); process(); } + + // calculate scale + calculate_scale(); } return false; } @@ -522,6 +576,7 @@ bool GLGizmoEmboss::on_init() m_rotate_gizmo.init(); ColorRGBA gray_color(.6f, .6f, .6f, .3f); m_rotate_gizmo.set_highlight_color(gray_color); + m_shortcut_key = WXK_CONTROL_T; return true; } @@ -558,16 +613,27 @@ void GLGizmoEmboss::on_render() { glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); } + bool is_left_handed = has_reflection(*m_temp_transformation); + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + glsafe(::glEnable(GL_DEPTH_TEST)); gl_volume.model.set_color(color); gl_volume.model.render(); glsafe(::glDisable(GL_DEPTH_TEST)); - if (is_transparent) glsafe(::glDisable(GL_BLEND)); + // set it back to pevious state + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + if (is_transparent) + glsafe(::glDisable(GL_BLEND)); shader->stop_using(); } + // prevent get local coordinate system on multi volumes + if (!selection.is_single_volume_or_modifier() && + !selection.is_single_volume_instance()) return; bool is_surface_dragging = m_temp_transformation.has_value(); // Do NOT render rotation grabbers when dragging object bool is_rotate_by_grabbers = m_dragging; @@ -639,22 +705,39 @@ static void draw_mouse_offset(const std::optional &offset) draw_list->AddLine(p1, p2, color, thickness); } #endif // SHOW_OFFSET_DURING_DRAGGING +namespace priv { +static void draw_origin_ball(const GLCanvas3D& canvas) { + auto draw_list = ImGui::GetOverlayDrawList(); + const Selection &selection = canvas.get_selection(); + Transform3d to_world = priv::world_matrix(selection); + Vec3d volume_zero = to_world * Vec3d::Zero(); + + const Camera &camera = wxGetApp().plater()->get_camera(); + Point screen_coor = CameraUtils::project(camera, volume_zero); + ImVec2 center(screen_coor.x(), screen_coor.y()); + float radius = 10.f; + ImU32 color = ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT); + draw_list->AddCircleFilled(center, radius, color); +} + +} // namespace priv void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) { if (!m_gui_cfg.has_value()) initialize(); - check_selection(); + set_volume_by_selection(); // Do not render window for not selected text volume if (m_volume == nullptr || !m_volume->text_configuration.has_value()) { close(); return; - } + } - // TODO: fix width - showing scroll in first draw of advanced. const ImVec2 &min_window_size = get_minimal_window_size(); ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, min_window_size); + priv::draw_origin_ball(m_parent); + #ifdef SHOW_FINE_POSITION draw_fine_position(m_parent.get_selection(), m_parent.get_canvas_size(), min_window_size); #endif // SHOW_FINE_POSITION @@ -665,23 +748,66 @@ void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit) draw_mouse_offset(m_dragging_mouse_offset); #endif // SHOW_OFFSET_DURING_DRAGGING - // check if is set window offset - if (m_set_window_offset.has_value()) { - ImGui::SetNextWindowPos(*m_set_window_offset, ImGuiCond_Always); - m_set_window_offset.reset(); + std::string window_title_string = on_get_name(); + const char* window_title = window_title_string.c_str(); + if (m_allow_open_near_volume){ + // check if is set window offset + if (m_set_window_offset.has_value()) { + ImGui::SetNextWindowPos(*m_set_window_offset, ImGuiCond_Always); + m_set_window_offset.reset(); + } + } else { + y = std::min(y, bottom_limit - min_window_size.y); + // position near toolbar + ImVec2 pos(x, y); + ImGui::SetNextWindowPos(pos, ImGuiCond_Once); } + bool is_opened = true; ImGuiWindowFlags flag = ImGuiWindowFlags_NoCollapse; - if (ImGui::Begin(on_get_name().c_str(), nullptr, flag)) { + if (ImGui::Begin(on_get_name().c_str(), &is_opened, flag)) { // Need to pop var before draw window ImGui::PopStyleVar(); // WindowMinSize draw_window(); } else { ImGui::PopStyleVar(); // WindowMinSize } + + // after change volume from object to volume it is necessary to recalculate + // minimal windows size because of set type + if (m_should_set_minimal_windows_size) { + m_should_set_minimal_windows_size = false; + ImGui::SetWindowSize(ImVec2(0.f, min_window_size.y), ImGuiCond_Always); + } + ImGui::End(); + if (!is_opened) + close(); } +namespace priv { +/// +/// Move window for edit emboss text near to embossed object +/// NOTE: embossed object must be selected +/// +ImVec2 calc_fine_position(const Selection &selection, const ImVec2 &windows_size, const Size &canvas_size) +{ + const Selection::IndicesList indices = selection.get_volume_idxs(); + // no selected volume + if (indices.empty()) return {}; + const GLVolume *volume = selection.get_volume(*indices.begin()); + // bad volume selected (e.g. deleted one) + if (volume == nullptr) return {}; + + const Camera &camera = wxGetApp().plater()->get_camera(); + Slic3r::Polygon hull = CameraUtils::create_hull2d(camera, *volume); + + ImVec2 c_size(canvas_size.get_width(), canvas_size.get_height()); + ImVec2 offset = ImGuiWrapper::suggest_location(windows_size, hull, c_size); + return offset; +} +} // namespace priv + void GLGizmoEmboss::on_set_state() { // enable / disable bed from picking @@ -713,11 +839,20 @@ void GLGizmoEmboss::on_set_state() // to reload fonts from system, when install new one wxFontEnumerator::InvalidateCache(); - // Try(when exist) set text configuration by volume - load_configuration(get_selected_volume()); + // Try(when exist) set text configuration by volume + set_volume(priv::get_selected_volume(m_parent.get_selection())); + + // when open window by "T" and no valid volume is selected, so Create new one + if (m_volume == nullptr) { + // reopen gizmo when new object is created + GLGizmoBase::m_state = GLGizmoBase::Off; + // start creating new object + create_volume(ModelVolumeType::MODEL_PART); + } // change position of just opened emboss window - set_fine_position(); + if (m_allow_open_near_volume) + m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); // when open by hyperlink it needs to show up // or after key 'T' windows doesn't appear @@ -768,13 +903,6 @@ void GLGizmoEmboss::initialize() int count_letter_M_in_input = 12; cfg.input_width = letter_m_size.x * count_letter_M_in_input; GuiCfg::Translations &tr = cfg.translations; - tr.type = _u8L("Type"); - tr.style = _u8L("Style"); - float max_style_text_width = std::max( - ImGui::CalcTextSize(tr.type.c_str()).x, - ImGui::CalcTextSize(tr.style.c_str()).x); - cfg.style_offset = max_style_text_width + 3 * space; - tr.font = _u8L("Font"); tr.size = _u8L("Height"); tr.depth = _u8L("Depth"); @@ -810,20 +938,26 @@ void GLGizmoEmboss::initialize() float window_title = line_height + 2*style.FramePadding.y; float input_height = line_height_with_spacing + 2*style.FramePadding.y; float tree_header = line_height_with_spacing; + float separator_height = 1 + style.FramePadding.y; + + // "Text is to object" + radio buttons + cfg.height_of_volume_type_selector = separator_height + line_height_with_spacing + input_height; + float window_height = window_title + // window title cfg.text_size.y + // text field - input_height * 6 + // type Radios + style selector + font name + - // height + depth + close button + input_height * 4 + // font name + height + depth + style selector tree_header + // advance tree + separator_height + // presets separator line + line_height_with_spacing + // "Presets" 2 * style.WindowPadding.y; - float window_width = cfg.style_offset + cfg.input_width + 2*style.WindowPadding.x - + 4 * (cfg.icon_width + space); + float window_width = cfg.input_offset + cfg.input_width + 2*style.WindowPadding.x + + 2 * (cfg.icon_width + space); cfg.minimal_window_size = ImVec2(window_width, window_height); // 6 = charGap, LineGap, Bold, italic, surfDist, angle // 4 = 1px for fix each edit image of drag float - float advance_height = input_height * 7 + 8; + float advance_height = input_height * 8 + 8; cfg.minimal_window_size_with_advance = ImVec2(cfg.minimal_window_size.x, cfg.minimal_window_size.y + advance_height); @@ -872,9 +1006,9 @@ EmbossStyles GLGizmoEmboss::create_default_styles() void GLGizmoEmboss::set_default_text(){ m_text = _u8L("Embossed text"); } #include "imgui/imgui_internal.h" // to unfocus input --> ClearActiveID -void GLGizmoEmboss::check_selection() +void GLGizmoEmboss::set_volume_by_selection() { - ModelVolume *vol = get_selected_volume(); + ModelVolume *vol = priv::get_selected_volume(m_parent.get_selection()); // is same volume selected? if (vol != nullptr && m_volume == vol) return; @@ -885,27 +1019,164 @@ void GLGizmoEmboss::check_selection() if (m_volume != nullptr) ImGui::ClearActiveID(); // is select embossed volume? - if (load_configuration(vol)) - // successfull load volume for editing - return; - - // behave like adding new text - m_volume = nullptr; - set_default_text(); + if (!set_volume(vol)) { + // Can't load so behave like adding new text + m_volume = nullptr; + set_default_text(); + } +} + +bool GLGizmoEmboss::set_volume(ModelVolume *volume) +{ + if (volume == nullptr) return false; + const std::optional tc_opt = volume->text_configuration; + if (!tc_opt.has_value()) return false; + const TextConfiguration &tc = *tc_opt; + const EmbossStyle &style = tc.style; + + // Could exist OS without getter on face_name, + // but it is able to restore font from descriptor + // Soo default value must be TRUE + bool is_font_installed = true; + wxString face_name; + std::optional face_name_opt = style.prop.face_name; + if (face_name_opt.has_value()) { + face_name = wxString(face_name_opt->c_str()); + + //* SWITCH for search in OS fonts + // search in enumerated fonts + // refresh list of installed font in the OS. + init_face_names(); + m_face_names.is_init = false; + auto cmp = [](const FaceName &fn, const wxString& face_name)->bool { return fn.wx_name < face_name; }; + const std::vector &faces = m_face_names.faces; + auto it = std::lower_bound(faces.begin(), faces.end(), face_name, cmp); + is_font_installed = it != faces.end() && it->wx_name == face_name; + /*/ + // test it by Wx + wxFontEnumerator::InvalidateCache(); + wxFont wx_font_; // temporary structure + is_font_installed = wx_font_.SetFaceName(face_name); + // */ + } + + wxFont wx_font; + // load wxFont from same OS when font name is installed + if (style.type == WxFontUtils::get_actual_type() && is_font_installed) + wx_font = WxFontUtils::load_wxFont(style.path); + + // Flag that is selected same font + bool is_exact_font = true; + // Different OS or try found on same OS + if (!wx_font.IsOk()) { + is_exact_font = false; + // Try create similar wx font by FontFamily + wx_font = WxFontUtils::create_wxFont(style); + if (is_font_installed) + is_exact_font = wx_font.SetFaceName(face_name); + + // Have to use some wxFont + if (!wx_font.IsOk()) + wx_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + } + assert(wx_font.IsOk()); + + // Load style to style manager + const auto& styles = m_style_manager.get_styles(); + auto has_same_name = [&style](const StyleManager::Item &style_item) -> bool { + const EmbossStyle &es = style_item.style; + return es.name == style.name; + }; + auto it = std::find_if(styles.begin(), styles.end(), has_same_name); + if (it == styles.end()) { + // style was not found + m_style_manager.load_style(style, wx_font); + } else { + // style name is in styles list + size_t style_index = it - styles.begin(); + if (!m_style_manager.load_style(style_index)) { + // can`t load stored style + m_style_manager.erase(style_index); + m_style_manager.load_style(style, wx_font); + } else { + // stored style is loaded, now set modification of style + m_style_manager.get_style() = style; + m_style_manager.set_wx_font(wx_font); + } + } + + if (!is_exact_font) { + create_notification_not_valid_font(tc); + + // update changed wxFont path + std::string path = WxFontUtils::store_wxFont(wx_font); + // current used style + EmbossStyle &act_style = m_style_manager.get_style(); + act_style.path = path; + act_style.type = WxFontUtils::get_actual_type(); + } + + // The change of volume could show or hide part with setter on volume type + if (m_volume == nullptr || + (m_volume->get_object()->volumes.size() == 1) != + (volume->get_object()->volumes.size() == 1)){ + m_should_set_minimal_windows_size = true; + } + + m_text = tc.text; + m_volume = volume; + + // store volume state before edit + m_unmodified_volume = {*volume->get_mesh_shared_ptr(), // copy + tc, volume->get_matrix(), volume->name}; + + // calculate scale for height and depth inside of scaled object instance + calculate_scale(); + return true; +} + +void GLGizmoEmboss::calculate_scale() { + Transform3d to_world = m_temp_transformation.has_value()? + *m_temp_transformation : + priv::world_matrix(m_parent.get_selection()); + auto to_world_linear = to_world.linear(); + auto calc = [&to_world_linear](const Vec3d &axe, std::optional& scale)->bool { + Vec3d axe_world = to_world_linear * axe; + double norm_sq = axe_world.squaredNorm(); + if (is_approx(norm_sq, 1.)) { + if (scale.has_value()) + scale.reset(); + else + return false; + } else { + scale = sqrt(norm_sq); + } + return true; + }; + + bool exist_change = calc(Vec3d::UnitY(), m_scale_height); + exist_change |= calc(Vec3d::UnitZ(), m_scale_depth); + + // Change of scale has to change font imgui font size + if (exist_change) + m_style_manager.clear_imgui_font(); +} + +ModelVolume *priv::get_model_volume(const GLVolume *gl_volume, const ModelObject *object) +{ + int volume_id = gl_volume->volume_idx(); + if (volume_id < 0 || static_cast(volume_id) >= object->volumes.size()) return nullptr; + return object->volumes[volume_id]; } ModelVolume *priv::get_model_volume(const GLVolume *gl_volume, const ModelObjectPtrs &objects) { - const GLVolume::CompositeID &id = gl_volume->composite_id; - - if (id.object_id < 0 || static_cast(id.object_id) >= objects.size()) return nullptr; - ModelObject *object = objects[id.object_id]; - - if (id.volume_id < 0 || static_cast(id.volume_id) >= object->volumes.size()) return nullptr; - return object->volumes[id.volume_id]; + int object_id = gl_volume->object_idx(); + if (object_id < 0 || static_cast(object_id) >= objects.size()) return nullptr; + return get_model_volume(gl_volume, objects[object_id]); } -ModelVolume *priv::get_selected_volume(const Selection &selection, const ModelObjectPtrs &objects) +ModelVolume *priv::get_selected_volume(const Selection &selection) { int object_idx = selection.get_object_idx(); // is more object selected? @@ -916,15 +1187,10 @@ ModelVolume *priv::get_selected_volume(const Selection &selection, const ModelOb if (volume_idxs.size() != 1) return nullptr; unsigned int vol_id_gl = *volume_idxs.begin(); const GLVolume *vol_gl = selection.get_volume(vol_id_gl); + const ModelObjectPtrs &objects = selection.get_model()->objects; return get_model_volume(vol_gl, objects); } -ModelVolume *GLGizmoEmboss::get_selected_volume() -{ - return priv::get_selected_volume(m_parent.get_selection(), - wxGetApp().plater()->model().objects); -} - // Run Job on main thread (blocking) - ONLY DEBUG static inline void execute_job(std::shared_ptr j) { @@ -944,6 +1210,17 @@ static inline void execute_job(std::shared_ptr j) }); } +namespace priv { +/// +/// Calculate translation of text volume onto surface of model +/// +/// Text +/// AABB trees of object. Actualize object containing text +/// Transformation of actual instance +/// Offset of volume in volume coordinate +std::optional calc_surface_offset(const ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection); +} // namespace priv + bool GLGizmoEmboss::process() { // no volume is selected -> selection from right panel @@ -954,7 +1231,7 @@ bool GLGizmoEmboss::process() if (m_text.empty()) return false; // exist loaded font file? - if (!m_style_manager.is_activ_font()) return false; + if (!m_style_manager.is_active_font()) return false; // Cancel previous Job, when it is in process // Can't use cancel, because I want cancel only previous EmbossUpdateJob no other jobs @@ -971,12 +1248,9 @@ bool GLGizmoEmboss::process() // check cutting from source mesh bool &use_surface = data.text_configuration.style.prop.use_surface; bool is_object = m_volume->get_object()->volumes.size() == 1; - if (use_surface && is_object) { - priv::message_disable_cut_surface(); + if (use_surface && is_object) use_surface = false; - } - - + if (use_surface) { // Model to cut surface from. SurfaceVolumeData::ModelSources sources = create_volume_sources(m_volume); @@ -987,6 +1261,13 @@ bool GLGizmoEmboss::process() if (fix_3mf.has_value()) text_tr = text_tr * fix_3mf->inverse(); + // when it is new applying of use surface than move origin onto surfaca + if (!m_volume->text_configuration->style.prop.use_surface) { + auto offset = priv::calc_surface_offset(*m_volume, m_raycast_manager, m_parent.get_selection()); + if (offset.has_value()) + text_tr *= Eigen::Translation(*offset); + } + bool is_outside = m_volume->is_model_part(); // check that there is not unexpected volume type assert(is_outside || m_volume->is_negative_volume() || @@ -1014,7 +1295,7 @@ void GLGizmoEmboss::close() // remove volume when text is empty if (m_volume != nullptr && m_volume->text_configuration.has_value() && - is_text_empty(m_text)) { + priv::is_text_empty(m_text)) { Plater &p = *wxGetApp().plater(); if (is_text_object(m_volume)) { // delete whole object @@ -1053,6 +1334,62 @@ void GLGizmoEmboss::discard_and_close() { // * Volume containing 3mf fix transformation - needs work around } +namespace priv { + +/// +/// Apply camera direction for emboss direction +/// +/// Define view vector +/// Containe Selected Model to modify +/// True when apply change otherwise false +static bool apply_camera_dir(const Camera &camera, GLCanvas3D &canvas); +} + +bool priv::apply_camera_dir(const Camera &camera, GLCanvas3D &canvas) { + const Vec3d &cam_dir = camera.get_dir_forward(); + + Selection &sel = canvas.get_selection(); + if (sel.is_empty()) return false; + + // camera direction transformed into volume coordinate system + Transform3d to_world = priv::world_matrix(sel); + Vec3d cam_dir_tr = to_world.inverse().linear() * cam_dir; + cam_dir_tr.normalize(); + + Vec3d emboss_dir(0., 0., -1.); + + // check wether cam_dir is already used + if (is_approx(cam_dir_tr, emboss_dir)) return false; + + assert(sel.get_volume_idxs().size() == 1); + GLVolume *vol = sel.get_volume(*sel.get_volume_idxs().begin()); + + Transform3d vol_rot; + Transform3d vol_tr = vol->get_volume_transformation().get_matrix(); + // check whether cam_dir is opposit to emboss dir + if (is_approx(cam_dir_tr, -emboss_dir)) { + // rotate 180 DEG by y + vol_rot = Eigen::AngleAxis(M_PI_2, Vec3d(0., 1., 0.)); + } else { + // calc params for rotation + Vec3d axe = emboss_dir.cross(cam_dir_tr); + axe.normalize(); + double angle = std::acos(emboss_dir.dot(cam_dir_tr)); + vol_rot = Eigen::AngleAxis(angle, axe); + } + + Vec3d offset = vol_tr * Vec3d::Zero(); + Vec3d offset_inv = vol_rot.inverse() * offset; + Transform3d res = vol_tr * + Eigen::Translation(-offset) * + vol_rot * + Eigen::Translation(offset_inv); + //Transform3d res = vol_tr * vol_rot; + vol->set_volume_transformation(Geometry::Transformation(res)); + priv::get_model_volume(vol, sel.get_model()->objects)->set_transformation(res); + return true; +} + void GLGizmoEmboss::draw_window() { #ifdef ALLOW_DEBUG_MODE @@ -1060,8 +1397,8 @@ void GLGizmoEmboss::draw_window() if (ImGui::Button("add svg")) choose_svg_file(); #endif // ALLOW_DEBUG_MODE - bool is_activ_font = m_style_manager.is_activ_font(); - if (!is_activ_font) + bool is_active_font = m_style_manager.is_active_font(); + if (!is_active_font) m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Warning: No font is selected. Select correct one.")); // Disable all except selection of font, when open text from 3mf with unknown font @@ -1071,9 +1408,7 @@ void GLGizmoEmboss::draw_window() }); draw_text_input(); - draw_model_type(); - draw_style_list(); - m_imgui->disabled_begin(!is_activ_font); + m_imgui->disabled_begin(!is_active_font); ImGui::TreePush(); draw_style_edit(); ImGui::TreePop(); @@ -1091,29 +1426,24 @@ void GLGizmoEmboss::draw_window() ImGui::TreePop(); } else if (m_is_advanced_edit_style) set_minimal_window_size(false); - m_imgui->disabled_end(); // !is_activ_font + + ImGui::Separator(); + + draw_style_list(); + + // Do not select volume type, when it is text object + if (m_volume->get_object()->volumes.size() != 1) { + ImGui::Separator(); + draw_model_type(); + } + + m_imgui->disabled_end(); // !is_active_font #ifdef SHOW_WX_FONT_DESCRIPTOR if (is_selected_style) m_imgui->text_colored(ImGuiWrapper::COL_GREY_DARK, m_style_manager.get_style().path); #endif // SHOW_WX_FONT_DESCRIPTOR - ImGui::PushStyleColor(ImGuiCol_Button, ImGuiWrapper::COL_GREY_DARK); - if (ImGui::Button(_u8L("Close").c_str())) - discard_and_close(); - else if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("Discard changes on embossed text and close.").c_str()); - ImGui::PopStyleColor(); - - ImGui::SameLine(); - if (ImGui::Button(_u8L("Apply").c_str())) { - if (m_is_unknown_font) { - process(); - } else { - close(); - } - } - #ifdef SHOW_CONTAIN_3MF_FIX if (m_volume!=nullptr && m_volume->text_configuration.has_value() && @@ -1143,7 +1473,19 @@ void GLGizmoEmboss::draw_window() const auto &atlas = m_style_manager.get_atlas(); ImGui::Image(atlas.TexID, ImVec2(atlas.TexWidth, atlas.TexHeight)); #endif // SHOW_IMGUI_ATLAS -} + +#ifdef ALLOW_OPEN_NEAR_VOLUME + ImGui::SameLine(); + if (ImGui::Checkbox("##ALLOW_OPEN_NEAR_VOLUME", &m_allow_open_near_volume)) { + if (m_allow_open_near_volume) + m_set_window_offset = priv::calc_fine_position(m_parent.get_selection(), get_minimal_window_size(), m_parent.get_canvas_size()); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", ((m_allow_open_near_volume) ? + _u8L("Fix settings possition"): + _u8L("Allow floating window near text")).c_str()); + } +#endif // ALLOW_FLOAT_WINDOW + } void GLGizmoEmboss::draw_text_input() { @@ -1155,10 +1497,11 @@ void GLGizmoEmboss::draw_text_input() return create_range_text(text, *ff.font_file, font_index, &exist_unknown); }; + double scale = m_scale_height.has_value() ? *m_scale_height : 1.; ImFont *imgui_font = m_style_manager.get_imgui_font(); if (imgui_font == nullptr) { // try create new imgui font - m_style_manager.create_imgui_font(create_range_text_prep()); + m_style_manager.create_imgui_font(create_range_text_prep(), scale); imgui_font = m_style_manager.get_imgui_font(); } bool exist_font = @@ -1188,7 +1531,7 @@ void GLGizmoEmboss::draw_text_input() tool_tip += t; } }; - if (is_text_empty(m_text)) append_warning(_u8L("Empty"), _u8L("Embossed text can NOT contain only white spaces.")); + if (priv::is_text_empty(m_text)) append_warning(_u8L("Empty"), _u8L("Embossed text can NOT contain only white spaces.")); if (m_text_contain_unknown_glyph) append_warning(_u8L("Bad symbol"), _u8L("Text contain character glyph (represented by '?') unknown by font.")); @@ -1198,7 +1541,7 @@ void GLGizmoEmboss::draw_text_input() if (prop.line_gap.has_value()) append_warning(_u8L("Line gap"), _u8L("Unsupported visualization of gap between lines inside text input.")); auto &ff = m_style_manager.get_font_file_with_cache(); - float imgui_size = StyleManager::get_imgui_font_size(prop, *ff.font_file); + float imgui_size = StyleManager::get_imgui_font_size(prop, *ff.font_file, scale); if (imgui_size > StyleManager::max_imgui_font_size) append_warning(_u8L("To tall"), _u8L("Diminished font height inside text input.")); if (imgui_size < StyleManager::min_imgui_font_size) @@ -1207,14 +1550,12 @@ void GLGizmoEmboss::draw_text_input() } // add border around input when warning appears -#ifndef __APPLE__ ScopeGuard input_border_sg; if (!warning.empty()) { ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); ImGui::PushStyleColor(ImGuiCol_Border, ImGuiWrapper::COL_ORANGE_LIGHT); - input_border_sg = ScopeGuard([]() { ImGui::PopStyleColor(); ImGui::PopStyleVar(); }); + input_border_sg.closure = []() { ImGui::PopStyleColor(); ImGui::PopStyleVar(); }; } -#endif // flag for extend font ranges if neccessary // ranges can't be extend during font is activ(pushed) @@ -1252,7 +1593,7 @@ void GLGizmoEmboss::draw_text_input() if (!range_text.empty() && !m_imgui->contain_all_glyphs(imgui_font, range_text) ) { m_style_manager.clear_imgui_font(); - m_style_manager.create_imgui_font(range_text); + m_style_manager.create_imgui_font(range_text, scale); } } @@ -1486,33 +1827,46 @@ void GLGizmoEmboss::init_font_name_texture() { void GLGizmoEmboss::draw_font_preview(FaceName& face, bool is_visible) { + // Limit for opened font files at one moment unsigned int &count_opened_fonts = m_face_names.count_opened_font_files; + // Size of texture ImVec2 size(m_gui_cfg->face_name_size.x(), m_gui_cfg->face_name_size.y()); - // set to pixel 0,0 in texture - ImVec2 uv0(0.f, 0.f), uv1(1.f / size.x, 1.f / size.y / m_face_names.count_cached_textures); - ImTextureID tex_id = (void *) (intptr_t) m_face_names.texture_id; + float count_cached_textures_f = static_cast(m_face_names.count_cached_textures); + std::string state_text; + // uv0 and uv1 set to pixel 0,0 in texture + ImVec2 uv0(0.f, 0.f), uv1(1.f / size.x, 1.f / size.y / count_cached_textures_f); if (face.is_created != nullptr) { + // not created preview if (*face.is_created) { + // Already created preview size_t texture_index = face.texture_index; - uv0 = ImVec2(0.f, texture_index / (float) m_face_names.count_cached_textures), - uv1 = ImVec2(1.f, (texture_index + 1) / (float) m_face_names.count_cached_textures); - } else if (!is_visible) { - face.is_created = nullptr; - face.cancel->store(true); + uv0 = ImVec2(0.f, texture_index / count_cached_textures_f); + uv1 = ImVec2(1.f, (texture_index + 1) / count_cached_textures_f); + } else { + // Not finished preview + if (is_visible) { + // when not canceled still loading + state_text = (face.cancel->load())? + _u8L(" No symbol"): + _u8L(" ... Loading"); + } else { + // not finished and not visible cancel job + face.is_created = nullptr; + face.cancel->store(true); + } } } else if (is_visible && count_opened_fonts < m_gui_cfg->max_count_opened_font_files) { ++count_opened_fonts; face.cancel = std::make_shared(false); face.is_created = std::make_shared(false); - std::string text = m_text.empty() ? "AaBbCc" : m_text; - const unsigned char gray_level = 5; // format type and level must match to texture data const GLenum format = GL_RGBA, type = GL_UNSIGNED_BYTE; const GLint level = 0; // select next texture index size_t texture_index = (m_face_names.texture_index + 1) % m_face_names.count_cached_textures; + // set previous cach as deleted for (FaceName &f : m_face_names.faces) if (f.texture_index == texture_index) { @@ -1523,12 +1877,9 @@ void GLGizmoEmboss::draw_font_preview(FaceName& face, bool is_visible) m_face_names.texture_index = texture_index; face.texture_index = texture_index; - // clear texture - - // render text to texture FontImageData data{ - text, + m_text, face.wx_name, m_face_names.encoding, m_face_names.texture_id, @@ -1545,9 +1896,18 @@ void GLGizmoEmboss::draw_font_preview(FaceName& face, bool is_visible) auto job = std::make_unique(std::move(data)); auto &worker = wxGetApp().plater()->get_ui_job_worker(); queue_job(worker, std::move(job)); + } else { + // cant start new thread at this moment so wait in queue + state_text = _u8L(" ... In queue"); + } + + if (!state_text.empty()) { + ImGui::SameLine(m_gui_cfg->face_name_texture_offset_x); + m_imgui->text(state_text); } ImGui::SameLine(m_gui_cfg->face_name_texture_offset_x); + ImTextureID tex_id = (void *) (intptr_t) m_face_names.texture_id; ImGui::Image(tex_id, size, uv0, uv1); } @@ -1570,7 +1930,7 @@ void GLGizmoEmboss::draw_font_list() { // Set partial wxString actual_face_name; - if (m_style_manager.is_activ_font()) { + if (m_style_manager.is_active_font()) { const std::optional &wx_font_opt = m_style_manager.get_wx_font(); if (wx_font_opt.has_value()) actual_face_name = wx_font_opt->GetFaceName(); @@ -1588,11 +1948,12 @@ void GLGizmoEmboss::draw_font_list() ScopeGuard unknown_font_sc; if (m_is_unknown_font) { m_imgui->disabled_end(); - unknown_font_sc = ScopeGuard([&]() { + unknown_font_sc.closure = [&]() { m_imgui->disabled_begin(true); - }); + }; } + ImGui::SetNextItemWidth(m_gui_cfg->input_width); if (ImGui::BeginCombo("##font_selector", selected)) { if (!m_face_names.is_init) init_face_names(); if (m_face_names.texture_id == 0) init_font_name_texture(); @@ -1644,6 +2005,13 @@ void GLGizmoEmboss::draw_font_list() store(m_face_names); } + if (m_is_unknown_font) { + ImGui::SameLine(); + // Apply for actual selected font + if (ImGui::Button(_u8L("Apply").c_str())) + process(); + } + #ifdef ALLOW_ADD_FONT_BY_FILE ImGui::SameLine(); // select font file by file browser @@ -1670,14 +2038,13 @@ void GLGizmoEmboss::draw_font_list() void GLGizmoEmboss::draw_model_type() { bool is_last_solid_part = is_text_object(m_volume); - const char * label = m_gui_cfg->translations.type.c_str(); + std::string title = _u8L("Text is to object"); if (is_last_solid_part) { ImVec4 color{.5f, .5f, .5f, 1.f}; - m_imgui->text_colored(color, label); + m_imgui->text_colored(color, title.c_str()); } else { - ImGui::Text("%s", label); + ImGui::Text("%s", title.c_str()); } - ImGui::SameLine(m_gui_cfg->style_offset); std::optional new_type; ModelVolumeType modifier = ModelVolumeType::PARAMETER_MODIFIER; @@ -1685,41 +2052,32 @@ void GLGizmoEmboss::draw_model_type() ModelVolumeType part = ModelVolumeType::MODEL_PART; ModelVolumeType type = m_volume->type(); - if (type == part) { - draw_icon(IconType::part, IconState::hovered); - } else { - if (draw_button(IconType::part)) new_type = part; - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", _u8L("Click to change text into object part.").c_str()); - } - + if (ImGui::RadioButton(_u8L("Added").c_str(), type == part)) + new_type = part; + else if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", _u8L("Click to change text into object part.").c_str()); ImGui::SameLine(); - if (type == negative) { - draw_icon(IconType::negative, IconState::hovered); - } else { - if (draw_button(IconType::negative, is_last_solid_part)) - new_type = negative; - if(ImGui::IsItemHovered()){ - if(is_last_solid_part) - ImGui::SetTooltip("%s", _u8L("You can't change a type of the last solid part of the object.").c_str()); - else if (type != negative) - ImGui::SetTooltip("%s", _u8L("Click to change part type into negative volume.").c_str()); - } + + std::string last_solid_part_hint = _u8L("You can't change a type of the last solid part of the object."); + if (ImGui::RadioButton(_u8L("Subtracted").c_str(), type == negative)) + new_type = negative; + else if (ImGui::IsItemHovered()) { + if (is_last_solid_part) + ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); + else if (type != negative) + ImGui::SetTooltip("%s", _u8L("Click to change part type into negative volume.").c_str()); } - if (wxGetApp().plater()->printer_technology() != ptSLA) { + // In simple mode are not modifiers + if (wxGetApp().plater()->printer_technology() != ptSLA && wxGetApp().get_mode() != ConfigOptionMode::comSimple) { ImGui::SameLine(); - if (type == modifier) { - draw_icon(IconType::modifier, IconState::hovered); - } else { - if(draw_button(IconType::modifier, is_last_solid_part)) - new_type = modifier; - if (ImGui::IsItemHovered()) { - if(is_last_solid_part) - ImGui::SetTooltip("%s", _u8L("You can't change a type of the last solid part of the object.").c_str()); - else if (type != modifier) - ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str()); - } + if (ImGui::RadioButton(_u8L("Modifier").c_str(), type == modifier)) + new_type = modifier; + else if (ImGui::IsItemHovered()) { + if (is_last_solid_part) + ImGui::SetTooltip("%s", last_solid_part_hint.c_str()); + else if (type != modifier) + ImGui::SetTooltip("%s", _u8L("Click to change part type into modifier.").c_str()); } } @@ -1818,6 +2176,7 @@ void GLGizmoEmboss::draw_style_rename_button() else ImGui::SetTooltip("%s", _u8L("Can't rename temporary style.").c_str()); } if (ImGui::BeginPopupModal(popup_id, 0, ImGuiWindowFlags_AlwaysAutoResize)) { + m_imgui->disable_background_fadeout_animation(); draw_style_rename_popup(); ImGui::EndPopup(); } @@ -1912,6 +2271,7 @@ void GLGizmoEmboss::draw_style_add_button() } if (ImGui::BeginPopupModal(popup_id, 0, ImGuiWindowFlags_AlwaysAutoResize)) { + m_imgui->disable_background_fadeout_animation(); draw_style_save_as_popup(); ImGui::EndPopup(); } @@ -1928,9 +2288,9 @@ void GLGizmoEmboss::draw_delete_style_button() { if (draw_button(IconType::erase, !can_delete)) { while (true) { // NOTE: can't use previous loaded activ index -> erase could change index - size_t activ_index = m_style_manager.get_style_index(); - next_style_index = (activ_index > 0) ? activ_index - 1 : - activ_index + 1; + size_t active_index = m_style_manager.get_style_index(); + next_style_index = (active_index > 0) ? active_index - 1 : + active_index + 1; if (next_style_index >= m_style_manager.get_styles().size()) { // can't remove last font style // TODO: inform user @@ -1944,7 +2304,7 @@ void GLGizmoEmboss::draw_delete_style_button() { } // load back - m_style_manager.load_style(activ_index); + m_style_manager.load_style(active_index); ImGui::OpenPopup(popup_id); break; } @@ -1960,13 +2320,14 @@ void GLGizmoEmboss::draw_delete_style_button() { } if (ImGui::BeginPopupModal(popup_id)) { + m_imgui->disable_background_fadeout_animation(); const std::string &style_name = m_style_manager.get_style().name; std::string text_in_popup = GUI::format(_L("Are you sure,\nthat you want permanently and unrecoverable \nremove style \"%1%\"?"), style_name); ImGui::Text("%s", text_in_popup.c_str()); if (ImGui::Button(_u8L("Yes").c_str())) { - size_t activ_index = m_style_manager.get_style_index(); + size_t active_index = m_style_manager.get_style_index(); m_style_manager.load_style(next_style_index); - m_style_manager.erase(activ_index); + m_style_manager.erase(active_index); m_style_manager.store_styles_to_app_config(wxGetApp().app_config); ImGui::CloseCurrentPopup(); process(); @@ -1978,6 +2339,7 @@ void GLGizmoEmboss::draw_delete_style_button() { } } +// FIX IT: it should not change volume position before successfull change void GLGizmoEmboss::fix_transformation(const FontProp &from, const FontProp &to) { @@ -2002,7 +2364,7 @@ void GLGizmoEmboss::fix_transformation(const FontProp &from, } void GLGizmoEmboss::draw_style_list() { - if (!m_style_manager.is_activ_font()) return; + if (!m_style_manager.is_active_font()) return; const EmbossStyle *stored_style = nullptr; bool is_stored = m_style_manager.exist_stored_style(); @@ -2021,11 +2383,12 @@ void GLGizmoEmboss::draw_style_list() { trunc_name = ImGuiWrapper::trunc(current_name, max_style_name_width); } + std::string title = _u8L("Presets"); if (m_style_manager.exist_stored_style()) - ImGui::Text("%s", m_gui_cfg->translations.style.c_str()); - else ImGui::TextColored(ImGuiWrapper::COL_ORANGE_LIGHT, "%s", m_gui_cfg->translations.style.c_str()); - - ImGui::SameLine(m_gui_cfg->style_offset); + ImGui::Text("%s", title.c_str()); + else + ImGui::TextColored(ImGuiWrapper::COL_ORANGE_LIGHT, "%s", title.c_str()); + ImGui::SetNextItemWidth(m_gui_cfg->input_width); auto add_text_modify = [&is_modified](const std::string& name) { if (!is_modified) return name; @@ -2092,7 +2455,7 @@ void GLGizmoEmboss::draw_style_list() { if (selected_style_index.has_value() && is_modified) { wxString title = _L("Style modification will be lost."); const EmbossStyle &style = m_style_manager.get_styles()[*selected_style_index].style; - wxString message = GUI::format_wxstr(_L("Changing style to '%1%' will discard actual style modification.\n\n Would you like to continue anyway?"), style.name); + wxString message = GUI::format_wxstr(_L("Changing style to '%1%' will discard current style modification.\n\n Would you like to continue anyway?"), style.name); MessageDialog not_loaded_style_message(nullptr, message, title, wxICON_WARNING | wxYES|wxNO); if (not_loaded_style_message.ShowModal() != wxID_YES) selected_style_index.reset(); @@ -2101,8 +2464,11 @@ void GLGizmoEmboss::draw_style_list() { // selected style from combo box if (selected_style_index.has_value()) { const EmbossStyle &style = m_style_manager.get_styles()[*selected_style_index].style; - fix_transformation(actual_style.prop, style.prop); + // create copy to be able do fix transformation only when successfully load style + FontProp act_prop = actual_style.prop; // copy + FontProp new_prop = style.prop; // copy if (m_style_manager.load_style(*selected_style_index)) { + fix_transformation(act_prop, new_prop); process(); } else { wxString title = _L("Not valid style."); @@ -2288,6 +2654,43 @@ bool GLGizmoEmboss::rev_input(const std::string &name, return revertible(name, value, default_value, undo_tooltip, undo_offset, draw_offseted_input); } +bool GLGizmoEmboss::rev_input_mm(const std::string &name, + float &value, + const float *default_value_ptr, + const std::string &undo_tooltip, + float step, + float step_fast, + const char *format, + bool use_inch, + const std::optional& scale) +{ + // _variable which temporary keep value + float value_ = value; + float default_value_; + if (use_inch) { + // calc value in inch + value_ *= ObjectManipulation::mm_to_in; + if (default_value_ptr) { + default_value_ = ObjectManipulation::mm_to_in * (*default_value_ptr); + default_value_ptr = &default_value_; + } + } + if (scale.has_value()) + value_ *= *scale; + bool use_correction = use_inch || scale.has_value(); + if (rev_input(name, use_correction ? value_ : value, default_value_ptr, undo_tooltip, step, step_fast, format)) { + if (use_correction) { + value = value_; + if (use_inch) + value *= ObjectManipulation::in_to_mm; + if (scale.has_value()) + value /= *scale; + } + return true; + } + return false; +} + bool GLGizmoEmboss::rev_checkbox(const std::string &name, bool &value, const bool *default_value, @@ -2303,60 +2706,73 @@ bool GLGizmoEmboss::rev_checkbox(const std::string &name, undo_offset, draw_offseted_input); } +bool is_font_changed( + const wxFont &wx_font, const wxFont &wx_font_stored, + const FontProp &prop, const FontProp &prop_stored) +{ + // Exist change in face name? + if(wx_font_stored.GetFaceName() != wx_font.GetFaceName()) return true; + + const std::optional &skew = prop.skew; + bool is_italic = skew.has_value() || WxFontUtils::is_italic(wx_font); + const std::optional &skew_stored = prop_stored.skew; + bool is_stored_italic = skew_stored.has_value() || WxFontUtils::is_italic(wx_font_stored); + // is italic changed + if (is_italic != is_stored_italic) + return true; + + const std::optional &boldness = prop.boldness; + bool is_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font); + const std::optional &boldness_stored = prop_stored.boldness; + bool is_stored_bold = boldness_stored.has_value() || WxFontUtils::is_bold(wx_font_stored); + // is bold changed + return is_bold != is_stored_bold; +} + +bool is_font_changed(const StyleManager &mng) { + const std::optional &wx_font_opt = mng.get_wx_font(); + if (!wx_font_opt.has_value()) + return false; + if (!mng.exist_stored_style()) + return false; + const EmbossStyle *stored_style = mng.get_stored_style(); + if (stored_style == nullptr) + return false; + + const std::optional &wx_font_stored_opt = mng.get_stored_wx_font(); + if (!wx_font_stored_opt.has_value()) + return false; + + return is_font_changed(*wx_font_opt, *wx_font_stored_opt, mng.get_style().prop, stored_style->prop); +} + void GLGizmoEmboss::draw_style_edit() { - const GuiCfg::Translations &tr = m_gui_cfg->translations; - const std::optional &wx_font_opt = m_style_manager.get_wx_font(); - EmbossStyle &style = m_style_manager.get_style(); - assert(wx_font_opt.has_value()); if (!wx_font_opt.has_value()) { ImGui::TextColored(ImGuiWrapper::COL_ORANGE_DARK, "%s", _u8L("WxFont is not loaded properly.").c_str()); return; } - bool exist_stored_style = m_style_manager.exist_stored_style(); - bool is_font_changed = false; - if (exist_stored_style && wx_font_opt.has_value()) { - const wxFont &wx_font = *wx_font_opt; - const EmbossStyle *stored_style = m_style_manager.get_stored_style(); - assert(stored_style != nullptr); - const std::optional &stored_wx = m_style_manager.get_stored_wx_font(); - assert(stored_wx.has_value()); - bool is_font_face_changed = stored_wx->GetFaceName() != wx_font.GetFaceName(); - - const std::optional &skew = m_style_manager.get_font_prop().skew; - bool is_italic = skew.has_value() || WxFontUtils::is_italic(wx_font); - const std::optional &skew_stored = stored_style->prop.skew; - bool is_stored_italic = skew_stored.has_value() || WxFontUtils::is_italic(*stored_wx); - bool is_italic_changed = is_italic != is_stored_italic; - - const std::optional &boldness = m_style_manager.get_font_prop().boldness; - bool is_bold = boldness.has_value() || WxFontUtils::is_bold(wx_font); - const std::optional &boldness_stored = stored_style->prop.boldness; - bool is_stored_bold = boldness_stored.has_value() || WxFontUtils::is_bold(*stored_wx); - bool is_bold_changed = is_bold != is_stored_bold; - - bool is_font_style_changed = is_italic_changed || is_bold_changed; - - is_font_changed = is_font_face_changed || is_font_style_changed; - } - - if (is_font_changed || !exist_stored_style) + bool exist_change_in_font = is_font_changed(m_style_manager); + const GuiCfg::Translations &tr = m_gui_cfg->translations; + if (exist_change_in_font || !exist_stored_style) ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr.font); else ImGuiWrapper::text(tr.font); ImGui::SameLine(m_gui_cfg->input_offset); - ImGui::SetNextItemWidth(m_gui_cfg->input_width); draw_font_list(); - ImGui::SameLine(); bool exist_change = false; - if (draw_italic_button()) exist_change = true; - - ImGui::SameLine(); - if (draw_bold_button()) exist_change = true; - - if (is_font_changed) { + if (!m_is_unknown_font) { + ImGui::SameLine(); + if (draw_italic_button()) + exist_change = true; + ImGui::SameLine(); + if (draw_bold_button()) + exist_change = true; + } + EmbossStyle &style = m_style_manager.get_style(); + if (exist_change_in_font) { ImGui::SameLine(ImGui::GetStyle().FramePadding.x); if (draw_button(IconType::undo)) { const EmbossStyle *stored_style = m_style_manager.get_stored_style(); @@ -2378,41 +2794,8 @@ void GLGizmoEmboss::draw_style_edit() { } bool use_inch = wxGetApp().app_config->get("use_inches") == "1"; - const std::string revert_text_size = _u8L("Revert text size."); - FontProp &font_prop = style.prop; - const float * def_size = exist_stored_style? - &m_style_manager.get_stored_style()->prop.size_in_mm : nullptr; - bool is_size_changed = false; - if (use_inch) { - float size_in_inch = ObjectManipulation::mm_to_in * font_prop.size_in_mm; - float def_size_inch = exist_stored_style ? ObjectManipulation::mm_to_in * (*def_size) : 0.f; - if (def_size != nullptr) def_size = &def_size_inch; - if (rev_input(tr.size, size_in_inch, def_size, revert_text_size, 0.1f, 1.f, "%.2f in")) { - font_prop.size_in_mm = ObjectManipulation::in_to_mm * size_in_inch; - is_size_changed = true; - } - } else { - if (rev_input(tr.size, font_prop.size_in_mm, def_size, revert_text_size, 0.1f, 1.f, "%.1f mm")) - is_size_changed = true; - } - - if (is_size_changed) { - // size can't be zero or negative - Limits::apply(font_prop.size_in_mm, limits.size_in_mm); - - // only different value need process - if (!is_approx(font_prop.size_in_mm, m_volume->text_configuration->style.prop.size_in_mm)) { - // store font size into path - if (style.type == WxFontUtils::get_actual_type()) { - if (wx_font_opt.has_value()) { - wxFont wx_font = *wx_font_opt; - wx_font.SetPointSize(static_cast(font_prop.size_in_mm)); - m_style_manager.set_wx_font(wx_font); - } - } - process(); - } - } + draw_height(use_inch); + draw_depth(use_inch); #ifdef SHOW_WX_WEIGHT_INPUT if (wx_font.has_value()) { @@ -2438,31 +2821,53 @@ void GLGizmoEmboss::draw_style_edit() { if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("wx Make bold").c_str()); } -#endif // SHOW_WX_WEIGHT_INPUT - - const std::string revert_emboss_depth = _u8L("Revert embossed depth."); - const float *def_depth = exist_stored_style ? - &m_style_manager.get_stored_style()->prop.emboss : nullptr; - bool is_depth_changed = false; - if (use_inch) { - float depthj_in_inch = ObjectManipulation::mm_to_in * font_prop.emboss; - float def_depth_inch = exist_stored_style ? ObjectManipulation::mm_to_in * (*def_depth) : 0.f; - if (def_depth != nullptr) def_depth = &def_depth_inch; - if (rev_input(tr.depth, depthj_in_inch, def_depth, revert_emboss_depth, 0.1f, 0.25, "%.3f in")) { - font_prop.emboss = ObjectManipulation::in_to_mm * depthj_in_inch; - is_depth_changed = true; - } - } else { - if (rev_input(tr.depth, font_prop.emboss, def_depth, revert_emboss_depth, 0.1f, 0.25, "%.2f mm")) - is_depth_changed = true; - } - - if (is_depth_changed) { - Limits::apply(font_prop.emboss, limits.emboss); - process(); - } +#endif // SHOW_WX_WEIGHT_INPUT } +void GLGizmoEmboss::draw_height(bool use_inch) +{ + float &value = m_style_manager.get_style().prop.size_in_mm; + const EmbossStyle* stored_style = m_style_manager.get_stored_style(); + const float *stored = ((stored_style)? &stored_style->prop.size_in_mm : nullptr); + const char *size_format = ((use_inch) ? "%.2f in" : "%.1f mm"); + const std::string revert_text_size = _u8L("Revert text size."); + const std::string& name = m_gui_cfg->translations.size; + if (rev_input_mm(name, value, stored, revert_text_size, 0.1f, 1.f, size_format, use_inch, m_scale_height)) { + // size can't be zero or negative + priv::Limits::apply(value, priv::limits.size_in_mm); + // only different value need process + if (!is_approx(value, m_volume->text_configuration->style.prop.size_in_mm)) { + // store font size into path + EmbossStyle &style = m_style_manager.get_style(); + if (style.type == WxFontUtils::get_actual_type()) { + const std::optional &wx_font_opt = m_style_manager.get_wx_font(); + if (wx_font_opt.has_value()) { + wxFont wx_font = *wx_font_opt; + wx_font.SetPointSize(static_cast(value)); + m_style_manager.set_wx_font(wx_font); + } + } + process(); + } + } +} + +void GLGizmoEmboss::draw_depth(bool use_inch) +{ + float &value = m_style_manager.get_style().prop.emboss; + const EmbossStyle* stored_style = m_style_manager.get_stored_style(); + const float *stored = ((stored_style)? &stored_style->prop.emboss : nullptr); + const std::string revert_emboss_depth = _u8L("Revert embossed depth."); + const char *size_format = ((use_inch) ? "%.3f in" : "%.2f mm"); + const std::string name = m_gui_cfg->translations.depth; + if (rev_input_mm(name, value, stored, revert_emboss_depth, 0.1f, 1.f, size_format, use_inch, m_scale_depth)) { + // size can't be zero or negative + priv::Limits::apply(value, priv::limits.emboss); + process(); + } +} + + bool GLGizmoEmboss::rev_slider(const std::string &name, std::optional& value, const std::optional *default_value, @@ -2538,7 +2943,7 @@ void GLGizmoEmboss::do_translate(const Vec3d &relative_move) selection.setup_cache(); selection.translate(relative_move, TransformationType::Local); - std::string snapshot_name; // empty meand no store undo / redo + std::string snapshot_name; // empty mean no store undo / redo // NOTE: it use L instead of _L macro because prefix _ is appended inside // function do_move // snapshot_name = L("Set surface distance"); @@ -2562,32 +2967,44 @@ void GLGizmoEmboss::do_rotate(float relative_z_angle) m_parent.do_rotate(snapshot_name); } -void GLGizmoEmboss::set_fine_position() -{ - const Selection &selection = m_parent.get_selection(); - const Selection::IndicesList indices = selection.get_volume_idxs(); - // no selected volume - if (indices.empty()) return; - const GLVolume *volume = selection.get_volume(*indices.begin()); - // bad volume selected (e.g. deleted one) - if (volume == nullptr) return; +std::optional priv::calc_surface_offset(const ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection) { + // Move object on surface + auto cond = RaycastManager::SkipVolume({volume.id().id}); + raycast_manager.actualize(volume.get_object(), &cond); - const Camera &camera = wxGetApp().plater()->get_camera(); - Polygon hull = CameraUtils::create_hull2d(camera, *volume); + //const Selection &selection = m_parent.get_selection(); + const GLVolume *gl_volume = priv::get_gl_volume(selection); + Transform3d to_world = priv::world_matrix(gl_volume, selection.get_model()); + Vec3d point = to_world * Vec3d::Zero(); + Vec3d direction = to_world.linear() * (-Vec3d::UnitZ()); - const ImVec2 &windows_size = get_minimal_window_size(); - Size c_size = m_parent.get_canvas_size(); - ImVec2 canvas_size(c_size.get_width(), c_size.get_height()); - ImVec2 offset = ImGuiWrapper::suggest_location(windows_size, hull, canvas_size); - m_set_window_offset = offset; - return; + // ray in direction of text projection(from volume zero to z-dir) + std::optional hit_opt = raycast_manager.unproject(point, direction, &cond); + // start point lay on surface could appear slightly behind surface + std::optional hit_opt_opposit = raycast_manager.unproject(point, -direction, &cond); + if (!hit_opt.has_value() || + (hit_opt_opposit.has_value() && hit_opt->squared_distance > hit_opt_opposit->squared_distance)) + hit_opt = hit_opt_opposit; - Polygon rect({Point(offset.x, offset.y), - Point(offset.x + windows_size.x, offset.y), - Point(offset.x + windows_size.x, offset.y + windows_size.y), - Point(offset.x, offset.y + windows_size.y)}); - ImGuiWrapper::draw(hull); - ImGuiWrapper::draw(rect); + // Try to find closest point when no hit object in emboss direction + if (!hit_opt.has_value()) + hit_opt = raycast_manager.closest(point); + + // It should NOT appear. Closest point always exists. + if (!hit_opt.has_value()) + return {}; + + // It is no neccesary to move with origin by very small value + if (hit_opt->squared_distance < EPSILON) + return {}; + + const RaycastManager::Hit &hit = *hit_opt; + Transform3d hit_tr = raycast_manager.get_transformation(hit.tr_key); + Vec3d hit_world = hit_tr * hit.position.cast(); + Vec3d offset_world = hit_world - point; // vector in world + // TIP: It should be close to only z move + Vec3d offset_volume = to_world.inverse().linear() * offset_world; + return offset_volume; } void GLGizmoEmboss::draw_advanced() @@ -2635,8 +3052,11 @@ void GLGizmoEmboss::draw_advanced() &stored_style->prop.use_surface : nullptr; if (rev_checkbox(tr.use_surface, font_prop.use_surface, def_use_surface, _u8L("Revert using of model surface."))) { - if (font_prop.use_surface) { + if (font_prop.use_surface) { + // when using surface distance is not used font_prop.distance.reset(); + + // there should be minimal embossing depth if (font_prop.emboss < 0.1) font_prop.emboss = 1; } @@ -2656,7 +3076,7 @@ void GLGizmoEmboss::draw_advanced() if (rev_slider(tr.char_gap, font_prop.char_gap, def_char_gap, _u8L("Revert gap between letters"), min_char_gap, max_char_gap, units_fmt, _L("Distance between letters"))){ // Condition prevent recalculation when insertint out of limits value by imgui input - if (!Limits::apply(font_prop.char_gap, limits.char_gap) || + if (!priv::Limits::apply(font_prop.char_gap, priv::limits.char_gap) || !m_volume->text_configuration->style.prop.char_gap.has_value() || m_volume->text_configuration->style.prop.char_gap != font_prop.char_gap) { // char gap is stored inside of imgui font atlas @@ -2672,7 +3092,7 @@ void GLGizmoEmboss::draw_advanced() if (rev_slider(tr.line_gap, font_prop.line_gap, def_line_gap, _u8L("Revert gap between lines"), min_line_gap, max_line_gap, units_fmt, _L("Distance between lines"))){ // Condition prevent recalculation when insertint out of limits value by imgui input - if (!Limits::apply(font_prop.line_gap, limits.line_gap) || + if (!priv::Limits::apply(font_prop.line_gap, priv::limits.line_gap) || !m_volume->text_configuration->style.prop.line_gap.has_value() || m_volume->text_configuration->style.prop.line_gap != font_prop.line_gap) { // line gap is planed to be stored inside of imgui font atlas @@ -2685,8 +3105,8 @@ void GLGizmoEmboss::draw_advanced() auto def_boldness = stored_style ? &stored_style->prop.boldness : nullptr; if (rev_slider(tr.boldness, font_prop.boldness, def_boldness, _u8L("Undo boldness"), - limits.boldness.gui.min, limits.boldness.gui.max, units_fmt, _L("Tiny / Wide glyphs"))){ - if (!Limits::apply(font_prop.boldness, limits.boldness.values) || + priv::limits.boldness.gui.min, priv::limits.boldness.gui.max, units_fmt, _L("Tiny / Wide glyphs"))){ + if (!priv::Limits::apply(font_prop.boldness, priv::limits.boldness.values) || !m_volume->text_configuration->style.prop.boldness.has_value() || m_volume->text_configuration->style.prop.boldness != font_prop.boldness) exist_change = true; @@ -2696,8 +3116,8 @@ void GLGizmoEmboss::draw_advanced() auto def_skew = stored_style ? &stored_style->prop.skew : nullptr; if (rev_slider(tr.italic, font_prop.skew, def_skew, _u8L("Undo letter's skew"), - limits.skew.gui.min, limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){ - if (!Limits::apply(font_prop.skew, limits.skew.values) || + priv::limits.skew.gui.min, priv::limits.skew.gui.max, "%.2f", _L("Italic strength ratio"))){ + if (!priv::Limits::apply(font_prop.skew, priv::limits.skew.values) || !m_volume->text_configuration->style.prop.skew.has_value() || m_volume->text_configuration->style.prop.skew != font_prop.skew) exist_change = true; @@ -2763,11 +3183,11 @@ void GLGizmoEmboss::draw_advanced() float* def_angle_deg = stored_style ? &def_angle_deg_val : nullptr; if (rev_slider(tr.angle, angle_deg, def_angle_deg, _u8L("Undo rotation"), - limits.angle.min, limits.angle.max, u8"%.2f °", + priv::limits.angle.min, priv::limits.angle.max, u8"%.2f °", _L("Rotate text Clock-wise."))) { // convert back to radians and CCW angle = -angle_deg * M_PI / 180.0; - to_range_pi_pi(*angle); + priv::to_range_pi_pi(*angle); if (is_approx(*angle, 0.f)) angle.reset(); @@ -2806,6 +3226,17 @@ void GLGizmoEmboss::draw_advanced() m_style_manager.clear_glyphs_cache(); process(); } + + if (ImGui::Button(_u8L("Set text to face camera").c_str())) { + assert(priv::get_selected_volume(m_parent.get_selection()) == m_volume); + const Camera &cam = wxGetApp().plater()->get_camera(); + bool use_surface = m_style_manager.get_style().prop.use_surface; + if (priv::apply_camera_dir(cam, m_parent) && use_surface) + process(); + } else if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", _u8L("Use camera direction for text orientation").c_str()); + } + #ifdef ALLOW_DEBUG_MODE ImGui::Text("family = %s", (font_prop.family.has_value() ? font_prop.family->c_str() : @@ -2838,11 +3269,20 @@ void GLGizmoEmboss::set_minimal_window_size(bool is_advance_edit_style) ImGuiCond_Always); } -const ImVec2 &GLGizmoEmboss::get_minimal_window_size() const +ImVec2 GLGizmoEmboss::get_minimal_window_size() const { - return (!m_is_advanced_edit_style) ? m_gui_cfg->minimal_window_size : - ((!m_style_manager.has_collections())? m_gui_cfg->minimal_window_size_with_advance : - m_gui_cfg->minimal_window_size_with_collections); + ImVec2 res; + if (!m_is_advanced_edit_style) + res = m_gui_cfg->minimal_window_size; + else if (!m_style_manager.has_collections()) + res = m_gui_cfg->minimal_window_size_with_advance; + else + res = m_gui_cfg->minimal_window_size_with_collections; + + bool is_object = m_volume->get_object()->volumes.size() == 1; + if (!is_object) + res.y += m_gui_cfg->height_of_volume_type_selector; + return res; } #ifdef ALLOW_ADD_FONT_BY_OS_SELECTOR @@ -2959,66 +3399,6 @@ bool GLGizmoEmboss::choose_svg_file() //return add_volume(name, its); } -bool GLGizmoEmboss::load_configuration(ModelVolume *volume) -{ - if (volume == nullptr) return false; - const std::optional tc_opt = volume->text_configuration; - if (!tc_opt.has_value()) return false; - const TextConfiguration &tc = *tc_opt; - const EmbossStyle &style = tc.style; - - auto has_same_name = [&style](const StyleManager::Item &style_item) -> bool { - const EmbossStyle &es = style_item.style; - return es.name == style.name; - }; - - wxFont wx_font; - bool is_path_changed = false; - if (style.type == WxFontUtils::get_actual_type()) - wx_font = WxFontUtils::load_wxFont(style.path); - if (!wx_font.IsOk()) { - create_notification_not_valid_font(tc); - // Try create similar wx font - wx_font = WxFontUtils::create_wxFont(style); - is_path_changed = wx_font.IsOk(); - } - - const auto& styles = m_style_manager.get_styles(); - auto it = std::find_if(styles.begin(), styles.end(), has_same_name); - if (it == styles.end()) { - // style was not found - if (wx_font.IsOk()) - m_style_manager.load_style(style, wx_font); - } else { - size_t style_index = it - styles.begin(); - if (!m_style_manager.load_style(style_index)) { - // can`t load stored style - m_style_manager.erase(style_index); - if (wx_font.IsOk()) - m_style_manager.load_style(style, wx_font); - - } else { - // stored style is loaded, now set modification of style - m_style_manager.get_style() = style; - m_style_manager.set_wx_font(wx_font); - } - } - - if (is_path_changed) { - std::string path = WxFontUtils::store_wxFont(wx_font); - m_style_manager.get_style().path = path; - } - - m_text = tc.text; - m_volume = volume; - - // store volume state before edit - m_unmodified_volume = {*volume->get_mesh_shared_ptr(), // copy - tc, volume->get_matrix(), volume->name}; - - return true; -} - void GLGizmoEmboss::create_notification_not_valid_font( const TextConfiguration &tc) { @@ -3082,10 +3462,7 @@ void GLGizmoEmboss::init_icons() "make_bold.svg", "make_unbold.svg", "search.svg", - "open.svg", - "add_text_part.svg", - "add_text_negative.svg", - "add_text_modifier.svg" + "open.svg" }; assert(filenames.size() == static_cast(IconType::_count)); std::string path = resources_dir() + "/icons/"; @@ -3229,7 +3606,7 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager& st }; auto create_configuration = [&]() -> TextConfiguration { - if (!style_manager.is_activ_font()) { + if (!style_manager.is_active_font()) { std::string default_text_for_emboss = _u8L("Embossed text"); EmbossStyle es = style_manager.get_style(); TextConfiguration tc{es, default_text_for_emboss}; @@ -3249,29 +3626,6 @@ DataBase priv::create_emboss_data_base(const std::string &text, StyleManager& st return Slic3r::GUI::Emboss::DataBase{style_manager.get_font_file_with_cache(), create_configuration(), create_volume_name()}; } - -Transform3d priv::create_transformation_on_bed(const Vec2d &screen_coor, const Camera &camera, const std::vector &bed_shape, double z) -{ - // Create new object - // calculate X,Y offset position for lay on platter in place of - // mouse click - Vec2d bed_coor = CameraUtils::get_z0_position(camera, screen_coor); - - // check point is on build plate: - Points bed_shape_; - bed_shape_.reserve(bed_shape.size()); - for (const Vec2d &p : bed_shape) bed_shape_.emplace_back(p.cast()); - Slic3r::Polygon bed(bed_shape_); - if (!bed.contains(bed_coor.cast())) - // mouse pose is out of build plate so create object in center of plate - bed_coor = bed.centroid().cast(); - - Vec3d offset(bed_coor.x(), bed_coor.y(), z); - // offset -= m_result.center(); - Transform3d::TranslationType tt(offset.x(), offset.y(), offset.z()); - return Transform3d(tt); -} - void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor) { // start creation of new object @@ -3284,10 +3638,8 @@ void priv::start_create_object_job(DataBase &emboss_data, const Vec2d &coor) if (prop.distance.has_value()) prop.distance.reset(); // can't create new object with using surface - if (prop.use_surface) { - priv::message_disable_cut_surface(); + if (prop.use_surface) prop.use_surface = false; - } // Transform3d volume_tr = priv::create_transformation_on_bed(mouse_pos, camera, bed_shape, prop.emboss / 2); DataCreateObject data{std::move(emboss_data), coor, camera, bed_shape}; @@ -3307,7 +3659,6 @@ void priv::start_create_volume_job(const ModelObject *object, // Model to cut surface from. SurfaceVolumeData::ModelSources sources = create_sources(object->volumes); if (sources.empty()) { - priv::message_disable_cut_surface(); use_surface = false; } else { bool is_outside = volume_type == ModelVolumeType::MODEL_PART; @@ -3348,7 +3699,7 @@ bool priv::start_create_volume_on_surface_job( const ModelObjectPtrs &objects = plater->model().objects; int object_idx = gl_volume->object_idx(); - if (object_idx < 0 || object_idx >= objects.size()) return false; + if (object_idx < 0 || static_cast(object_idx) >= objects.size()) return false; ModelObject *obj = objects[object_idx]; size_t vol_id = obj->volumes[gl_volume->volume_idx()]->id().id; auto cond = RaycastManager::AllowVolumes({vol_id}); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp index fe3ccf73b..6340b6fae 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp @@ -87,8 +87,10 @@ private: // localized default text void set_default_text(); - void check_selection(); - ModelVolume *get_selected_volume(); + void set_volume_by_selection(); + // load text configuration from volume into gizmo + bool set_volume(ModelVolume *volume); + // create volume from text - main functionality bool process(); void close(); @@ -109,6 +111,9 @@ private: void draw_font_preview(FaceName &face, bool is_visible); void draw_font_list(); void draw_style_edit(); + void draw_height(bool use_inch); + void draw_depth(bool use_inch); + bool draw_italic_button(); bool draw_bold_button(); void draw_advanced(); @@ -119,11 +124,9 @@ private: void do_translate(const Vec3d& relative_move); void do_rotate(float relative_z_angle); - /// - /// Move window for edit emboss text near to embossed object - /// NOTE: embossed object must be selected - /// - void set_fine_position(); + bool rev_input_mm(const std::string &name, float &value, const float *default_value, + const std::string &undo_tooltip, float step, float step_fast, const char *format, + bool use_inch, const std::optional& scale); /// /// Reversible input float with option to restor default value @@ -143,8 +146,9 @@ private: template bool revertible(const std::string &name, T &value, const T *default_value, const std::string &undo_tooltip, float undo_offset, Draw draw); + bool m_should_set_minimal_windows_size = false; void set_minimal_window_size(bool is_advance_edit_style); - const ImVec2 &get_minimal_window_size() const; + ImVec2 get_minimal_window_size() const; // process mouse event bool on_mouse_for_rotation(const wxMouseEvent &mouse_event); @@ -154,8 +158,6 @@ private: bool choose_true_type_file(); bool choose_svg_file(); - bool load_configuration(ModelVolume *volume); - // When open text loaded from .3mf it could be written with unknown font bool m_is_unknown_font; void create_notification_not_valid_font(const TextConfiguration& tc); @@ -170,15 +172,15 @@ private: ImVec2 minimal_window_size = ImVec2(0, 0); ImVec2 minimal_window_size_with_advance = ImVec2(0, 0); ImVec2 minimal_window_size_with_collections = ImVec2(0, 0); - float input_width = 0.f; - float delete_pos_x = 0.f; - float max_style_name_width = 0.f; - unsigned int icon_width = 0; + float height_of_volume_type_selector = 0.f; + float input_width = 0.f; + float delete_pos_x = 0.f; + float max_style_name_width = 0.f; + unsigned int icon_width = 0; // maximal width and height of style image Vec2i max_style_image_size = Vec2i(0, 0); - float style_offset = 0.f; float input_offset = 0.f; float advanced_input_offset = 0.f; @@ -195,8 +197,6 @@ private: // Only translations needed for calc GUI size struct Translations { - std::string type; - std::string style; std::string font; std::string size; std::string depth; @@ -216,9 +216,13 @@ private: GuiCfg() = default; }; std::optional m_gui_cfg; + bool m_is_advanced_edit_style = false; + + // when true window will appear near to text volume when open + // When false it opens on last position + bool m_allow_open_near_volume = false; // setted only when wanted to use - not all the time std::optional m_set_window_offset; - bool m_is_advanced_edit_style = false; Emboss::StyleManager m_style_manager; @@ -254,6 +258,7 @@ private: // protection for open too much font files together // Gtk:ERROR:../../../../gtk/gtkiconhelper.c:494:ensure_surface_for_gicon: assertion failed (error == NULL): Failed to load /usr/share/icons/Yaru/48x48/status/image-missing.png: Error opening file /usr/share/icons/Yaru/48x48/status/image-missing.png: Too many open files (g-io-error-quark, 31) + // This variable must exist until no CreateFontImageJob is running unsigned int count_opened_font_files = 0; // Configuration for texture height @@ -307,6 +312,11 @@ private: // Only when drag text object it stores world position std::optional m_temp_transformation; + // For text on scaled objects + std::optional m_scale_height; + std::optional m_scale_depth; + void calculate_scale(); + // drawing icons GLTexture m_icons_texture; void init_icons(); @@ -322,10 +332,6 @@ private: unbold, system_selector, open_file, - // VolumeType icons - part, - negative, - modifier, // automatic calc of icon's count _count }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 5f0a110ce..3827cd331 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -3,6 +3,8 @@ #include "libslic3r/Model.hpp" //#include "slic3r/GUI/3DScene.hpp" +#include "libslic3r/SupportSpotsGenerator.hpp" +#include "libslic3r/TriangleSelectorWrapper.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" @@ -392,44 +394,63 @@ void GLGizmoFdmSupports::data_changed() ModelObject* mo = m_c->selection_info()->model_object(); if (mo && this->waiting_for_autogenerated_supports) { - get_data_from_backend(); + apply_data_from_backend(); } else { this->waiting_for_autogenerated_supports = false; } } -void GLGizmoFdmSupports::get_data_from_backend() +void GLGizmoFdmSupports::apply_data_from_backend() { - if (! has_backend_supports()) + if (!has_backend_supports()) return; - ModelObject* mo = m_c->selection_info()->model_object(); + ModelObject *mo = m_c->selection_info()->model_object(); + if (!mo) { + this->waiting_for_autogenerated_supports = false; + return; + } // find the respective PrintObject, we need a pointer to it - for (const PrintObject* po : m_parent.fff_print()->objects()) { + for (const PrintObject *po : m_parent.fff_print()->objects()) { if (po->model_object()->id() == mo->id()) { - std::unordered_map mvs; - for (const ModelVolume* mv : po->model_object()->volumes) { - if (mv->is_model_part()) { - mvs.emplace(mv->id().id, mv); + std::unordered_map selectors; + SupportSpotsGenerator::SupportPoints support_points = po->shared_regions()->generated_support_points->support_points; + auto obj_transform = po->shared_regions()->generated_support_points->object_transform; + for (ModelVolume *model_volume : po->model_object()->volumes) { + if (model_volume->is_model_part()) { + Transform3d mesh_transformation = obj_transform * model_volume->get_matrix(); + Transform3d inv_transform = mesh_transformation.inverse(); + selectors.emplace(model_volume->id().id, TriangleSelectorWrapper{model_volume->mesh(), mesh_transformation}); + + for (const SupportSpotsGenerator::SupportPoint &support_point : support_points) { + Vec3f point = Vec3f(inv_transform.cast() * support_point.position); + Vec3f origin = Vec3f(inv_transform.cast() * + Vec3f(support_point.position.x(), support_point.position.y(), 0.0f)); + selectors.at(model_volume->id().id).enforce_spot(point, origin, support_point.spot_radius); + } } } + int mesh_id = -1.0f; - for (ModelVolume* mv : mo->volumes){ - if (mv->is_model_part()){ + for (ModelVolume *mv : mo->volumes) { + if (mv->is_model_part()) { mesh_id++; - mv->supported_facets.assign(mvs[mv->id().id]->supported_facets); - m_triangle_selectors[mesh_id]->deserialize(mv->supported_facets.get_data(), true); - m_triangle_selectors[mesh_id]->request_update_render_data(); + auto selector = selectors.find(mv->id().id); + if (selector != selectors.end()) { + mv->supported_facets.set(selector->second.selector); + m_triangle_selectors[mesh_id]->deserialize(mv->supported_facets.get_data(), true); + m_triangle_selectors[mesh_id]->request_update_render_data(); + } } } - this->waiting_for_autogenerated_supports = false; + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); m_parent.set_as_dirty(); } + this->waiting_for_autogenerated_supports = false; } } - void GLGizmoFdmSupports::update_model_object() const { bool updated = false; @@ -474,18 +495,26 @@ void GLGizmoFdmSupports::update_from_model_object() } } -bool GLGizmoFdmSupports::has_backend_supports() const +bool GLGizmoFdmSupports::has_backend_supports() { - const ModelObject* mo = m_c->selection_info()->model_object(); - if (! mo) + const ModelObject *mo = m_c->selection_info()->model_object(); + if (!mo) { + waiting_for_autogenerated_supports = false; return false; + } // find PrintObject with this ID - for (const PrintObject* po : m_parent.fff_print()->objects()) { + bool done = false; + for (const PrintObject *po : m_parent.fff_print()->objects()) { if (po->model_object()->id() == mo->id()) - return po->is_step_done(posSupportSpotsSearch); + done = po->is_step_done(posSupportSpotsSearch); } - return false; + + if (!done && !wxGetApp().plater()->is_background_process_update_scheduled()) { + waiting_for_autogenerated_supports = false; + } + + return done; } void GLGizmoFdmSupports::reslice_FDM_supports(bool postpone_error_messages) const { @@ -520,8 +549,6 @@ void GLGizmoFdmSupports::auto_generate() } } - mo->config.set("support_material", true); - mo->config.set("support_material_auto", false); this->waiting_for_autogenerated_supports = true; wxGetApp().CallAfter([this]() { reslice_FDM_supports(); }); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 2820298b6..fb20181d8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -43,10 +43,10 @@ private: bool waiting_for_autogenerated_supports = false; - bool has_backend_supports() const; + bool has_backend_supports(); void reslice_FDM_supports(bool postpone_error_messages = false) const; void auto_generate(); - void get_data_from_backend(); + void apply_data_from_backend(); }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp index cc786b1ad..fc99e52c0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -1359,10 +1359,10 @@ void GLGizmoMeasure::render_dimensioning() action_exit(); ImGui::SameLine(); - if (m_imgui->button(_u8L("Scale"))) + if (m_imgui->button(_CTX(L_CONTEXT("Scale", "Verb"), "Verb"))) action_scale(edit_value, curr_value); ImGui::SameLine(); - if (m_imgui->button(_u8L("Cancel"))) + if (m_imgui->button(_L("Cancel"))) action_exit(); ImGui::EndPopup(); } @@ -1991,7 +1991,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit radius = (on_circle - center).norm(); if (use_inches) radius = ObjectManipulation::mm_to_in * radius; - text += " (" + _u8L("Diameter:") + " " + format_double(2.0 * radius) + units + ")"; + text += " (" + _u8L("Diameter") + ": " + format_double(2.0 * radius) + units + ")"; } return text; }; @@ -2004,7 +2004,7 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit } m_imgui->disabled_begin(!m_selected_features.first.feature.has_value()); - if (m_imgui->button(_u8L("Restart selection"))) { + if (m_imgui->button(_L("Restart selection"))) { m_selected_features.reset(); m_selected_sphere_raycasters.clear(); m_imgui->set_requires_extra_frame(); @@ -2040,22 +2040,26 @@ void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit ++measure_row_count; ImGui::PopID(); } + + const bool show_strict = measure.distance_strict.has_value() && + (!measure.distance_infinite.has_value() || std::abs(measure.distance_strict->dist - measure.distance_infinite->dist) > EPSILON); + if (measure.distance_infinite.has_value()) { double distance = measure.distance_infinite->dist; if (use_inches) distance = ObjectManipulation::mm_to_in * distance; ImGui::PushID("ClipboardDistanceInfinite"); - add_measure_row_to_table(_u8L("Distance"), ImGuiWrapper::COL_ORANGE_LIGHT, format_double(distance) + units, + add_measure_row_to_table(show_strict ? _u8L("Perpendicular distance") : _u8L("Distance"), ImGuiWrapper::COL_ORANGE_LIGHT, format_double(distance) + units, ImGui::GetStyleColorVec4(ImGuiCol_Text)); ++measure_row_count; ImGui::PopID(); } - if (measure.distance_strict.has_value() && !measure.distance_infinite.has_value()) { + if (show_strict) { double distance = measure.distance_strict->dist; if (use_inches) distance = ObjectManipulation::mm_to_in * distance; ImGui::PushID("ClipboardDistanceStrict"); - add_measure_row_to_table(_u8L("Distance"), ImGuiWrapper::COL_ORANGE_LIGHT, format_double(distance) + units, + add_measure_row_to_table(_u8L("Direct distance"), ImGuiWrapper::COL_ORANGE_LIGHT, format_double(distance) + units, ImGui::GetStyleColorVec4(ImGuiCol_Text)); ++measure_row_count; ImGui::PopID(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 69fcc5414..7656226b5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -149,7 +149,10 @@ void GLGizmoMove3D::on_render() glsafe(::glEnable(GL_DEPTH_TEST)); #if ENABLE_WORLD_COORDINATE - calc_selection_box_and_center(); + const Selection& selection = m_parent.get_selection(); + const auto& [box, box_trafo] = selection.get_bounding_box_in_current_reference_system(); + m_bounding_box = box; + m_center = box_trafo.translation(); const Transform3d base_matrix = local_transform(m_parent.get_selection()); for (int i = 0; i < 3; ++i) { m_grabbers[i].matrix = base_matrix; @@ -363,33 +366,6 @@ Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const } return ret; } - -void GLGizmoMove3D::calc_selection_box_and_center() -{ - const Selection& selection = m_parent.get_selection(); - const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); - if (coordinates_type == ECoordinatesType::World) { - m_bounding_box = selection.get_bounding_box(); - m_center = m_bounding_box.center(); - } - else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_first_volume(); - m_bounding_box = v.transformed_convex_hull_bounding_box( - v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); - m_center = v.world_matrix() * m_bounding_box.center(); - } - else { - m_bounding_box.reset(); - const Selection::IndicesList& ids = selection.get_volume_idxs(); - for (unsigned int id : ids) { - const GLVolume& v = *selection.get_volume(id); - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); - } - const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation(); - m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix()); - m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center(); - } -} #endif // ENABLE_WORLD_COORDINATE } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 5f1d562e9..cd92d7472 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -67,7 +67,6 @@ private: double calc_projection(const UpdateData& data) const; #if ENABLE_WORLD_COORDINATE Transform3d local_transform(const Selection& selection) const; - void calc_selection_box_and_center(); #endif // ENABLE_WORLD_COORDINATE }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 6e7cf9c83..345d733af 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -234,50 +234,17 @@ void GLGizmoRotate::on_render() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { - ECoordinatesType coordinates_type; - if (m_using_local_coordinate || - selection.is_wipe_tower()) - coordinates_type = ECoordinatesType::Local; - else - coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); - if (coordinates_type == ECoordinatesType::World) { - m_bounding_box = selection.get_bounding_box(); - m_center = m_bounding_box.center(); - } - else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_first_volume(); - m_bounding_box = v.transformed_convex_hull_bounding_box( - v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); - m_center = v.world_matrix() * m_bounding_box.center(); - } - else { - m_bounding_box.reset(); - const Selection::IndicesList& ids = selection.get_volume_idxs(); - for (unsigned int id : ids) { - const GLVolume& v = *selection.get_volume(id); - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); - } - const Geometry::Transformation inst_trafo = selection.get_first_volume()->get_instance_transformation(); - m_bounding_box = m_bounding_box.transformed(inst_trafo.get_scaling_factor_matrix()); - m_center = inst_trafo.get_matrix_no_scaling_factor() * m_bounding_box.center(); - } + const auto [box, box_trafo] = m_force_local_coordinate ? + selection.get_bounding_box_in_reference_system(ECoordinatesType::Local) : selection.get_bounding_box_in_current_reference_system(); + m_bounding_box = box; + m_center = box_trafo.translation(); + m_orient_matrix = box_trafo; m_radius = Offset + m_bounding_box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius; m_snap_fine_in_radius = m_radius; m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; - - if (coordinates_type == ECoordinatesType::World) - m_orient_matrix = Transform3d::Identity(); - else if (coordinates_type == ECoordinatesType::Local && (selection.is_wipe_tower() || selection.is_single_volume_or_modifier())) { - const GLVolume& v = *selection.get_first_volume(); - m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); - } - else { - const GLVolume& v = *selection.get_first_volume(); - m_orient_matrix = v.get_instance_transformation().get_rotation_matrix(); - } } #endif // ENABLE_WORLD_COORDINATE @@ -531,7 +498,7 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const } #if ENABLE_WORLD_COORDINATE - return Geometry::translation_transform(m_center) * m_orient_matrix * ret; + return m_orient_matrix * ret; #else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) ret = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true) * ret; @@ -546,7 +513,7 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray) cons Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const #endif // ENABLE_WORLD_COORDINATE { - double half_pi = 0.5 * double(PI); + const double half_pi = 0.5 * double(PI); Transform3d m = Transform3d::Identity(); @@ -573,7 +540,7 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons } #if ENABLE_WORLD_COORDINATE - m = m * m_orient_matrix.inverse(); + m = m * Geometry::Transformation(m_orient_matrix).get_matrix_no_offset().inverse(); #else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) m = m * selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true).inverse(); @@ -614,7 +581,7 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; if (m_parent.get_selection().is_wipe_tower()) - transformation_type = TransformationType::Instance_Relative_Joint; + transformation_type = TransformationType::World_Relative_Joint; else { switch (wxGetApp().obj_manipul()->get_coordinates_type()) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index fa52e3a78..ebfed1920 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -56,7 +56,7 @@ private: float m_old_angle{ 0.0f }; // emboss need to draw rotation gizmo in local coordinate systems - bool m_using_local_coordinate{false}; + bool m_force_local_coordinate{ false }; ColorRGBA m_drag_color; ColorRGBA m_highlight_color; @@ -71,7 +71,7 @@ public: std::string get_tooltip() const override; void set_group_id(int group_id) { m_group_id = group_id; } - void set_using_local_coordinate(bool use) { m_using_local_coordinate =use;} + void set_force_local_coordinate(bool use) { m_force_local_coordinate = use; } void start_dragging(); void stop_dragging(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 1787e5cf8..173585ec0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -206,6 +206,7 @@ void GLGizmoScale3D::on_dragging(const UpdateData& data) do_scale_uniform(data); } +#if ENABLE_WORLD_COORDINATE void GLGizmoScale3D::on_render() { const Selection& selection = m_parent.get_selection(); @@ -213,98 +214,20 @@ void GLGizmoScale3D::on_render() glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - m_bounding_box.reset(); -#if ENABLE_WORLD_COORDINATE - m_grabbers_transform = Transform3d::Identity(); - m_center = Vec3d::Zero(); + const auto& [box, box_trafo] = selection.get_bounding_box_in_current_reference_system(); + m_bounding_box = box; + m_center = box_trafo.translation(); + m_grabbers_transform = box_trafo; m_instance_center = Vec3d::Zero(); - if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { -#else - m_transform = Transform3d::Identity(); - // Transforms grabbers' offsets to world refefence system - Transform3d offsets_transform = Transform3d::Identity(); - m_offsets_transform = Transform3d::Identity(); - Vec3d angles = Vec3d::Zero(); - - if (selection.is_single_full_instance()) { -#endif // ENABLE_WORLD_COORDINATE - // calculate bounding box in instance local reference system - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - for (unsigned int idx : idxs) { - const GLVolume& v = *selection.get_volume(idx); - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); - } - -#if ENABLE_WORLD_COORDINATE - m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix()); -#endif // ENABLE_WORLD_COORDINATE - - // gets transform from first selected volume - const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_WORLD_COORDINATE - const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor(); - m_grabbers_transform = inst_trafo * Geometry::translation_transform(m_bounding_box.center()); - m_center = inst_trafo * m_bounding_box.center(); - m_instance_center = v.get_instance_offset(); - } - else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { -#else - m_transform = v.get_instance_transformation().get_matrix(); - - // gets angles from first selected volume - angles = v.get_instance_rotation(); - // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror()); - m_offsets_transform = offsets_transform; - } - else if (selection.is_single_modifier() || selection.is_single_volume()) { -#endif // ENABLE_WORLD_COORDINATE - const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_WORLD_COORDINATE - m_bounding_box.merge(v.transformed_convex_hull_bounding_box( - v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_matrix_no_offset())); - Geometry::Transformation trafo(v.get_instance_transformation().get_rotation_matrix()); - trafo.set_offset(v.world_matrix().translation()); - m_grabbers_transform = trafo.get_matrix(); - m_center = v.world_matrix() * m_bounding_box.center(); + if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) + m_instance_center = selection.get_first_volume()->get_instance_offset(); + else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) m_instance_center = m_center; - } - else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { - const GLVolume& v = *selection.get_first_volume(); - m_bounding_box.merge(v.transformed_convex_hull_bounding_box( - v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix())); - Geometry::Transformation trafo(v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix()); - trafo.set_offset(v.world_matrix().translation()); - m_grabbers_transform = trafo.get_matrix(); - m_center = v.world_matrix() * m_bounding_box.center(); + else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) m_instance_center = m_center; - } - else { - m_bounding_box = selection.get_bounding_box(); - m_grabbers_transform = Geometry::translation_transform(m_bounding_box.center()); - m_center = m_bounding_box.center(); - m_instance_center = selection.is_single_full_instance() ? selection.get_first_volume()->get_instance_offset() : m_center; - } -#else - m_bounding_box = v.bounding_box(); - m_transform = v.world_matrix(); - angles = Geometry::extract_rotation(m_transform); - // consider rotation+mirror only components of the transform for offsets - offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror()); - m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation(), Vec3d::Ones(), v.get_volume_mirror()); - } else - m_bounding_box = selection.get_bounding_box(); + m_instance_center = selection.is_single_full_instance() ? selection.get_first_volume()->get_instance_offset() : m_center; - const Vec3d& center = m_bounding_box.center(); - const Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); - const Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); - const Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); - - const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); -#endif // ENABLE_WORLD_COORDINATE - -#if ENABLE_WORLD_COORDINATE // x axis const Vec3d box_half_size = 0.5 * m_bounding_box.size(); bool use_constrain = wxGetKeyState(WXK_CONTROL) && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); @@ -335,57 +258,18 @@ void GLGizmoScale3D::on_render() m_grabbers[8].color = (use_constrain && m_hover_id == 6) ? CONSTRAINED_COLOR : m_highlight_color; m_grabbers[9].center = { -(box_half_size.x() + Offset), box_half_size.y() + Offset, 0.0 }; m_grabbers[9].color = (use_constrain && m_hover_id == 7) ? CONSTRAINED_COLOR : m_highlight_color; -#else - // x axis - m_grabbers[0].center = m_transform * Vec3d(m_bounding_box.min.x(), center.y(), center.z()) - offset_x; - m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; - m_grabbers[1].center = m_transform * Vec3d(m_bounding_box.max.x(), center.y(), center.z()) + offset_x; - m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; - - // y axis - m_grabbers[2].center = m_transform * Vec3d(center.x(), m_bounding_box.min.y(), center.z()) - offset_y; - m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; - m_grabbers[3].center = m_transform * Vec3d(center.x(), m_bounding_box.max.y(), center.z()) + offset_y; - m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; - - // z axis - m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.min.z()) - offset_z; - m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; - m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.max.z()) + offset_z; - m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; - - // uniform - m_grabbers[6].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.min.y(), center.z()) - offset_x - offset_y; - m_grabbers[7].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.min.y(), center.z()) + offset_x - offset_y; - m_grabbers[8].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.max.y(), center.z()) + offset_x + offset_y; - m_grabbers[9].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.max.y(), center.z()) - offset_x + offset_y; - - for (int i = 6; i < 10; ++i) { - m_grabbers[i].color = m_highlight_color; - } - - // sets grabbers orientation - for (int i = 0; i < 10; ++i) { - m_grabbers[i].angles = angles; - } -#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_CORE_PROFILE if (!OpenGLManager::get_gl_info().is_core_profile()) #endif // ENABLE_GL_CORE_PROFILE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); -#if ENABLE_WORLD_COORDINATE const Transform3d base_matrix = local_transform(selection); for (int i = 0; i < 10; ++i) { m_grabbers[i].matrix = base_matrix; } const float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0); -#else - const BoundingBoxf3& selection_box = selection.get_bounding_box(); - const float grabber_mean_size = (float)((selection_box.size().x() + selection_box.size().y() + selection_box.size().z()) / 3.0); -#endif // ENABLE_WORLD_COORDINATE if (m_hover_id == -1) { // draw connections @@ -397,11 +281,7 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_WORLD_COORDINATE shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); -#else - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); -#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -435,11 +315,7 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_WORLD_COORDINATE shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); -#else - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); -#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -471,11 +347,7 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_WORLD_COORDINATE shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); -#else - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); -#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -507,11 +379,7 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_WORLD_COORDINATE shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); -#else - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); -#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -543,11 +411,7 @@ void GLGizmoScale3D::on_render() if (shader != nullptr) { shader->start_using(); const Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_WORLD_COORDINATE shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); -#else - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); -#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #if ENABLE_GL_CORE_PROFILE const std::array& viewport = camera.get_viewport(); @@ -574,6 +438,267 @@ void GLGizmoScale3D::on_render() } } } +#else +void GLGizmoScale3D::on_render() +{ + const Selection& selection = m_parent.get_selection(); + + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + m_bounding_box.reset(); + m_transform = Transform3d::Identity(); + // Transforms grabbers' offsets to world refefence system + Transform3d offsets_transform = Transform3d::Identity(); + m_offsets_transform = Transform3d::Identity(); + Vec3d angles = Vec3d::Zero(); + + if (selection.is_single_full_instance()) { + // calculate bounding box in instance local reference system + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + for (unsigned int idx : idxs) { + const GLVolume& v = *selection.get_volume(idx); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); + } + + // gets transform from first selected volume + const GLVolume& v = *selection.get_first_volume(); + m_transform = v.get_instance_transformation().get_matrix(); + + // gets angles from first selected volume + angles = v.get_instance_rotation(); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror()); + m_offsets_transform = offsets_transform; + } + else if (selection.is_single_modifier() || selection.is_single_volume()) { + const GLVolume& v = *selection.get_first_volume(); + m_bounding_box = v.bounding_box(); + m_transform = v.world_matrix(); + angles = Geometry::extract_rotation(m_transform); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror()); + m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation(), Vec3d::Ones(), v.get_volume_mirror()); + } + else + m_bounding_box = selection.get_bounding_box(); + + const Vec3d& center = m_bounding_box.center(); + const Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0); + const Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0); + const Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset); + + const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); + + // x axis + m_grabbers[0].center = m_transform * Vec3d(m_bounding_box.min.x(), center.y(), center.z()) - offset_x; + m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + m_grabbers[1].center = m_transform * Vec3d(m_bounding_box.max.x(), center.y(), center.z()) + offset_x; + m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + + // y axis + m_grabbers[2].center = m_transform * Vec3d(center.x(), m_bounding_box.min.y(), center.z()) - offset_y; + m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + m_grabbers[3].center = m_transform * Vec3d(center.x(), m_bounding_box.max.y(), center.z()) + offset_y; + m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + + // z axis + m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.min.z()) - offset_z; + m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; + m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.max.z()) + offset_z; + m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; + + // uniform + m_grabbers[6].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.min.y(), center.z()) - offset_x - offset_y; + m_grabbers[7].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.min.y(), center.z()) + offset_x - offset_y; + m_grabbers[8].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.max.y(), center.z()) + offset_x + offset_y; + m_grabbers[9].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.max.y(), center.z()) - offset_x + offset_y; + + for (int i = 6; i < 10; ++i) { + m_grabbers[i].color = m_highlight_color; + } + + // sets grabbers orientation + for (int i = 0; i < 10; ++i) { + m_grabbers[i].angles = angles; + } + +#if ENABLE_GL_CORE_PROFILE + if (!OpenGLManager::get_gl_info().is_core_profile()) +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); + + const BoundingBoxf3& selection_box = selection.get_bounding_box(); + const float grabber_mean_size = (float)((selection_box.size().x() + selection_box.size().y() + selection_box.size().z()) / 3.0); + + if (m_hover_id == -1) { + // draw connections +#if ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_CORE_PROFILE + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#if ENABLE_GL_CORE_PROFILE + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#endif // ENABLE_GL_CORE_PROFILE + if (m_grabbers[0].enabled && m_grabbers[1].enabled) + render_grabbers_connection(0, 1, m_grabbers[0].color); + if (m_grabbers[2].enabled && m_grabbers[3].enabled) + render_grabbers_connection(2, 3, m_grabbers[2].color); + if (m_grabbers[4].enabled && m_grabbers[5].enabled) + render_grabbers_connection(4, 5, m_grabbers[4].color); + render_grabbers_connection(6, 7, m_base_color); + render_grabbers_connection(7, 8, m_base_color); + render_grabbers_connection(8, 9, m_base_color); + render_grabbers_connection(9, 6, m_base_color); + shader->stop_using(); + } + + // draw grabbers + render_grabbers(grabber_mean_size); + } + else if ((m_hover_id == 0 || m_hover_id == 1) && m_grabbers[0].enabled && m_grabbers[1].enabled) { + // draw connections +#if ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_CORE_PROFILE + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#if ENABLE_GL_CORE_PROFILE + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#endif // ENABLE_GL_CORE_PROFILE + render_grabbers_connection(0, 1, m_grabbers[0].color); + shader->stop_using(); + } + + // draw grabbers + shader = wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + m_grabbers[0].render(true, grabber_mean_size); + m_grabbers[1].render(true, grabber_mean_size); + shader->stop_using(); + } + } + else if ((m_hover_id == 2 || m_hover_id == 3) && m_grabbers[2].enabled && m_grabbers[3].enabled) { + // draw connections +#if ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_CORE_PROFILE + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#if ENABLE_GL_CORE_PROFILE + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#endif // ENABLE_GL_CORE_PROFILE + render_grabbers_connection(2, 3, m_grabbers[2].color); + shader->stop_using(); + } + + // draw grabbers + shader = wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + m_grabbers[2].render(true, grabber_mean_size); + m_grabbers[3].render(true, grabber_mean_size); + shader->stop_using(); + } + } + else if ((m_hover_id == 4 || m_hover_id == 5) && m_grabbers[4].enabled && m_grabbers[5].enabled) { + // draw connections +#if ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_CORE_PROFILE + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#if ENABLE_GL_CORE_PROFILE + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#endif // ENABLE_GL_CORE_PROFILE + render_grabbers_connection(4, 5, m_grabbers[4].color); + shader->stop_using(); + } + + // draw grabbers + shader = wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + m_grabbers[4].render(true, grabber_mean_size); + m_grabbers[5].render(true, grabber_mean_size); + shader->stop_using(); + } + } + else if (m_hover_id >= 6) { + // draw connections +#if ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_CORE_PROFILE + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#if ENABLE_GL_CORE_PROFILE + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#endif // ENABLE_GL_CORE_PROFILE + render_grabbers_connection(6, 7, m_drag_color); + render_grabbers_connection(7, 8, m_drag_color); + render_grabbers_connection(8, 9, m_drag_color); + render_grabbers_connection(9, 6, m_drag_color); + shader->stop_using(); + } + + // draw grabbers + shader = wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + for (int i = 6; i < 10; ++i) { + m_grabbers[i].render(true, grabber_mean_size); + } + shader->stop_using(); + } + } +} +#endif // ENABLE_WORLD_COORDINATE void GLGizmoScale3D::on_register_raycasters_for_picking() { diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 64cf7d9ed..20913f66d 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -96,7 +96,12 @@ static const std::map font_icons_large = { {ImGui::DocumentationButton , "notification_documentation" }, {ImGui::DocumentationHoverButton, "notification_documentation_hover"}, {ImGui::InfoMarker , "notification_info" }, - + {ImGui::PlayButton , "notification_play" }, + {ImGui::PlayHoverButton , "notification_play_hover" }, + {ImGui::PauseButton , "notification_pause" }, + {ImGui::PauseHoverButton , "notification_pause_hover" }, + {ImGui::OpenButton , "notification_open" }, + {ImGui::OpenHoverButton , "notification_open_hover" }, }; static const std::map font_icons_extra_large = { @@ -257,20 +262,39 @@ bool ImGuiWrapper::update_key_data(wxKeyEvent &evt) return false; } - ImGuiIO& io = ImGui::GetIO(); + auto to_string = [](wxEventType type) -> std::string { + if (type == wxEVT_CHAR) return "Char"; + if (type == wxEVT_KEY_DOWN) return "KeyDown"; + if (type == wxEVT_KEY_UP) return "KeyUp"; + return "Other"; + }; - if (evt.GetEventType() == wxEVT_CHAR) { + wxEventType type = evt.GetEventType(); + ImGuiIO& io = ImGui::GetIO(); + BOOST_LOG_TRIVIAL(debug) << "ImGui - key event(" << to_string(type) << "):" + //<< " Unicode(" << evt.GetUnicodeKey() << ")" + << " KeyCode(" << evt.GetKeyCode() << ")"; + + if (type == wxEVT_CHAR) { // Char event - const auto key = evt.GetUnicodeKey(); + const auto key = evt.GetUnicodeKey(); + + // Release BackSpace, Delete, ... when miss wxEVT_KEY_UP event + // Already Fixed at begining of new frame + // unsigned int key_u = static_cast(key); + //if (key_u >= 0 && key_u < IM_ARRAYSIZE(io.KeysDown) && io.KeysDown[key_u]) { + // io.KeysDown[key_u] = false; + //} + if (key != 0) { io.AddInputCharacter(key); } - } else { + } else if (type == wxEVT_KEY_DOWN || type == wxEVT_KEY_UP) { // Key up/down event int key = evt.GetKeyCode(); wxCHECK_MSG(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown), false, "Received invalid key code"); - io.KeysDown[key] = evt.GetEventType() == wxEVT_KEY_DOWN; + io.KeysDown[key] = (type == wxEVT_KEY_DOWN); io.KeyShift = evt.ShiftDown(); io.KeyCtrl = evt.ControlDown(); io.KeyAlt = evt.AltDown(); @@ -282,6 +306,7 @@ bool ImGuiWrapper::update_key_data(wxKeyEvent &evt) return ret; } +#include void ImGuiWrapper::new_frame() { if (m_new_frame_open) { @@ -292,6 +317,35 @@ void ImGuiWrapper::new_frame() init_font(true); } + ImGuiIO& io = ImGui::GetIO(); + // synchronize key states + // when the application loses the focus it may happen that the key up event is not processed + + // synchronize modifier keys + constexpr std::array, 3> imgui_mod_keys{ + std::make_pair(ImGuiKeyModFlags_Ctrl, WXK_CONTROL), + std::make_pair(ImGuiKeyModFlags_Shift, WXK_SHIFT), + std::make_pair(ImGuiKeyModFlags_Alt, WXK_ALT)}; + for (const std::pair& key : imgui_mod_keys) { + if ((io.KeyMods & key.first) != 0 && !wxGetKeyState(key.second)) + io.KeyMods &= ~key.first; + } + + // Not sure if it is neccessary + // values from 33 to 126 are reserved for the standard ASCII characters + for (size_t i = 33; i <= 126; ++i) { + wxKeyCode keycode = static_cast(i); + if (io.KeysDown[i] && keycode != WXK_NONE && !wxGetKeyState(keycode)) + io.KeysDown[i] = false; + } + + // special keys: delete, backspace, ... + for (int key: io.KeyMap) { + wxKeyCode keycode = static_cast(key); + if (io.KeysDown[key] && keycode != WXK_NONE && !wxGetKeyState(keycode)) + io.KeysDown[key] = false; + } + ImGui::NewFrame(); m_new_frame_open = true; } diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index 81d5e01fe..4831b9084 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -373,6 +373,7 @@ bool instance_check(int argc, char** argv, bool app_config_single_instance) namespace GUI { wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent); +wxDEFINE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent); wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent); void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler) @@ -501,12 +502,20 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message) } std::vector paths; + std::vector downloads; // Skip the first argument, it is the path to the slicer executable. auto it = args.begin(); for (++ it; it != args.end(); ++ it) { boost::filesystem::path p = MessageHandlerInternal::get_path(*it); if (! p.string().empty()) paths.emplace_back(p); +// TODO: There is a misterious slash appearing in recieved msg on windows +#ifdef _WIN32 + else if (it->rfind("prusaslicer://open/?file=", 0) == 0) +#else + else if (it->rfind("prusaslicer://open?file=", 0) == 0) +#endif + downloads.emplace_back(*it); } if (! paths.empty()) { //wxEvtHandler* evt_handler = wxGetApp().plater(); //assert here? @@ -514,6 +523,10 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message) wxPostEvent(m_callback_evt_handler, LoadFromOtherInstanceEvent(GUI::EVT_LOAD_MODEL_OTHER_INSTANCE, std::vector(std::move(paths)))); //} } + if (!downloads.empty()) + { + wxPostEvent(m_callback_evt_handler, StartDownloadOtherInstanceEvent(GUI::EVT_START_DOWNLOAD_OTHER_INSTANCE, std::vector(std::move(downloads)))); + } } #ifdef __APPLE__ @@ -545,6 +558,9 @@ namespace MessageHandlerDBusInternal " " " " " " + " " + " " + " " " " " "; @@ -553,6 +569,7 @@ namespace MessageHandlerDBusInternal dbus_connection_send(connection, reply, NULL); dbus_message_unref(reply); } + //method AnotherInstance receives message from another PrusaSlicer instance static void handle_method_another_instance(DBusConnection *connection, DBusMessage *request) { @@ -587,6 +604,9 @@ namespace MessageHandlerDBusInternal } else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("AnotherInstance", member_name)) { handle_method_another_instance(connection, message); return DBUS_HANDLER_RESULT_HANDLED; + } else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("Introspect", member_name)) { + respond_to_introspect(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } diff --git a/src/slic3r/GUI/InstanceCheck.hpp b/src/slic3r/GUI/InstanceCheck.hpp index 10ccf7b92..54ec12c31 100644 --- a/src/slic3r/GUI/InstanceCheck.hpp +++ b/src/slic3r/GUI/InstanceCheck.hpp @@ -43,8 +43,9 @@ class MainFrame; #endif // __linux__ using LoadFromOtherInstanceEvent = Event>; +using StartDownloadOtherInstanceEvent = Event>; wxDECLARE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent); - +wxDECLARE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent); using InstanceGoToFrontEvent = SimpleEvent; wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent); diff --git a/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp index 753dcdbfd..4057b730d 100644 --- a/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp +++ b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.cpp @@ -19,10 +19,11 @@ using namespace Slic3r; using namespace Slic3r::GUI; +const std::string CreateFontImageJob::default_text = "AaBbCc 123"; + CreateFontImageJob::CreateFontImageJob(FontImageData &&input) : m_input(std::move(input)) { - assert(!m_input.text.empty()); assert(wxFontEnumerator::IsValidFacename(m_input.font_name)); assert(m_input.gray_level > 0 && m_input.gray_level < 255); assert(m_input.texture_id != 0); @@ -41,9 +42,11 @@ void CreateFontImageJob::process(Ctl &ctl) if (font_file == nullptr) return; Emboss::FontFileWithCache font_file_with_cache(std::move(font_file)); - FontProp fp; // use only first line of text - std::string text = m_input.text; + std::string& text = m_input.text; + if (text.empty()) + text = default_text; // copy + size_t enter_pos = text.find('\n'); if (enter_pos < text.size()) { // text start with enter @@ -57,24 +60,39 @@ void CreateFontImageJob::process(Ctl &ctl) if (cancel->load()) return true; return false; }; + + FontProp fp; // create default font parameters ExPolygons shapes = Emboss::text2shapes(font_file_with_cache, text.c_str(), fp, was_canceled); + + // select some character from font e.g. default text + if (shapes.empty()) + shapes = Emboss::text2shapes(font_file_with_cache, default_text.c_str(), fp, was_canceled); + if (shapes.empty()) { + m_input.cancel->store(true); + return; + } + // normalize height of font BoundingBox bounding_box; for (ExPolygon &shape : shapes) bounding_box.merge(BoundingBox(shape.contour.points)); - if (bounding_box.size().x() < 1 || bounding_box.size().y() < 1) return; - double scale = m_input.size.y() / (double) bounding_box.size().y(); + if (bounding_box.size().x() < 1 || bounding_box.size().y() < 1) { + m_input.cancel->store(true); + return; + } + double scale = m_input.size.y() / (double) bounding_box.size().y(); BoundingBoxf bb2(bounding_box.min.cast(), bounding_box.max.cast()); bb2.scale(scale); Vec2d size_f = bb2.size(); - m_tex_size = Point(std::ceil(size_f.x()), std::ceil(size_f.y())); + m_tex_size = Point(std::ceil(size_f.x()), std::ceil(size_f.y())); // crop image width if (m_tex_size.x() > m_input.size.x()) m_tex_size.x() = m_input.size.x(); if (m_tex_size.y() > m_input.size.y()) m_tex_size.y() = m_input.size.y(); // Set up result - m_result = std::vector(m_tex_size.x() * m_tex_size.y() * 4, {255}); + unsigned bit_count = 4; // RGBA + m_result = std::vector(m_tex_size.x() * m_tex_size.y() * bit_count, {255}); sla::Resolution resolution(m_tex_size.x(), m_tex_size.y()); double pixel_dim = SCALING_FACTOR / scale; @@ -123,13 +141,17 @@ void CreateFontImageJob::finalize(bool canceled, std::exception_ptr &) const GLenum target = GL_TEXTURE_2D; glsafe(::glBindTexture(target, m_input.texture_id)); - GLint - w = m_tex_size.x(), h = m_tex_size.y(), - xoffset = m_input.size.x() - m_tex_size.x(), // arrange right - yoffset = m_input.size.y() * m_input.index; + GLsizei w = m_tex_size.x(), h = m_tex_size.y(); + GLint xoffset = m_input.size.x() - m_tex_size.x(), // arrange right + yoffset = m_input.size.y() * m_input.index; glsafe(::glTexSubImage2D(target, m_input.level, xoffset, yoffset, w, h, m_input.format, m_input.type, m_result.data())); + // clear rest of texture + std::vector empty_data(xoffset * h * 4, {0}); + glsafe(::glTexSubImage2D(target, m_input.level, 0, yoffset, xoffset, h, + m_input.format, m_input.type, empty_data.data())); + // bind default texture GLuint no_texture_id = 0; glsafe(::glBindTexture(target, no_texture_id)); diff --git a/src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp index da96b2696..36c3e5e7f 100644 --- a/src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp +++ b/src/slic3r/GUI/Jobs/CreateFontNameImageJob.hpp @@ -68,6 +68,12 @@ public: /// /// void finalize(bool canceled, std::exception_ptr &) override; + + /// + /// Text used for generate preview for empty text + /// and when no glyph for given m_input.text + /// + static const std::string default_text; }; } // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/EmbossJob.cpp b/src/slic3r/GUI/Jobs/EmbossJob.cpp index 56d8c7e45..b04c519e0 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.cpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.cpp @@ -63,7 +63,8 @@ static TriangleMesh create_default_mesh(); /// /// New mesh data /// Text configuration, ... -static void update_volume(TriangleMesh &&mesh, const DataUpdate &data); +/// Transformation of volume +static void update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d *tr = nullptr); /// /// Add new volume to object @@ -316,16 +317,8 @@ void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) { // doesn't care about exception when process was canceled by user if (canceled) return; if (priv::process(eptr)) return; - - // TODO: Find better way to Not center volume data when add !!! - TriangleMesh mesh = m_result; // Part1: copy - priv::create_volume(std::move(m_result), m_input.object_id, m_input.volume_type, m_input.text_tr, m_input); - - // Part2: update volume data - //auto vol = wxGetApp().plater()->model().objects[m_input.object_idx]->volumes.back(); - //UpdateJob::update_volume(vol, std::move(mesh), m_input.text_configuration, m_input.volume_name); } ///////////////// @@ -358,7 +351,11 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) } if (canceled) return; if (priv::process(eptr)) return; - priv::update_volume(std::move(m_result), m_input); + + // when start using surface it is wanted to move text origin on surface of model + // also when repeteadly move above surface result position should match + Transform3d *tr = &m_input.text_tr; + priv::update_volume(std::move(m_result), m_input, tr); } //////////////////////////// @@ -483,7 +480,7 @@ TriangleMesh priv::create_mesh(DataBase &input, Fnc was_canceled, Job::Ctl& ctl) TriangleMesh priv::create_default_mesh() { // When cant load any font use default object loaded from file - std::string path = Slic3r::resources_dir() + "/data/embossed_text.stl"; + std::string path = Slic3r::resources_dir() + "/data/embossed_text.obj"; TriangleMesh triangle_mesh; if (!load_obj(path.c_str(), &triangle_mesh)) { // when can't load mesh use cube @@ -527,43 +524,48 @@ void UpdateJob::update_volume(ModelVolume *volume, obj_list->update_name_in_list(object_idx, volume_idx); } - // update printable state on canvas - if (volume->type() == ModelVolumeType::MODEL_PART) - canvas->update_instance_printable_state_for_object((size_t) object_idx); - - // Move object on bed - if (GLGizmoEmboss::is_text_object(volume)) volume->get_object()->ensure_on_bed(); + // When text is object. + // When text positive volume is lowest part of object than modification of text + // have to move object on bed. + if (volume->type() == ModelVolumeType::MODEL_PART) + volume->get_object()->ensure_on_bed(); // redraw scene bool refresh_immediately = false; canvas->reload_scene(refresh_immediately); } -void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data) +void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d* tr) { // for sure that some object will be created - if (mesh.its.empty()) - return priv::create_message("Empty mesh can't be created."); + if (mesh.its.empty()) + return create_message("Empty mesh can't be created."); Plater *plater = wxGetApp().plater(); GLCanvas3D *canvas = plater->canvas3D(); // Check emboss gizmo is still open GLGizmosManager &manager = canvas->get_gizmos_manager(); - if (manager.get_current_type() != GLGizmosManager::Emboss) return; + if (manager.get_current_type() != GLGizmosManager::Emboss) + return; std::string snap_name = GUI::format(_L("Text: %1%"), data.text_configuration.text); Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction); ModelVolume *volume = get_volume(plater->model().objects, data.volume_id); + // could appear when user delete edited volume if (volume == nullptr) return; - // apply fix matrix made by store to .3mf - const auto &tc = volume->text_configuration; - assert(tc.has_value()); - if (tc.has_value() && tc->fix_3mf_tr.has_value()) - volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse()); + if (tr) { + volume->set_transformation(*tr); + } else { + // apply fix matrix made by store to .3mf + const auto &tc = volume->text_configuration; + assert(tc.has_value()); + if (tc.has_value() && tc->fix_3mf_tr.has_value()) + volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse()); + } UpdateJob::update_volume(volume, std::move(mesh), data.text_configuration, data.volume_name); } @@ -618,6 +620,12 @@ void priv::create_volume( volume->text_configuration = data.text_configuration; // copy volume->set_transformation(trmat); + // update printable state on canvas + if (type == ModelVolumeType::MODEL_PART) { + volume->get_object()->ensure_on_bed(); + canvas->update_instance_printable_state_for_object(object_idx); + } + // update volume name in object list // updata selection after new volume added // change name of volume in right panel @@ -627,9 +635,6 @@ void priv::create_volume( wxDataViewItemArray sel = obj_list->reorder_volumes_and_get_selection(object_idx, add_to_selection); if (!sel.IsEmpty()) obj_list->select_item(sel.front()); - // update printable state on canvas - if (type == ModelVolumeType::MODEL_PART) canvas->update_instance_printable_state_for_object(object_idx); - obj_list->selection_changed(); // Now is valid text volume selected open emboss gizmo @@ -679,7 +684,7 @@ OrthoProject3d priv::create_emboss_projection( bool is_outside, float emboss, Transform3d tr, SurfaceCut &cut) { // Offset of clossed side to model - const float surface_offset = 1e-3f; // [in mm] + const float surface_offset = 0.015f; // [in mm] float front_move = (is_outside) ? emboss : surface_offset, back_move = -((is_outside) ? surface_offset : emboss); @@ -730,44 +735,68 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2 biggest_count = its.vertices.size(); biggest = &s; } - s_to_itss[&s - &sources.front()] = itss.size(); + size_t source_index = &s - &sources.front(); + size_t its_index = itss.size(); + s_to_itss[source_index] = its_index; itss.emplace_back(std::move(its)); } - if (itss.empty()) throw JobException(_u8L("There is no volume in projection direction.").c_str()); + if (itss.empty()) + throw JobException(_u8L("There is no volume in projection direction.").c_str()); + + Transform3d tr_inv = biggest->tr.inverse(); + Transform3d cut_projection_tr = tr_inv * input2.text_tr; - Transform3d tr_inv = biggest->tr.inverse(); size_t itss_index = s_to_itss[biggest - &sources.front()]; BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]); for (const SurfaceVolumeData::ModelSource &s : sources) { - if (&s == biggest) continue; size_t itss_index = s_to_itss[&s - &sources.front()]; if (itss_index == std::numeric_limits::max()) continue; - Transform3d tr = s.tr * tr_inv; + if (&s == biggest) + continue; + + Transform3d tr = s.tr * tr_inv; + bool fix_reflected = true; indexed_triangle_set &its = itss[itss_index]; - its_transform(its, tr); + its_transform(its, tr, fix_reflected); BoundingBoxf3 bb = bounding_box(its); mesh_bb.merge(bb); } // tr_inv = transformation of mesh inverted - Transform3d cut_projection_tr = tr_inv * input2.text_tr; - Transform3d emboss_tr = cut_projection_tr.inverse(); - BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr); + Transform3d emboss_tr = cut_projection_tr.inverse(); + BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr); std::pair z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()}; OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range); float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension); + bool is_text_reflected = Slic3r::has_reflection(input2.text_tr); + if (is_text_reflected) { + // revert order of points in expolygons + // CW --> CCW + for (ExPolygon &shape : shapes) { + shape.contour.reverse(); + for (Slic3r::Polygon &hole : shape.holes) + hole.reverse(); + } + } + // Use CGAL to cut surface from triangle mesh SurfaceCut cut = cut_surface(shapes, itss, cut_projection, projection_ratio); + + if (is_text_reflected) { + for (SurfaceCut::Contour &c : cut.contours) + std::reverse(c.begin(), c.end()); + for (Vec3i &t : cut.indices) + std::swap(t[0], t[1]); + } + if (cut.empty()) throw JobException(_u8L("There is no valid surface for text projection.").c_str()); if (was_canceled()) return {}; // !! Projection needs to transform cut OrthoProject3d projection = create_emboss_projection(input2.is_outside, fp.emboss, emboss_tr, cut); - indexed_triangle_set new_its = cut2model(cut, projection); assert(!new_its.empty()); - if (was_canceled()) return {}; return TriangleMesh(std::move(new_its)); } diff --git a/src/slic3r/GUI/Jobs/EmbossJob.hpp b/src/slic3r/GUI/Jobs/EmbossJob.hpp index c8ef7fee6..0081024a0 100644 --- a/src/slic3r/GUI/Jobs/EmbossJob.hpp +++ b/src/slic3r/GUI/Jobs/EmbossJob.hpp @@ -5,7 +5,6 @@ #include #include #include -//#include #include "slic3r/Utils/RaycastManager.hpp" #include "slic3r/GUI/Camera.hpp" #include "Job.hpp" diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 04668b593..66961b2cd 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -53,6 +53,15 @@ KBShortcutsDialog::KBShortcutsDialog() SetSizer(main_sizer); main_sizer->SetSizeHints(this); this->CenterOnParent(); + +#ifdef __linux__ + // workaround to correct pages layout + book->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [book](wxBookCtrlEvent& e) { + book->GetPage(e.GetSelection())->Fit(); + }); + const wxSize sz = this->GetBestSize(); + this->SetSize(sz.x + 1, sz.y); +#endif } void KBShortcutsDialog::on_dpi_changed(const wxRect& suggested_rect) @@ -154,6 +163,7 @@ void KBShortcutsDialog::fill_shortcuts() { "L", L("Gizmo FDM paint-on supports") }, { "P", L("Gizmo FDM paint-on seam") }, { "N", L("Gizmo Multi Material painting") }, + { "T", L("Gizmo Text emboss / engrave")}, { "Esc", L("Unselect gizmo or clear selection") }, { "K", L("Change camera type (perspective, orthographic)") }, { "B", L("Zoom to Bed") }, diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b0f06541c..8a4bee03e 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -307,9 +307,7 @@ void MainFrame::bind_diff_dialog() auto process_options = [this](std::function process) { const Preset::Type diff_dlg_type = diff_dialog.view_type(); if (diff_dlg_type == Preset::TYPE_INVALID) { - for (const Preset::Type& type : diff_dialog.printer_technology() == ptFFF ? - std::initializer_list{Preset::TYPE_PRINTER, Preset::TYPE_PRINT, Preset::TYPE_FILAMENT} : - std::initializer_list{ Preset::TYPE_PRINTER, Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL } ) + for (const Preset::Type& type : diff_dialog.types_list() ) process(type); } else @@ -715,6 +713,8 @@ void MainFrame::update_title() void MainFrame::init_tabpanel() { + wxGetApp().update_ui_colours_from_appconfig(); + // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 // with multiple high resolution displays connected. #ifdef _MSW_DARK_MODE @@ -847,7 +847,6 @@ void MainFrame::register_win32_callbacks() void MainFrame::create_preset_tabs() { - wxGetApp().update_label_colours_from_appconfig(); add_created_tab(new TabPrint(m_tabpanel), "cog"); add_created_tab(new TabFilament(m_tabpanel), "spool"); add_created_tab(new TabSLAPrint(m_tabpanel), "cog"); @@ -1091,7 +1090,9 @@ void MainFrame::on_sys_color_changed() wxBusyCursor wait; // update label colors in respect to the system mode - wxGetApp().init_label_colours(); + wxGetApp().init_ui_colours(); + // but if there are some ui colors in appconfig, they have to be applied + wxGetApp().update_ui_colours_from_appconfig(); #ifdef __WXMSW__ wxGetApp().UpdateDarkUI(m_tabpanel); // m_statusbar->update_dark_ui(); @@ -1114,6 +1115,24 @@ void MainFrame::on_sys_color_changed() this->Refresh(); } +void MainFrame::update_mode_markers() +{ +#ifdef __WXMSW__ +#ifdef _MSW_DARK_MODE + // update markers in common mode sizer + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(m_tabpanel)->UpdateModeMarkers(); +#endif +#endif + + // update mode markers on side_bar + wxGetApp().sidebar().update_mode_markers(); + + // update mode markers in tabs + for (auto tab : wxGetApp().tabs_list) + tab->update_mode_markers(); +} + #ifdef _MSC_VER // \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators, // as the simple numeric accelerators spoil all numeric data entry. diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 66adf806a..78ec13f64 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -143,6 +143,7 @@ public: ~MainFrame() = default; void update_layout(); + void update_mode_markers(); // Called when closing the application and when switching the application language. void shutdown(); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index faa970c4a..bb93afc8d 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/CameraUtils.hpp" #include @@ -24,7 +25,7 @@ namespace GUI { void MeshClipper::set_behaviour(bool fill_cut, double contour_width) { - if (fill_cut != m_fill_cut || is_approx(contour_width, m_contour_width)) + if (fill_cut != m_fill_cut || ! is_approx(contour_width, m_contour_width)) m_result.reset(); m_fill_cut = fill_cut; m_contour_width = contour_width; @@ -366,39 +367,24 @@ Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const return m_normals[facet_idx]; } -void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3d& point, Vec3d& direction) +void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction) { - Matrix4d modelview = camera.get_view_matrix().matrix(); - Matrix4d projection= camera.get_projection_matrix().matrix(); - Vec4i viewport(camera.get_viewport().data()); - - Vec3d pt1; - Vec3d pt2; - igl::unproject(Vec3d(mouse_pos.x(), viewport[3] - mouse_pos.y(), 0.), - modelview, projection, viewport, pt1); - igl::unproject(Vec3d(mouse_pos.x(), viewport[3] - mouse_pos.y(), 1.), - modelview, projection, viewport, pt2); - + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); Transform3d inv = trafo.inverse(); - pt1 = inv * pt1; - pt2 = inv * pt2; - - point = pt1; - direction = pt2-pt1; + point = inv*point; + direction = inv.linear()*direction; } - bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, - size_t* facet_idx, bool* was_clipping_plane_hit) const + size_t* facet_idx) const { - if (was_clipping_plane_hit) - *was_clipping_plane_hit = false; - Vec3d point; Vec3d direction; - line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); + Transform3d inv = trafo.inverse(); + point = inv*point; + direction = inv.linear()*direction; std::vector hits = m_emesh.query_ray_hits(point, direction); @@ -416,26 +402,9 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& break; } - if (i==hits.size()) { - // All hits are clipped. - return false; - } - if (clipping_plane && (hits.size()-i) % 2 != 0) { - // There is an odd number of unclipped hits - meaning the nearest must be from inside the mesh. - // In that case, calculate intersection with the clipping place. - if (was_clipping_plane_hit) { - direction = direction + point; - point = trafo * point; // transform to world coords - direction = trafo * direction - point; - - Vec3d normal = -clipping_plane->get_normal().cast(); - double den = normal.dot(direction); - if (den != 0.) { - double t = (-clipping_plane->get_offset() - normal.dot(point))/den; - position = (point + t * direction).cast(); - *was_clipping_plane_hit = true; - } - } + if (i==hits.size() || (hits.size()-i) % 2 != 0) { + // All hits are either clipped, or there is an odd number of unclipped + // hits - meaning the nearest must be from inside the mesh. return false; } @@ -449,24 +418,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& return true; } -bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3d& position, Vec3d& normal) const -{ - Vec3d point; - Vec3d direction; - line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); - std::vector hits = m_emesh.query_ray_hits(point, direction); - - if (hits.empty()) - return false; // no intersection found - - // Now stuff the points in the provided vector and calculate normals if asked about them: - position = hits[0].position(); - normal = hits[0].normal(); - - return true; -} bool MeshRaycaster::is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const { diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index c9ba916ad..b10fa5ff8 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -168,6 +168,7 @@ public: : MeshRaycaster(std::make_unique(mesh)) {} + // DEPRICATED - use CameraUtils::ray_from_screen_pos static void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction); @@ -176,15 +177,13 @@ public: const Vec2d& mouse_pos, const Transform3d& trafo, // how to get the mesh into world coords const Camera& camera, // current camera position - Vec3f& position, // where to save the positibon of the hit (mesh coords if mesh, world coords if clipping plane) + Vec3f& position, // where to save the positibon of the hit (mesh coords) Vec3f& normal, // normal of the triangle that was hit const ClippingPlane* clipping_plane = nullptr, // clipping plane (if active) - size_t* facet_idx = nullptr, // index of the facet hit - bool* was_clipping_plane_hit = nullptr // is the hit on the clipping place cross section? + size_t* facet_idx = nullptr // index of the facet hit ) const; - - // Given a mouse position, this returns true in case it is on the mesh. - bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& position, Vec3d& normal) const; + + const AABBMesh &get_aabb_mesh() const { return m_emesh; } bool is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const; diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index e5ad5d64f..1ce671753 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -668,6 +668,11 @@ void Mouse3DController::init() #ifndef _WIN32 // Don't start the background thread on Windows, as the HID messages are sent as Windows messages. m_thread = std::thread(&Mouse3DController::run, this); +#else + // For some reason, HID message routing does not work well with remote session. Requires further investigation + if (::GetSystemMetrics(SM_REMOTESESSION)) { + m_thread = std::thread(&Mouse3DController::run, this); + } #endif // _WIN32 } } diff --git a/src/slic3r/GUI/Notebook.cpp b/src/slic3r/GUI/Notebook.cpp index 380b402d5..71d095160 100644 --- a/src/slic3r/GUI/Notebook.cpp +++ b/src/slic3r/GUI/Notebook.cpp @@ -109,9 +109,16 @@ void ButtonsListCtrl::OnColorsChanged() for (ScalableButton* btn : m_pageButtons) btn->sys_color_changed(); + m_mode_sizer->sys_color_changed(); + m_sizer->Layout(); } +void ButtonsListCtrl::UpdateModeMarkers() +{ + m_mode_sizer->update_mode_markers(); +} + void ButtonsListCtrl::SetSelection(int sel) { if (m_selection == sel) diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp index bd6c5d85a..c945be300 100644 --- a/src/slic3r/GUI/Notebook.hpp +++ b/src/slic3r/GUI/Notebook.hpp @@ -22,6 +22,7 @@ public: void UpdateMode(); void Rescale(); void OnColorsChanged(); + void UpdateModeMarkers(); bool InsertPage(size_t n, const wxString& text, bool bSelect = false, const std::string& bmp_name = ""); void RemovePage(size_t n); bool SetPageImage(size_t n, const std::string& bmp_name) const; @@ -251,6 +252,11 @@ public: GetBtnsListCtrl()->OnColorsChanged(); } + void UpdateModeMarkers() + { + GetBtnsListCtrl()->UpdateModeMarkers(); + } + void OnNavigationKey(wxNavigationKeyEvent& event) { if (event.IsWindowChange()) { diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index b2e9bf008..e6f9d952a 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -422,7 +422,7 @@ void NotificationManager::PopNotification::init() count_spaces(); count_lines(); - if (m_lines_count == 3) + if (m_lines_count == m_normal_lines_count + 1) m_multiline = true; m_notification_start = GLCanvas3D::timestamp_now(); if (m_state == EState::Unknown) @@ -431,8 +431,8 @@ void NotificationManager::PopNotification::init() void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui) { m_window_height = m_multiline ? - std::max(m_lines_count, (size_t)2) * m_line_height : - 2 * m_line_height; + std::max(m_lines_count, m_normal_lines_count) * m_line_height : + m_normal_lines_count * m_line_height; m_window_height += 1 * m_line_height; // top and bottom } @@ -444,19 +444,20 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons float shift_y = m_line_height; std::string line; - for (size_t i = 0; i < (m_multiline ? m_endlines.size() : std::min(m_endlines.size(), (size_t)2)); i++) { + for (size_t i = 0; i < (m_multiline ? m_endlines.size() : std::min(m_endlines.size(), m_normal_lines_count)); i++) { assert(m_endlines.size() > i && m_text1.size() >= m_endlines[i]); line.clear(); ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(starting_y + i * shift_y); if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) { - if (i == 1 && m_endlines.size() > 2 && !m_multiline) { + if (i == m_normal_lines_count - 1 && m_endlines.size() > m_normal_lines_count && !m_multiline) { // second line with "more" hypertext - line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) { + assert(m_normal_lines_count - 2 >= 0); + line = m_text1.substr(m_endlines[m_normal_lines_count - 2] + (m_text1[m_endlines[m_normal_lines_count - 2]] == '\n' || m_text1[m_endlines[m_normal_lines_count - 2]] == ' ' ? 1 : 0), m_endlines[m_normal_lines_count - 1] - m_endlines[m_normal_lines_count - 2] - (m_text1[m_endlines[m_normal_lines_count - 2]] == '\n' || m_text1[m_endlines[m_normal_lines_count - 2]] == ' ' ? 1 : 0)); + while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((" [" + _u8L("More") + "]").c_str()).x) { line = line.substr(0, line.length() - 1); } - line += ".."; + line += " ";//".."; } else { // regural line @@ -469,18 +470,18 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons } } //hyperlink text - if (!m_multiline && m_lines_count > 2) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + shift_y, _u8L("More"), true); + if (!m_multiline && m_lines_count > m_normal_lines_count) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + (m_normal_lines_count -1) *shift_y, "[" + _u8L("More") + "]", true); } else if (!m_hypertext.empty()) { render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + (line.empty() ? "" : " ")).c_str()).x, starting_y + (m_endlines.size() - 1) * shift_y, m_hypertext); } // text2 - if (!m_text2.empty() && (m_multiline|| m_lines_count <= 2)) { + if (!m_text2.empty() && (m_multiline|| m_lines_count <= m_normal_lines_count)) { starting_y += (m_endlines.size() - 1) * shift_y; last_end = 0; - for (size_t i = 0; i < (m_multiline ? m_endlines2.size() : 2); i++) { + for (size_t i = 0; i < (m_multiline ? m_endlines2.size() : m_normal_lines_count); i++) { if (i == 0) //first line X is shifted by hypertext ImGui::SetCursorPosX(x_offset + ImGui::CalcTextSize((line + m_hypertext + (line.empty() ? " " : " ")).c_str()).x); else @@ -514,7 +515,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, { if (more) { - m_multiline = true; + on_more_hypertext_click(); set_next_window_size(imgui); } else if (on_text_click()) { @@ -635,6 +636,11 @@ bool NotificationManager::PopNotification::on_text_click() return m_data.callback(m_evt_handler); return false; } +void NotificationManager::PopNotification::on_more_hypertext_click() +{ + m_multiline = true; +} + void NotificationManager::PopNotification::update(const NotificationData& n) { m_text1 = n.text1; @@ -875,7 +881,7 @@ void NotificationManager::ProgressBarNotification::init() m_endlines.push_back(0); } if(m_lines_count >= 2) { - m_lines_count = 3; + m_lines_count = 3; m_multiline = true; while (m_endlines.size() < 3) m_endlines.push_back(m_endlines.back()); @@ -941,6 +947,7 @@ void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgu imgui.text(text.c_str()); } } + //------ProgressBarWithCancelNotification---------------- void NotificationManager::ProgressBarWithCancelNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) @@ -1054,12 +1061,280 @@ void NotificationManager::ProgressBarWithCancelNotification::render_bar(ImGuiWra ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); imgui.text(text.c_str()); } + + + + + + + + + + + + + +//------URLDownloadNotification---------------- + +void NotificationManager::URLDownloadNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + if (m_percentage < 0.f || m_percentage >= 1.f) { + render_close_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + if (m_percentage >= 1.f) + render_open_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } else + render_pause_cancel_buttons_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); +} +void NotificationManager::URLDownloadNotification::render_close_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + + std::string button_text; + button_text = ImGui::CloseNotifButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)), + true)) + { + button_text = ImGui::CloseNotifHoverButton; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + close(); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + { + close(); + } + ImGui::PopStyleColor(5); + +} + +void NotificationManager::URLDownloadNotification::render_pause_cancel_buttons_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + + render_cancel_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_pause_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); +} +void NotificationManager::URLDownloadNotification::render_pause_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + std::wstring button_text; + button_text = (m_download_paused ? ImGui::PlayButton : ImGui::PauseButton); + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y), + ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y), + true)) + { + button_text = (m_download_paused ? ImGui::PlayHoverButton : ImGui::PauseHoverButton); + } + + ImVec2 button_pic_size = ImGui::CalcTextSize(boost::nowide::narrow(button_text).c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + trigger_user_action_callback(m_download_paused ? DownloaderUserAction::DownloadUserContinued : DownloaderUserAction::DownloadUserPaused); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.f, win_size.y)) + { + trigger_user_action_callback(m_download_paused ? DownloaderUserAction::DownloadUserContinued : DownloaderUserAction::DownloadUserPaused); + } + ImGui::PopStyleColor(5); +} + +void NotificationManager::URLDownloadNotification::render_open_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + std::wstring button_text; + button_text = ImGui::OpenButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y), + ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y), + true)) + { + button_text = ImGui::OpenHoverButton; + } + + ImVec2 button_pic_size = ImGui::CalcTextSize(boost::nowide::narrow(button_text).c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + trigger_user_action_callback(DownloaderUserAction::DownloadUserOpenedFolder); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.f, win_size.y)) + { + trigger_user_action_callback(DownloaderUserAction::DownloadUserOpenedFolder); + } + ImGui::PopStyleColor(5); +} + +void NotificationManager::URLDownloadNotification::render_cancel_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + + std::string button_text; + button_text = ImGui::CancelButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)), + true)) + { + button_text = ImGui::CancelHoverButton; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + trigger_user_action_callback(DownloaderUserAction::DownloadUserCanceled); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + { + trigger_user_action_callback(DownloaderUserAction::DownloadUserCanceled); + } + ImGui::PopStyleColor(5); + +} + +void NotificationManager::URLDownloadNotification::trigger_user_action_callback(DownloaderUserAction action) +{ + if (m_user_action_callback) { + if (m_user_action_callback(action, m_download_id)) {} + } +} + + +void NotificationManager::URLDownloadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + std::string text; + if (m_percentage < 0.f) { + text = _u8L("ERROR") + ": " + m_error_message; + } else if (m_percentage >= 1.f) { + text = _u8L("COMPLETED"); + } else { + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%"; + text = stream.str(); + } + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); + imgui.text(text.c_str()); +} + +void NotificationManager::URLDownloadNotification::count_spaces() +{ + ProgressBarNotification::count_spaces(); + m_window_width_offset = m_line_height * 6; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + //------PrintHostUploadNotification---------------- void NotificationManager::PrintHostUploadNotification::init() { - ProgressBarNotification::init(); - if (m_state == EState::NotFading && m_uj_state == UploadJobState::PB_COMPLETED) + if (is_finished()) + return; + // count_spaces before text - generate_text needs to know width of line + count_spaces(); + generate_text(); + + if (m_uj_state == UploadJobState::PB_COMPLETED || m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) { + PopNotification::init(); + m_multiline = m_more_hypertext_used; + } else + ProgressBarNotification::init(); + + if (m_state == EState::NotFading && (m_uj_state == UploadJobState::PB_COMPLETED || m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING)) m_state = EState::Shown; } void NotificationManager::PrintHostUploadNotification::count_spaces() @@ -1068,40 +1343,147 @@ void NotificationManager::PrintHostUploadNotification::count_spaces() m_line_height = ImGui::CalcTextSize("A").y; m_left_indentation = m_line_height; - if (m_uj_state == UploadJobState::PB_ERROR) { + if (m_uj_state == UploadJobState::PB_ERROR || m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) { std::string text; - text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); + //text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); its always progressbar level (not error or warning) + text = (m_uj_state == UploadJobState::PB_ERROR ? ImGui::ErrorMarker : ImGui::WarningMarker); float picture_width = ImGui::CalcTextSize(text.c_str()).x; m_left_indentation = picture_width + m_line_height / 2; } - m_window_width_offset = m_line_height * 6; //(m_has_cancel_button ? 6 : 4); + m_window_width_offset = m_line_height * 6; m_window_width = m_line_height * 25; } bool NotificationManager::PrintHostUploadNotification::push_background_color() { - if (m_uj_state == UploadJobState::PB_ERROR) { ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); backcolor.x += 0.3f; push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); return true; + } else if (m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) { + ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + backcolor.x += 0.3f; + backcolor.y += 0.15f; + push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); + return true; } return false; } + +void NotificationManager::PrintHostUploadNotification::generate_text() +{ + auto shorten_to_line = [this](const std::string& text, bool dots) -> std::string { + std::string line = text; + bool did_shorten = false; + while (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset) { + line = line.substr(0, line.length() - 1); + did_shorten = true; + } + if (did_shorten && dots) { + line = line.substr(0, line.length() - 2); + line += "..."; + } + return line; + }; + + // whole text is no longer than 2 lines, filename is max 1 line long. + std::string rest = " -> " + (m_original_host == m_host ? m_host : m_host + " (" + m_original_host + ")"); + std::string line1; + if (ImGui::CalcTextSize(m_filename.c_str()).x > m_window_width - m_window_width_offset) { + line1 = shorten_to_line(m_filename, true); + } else { + line1 = shorten_to_line(m_filename + rest, false); + size_t over = line1.size() - m_filename.size(); + if (over < 0) + over = 0; + if (over < rest.size()) + rest = rest.substr(over); + else if (over >= rest.size()) + rest.clear(); + } + std::string line2 = shorten_to_line(rest, true); + + // ... if in total that makes more than 1 line, whole notification will behave as 3 line notification (as base height) + if (ImGui::CalcTextSize((line1 + line2).c_str()).x > m_window_width - m_window_width_offset) + m_normal_lines_count = 3; + else + m_normal_lines_count = 2; + + if (m_uj_state == UploadJobState::PB_COMPLETED || m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) + m_text1 = line1 + line2 + "\n" + _u8L("COMPLETED") + "\n" + m_status_message; + else + m_text1 = line1 + line2; +} + void NotificationManager::PrintHostUploadNotification::set_percentage(float percent) { m_percentage = percent; - if (percent >= 1.0f) { - m_uj_state = UploadJobState::PB_COMPLETED; - m_has_cancel_button = false; - init(); + if (m_complete_on_100 && percent >= 1.0f) { + complete(); } else if (percent < 0.0f) { error(); - } else { + } else if (m_uj_state != UploadJobState::PB_COMPLETED && m_uj_state != UploadJobState::PB_COMPLETED_WITH_WARNING){ + if (m_percentage > 1.f) + m_percentage = 1.f; m_uj_state = UploadJobState::PB_PROGRESS; m_has_cancel_button = true; } } + +void NotificationManager::PrintHostUploadNotification::complete() +{ + m_uj_state = UploadJobState::PB_COMPLETED; + m_has_cancel_button = false; + init(); +} + +void NotificationManager::PrintHostUploadNotification::complete_with_warning() +{ + m_uj_state = UploadJobState::PB_COMPLETED_WITH_WARNING; + m_has_cancel_button = false; + init(); +} + +void NotificationManager::PrintHostUploadNotification::render_text(ImGuiWrapper& imgui,const float win_size_x, const float win_size_y,const float win_pos_x, const float win_pos_y) +{ + // If not completed, the text rendering is very similar to progressbar notification except it doesnt use m_multiline to decide. + // If completed, whole text is part of m_text_1 and is rendered by PopNotification function. + + if (m_uj_state != UploadJobState::PB_COMPLETED && m_uj_state != UploadJobState::PB_COMPLETED_WITH_WARNING) { + // hypertext is not rendered at all. If it is needed, it needs to be added here. + // m_endlines should have endline for each line and then for hypertext thus m_endlines[1] should always be in m_text1 + if (m_endlines[0] != m_endlines[1]) { + assert(m_text1.size() >= m_endlines[0] || m_text1.size() >= m_endlines[1]); + if (m_endlines[0] > m_text1.size() || m_endlines[1] > m_text1.size()) + return; + // two lines text (what doesnt fit, wont show), one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height / 4); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(m_line_height + m_line_height / 4); + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + imgui.text(line.c_str()); + // uncomment only if close and stop button should be next to each other + //if (m_has_cancel_button) + // render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } + else { + assert(m_text1.size() >= m_endlines[0]); + if (m_endlines[0] > m_text1.size()) + return; + //one line text, one line bar + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(/*win_size_y / 2 - win_size_y / 6 -*/ m_line_height / 4); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + } + } else + PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); +} void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { std::string text; @@ -1117,6 +1499,11 @@ void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); break; } + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_RESOLVING: + text = _u8L("RESOLVING ADDRESS"); + ImGui::SetCursorPosX(m_left_indentation); + ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); + break; case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_ERROR: text = _u8L("ERROR"); ImGui::SetCursorPosX(m_left_indentation); @@ -1128,9 +1515,8 @@ void NotificationManager::PrintHostUploadNotification::render_bar(ImGuiWrapper& ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); break; case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED: - text = _u8L("COMPLETED"); - ImGui::SetCursorPosX(m_left_indentation); - ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? m_line_height / 4 : m_line_height / 2)); + case Slic3r::GUI::NotificationManager::PrintHostUploadNotification::UploadJobState::PB_COMPLETED_WITH_WARNING: + // whole text with both "COMPLETED" and status message is generated in generate_text() break; } @@ -1145,8 +1531,23 @@ void NotificationManager::PrintHostUploadNotification::render_left_sign(ImGuiWra ImGui::SetCursorPosX(m_line_height / 3); ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); imgui.text(text.c_str()); + } else if (m_uj_state == UploadJobState::PB_COMPLETED_WITH_WARNING) { + std::string text; + text = ImGui::WarningMarker; + ImGui::SetCursorPosX(m_line_height / 3); + ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); + imgui.text(text.c_str()); } } + +void NotificationManager::PrintHostUploadNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) +{ + if (m_has_cancel_button) + render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); + else + ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); +} + void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ImVec2 win_size(win_size_x, win_size_y); @@ -1157,6 +1558,56 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + + std::string button_text; + button_text = ImGui::CancelButton; + + if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), + ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)), + true)) + { + button_text = ImGui::CancelHoverButton; + // tooltip + long time_now = wxGetLocalTime(); + if (m_hover_time > 0 && m_hover_time < time_now) { + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(_u8L("Cancel upload") + " " + GUI::shortkey_ctrl_prefix() + "T"); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + } + if (m_hover_time == 0) + m_hover_time = time_now; + } + ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); + ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); + ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); + if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) + { + wxGetApp().printhost_job_queue().cancel(m_job_id - 1); + } + + //invisible large button + ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); + ImGui::SetCursorPosY(0); + if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) + { + wxGetApp().printhost_job_queue().cancel(m_job_id - 1); + } + ImGui::PopStyleColor(5); + + // bellow is version where both close and stop button are rendered next to each other + + /* + ImVec2 win_size(win_size_x, win_size_y); + ImVec2 win_pos(win_pos_x, win_pos_y); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + std::string button_text; button_text = ImGui::CancelButton; @@ -1197,6 +1648,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu wxGetApp().printhost_job_queue().cancel(m_job_id - 1); } ImGui::PopStyleColor(5); + */ } //------UpdatedItemsInfoNotification------- void NotificationManager::UpdatedItemsInfoNotification::count_spaces() @@ -1847,7 +2299,10 @@ void NotificationManager::push_upload_job_notification(int id, float filesize, return; } } - std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); + // filename is created from boost::filesystem::path.string() which if created by path / "file" return \\ as folder division. But could also contain / as folder division. Lets unite this into "/" only. + std::string correct_filename(filename); + std::replace(correct_filename.begin(), correct_filename.end(), '\\', '/'); + std::string text = correct_filename + " -> " + host; NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotificationLevel, 10, text }; push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, 0, id, filesize, filename, host), 0); } @@ -1879,6 +2334,62 @@ void NotificationManager::set_upload_job_notification_host(int id, const std::st } } } + +void NotificationManager::set_upload_job_notification_status(int id, const std::string& status) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if (phun->compare_job_id(id)) { + phun->set_status(status); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } + } + } +} + +void NotificationManager::set_upload_job_notification_comp_on_100(int id, bool comp) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if (phun->compare_job_id(id)) { + phun->set_complete_on_100(comp); + break; + } + } + } +} + +void NotificationManager::set_upload_job_notification_completed(int id) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if (phun->compare_job_id(id)) { + phun->complete(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } + } + } +} + +void NotificationManager::set_upload_job_notification_completed_with_warning(int id) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::PrintHostUpload) { + PrintHostUploadNotification* phun = dynamic_cast(notification.get()); + if (phun->compare_job_id(id)) { + phun->complete_with_warning(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + break; + } + } + } +} + void NotificationManager::upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host) { for (std::unique_ptr& notification : m_pop_notifications) { @@ -1909,7 +2420,6 @@ void NotificationManager::upload_job_notification_show_error(int id, const std:: } } } - void NotificationManager::push_download_progress_notification(const std::string& text, std::function cancel_callback) { // If already exists, change text and reset progress @@ -1941,6 +2451,81 @@ void NotificationManager::set_download_progress_percentage(float percentage) } } +void NotificationManager::push_download_URL_progress_notification(size_t id, const std::string& text, std::function user_action_callback) +{ + // If already exists + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::URLDownload && dynamic_cast(notification.get())->get_download_id() == id) { + return; + } + } + // push new one + NotificationData data{ NotificationType::URLDownload, NotificationLevel::ProgressBarNotificationLevel, 5, _utf8("Download:") + " " + text }; + push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, id, user_action_callback), 0); +} + +void NotificationManager::set_download_URL_progress(size_t id, float percentage) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::URLDownload) { + URLDownloadNotification* ntf = dynamic_cast(notification.get()); + if (ntf->get_download_id() != id) + continue; + // if this changes the percentage, it should be shown now + float percent_b4 = ntf->get_percentage(); + ntf->set_percentage(percentage); + ntf->set_paused(false); + if (ntf->get_percentage() != percent_b4) + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + return; + } + } +} + +void NotificationManager::set_download_URL_paused(size_t id) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::URLDownload) { + URLDownloadNotification* ntf = dynamic_cast(notification.get()); + if (ntf->get_download_id() != id) + continue; + ntf->set_paused(true); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + return; + } + } +} + +void NotificationManager::set_download_URL_canceled(size_t id) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::URLDownload) { + URLDownloadNotification* ntf = dynamic_cast(notification.get()); + if (ntf->get_download_id() != id) + continue; + ntf->close(); + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + return; + } + } +} +void NotificationManager::set_download_URL_error(size_t id, const std::string& text) +{ + for (std::unique_ptr& notification : m_pop_notifications) { + if (notification->get_type() == NotificationType::URLDownload) { + URLDownloadNotification* ntf = dynamic_cast(notification.get()); + if (ntf->get_download_id() != id) + continue; + float percent_b4 = ntf->get_percentage(); + ntf->set_percentage(-1.f); + ntf->set_error_message(text); + if (ntf->get_percentage() != percent_b4) + wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); + return; + } + } +} + void NotificationManager::init_slicing_progress_notification(std::function cancel_callback) { for (std::unique_ptr& notification : m_pop_notifications) { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 23d4d20b0..7cd77a304 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -7,6 +7,7 @@ #include "Event.hpp" #include "I18N.hpp" #include "Jobs/ProgressIndicator.hpp" +#include "Downloader.hpp" #include #include @@ -117,6 +118,8 @@ enum class NotificationType NetfabbFinished, // Short meesage to fill space between start and finish of export ExportOngoing, + // Progressbar of download from prusaslicer:// url + URLDownload }; class NotificationManager @@ -206,11 +209,21 @@ public: void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); void set_upload_job_notification_host(int id, const std::string& host); + void set_upload_job_notification_status(int id, const std::string& status); + void set_upload_job_notification_comp_on_100(int id, bool comp); + void set_upload_job_notification_completed(int id); + void set_upload_job_notification_completed_with_warning(int id); void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); // Download App progress void push_download_progress_notification(const std::string& text, std::function cancel_callback); void set_download_progress_percentage(float percentage); + // Download URL progress notif + void push_download_URL_progress_notification(size_t id, const std::string& text, std::function user_action_callback); + void set_download_URL_progress(size_t id, float percentage); + void set_download_URL_paused(size_t id); + void set_download_URL_canceled(size_t id); + void set_download_URL_error(size_t id, const std::string& text); // slicing progress void init_slicing_progress_notification(std::function cancel_callback); void set_slicing_progress_began(); @@ -352,7 +365,8 @@ private: // Hypertext action, returns true if notification should close. // Action is stored in NotificationData::callback as std::function virtual bool on_text_click(); - + // "More" hypertext to show full message + virtual void on_more_hypertext_click(); // Part of init(), counts horizontal spacing like left indentation virtual void count_spaces(); // Part of init(), counts end lines @@ -413,6 +427,8 @@ private: // True if minimized button is rendered, helps to decide where is area for invisible close button bool m_minimize_b_visible { false }; size_t m_lines_count{ 1 }; + // Number of lines to be shown when m_multiline = false. If m_lines_count = m_normal_lines_count + 1 -> all lines are shown, + size_t m_normal_lines_count { 2 }; // Target for wxWidgets events sent by clicking on the hyperlink available at some notifications. wxEvtHandler* m_evt_handler; }; @@ -498,6 +514,62 @@ private: long m_hover_time{ 0 }; }; + class URLDownloadNotification : public ProgressBarNotification + { + public: + URLDownloadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, size_t download_id, std::function user_action_callback) + //: ProgressBarWithCancelNotification(n, id_provider, evt_handler, cancel_callback) + : ProgressBarNotification(n, id_provider, evt_handler) + , m_download_id(download_id) + , m_user_action_callback(user_action_callback) + { + } + void set_percentage(float percent) override + { + m_percentage = percent; + if (m_percentage >= 1.f) { + m_notification_start = GLCanvas3D::timestamp_now(); + m_state = EState::Shown; + } else + m_state = EState::NotFading; + } + size_t get_download_id() { return m_download_id; } + void set_user_action_callback(std::function user_action_callback) { m_user_action_callback = user_action_callback; } + void set_paused(bool paused) { m_download_paused = paused; } + void set_error_message(const std::string& message) { m_error_message = message; } + bool compare_text(const std::string& text) const override { return false; }; + protected: + void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void render_close_button_inner(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + void render_pause_cancel_buttons_inner(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + void render_open_button_inner(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + void render_cancel_button_inner(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + void render_pause_button_inner(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y); + void render_bar(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + void trigger_user_action_callback(DownloaderUserAction action); + + void count_spaces() override; + + size_t m_download_id; + std::function m_user_action_callback; + bool m_download_paused {false}; + std::string m_error_message; + }; + class PrintHostUploadNotification : public ProgressBarNotification { public: @@ -506,7 +578,9 @@ private: PB_PROGRESS, PB_ERROR, PB_CANCELLED, - PB_COMPLETED + PB_COMPLETED, + PB_COMPLETED_WITH_WARNING, + PB_RESOLVING }; PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize, const std::string& filename, const std::string& host) :ProgressBarNotification(n, id_provider, evt_handler) @@ -514,37 +588,57 @@ private: , m_file_size(filesize) , m_filename(filename) , m_host(host) + , m_original_host(host) { m_has_cancel_button = true; - set_percentage(percentage); + if (percentage != 0.f) + set_percentage(percentage); } - static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; } void set_percentage(float percent) override; void cancel() { m_uj_state = UploadJobState::PB_CANCELLED; m_has_cancel_button = false; } void error() { m_uj_state = UploadJobState::PB_ERROR; m_has_cancel_button = false; init(); } bool compare_job_id(const int other_id) const { return m_job_id == other_id; } bool compare_text(const std::string& text) const override { return false; } - void set_host(const std::string& host) { m_host = host; update({ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotificationLevel, 10, get_upload_job_text(m_id, m_filename, m_host)}); } + void set_host(const std::string& host) { m_host = host; init(); } std::string get_host() const { return m_host; } + void set_status(const std::string& status) { m_status_message = status; init(); } + void set_complete_on_100(bool val) { m_complete_on_100 = val; } + void complete(); + void complete_with_warning(); protected: void init() override; void count_spaces() override; bool push_background_color() override; + virtual void render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; void render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; + virtual void render_close_button(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; void render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; void render_left_sign(ImGuiWrapper& imgui) override; + + void generate_text(); + void on_more_hypertext_click() override { ProgressBarNotification::on_more_hypertext_click(); m_more_hypertext_used = true; } + // Identifies job in cancel callback int m_job_id; // Size of uploaded size to be displayed in MB float m_file_size; long m_hover_time{ 0 }; - UploadJobState m_uj_state{ UploadJobState::PB_PROGRESS }; + UploadJobState m_uj_state{ UploadJobState::PB_RESOLVING }; std::string m_filename; std::string m_host; + std::string m_original_host; // when hostname is resolved into ip address, we can still display original hostname (that user inserted) + std::string m_status_message; + bool m_more_hypertext_used { false }; + // When m_complete_on_100 is set to false - percent >= 1 wont switch to PB_COMPLETED state. + bool m_complete_on_100 { true }; }; class SlicingProgressNotification : public ProgressBarNotification @@ -790,7 +884,8 @@ private: NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload, - NotificationType::SimplifySuggestion + NotificationType::SimplifySuggestion, + NotificationType::URLDownload }; //prepared (basic) notifications // non-static so its not loaded too early. If static, the translations wont load correctly. diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 0c845fc68..2f4f91302 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -45,7 +45,7 @@ OG_CustomCtrl::OG_CustomCtrl( wxWindow* parent, m_v_gap = lround(1.0 * m_em_unit); m_h_gap = lround(0.2 * m_em_unit); - m_bmp_mode_sz = get_bitmap_size(get_bmp_bundle("mode_simple", wxOSX ? 10 : 12), this); + m_bmp_mode_sz = get_bitmap_size(get_bmp_bundle("mode", wxOSX ? 10 : 12), this); m_bmp_blinking_sz = get_bitmap_size(get_bmp_bundle("search_blink"), this); init_ctrl_lines();// from og.lines() @@ -188,7 +188,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) #else GetTextExtent(label, &label_w, &label_h, 0, 0, &m_font); #endif //__WXMSW__ - h_pos += label_w + 1 + m_h_gap; + h_pos += label_w + m_h_gap; } h_pos += (opt.opt.gui_type == ConfigOptionDef::GUIType::legend ? 1 : 3) * blinking_button_width; @@ -199,7 +199,7 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/) if (opt.opt.gui_type == ConfigOptionDef::GUIType::legend) h_pos += 2 * blinking_button_width; - h_pos += field->getWindow()->GetSize().x; + h_pos += field->getWindow()->GetSize().x + m_h_gap; if (option_set.size() == 1 && option_set.front().opt.full_width) break; @@ -418,7 +418,7 @@ void OG_CustomCtrl::msw_rescale() m_v_gap = lround(1.0 * m_em_unit); m_h_gap = lround(0.2 * m_em_unit); - m_bmp_mode_sz = get_bitmap_size(get_bmp_bundle("mode_simple", wxOSX ? 10 : 12), this); + m_bmp_mode_sz = get_bitmap_size(get_bmp_bundle("mode", wxOSX ? 10 : 12), this); m_bmp_blinking_sz = get_bitmap_size(get_bmp_bundle("search_blink"), this); init_max_win_width(); @@ -666,9 +666,7 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos) return ctrl->m_h_gap; ConfigOptionMode mode = og_line.get_options()[0].opt.mode; - const std::string& bmp_name = mode == ConfigOptionMode::comSimple ? "mode_simple" : - mode == ConfigOptionMode::comAdvanced ? "mode_advanced" : "mode_expert"; - wxBitmapBundle* bmp = get_bmp_bundle(bmp_name, wxOSX ? 10 : 12); + wxBitmapBundle* bmp = get_bmp_bundle("mode", wxOSX ? 10 : 12, wxGetApp().get_mode_btn_color(mode)); wxCoord y_draw = v_pos + lround((height - get_bitmap_size(bmp, ctrl).GetHeight()) / 2); if (og_line.get_options().front().opt.gui_type != ConfigOptionDef::GUIType::legend) diff --git a/src/slic3r/GUI/OpenGLManager.cpp b/src/slic3r/GUI/OpenGLManager.cpp index 0366031d2..8959553e6 100644 --- a/src/slic3r/GUI/OpenGLManager.cpp +++ b/src/slic3r/GUI/OpenGLManager.cpp @@ -199,6 +199,8 @@ std::string OpenGLManager::GLInfo::to_string(bool for_github) const out << b_start << "Vendor: " << b_end << m_vendor << line_end; out << b_start << "Renderer: " << b_end << m_renderer << line_end; out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end; + out << b_start << "Textures compression: " << b_end << (are_compressed_textures_supported() ? "Enabled" : "Disabled") << line_end; + out << b_start << "Textures mipmap generation: " << b_end << (use_manually_generated_mipmaps() ? "Manual" : "Automatic") << line_end; { #if ENABLE_GL_CORE_PROFILE @@ -255,7 +257,7 @@ std::vector OpenGLManager::GLInfo::get_extensions_list() const OpenGLManager::GLInfo OpenGLManager::s_gl_info; bool OpenGLManager::s_compressed_textures_supported = false; -bool OpenGLManager::m_use_manually_generated_mipmaps = true; +bool OpenGLManager::s_use_manually_generated_mipmaps = true; OpenGLManager::EMultisampleState OpenGLManager::s_multisample = OpenGLManager::EMultisampleState::Unknown; OpenGLManager::EFramebufferType OpenGLManager::s_framebuffers_type = OpenGLManager::EFramebufferType::Unknown; @@ -347,17 +349,11 @@ bool OpenGLManager::init_gl() #endif // ENABLE_GL_CORE_PROFILE m_gl_initialized = true; -#if ENABLE_GL_CORE_PROFILE - if (GLEW_ARB_texture_compression) - s_compressed_textures_supported = true; - else - s_compressed_textures_supported = false; -#else + if (GLEW_EXT_texture_compression_s3tc) s_compressed_textures_supported = true; else s_compressed_textures_supported = false; -#endif // ENABLE_GL_CORE_PROFILE if (GLEW_ARB_framebuffer_object) s_framebuffers_type = EFramebufferType::Arb; @@ -416,29 +412,18 @@ bool OpenGLManager::init_gl() #ifdef _WIN32 // Since AMD driver version 22.7.1, there is probably some bug in the driver that causes the issue with the missing - // texture of the bed. It seems that this issue only triggers when mipmaps are generated manually - // (combined with a texture compression) and when mipmaps are generated through OpenGL glGenerateMipmap is working. - // So, for newer drivers than 22.6.1, the last working driver version, we use mipmaps generated through OpenGL. - if (const auto gl_info = OpenGLManager::get_gl_info(); boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.")) { - // WHQL drivers seem to have one more version number at the end besides non-WHQL drivers. - // WHQL: 4.6.14800 Compatibility Profile Context 22.6.1 30.0.21023.1015 - // Non-WHQL: 4.6.0 Compatibility Profile Context 22.8.1.220810 - std::regex version_rgx(R"(Compatibility\sProfile\sContext\s(\d+)\.(\d+)\.(\d+))"); - if (std::smatch matches; std::regex_search(gl_info.get_version_string(), matches, version_rgx) && matches.size() == 4) { - int version_major = std::stoi(matches[1].str()); - int version_minor = std::stoi(matches[2].str()); - int version_patch = std::stoi(matches[3].str()); - BOOST_LOG_TRIVIAL(debug) << "Found AMD driver version: " << version_major << "." << version_minor << "." << version_patch; - - if (version_major > 22 || (version_major == 22 && version_minor > 6) || (version_major == 22 && version_minor == 6 && version_patch > 1)) { - m_use_manually_generated_mipmaps = false; - BOOST_LOG_TRIVIAL(debug) << "Mipmapping through OpenGL was enabled."; - } - } else { - BOOST_LOG_TRIVIAL(error) << "Not recognized format of version."; - } - } else { - BOOST_LOG_TRIVIAL(error) << "Unable to parse version of AMD driver."; + // texture of the bed (see: https://github.com/prusa3d/PrusaSlicer/issues/8417). + // It seems that this issue only triggers when mipmaps are generated manually + // (combined with a texture compression) with texture size not being power of two. + // When mipmaps are generated through OpenGL function glGenerateMipmap() the driver works fine. + // There is no an easy way to detect the driver version without using Win32 API because the strings returned by OpenGL + // have no standardized format, only some of them contain the driver version. + // Until we do not know that driver will be fixed (if ever) we force the use of glGenerateMipmap() on all cards + // containing the string 'Radeon' in the string returned by glGetString(GL_RENDERER) + const auto& gl_info = OpenGLManager::get_gl_info(); + if (boost::contains(gl_info.get_vendor(), "ATI Technologies Inc.") && boost::contains(gl_info.get_renderer(), "Radeon")) { + s_use_manually_generated_mipmaps = false; + BOOST_LOG_TRIVIAL(debug) << "Mipmapping through OpenGL was enabled."; } #endif } diff --git a/src/slic3r/GUI/OpenGLManager.hpp b/src/slic3r/GUI/OpenGLManager.hpp index ece900962..6b947deb5 100644 --- a/src/slic3r/GUI/OpenGLManager.hpp +++ b/src/slic3r/GUI/OpenGLManager.hpp @@ -110,10 +110,11 @@ private: static OSInfo s_os_info; #endif //__APPLE__ static bool s_compressed_textures_supported; + static bool s_use_manually_generated_mipmaps; + static EMultisampleState s_multisample; static EFramebufferType s_framebuffers_type; - static bool m_use_manually_generated_mipmaps; public: OpenGLManager() = default; ~OpenGLManager(); @@ -138,7 +139,7 @@ public: static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; } static wxGLCanvas* create_wxglcanvas(wxWindow& parent); static const GLInfo& get_gl_info() { return s_gl_info; } - static bool use_manually_generated_mipmaps() { return m_use_manually_generated_mipmaps; } + static bool use_manually_generated_mipmaps() { return s_use_manually_generated_mipmaps; } private: #if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index cfa2ef15a..d9ddf5e89 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -54,6 +54,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co default: switch (opt.type) { case coFloatOrPercent: + case coFloatsOrPercents: case coFloat: case coFloats: case coPercent: @@ -118,6 +119,24 @@ OptionsGroup::OptionsGroup( wxWindow* _parent, const wxString& title, { } +void Line::clear() +{ + if (near_label_widget_win) + near_label_widget_win = nullptr; + + if (widget_sizer) { + widget_sizer->Clear(true); + delete widget_sizer; + widget_sizer = nullptr; + } + + if (extra_widget_sizer) { + extra_widget_sizer->Clear(true); + delete extra_widget_sizer; + extra_widget_sizer = nullptr; + } +} + wxWindow* OptionsGroup::ctrl_parent() const { return this->custom_ctrl && m_use_custom_ctrl_as_parent ? static_cast(this->custom_ctrl) : (this->stb ? static_cast(this->stb) : this->parent()); @@ -231,7 +250,7 @@ void OptionsGroup::activate_line(Line& line) } } - auto option_set = line.get_options(); + const std::vector